diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index fe4176a766a..bc2ee6fe0f8 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -1,4 +1,4 @@ -/* @internal */ +/* @internal */ namespace ts.codefix { registerCodeFix({ errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1.code], @@ -13,22 +13,27 @@ namespace ts.codefix { // this.missing = 1; // ^^^^^^^ const token = getTokenAtPosition(sourceFile, start); - if (token.kind != SyntaxKind.Identifier) { return undefined; } - const classDeclaration = getContainingClass(token); - if (!classDeclaration) { - return undefined; - } - if (!isPropertyAccessExpression(token.parent) || token.parent.expression.kind !== SyntaxKind.ThisKeyword) { return undefined; } - return isInJavaScriptFile(sourceFile) ? getActionsForAddMissingMemberInJavaScriptFile() : getActionsForAddMissingMemberInTypeScriptFile(); + const classMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false); + if (!isClassElement(classMemberDeclaration)) { + return undefined; + } + const classDeclaration = classMemberDeclaration.parent; + if (!classDeclaration || !isClassLike(classDeclaration)) { + return undefined; + } + + const isStatic = hasModifier(getThisContainer(token, /*includeArrowFunctions*/ false), ModifierFlags.Static); + + return isInJavaScriptFile(sourceFile) ? getActionsForAddMissingMemberInJavaScriptFile() : getActionsForAddMissingMemberInTypeScriptFile(); function getActionsForAddMissingMemberInTypeScriptFile(): CodeAction[] | undefined { let typeString = "any"; @@ -43,47 +48,71 @@ namespace ts.codefix { const startPos = classDeclaration.members.pos; - return [{ + const actions = [{ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [token.getText()]), changes: [{ fileName: sourceFile.fileName, textChanges: [{ span: { start: startPos, length: 0 }, - newText: `${token.getFullText(sourceFile)}: ${typeString};` - }] - }] - }, - { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [token.getText()]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: `[name: string]: ${typeString};` + newText: `${isStatic ? "static " : ""}${token.getFullText(sourceFile)}: ${typeString};` }] }] }]; + + if (!isStatic) { + actions.push({ + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [token.getText()]), + changes: [{ + fileName: sourceFile.fileName, + textChanges: [{ + span: { start: startPos, length: 0 }, + newText: `[x: string]: ${typeString};` + }] + }] + }); + } + + return actions; } function getActionsForAddMissingMemberInJavaScriptFile(): CodeAction[] | undefined { - const classConstructor = getFirstConstructorWithBody(classDeclaration); - if (!classConstructor) { - return undefined; - } - const memberName = token.getText(); - const startPos = classConstructor.body.getEnd() - 1; - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [memberName]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: `this.${memberName} = undefined;` + if (isStatic) { + if (classDeclaration.kind === SyntaxKind.ClassExpression) { + return undefined; + } + + const className = classDeclaration.name.getText(); + + return [{ + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [memberName]), + changes: [{ + fileName: sourceFile.fileName, + textChanges: [{ + span: { start: classDeclaration.getEnd(), length: 0 }, + newText: `${context.newLineCharacter}${className}.${memberName} = undefined;${context.newLineCharacter}` + }] }] - }] - }]; + }]; + } + else { + const classConstructor = getFirstConstructorWithBody(classDeclaration); + if (!classConstructor) { + return undefined; + } + + return [{ + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [memberName]), + changes: [{ + fileName: sourceFile.fileName, + textChanges: [{ + span: { start: classConstructor.body.getEnd() - 1, length: 0 }, + newText: `this.${memberName} = undefined;${context.newLineCharacter}` + }] + }] + }]; + } } } } \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember.ts b/tests/cases/fourslash/codeFixAddMissingMember.ts new file mode 100644 index 00000000000..43c0bc3b765 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember.ts @@ -0,0 +1,14 @@ +/// + +////[|class C { +//// method() { +//// this.foo = 10; +//// } +////}|] + +verify.rangeAfterCodeFix(`class C { + foo: number; + method() { + this.foo = 10; + } +}`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember2.ts b/tests/cases/fourslash/codeFixAddMissingMember2.ts new file mode 100644 index 00000000000..7b64fde8c01 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember2.ts @@ -0,0 +1,14 @@ +/// + +////[|class C { +//// method() { +//// this.foo = 10; +//// } +////}|] + +verify.rangeAfterCodeFix(`class C { + [x:string]: number; + method() { + this.foo = 10; + } +}`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 1); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember3.ts b/tests/cases/fourslash/codeFixAddMissingMember3.ts new file mode 100644 index 00000000000..5864c9c1029 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember3.ts @@ -0,0 +1,14 @@ +/// + +////[|class C { +//// static method() { +//// this.foo = 10; +//// } +////}|] + +verify.rangeAfterCodeFix(`class C { + static foo: number; + static method() { + this.foo = 10; + } +}`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember4.ts b/tests/cases/fourslash/codeFixAddMissingMember4.ts new file mode 100644 index 00000000000..08df9ccabc2 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember4.ts @@ -0,0 +1,22 @@ +/// + +// @checkJs: true +// @allowJs: true + +// @Filename: a.js +////[|class C { +//// constructor() { +//// } +//// method() { +//// this.foo === 10; +//// } +////}|] + +verify.rangeAfterCodeFix(`class C { + constructor() { + this.foo = undefined; +} + method() { + this.foo === 10; + } +}`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember5.ts b/tests/cases/fourslash/codeFixAddMissingMember5.ts new file mode 100644 index 00000000000..cbabc0ba6c5 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember5.ts @@ -0,0 +1,19 @@ +/// + +// @checkJs: true +// @allowJs: true + +// @Filename: a.js +////[|class C { +//// static method() { +//// ()=>{ this.foo === 10 }; +//// } +////} +////|] + +verify.rangeAfterCodeFix(`class C { + static method() { + ()=>{ this.foo === 10 }; + } +} +C.foo = undefined;`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember6.ts b/tests/cases/fourslash/codeFixAddMissingMember6.ts new file mode 100644 index 00000000000..2f3911d779e --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember6.ts @@ -0,0 +1,18 @@ +/// + +// @checkJs: true +// @allowJs: true + +// @Filename: a.js +////[|class C { +//// constructor() { +//// } +//// prop = ()=>{ this.foo === 10 }; +////}|] + +verify.rangeAfterCodeFix(`class C { + constructor() { + this.foo = undefined; + } + prop = ()=>{ this.foo === 10 }; +}`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember7.ts b/tests/cases/fourslash/codeFixAddMissingMember7.ts new file mode 100644 index 00000000000..0e937a3ab6e --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember7.ts @@ -0,0 +1,15 @@ +/// + +// @checkJs: true +// @allowJs: true + +// @Filename: a.js +////[|class C { +//// static p = ()=>{ this.foo === 10 }; +////} +////|] + +verify.rangeAfterCodeFix(`class C { + static p = ()=>{ this.foo === 10 }; +} +C.foo = undefined;`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 2); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts b/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts index 2e49a8184eb..0a2b0ee799d 100644 --- a/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts +++ b/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts @@ -8,7 +8,7 @@ verify.rangeAfterCodeFix(` class A { - [name: string]: number; + [x: string]: number; constructor() { this.x = 10;