diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index ce1ccf1714548..93520f320b1b5 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4683,5 +4683,13 @@ "Generate types for all packages without types": { "category": "Message", "code": 95068 + }, + "Make all const or readonly expressions reassignable": { + "category": "Message", + "code": 95069 + }, + "Remove '{0}' modifier": { + "category": "Message", + "code": 95070 } } \ No newline at end of file diff --git a/src/services/codefixes/fixConvertConstantVariableOrProperty.ts b/src/services/codefixes/fixConvertConstantVariableOrProperty.ts new file mode 100644 index 0000000000000..f75c0262cb0a0 --- /dev/null +++ b/src/services/codefixes/fixConvertConstantVariableOrProperty.ts @@ -0,0 +1,51 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "fixConvertConstantVariableOrProperty"; + const errorCodes = [ + Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property.code + ]; + registerCodeFix({ + errorCodes, + getCodeActions(context) { + const { sourceFile, span, program } = context; + const declaration = getDeclaration(sourceFile, span.start, program.getTypeChecker()); + if (!declaration) return undefined; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, declaration)); + const type = declaration.kind === SyntaxKind.VariableDeclaration ? "const" : "readonly"; + return [createCodeFixAction(fixId, changes, [Diagnostics.Remove_0_modifier, type], fixId, Diagnostics.Make_all_const_or_readonly_expressions_reassignable)]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const declaration = getDeclaration(diag.file, diag.start, context.program.getTypeChecker()); + if (declaration) doChange(changes, context.sourceFile, declaration); + }), + }); + function getDeclaration(sourceFile: SourceFile, pos: number, checker: TypeChecker): Declaration | undefined { + const node = getTokenAtPosition(sourceFile, pos); + const symbol = checker.getSymbolAtLocation(node); + if (symbol) { + return symbol.valueDeclaration; + } + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Declaration) { + if (node.kind === SyntaxKind.VariableDeclaration) { + const oldDeclarationList = node.parent as VariableDeclarationList; + const declarationList = createVariableDeclarationList(oldDeclarationList.declarations, NodeFlags.Let); + changes.replaceNode(sourceFile, oldDeclarationList, declarationList); + } + else if (node.kind === SyntaxKind.PropertyDeclaration) { + const readonlyToken = findAnyChildOfKind(node, SyntaxKind.ReadonlyKeyword); + if (readonlyToken) { + changes.delete(sourceFile, readonlyToken); + } + } + } + + function findAnyChildOfKind(node: Node, kind: SyntaxKind): Node | undefined { + return node.forEachChild(child => { + if (child.kind === kind) return child; + else findAnyChildOfKind(child, kind); + }); + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 6f718b43730f0..797c8ffe3abdd 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -57,6 +57,7 @@ "codefixes/fixClassDoesntImplementInheritedAbstractMember.ts", "codefixes/fixClassSuperMustPrecedeThisAccess.ts", "codefixes/fixConstructorForDerivedNeedSuperCall.ts", + "codefixes/fixConvertConstantVariableOrProperty.ts", "codefixes/fixExtendsInterfaceBecomesImplements.ts", "codefixes/fixForgottenThisPropertyAccess.ts", "codefixes/fixUnusedIdentifier.ts", diff --git a/tests/cases/fourslash/codeFixConvertConstantVariableOrProperty_const.tsx b/tests/cases/fourslash/codeFixConvertConstantVariableOrProperty_const.tsx new file mode 100644 index 0000000000000..ed75822c9162c --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertConstantVariableOrProperty_const.tsx @@ -0,0 +1,11 @@ +/// + +////const variable = 5; +////variable = 4; + +verify.codeFix({ + description: "Remove 'const' modifier", + newFileContent: +`let variable = 5; +variable = 4;`, +}); diff --git a/tests/cases/fourslash/codeFixConvertConstantVariableOrProperty_readonly.tsx b/tests/cases/fourslash/codeFixConvertConstantVariableOrProperty_readonly.tsx new file mode 100644 index 0000000000000..629e10433eb6b --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertConstantVariableOrProperty_readonly.tsx @@ -0,0 +1,17 @@ +/// + +////class Test { +//// readonly prop = 5; +////} +////let testInstance = new Test(); +////testInstance.prop = 3; + +verify.codeFix({ + description: "Remove 'readonly' modifier", + newFileContent: +`class Test { + prop = 5; +} +let testInstance = new Test(); +testInstance.prop = 3;`, +});