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;`,
+});