From 8d28f9230cb3b4e032218b8019da22e61dd9abdd Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 11 Jan 2019 09:20:12 -0500 Subject: [PATCH 1/3] 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(); From 7b6adae6dd438e2393d55f8b8a932c82ad67a10a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 11 Jan 2019 15:05:24 -0500 Subject: [PATCH 2/3] Extracted compilerOptions setting to helper function --- .../fixEnableExperimentalDecorators.ts | 38 +------------- src/services/codefixes/helpers.ts | 50 ++++++++++++++++--- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/services/codefixes/fixEnableExperimentalDecorators.ts b/src/services/codefixes/fixEnableExperimentalDecorators.ts index 8179abdfd1d..8aeefe48a47 100644 --- a/src/services/codefixes/fixEnableExperimentalDecorators.ts +++ b/src/services/codefixes/fixEnableExperimentalDecorators.ts @@ -19,42 +19,6 @@ namespace ts.codefix { }); 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()); + setJsonCompilerOptionValue(changeTracker, configFile, "experimentalDecorators", createTrue()); } } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 0344f51a854..66488d5d697 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -1,13 +1,5 @@ /* @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. @@ -257,4 +249,46 @@ namespace ts.codefix { } return undefined; } + + export function setJsonCompilerOptionValue( + changeTracker: textChanges.ChangeTracker, + configFile: TsConfigSourceFile, + optionName: string, + optionValue: Expression, + ) { + const tsconfigObjectLiteral = getTsConfigObjectLiteralExpression(configFile); + if (!tsconfigObjectLiteral) return undefined; + + const compilerOptionsProperty = findJsonProperty(tsconfigObjectLiteral, "compilerOptions"); + if (compilerOptionsProperty === undefined) { + changeTracker.insertNodeAtObjectStart(configFile, tsconfigObjectLiteral, createJsonPropertyAssignment( + "compilerOptions", + createObjectLiteral([ + createJsonPropertyAssignment(optionName, optionValue), + ]))); + return; + } + + const compilerOptions = compilerOptionsProperty.initializer; + if (!isObjectLiteralExpression(compilerOptions)) { + return; + } + + const optionProperty = findJsonProperty(compilerOptions, optionName); + + if (optionProperty === undefined) { + changeTracker.insertNodeAtObjectStart(configFile, compilerOptions, createJsonPropertyAssignment(optionName, optionValue)); + } + else { + changeTracker.replaceNode(configFile, optionProperty.initializer, optionValue); + } + } + + 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); + } } From fadd95f72b5ad7f7f1cffa2b6ac82f612694462c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 11 Jan 2019 14:24:49 -0800 Subject: [PATCH 3/3] Fix unneeded cast lints (#29383) --- src/compiler/builder.ts | 3 ++- src/compiler/parser.ts | 13 +++++++------ src/harness/compiler.ts | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 62dd670fbc5..06615b3a97f 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -78,7 +78,8 @@ namespace ts { if (useOldState) { // Verify the sanity of old state if (!oldState!.currentChangedFilePath) { - Debug.assert(!oldState!.affectedFiles && (!oldState!.currentAffectedFilesSignatures || !oldState!.currentAffectedFilesSignatures!.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); + const affectedSignatures = oldState!.currentAffectedFilesSignatures; + Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); } if (canCopySemanticDiagnostics) { Debug.assert(!forEachKey(oldState!.changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files"); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f976f77ea98..53e45505014 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7774,17 +7774,18 @@ namespace ts { const libReferenceDirectives = context.libReferenceDirectives; forEach(toArray(entryOrList), (arg: PragmaPseudoMap["reference"]) => { // TODO: GH#18217 + const { types, lib, path } = arg!.arguments; if (arg!.arguments["no-default-lib"]) { context.hasNoDefaultLib = true; } - else if (arg!.arguments.types) { - typeReferenceDirectives.push({ pos: arg!.arguments.types!.pos, end: arg!.arguments.types!.end, fileName: arg!.arguments.types!.value }); + else if (types) { + typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value }); } - else if (arg!.arguments.lib) { - libReferenceDirectives.push({ pos: arg!.arguments.lib!.pos, end: arg!.arguments.lib!.end, fileName: arg!.arguments.lib!.value }); + else if (lib) { + libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); } - else if (arg!.arguments.path) { - referencedFiles.push({ pos: arg!.arguments.path!.pos, end: arg!.arguments.path!.end, fileName: arg!.arguments.path!.value }); + else if (path) { + referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value }); } else { reportDiagnostic(arg!.range.pos, arg!.range.end - arg!.range.pos, Diagnostics.Invalid_reference_directive_syntax); diff --git a/src/harness/compiler.ts b/src/harness/compiler.ts index 532a2d65578..70bda90ef32 100644 --- a/src/harness/compiler.ts +++ b/src/harness/compiler.ts @@ -183,8 +183,9 @@ namespace compiler { } public getSourceMapRecord(): string | undefined { - if (this.result!.sourceMaps && this.result!.sourceMaps!.length > 0) { - return Harness.SourceMapRecorder.getSourceMapRecord(this.result!.sourceMaps!, this.program!, Array.from(this.js.values()).filter(d => !ts.fileExtensionIs(d.file, ts.Extension.Json)), Array.from(this.dts.values())); + const maps = this.result!.sourceMaps; + if (maps && maps.length > 0) { + return Harness.SourceMapRecorder.getSourceMapRecord(maps, this.program!, Array.from(this.js.values()).filter(d => !ts.fileExtensionIs(d.file, ts.Extension.Json)), Array.from(this.dts.values())); } }