From 8d28f9230cb3b4e032218b8019da22e61dd9abdd Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 11 Jan 2019 09:20:12 -0500 Subject: [PATCH] Added codefix to enable experimentalDecorators in the user's config file Starts on #29035 by creating a codefix to enable the `experimentalDecorators` setting in a user's config file, if one exists. The issue's discussion also mentions giving a more precise error message if the user has a jsconfig or tsconfig or creating one if not; I'd rather tackle those in separate PRs to keep this one small. Doesn't create the code action if no config file is present. Otherwise keeps to the precedent of returning without action when the config file contents aren't the expected JSON structure (looking at `fixCannotFindModule.ts`). Moves a couple JSON helpers from that file into the sibling `helpers.ts` so both codefixes can use them. --- src/compiler/diagnosticMessages.json | 4 ++ src/services/codefixes/fixCannotFindModule.ts | 14 +---- .../fixEnableExperimentalDecorators.ts | 60 +++++++++++++++++++ src/services/codefixes/helpers.ts | 8 +++ src/services/tsconfig.json | 1 + ...rimentalDecorators_blankCompilerOptions.ts | 26 ++++++++ ...talDecorators_disabledInCompilerOptions.ts | 27 +++++++++ ...mentalDecorators_missingCompilerOptions.ts | 22 +++++++ ...EnableExperimentalDecorators_noTsconfig.ts | 10 ++++ 9 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 src/services/codefixes/fixEnableExperimentalDecorators.ts create mode 100644 tests/cases/fourslash/codefixEnableExperimentalDecorators_blankCompilerOptions.ts create mode 100644 tests/cases/fourslash/codefixEnableExperimentalDecorators_disabledInCompilerOptions.ts create mode 100644 tests/cases/fourslash/codefixEnableExperimentalDecorators_missingCompilerOptions.ts create mode 100644 tests/cases/fourslash/codefixEnableExperimentalDecorators_noTsconfig.ts 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();