From 30545006df59bd3bd88e17ce15377b1666d10d8d Mon Sep 17 00:00:00 2001 From: Alexander T Date: Sat, 25 Jan 2020 04:28:42 +0200 Subject: [PATCH] =?UTF-8?q?fix(36068):=20Incorrect=20quick=20fix=20for=20u?= =?UTF-8?q?ndeclared=20private=20field=20i=E2=80=A6=20(#36373)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compiler/diagnosticMessages.json | 4 ++ src/services/codefixes/fixAddMissingMember.ts | 40 +++++++++++++++---- .../codeFixInitializePrivatePropertyJS.ts | 29 ++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/codeFixInitializePrivatePropertyJS.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b71e9fef7dd..e6a384b45c3 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -5064,6 +5064,10 @@ "category": "Message", "code": 90034 }, + "Declare a private field named '{0}'.": { + "category": "Message", + "code": 90053 + }, "Convert function to an ES2015 class": { "category": "Message", "code": 95001 diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 5c1e7a655a3..a75e58135e0 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -23,7 +23,7 @@ namespace ts.codefix { const { parentDeclaration, declSourceFile, inJs, makeStatic, token, call } = info; const methodCodeAction = call && getActionForMethodDeclaration(context, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, context.preferences); const addMember = inJs && !isInterfaceDeclaration(parentDeclaration) ? - singleElementArray(getActionsForAddMissingMemberInJavascriptFile(context, declSourceFile, parentDeclaration, token.text, makeStatic)) : + singleElementArray(getActionsForAddMissingMemberInJavascriptFile(context, declSourceFile, parentDeclaration, token, makeStatic)) : getActionsForAddMissingMemberInTypeScriptFile(context, declSourceFile, parentDeclaration, token, makeStatic); return concatenate(singleElementArray(methodCodeAction), addMember); }, @@ -70,7 +70,7 @@ namespace ts.codefix { } else { if (inJs && !isInterfaceDeclaration(parentDeclaration)) { - addMissingMemberInJs(changes, declSourceFile, parentDeclaration, token.text, makeStatic); + addMissingMemberInJs(changes, declSourceFile, parentDeclaration, token, makeStatic); } else { const typeNode = getTypeNode(program.getTypeChecker(), parentDeclaration, token); @@ -140,7 +140,7 @@ namespace ts.codefix { if (classOrInterface && !program.isSourceFileFromExternalLibrary(classOrInterface.getSourceFile())) { const makeStatic = ((leftExpressionType as TypeReference).target || leftExpressionType) !== checker.getDeclaredTypeOfSymbol(symbol); // Static private identifier properties are not supported yet. - if (makeStatic && isPrivateIdentifier(token)) { return undefined; } + if (makeStatic && isPrivateIdentifier(token)) return undefined; const declSourceFile = classOrInterface.getSourceFile(); const inJs = isSourceFileJS(declSourceFile); const call = tryCast(parent.parent, isCallExpression); @@ -153,13 +153,20 @@ namespace ts.codefix { return undefined; } - function getActionsForAddMissingMemberInJavascriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined { - const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, classDeclaration, tokenName, makeStatic)); - return changes.length === 0 ? undefined - : createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor, tokenName], fixId, Diagnostics.Add_all_missing_members); + function getActionsForAddMissingMemberInJavascriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier | PrivateIdentifier, makeStatic: boolean): CodeFixAction | undefined { + const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, classDeclaration, token, makeStatic)); + if (changes.length === 0) { + return undefined; + } + + const diagnostic = makeStatic ? Diagnostics.Initialize_static_property_0 : + isPrivateIdentifier(token) ? Diagnostics.Declare_a_private_field_named_0 : Diagnostics.Initialize_property_0_in_the_constructor; + + return createCodeFixAction(fixName, changes, [diagnostic, token.text], fixId, Diagnostics.Add_all_missing_members); } - function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): void { + function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier | PrivateIdentifier, makeStatic: boolean): void { + const tokenName = token.text; if (makeStatic) { if (classDeclaration.kind === SyntaxKind.ClassExpression) { return; @@ -168,6 +175,23 @@ namespace ts.codefix { const staticInitialization = initializePropertyToUndefined(createIdentifier(className), tokenName); changeTracker.insertNodeAfter(declSourceFile, classDeclaration, staticInitialization); } + else if (isPrivateIdentifier(token)) { + const property = createProperty( + /*decorators*/ undefined, + /*modifiers*/ undefined, + tokenName, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined); + + const lastProp = getNodeToInsertPropertyAfter(classDeclaration); + if (lastProp) { + changeTracker.insertNodeAfter(declSourceFile, lastProp, property); + } + else { + changeTracker.insertNodeAtClassStart(declSourceFile, classDeclaration, property); + } + } else { const classConstructor = getFirstConstructorWithBody(classDeclaration); if (!classConstructor) { diff --git a/tests/cases/fourslash/codeFixInitializePrivatePropertyJS.ts b/tests/cases/fourslash/codeFixInitializePrivatePropertyJS.ts new file mode 100644 index 00000000000..1b991d06a1c --- /dev/null +++ b/tests/cases/fourslash/codeFixInitializePrivatePropertyJS.ts @@ -0,0 +1,29 @@ +/// + +// @allowjs: true +// @checkJs: true + +// @Filename: /a.js +////class Foo { +//// constructor(name) { +//// this.[|#name|] = name; +//// } +////} + +verify.codeFixAvailable([ + { description: "Declare a private field named '#name'." }, + { description: "Ignore this error message" }, + { description: "Disable checking for this file" }, + { description: "Infer parameter types from usage" }, +]); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Declare_a_private_field_named_0.message, '#name'], + newFileContent: `class Foo { + #name; + constructor(name) { + this.#name = name; + } +}` +});