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.
This commit is contained in:
Josh Goldberg 2019-01-11 09:20:12 -05:00
parent 52b82560e8
commit 8d28f9230c
9 changed files with 161 additions and 11 deletions

View File

@ -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
}
}

View File

@ -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 };
}

View File

@ -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());
}
}

View File

@ -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.

View File

@ -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",

View File

@ -0,0 +1,26 @@
/// <reference path='fourslash.ts' />
// @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,
}
}`,
},
});

View File

@ -0,0 +1,27 @@
/// <reference path='fourslash.ts' />
// @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,
}
}`,
},
});

View File

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
// @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 },
}`,
},
});

View File

@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />
// @Filename: /dir/a.ts
////declare const decorator: any;
////class A {
//// @decorator method() {};
////};
goTo.file("/dir/a.ts");
verify.not.codeFixAvailable();