diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9a918507dc4..5bcaf448599 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4811,5 +4811,9 @@ "Add names to all parameters without names": { "category": "Message", "code": 95073 + }, + "Enable the 'experimentalDecorators' option in your configuration file": { + "category": "Message", + "code": 95074 } } diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 1c7070c74bf..1baaa9714f2 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -74,7 +74,7 @@ namespace ts.codefix { const tsconfigObjectLiteral = getTsConfigObjectLiteralExpression(configFile); if (!tsconfigObjectLiteral) return undefined; - const compilerOptionsProperty = findProperty(tsconfigObjectLiteral, "compilerOptions"); + const compilerOptionsProperty = findJsonProperty(tsconfigObjectLiteral, "compilerOptions"); if (!compilerOptionsProperty) { const newCompilerOptions = createObjectLiteral([makeDefaultBaseUrl(), makeDefaultPaths()]); changes.insertNodeAtObjectStart(configFile, tsconfigObjectLiteral, createJsonPropertyAssignment("compilerOptions", newCompilerOptions)); @@ -94,7 +94,7 @@ namespace ts.codefix { return createJsonPropertyAssignment("baseUrl", createStringLiteral(defaultBaseUrl)); } function getOrAddBaseUrl(changes: textChanges.ChangeTracker, tsconfig: TsConfigSourceFile, compilerOptions: ObjectLiteralExpression): string { - const baseUrlProp = findProperty(compilerOptions, "baseUrl"); + const baseUrlProp = findJsonProperty(compilerOptions, "baseUrl"); if (baseUrlProp) { return isStringLiteral(baseUrlProp.initializer) ? baseUrlProp.initializer.text : defaultBaseUrl; } @@ -112,7 +112,7 @@ namespace ts.codefix { return createJsonPropertyAssignment("paths", createObjectLiteral([makeDefaultPathMapping()])); } function getOrAddPathMapping(changes: textChanges.ChangeTracker, tsconfig: TsConfigSourceFile, compilerOptions: ObjectLiteralExpression) { - const paths = findProperty(compilerOptions, "paths"); + const paths = findJsonProperty(compilerOptions, "paths"); if (!paths || !isObjectLiteralExpression(paths.initializer)) { changes.insertNodeAtObjectStart(tsconfig, compilerOptions, makeDefaultPaths()); return defaultTypesDirectoryName; @@ -129,14 +129,6 @@ namespace ts.codefix { return defaultTypesDirectoryName; } - function createJsonPropertyAssignment(name: string, initializer: Expression) { - return createPropertyAssignment(createStringLiteral(name), initializer); - } - - function findProperty(obj: ObjectLiteralExpression, name: string): PropertyAssignment | undefined { - return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name); - } - function getInstallCommand(fileName: string, packageName: string): InstallPackageAction { return { type: "install package", file: fileName, packageName }; } diff --git a/src/services/codefixes/fixEnableExperimentalDecorators.ts b/src/services/codefixes/fixEnableExperimentalDecorators.ts new file mode 100644 index 00000000000..8179abdfd1d --- /dev/null +++ b/src/services/codefixes/fixEnableExperimentalDecorators.ts @@ -0,0 +1,60 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "enableExperimentalDecorators"; + const errorCodes = [ + Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_to_remove_this_warning.code + ]; + registerCodeFix({ + errorCodes, + getCodeActions: (context) => { + const { configFile } = context.program.getCompilerOptions(); + if (configFile === undefined) { + return undefined; + } + + const changes = textChanges.ChangeTracker.with(context, changeTracker => makeChange(changeTracker, configFile)); + return [createCodeFixActionNoFixId(fixId, changes, Diagnostics.Enable_the_experimentalDecorators_option_in_your_configuration_file)]; + }, + fixIds: [fixId], + }); + + function makeChange(changeTracker: textChanges.ChangeTracker, configFile: TsConfigSourceFile) { + const tsconfigObjectLiteral = getTsConfigObjectLiteralExpression(configFile); + if (tsconfigObjectLiteral === undefined) { + return; + } + + const compilerOptionsProperty = findJsonProperty(tsconfigObjectLiteral, "compilerOptions"); + if (compilerOptionsProperty === undefined) { + changeTracker.insertNodeAtObjectStart(configFile, tsconfigObjectLiteral, createCompilerOptionsAssignment()); + return; + } + + const compilerOptions = compilerOptionsProperty.initializer; + if (!isObjectLiteralExpression(compilerOptions)) { + return; + } + + const experimentalDecoratorsProperty = findJsonProperty(compilerOptions, "experimentalDecorators"); + + if (experimentalDecoratorsProperty === undefined) { + changeTracker.insertNodeAtObjectStart(configFile, compilerOptions, createExperimentalDecoratorsAssignment()); + } + else { + changeTracker.replaceNodeWithText(configFile, experimentalDecoratorsProperty.initializer, "true"); + } + } + + function createCompilerOptionsAssignment() { + return createJsonPropertyAssignment( + "compilerOptions", + createObjectLiteral([ + createExperimentalDecoratorsAssignment(), + ]), + ); + } + + function createExperimentalDecoratorsAssignment() { + return createJsonPropertyAssignment("experimentalDecorators", createTrue()); + } +} diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 461771c0b5e..0344f51a854 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -1,5 +1,13 @@ /* @internal */ namespace ts.codefix { + export function createJsonPropertyAssignment(name: string, initializer: Expression) { + return createPropertyAssignment(createStringLiteral(name), initializer); + } + + export function findJsonProperty(obj: ObjectLiteralExpression, name: string): PropertyAssignment | undefined { + return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name); + } + /** * Finds members of the resolved type that are missing in the class pointed to by class decl * and generates source code for the missing members. diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 15044416f84..21be663055a 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -61,6 +61,7 @@ "codefixes/fixClassDoesntImplementInheritedAbstractMember.ts", "codefixes/fixClassSuperMustPrecedeThisAccess.ts", "codefixes/fixConstructorForDerivedNeedSuperCall.ts", + "codefixes/fixEnableExperimentalDecorators.ts", "codefixes/fixExtendsInterfaceBecomesImplements.ts", "codefixes/fixForgottenThisPropertyAccess.ts", "codefixes/fixUnusedIdentifier.ts", diff --git a/tests/cases/fourslash/codefixEnableExperimentalDecorators_blankCompilerOptions.ts b/tests/cases/fourslash/codefixEnableExperimentalDecorators_blankCompilerOptions.ts new file mode 100644 index 00000000000..0e19b42d921 --- /dev/null +++ b/tests/cases/fourslash/codefixEnableExperimentalDecorators_blankCompilerOptions.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: /dir/a.ts +////declare const decorator: any; +////class A { +//// @decorator method() {}; +////}; + +// @Filename: /dir/tsconfig.json +////{ +//// "compilerOptions": { +//// } +////} + +goTo.file("/dir/a.ts"); +verify.codeFix({ + description: "Enable the 'experimentalDecorators' option in your configuration file", + newFileContent: { + "/dir/tsconfig.json": +`{ + "compilerOptions": { + "experimentalDecorators": true, + } +}`, + }, +}); diff --git a/tests/cases/fourslash/codefixEnableExperimentalDecorators_disabledInCompilerOptions.ts b/tests/cases/fourslash/codefixEnableExperimentalDecorators_disabledInCompilerOptions.ts new file mode 100644 index 00000000000..056e7e15a48 --- /dev/null +++ b/tests/cases/fourslash/codefixEnableExperimentalDecorators_disabledInCompilerOptions.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: /dir/a.ts +////declare const decorator: any; +////class A { +//// @decorator method() {}; +////}; + +// @Filename: /dir/tsconfig.json +////{ +//// "compilerOptions": { +//// "experimentalDecorators": false, +//// } +////} + +goTo.file("/dir/a.ts"); +verify.codeFix({ + description: "Enable the 'experimentalDecorators' option in your configuration file", + newFileContent: { + "/dir/tsconfig.json": +`{ + "compilerOptions": { + "experimentalDecorators": true, + } +}`, + }, +}); diff --git a/tests/cases/fourslash/codefixEnableExperimentalDecorators_missingCompilerOptions.ts b/tests/cases/fourslash/codefixEnableExperimentalDecorators_missingCompilerOptions.ts new file mode 100644 index 00000000000..6d7006f3ce1 --- /dev/null +++ b/tests/cases/fourslash/codefixEnableExperimentalDecorators_missingCompilerOptions.ts @@ -0,0 +1,22 @@ +/// + +// @Filename: /dir/a.ts +////declare const decorator: any; +////class A { +//// @decorator method() {}; +////}; + +// @Filename: /dir/tsconfig.json +////{ +////} + +goTo.file("/dir/a.ts"); +verify.codeFix({ + description: "Enable the 'experimentalDecorators' option in your configuration file", + newFileContent: { + "/dir/tsconfig.json": +`{ + "compilerOptions": { "experimentalDecorators": true }, +}`, + }, +}); diff --git a/tests/cases/fourslash/codefixEnableExperimentalDecorators_noTsconfig.ts b/tests/cases/fourslash/codefixEnableExperimentalDecorators_noTsconfig.ts new file mode 100644 index 00000000000..8430fd0734b --- /dev/null +++ b/tests/cases/fourslash/codefixEnableExperimentalDecorators_noTsconfig.ts @@ -0,0 +1,10 @@ +/// + +// @Filename: /dir/a.ts +////declare const decorator: any; +////class A { +//// @decorator method() {}; +////}; + +goTo.file("/dir/a.ts"); +verify.not.codeFixAvailable();