diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index f7a2ce5e13177..b4fe921be1f5d 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -83,6 +83,8 @@ namespace ts {
getSignaturesOfType,
getIndexTypeOfType,
getBaseTypes,
+ getBaseTypeOfLiteralType,
+ getWidenedType,
getTypeFromTypeNode,
getParameterType: getTypeAtPosition,
getReturnTypeOfSignature,
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 3be35036144fc..db4bf23d78583 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -3315,6 +3315,14 @@
"category": "Message",
"code": 90015
},
+ "Add declaration for missing property '{0}'": {
+ "category": "Message",
+ "code": 90016
+ },
+ "Add index signature for missing property '{0}'": {
+ "category": "Message",
+ "code": 90017
+ },
"Octal literal types must use ES2015 syntax. Use the syntax '{0}'.": {
"category": "Error",
"code": 8017
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index a48a6367bd024..7fc77b487088c 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -2388,6 +2388,8 @@
getSignaturesOfType(type: Type, kind: SignatureKind): Signature[];
getIndexTypeOfType(type: Type, kind: IndexKind): Type;
getBaseTypes(type: InterfaceType): BaseType[];
+ getBaseTypeOfLiteralType(type: Type): Type;
+ getWidenedType(type: Type): Type;
getReturnTypeOfSignature(signature: Signature): Type;
/**
* Gets the type of a parameter at a given position in a signature.
diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts
index 34aa9580d6d78..49d1336c87464 100644
--- a/src/harness/fourslash.ts
+++ b/src/harness/fourslash.ts
@@ -2122,7 +2122,7 @@ namespace FourSlash {
* Because codefixes are only applied on the working file, it is unsafe
* to apply this more than once (consider a refactoring across files).
*/
- public verifyRangeAfterCodeFix(expectedText: string, errorCode?: number, includeWhiteSpace?: boolean) {
+ public verifyRangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number) {
const ranges = this.getRanges();
if (ranges.length !== 1) {
this.raiseError("Exactly one range should be specified in the testfile.");
@@ -2130,7 +2130,7 @@ namespace FourSlash {
const fileName = this.activeFile.fileName;
- this.applyCodeFixActions(fileName, this.getCodeFixActions(fileName, errorCode));
+ this.applyCodeAction(fileName, this.getCodeFixActions(fileName, errorCode), index);
const actualText = this.rangeText(ranges[0]);
@@ -2155,7 +2155,7 @@ namespace FourSlash {
public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) {
fileName = fileName ? fileName : this.activeFile.fileName;
- this.applyCodeFixActions(fileName, this.getCodeFixActions(fileName));
+ this.applyCodeAction(fileName, this.getCodeFixActions(fileName));
const actualContents: string = this.getFileContent(fileName);
if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) {
@@ -2193,12 +2193,20 @@ namespace FourSlash {
return actions;
}
- private applyCodeFixActions(fileName: string, actions: ts.CodeAction[]): void {
- if (!(actions && actions.length === 1)) {
- this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
+ private applyCodeAction(fileName: string, actions: ts.CodeAction[], index?: number): void {
+ if (index === undefined) {
+ if (!(actions && actions.length === 1)) {
+ this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
+ }
+ index = 0;
+ }
+ else {
+ if (!(actions && actions.length >= index + 1)) {
+ this.raiseError(`Should find at least ${index + 1} codefix(es), but ${actions ? actions.length : "none"} found.`);
+ }
}
- const fileChanges = ts.find(actions[0].changes, change => change.fileName === fileName);
+ const fileChanges = ts.find(actions[index].changes, change => change.fileName === fileName);
if (!fileChanges) {
this.raiseError("The CodeFix found doesn't provide any changes in this file.");
}
@@ -3535,8 +3543,8 @@ namespace FourSlashInterface {
this.DocCommentTemplate(/*expectedText*/ undefined, /*expectedOffset*/ undefined, /*empty*/ true);
}
- public rangeAfterCodeFix(expectedText: string, errorCode?: number, includeWhiteSpace?: boolean): void {
- this.state.verifyRangeAfterCodeFix(expectedText, errorCode, includeWhiteSpace);
+ public rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void {
+ this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index);
}
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts
new file mode 100644
index 0000000000000..6ae2ba3f51c99
--- /dev/null
+++ b/src/services/codefixes/fixAddMissingMember.ts
@@ -0,0 +1,67 @@
+/* @internal */
+namespace ts.codefix {
+ registerCodeFix({
+ errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1.code],
+ getCodeActions: getActionsForAddMissingMember
+ });
+
+ function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined {
+
+ const sourceFile = context.sourceFile;
+ const start = context.span.start;
+ // This is the identifier of the missing property. eg:
+ // this.missing = 1;
+ // ^^^^^^^
+ const token = getTokenAtPosition(sourceFile, start);
+
+ if (token.kind != SyntaxKind.Identifier) {
+ return undefined;
+ }
+
+ const classDeclaration = getContainingClass(token);
+ if (!classDeclaration) {
+ return undefined;
+ }
+
+ if (!(token.parent && token.parent.kind === SyntaxKind.PropertyAccessExpression)) {
+ return undefined;
+ }
+
+ if ((token.parent as PropertyAccessExpression).expression.kind !== SyntaxKind.ThisKeyword) {
+ return undefined;
+ }
+
+ let typeString = "any";
+
+ if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
+ const binaryExpression = token.parent.parent as BinaryExpression;
+
+ const checker = context.program.getTypeChecker();
+ const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right)));
+ typeString = checker.typeToString(widenedType);
+ }
+
+ const startPos = classDeclaration.members.pos;
+
+ return [{
+ 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};`
+ }]
+ }]
+ }];
+ }
+}
\ No newline at end of file
diff --git a/src/services/codefixes/fixes.ts b/src/services/codefixes/fixes.ts
index 3bd173e04f604..76be34c67cda5 100644
--- a/src/services/codefixes/fixes.ts
+++ b/src/services/codefixes/fixes.ts
@@ -1,4 +1,5 @@
///
+///
///
///
///
diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 7bee4f5a0ed91..3eab994f84ce5 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -32,7 +32,7 @@ namespace ts.codefix {
const declaration = declarations[0] as Declaration;
const name = declaration.name ? declaration.name.getText() : undefined;
- const visibility = getVisibilityPrefix(getModifierFlags(declaration));
+ const visibility = getVisibilityPrefixWithSpace(getModifierFlags(declaration));
switch (declaration.kind) {
case SyntaxKind.GetAccessor:
@@ -58,7 +58,7 @@ namespace ts.codefix {
if (declarations.length === 1) {
Debug.assert(signatures.length === 1);
const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
- return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
+ return getStubbedMethod(visibility, name, sigString, newlineChar);
}
let result = "";
@@ -78,7 +78,7 @@ namespace ts.codefix {
bodySig = createBodySignatureWithAnyTypes(signatures, enclosingDeclaration, checker);
}
const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
- result += `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
+ result += getStubbedMethod(visibility, name, sigString, newlineChar);
return result;
default:
@@ -138,11 +138,15 @@ namespace ts.codefix {
}
}
- function getMethodBodyStub(newLineChar: string) {
- return ` {${newLineChar}throw new Error('Method not implemented.');${newLineChar}}${newLineChar}`;
+ export function getStubbedMethod(visibility: string, name: string, sigString = "()", newlineChar: string): string {
+ return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
}
- function getVisibilityPrefix(flags: ModifierFlags): string {
+ function getMethodBodyStub(newlineChar: string) {
+ return ` {${newlineChar}throw new Error('Method not implemented.');${newlineChar}}${newlineChar}`;
+ }
+
+ function getVisibilityPrefixWithSpace(flags: ModifierFlags): string {
if (flags & ModifierFlags.Public) {
return "public ";
}
diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json
index b4e8289f367bd..d88cf137afa8b 100644
--- a/src/services/tsconfig.json
+++ b/src/services/tsconfig.json
@@ -78,6 +78,7 @@
"formatting/smartIndenter.ts",
"formatting/tokenRange.ts",
"codeFixProvider.ts",
+ "codefixes/fixAddMissingMember.ts",
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
"codefixes/fixClassDoesntImplementInheritedAbstractMember.ts",
diff --git a/tests/cases/fourslash/codeFixUndeclaredClassInstance.ts b/tests/cases/fourslash/codeFixUndeclaredClassInstance.ts
new file mode 100644
index 0000000000000..024ec144092c8
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredClassInstance.ts
@@ -0,0 +1,22 @@
+///
+
+//// class A {
+//// a: number;
+//// b: string;
+//// constructor(public x: any) {}
+//// }
+//// [|class B {
+//// constructor() {
+//// this.x = new A(3);
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class B {
+ x: A;
+
+ constructor() {
+ this.x = new A(3);
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
\ No newline at end of file
diff --git a/tests/cases/fourslash/codeFixUndeclaredClassInstanceWithTypeParams.ts b/tests/cases/fourslash/codeFixUndeclaredClassInstanceWithTypeParams.ts
new file mode 100644
index 0000000000000..34e359feea89f
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredClassInstanceWithTypeParams.ts
@@ -0,0 +1,22 @@
+///
+
+//// class A {
+//// a: number;
+//// b: string;
+//// constructor(public x: T) {}
+//// }
+//// [|class B {
+//// constructor() {
+//// this.x = new A(3);
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class B {
+ x: A;
+
+ constructor() {
+ this.x = new A(3);
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
\ No newline at end of file
diff --git a/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts b/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts
new file mode 100644
index 0000000000000..2e49a8184eb73
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts
@@ -0,0 +1,17 @@
+///
+
+//// [|class A {
+//// constructor() {
+//// this.x = 10;
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ [name: string]: number;
+
+ constructor() {
+ this.x = 10;
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 1);
\ No newline at end of file
diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyFunctionEmptyClass.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyFunctionEmptyClass.ts
new file mode 100644
index 0000000000000..6f0a5de3557b1
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredPropertyFunctionEmptyClass.ts
@@ -0,0 +1,20 @@
+///
+
+//// [|class A {
+//// constructor() {
+//// this.x = function(x: number, y?: A){
+//// return x > 0 ? x : y;
+//// }
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ x: (x: number, y?: A) => A;
+ constructor() {
+ this.x = function(x: number, y?: A){
+ return x > 0 ? x : y;
+ }
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyFunctionNonEmptyClass.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyFunctionNonEmptyClass.ts
new file mode 100644
index 0000000000000..b05fb0d42b568
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredPropertyFunctionNonEmptyClass.ts
@@ -0,0 +1,22 @@
+///
+
+//// [|class A {
+//// y: number;
+//// constructor(public a: number) {
+//// this.x = function(x: number, y?: A){
+//// return x > 0 ? x : y;
+//// }
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ x: (x: number, y?: A) => number | A;
+ y: number;
+ constructor(public a: number) {
+ this.x = function(x: number, y?: A){
+ return x > 0 ? x : y;
+ }
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyNumericLiteral.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyNumericLiteral.ts
new file mode 100644
index 0000000000000..eaae355904b16
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredPropertyNumericLiteral.ts
@@ -0,0 +1,17 @@
+///
+
+//// [|class A {
+//// constructor() {
+//// this.x = 10;
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ x: number;
+
+ constructor() {
+ this.x = 10;
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
\ No newline at end of file
diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyObjectLiteral.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyObjectLiteral.ts
new file mode 100644
index 0000000000000..a2647c5a21e33
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredPropertyObjectLiteral.ts
@@ -0,0 +1,19 @@
+///
+
+//// [|class A {
+//// constructor() {
+//// let e: any = 10;
+//// this.x = { a: 10, b: "hello", c: undefined, d: null, e: e };
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ x: { a: number; b: string; c: any; d: any; e: any; };
+
+ constructor() {
+ let e: any = 10;
+ this.x = { a: 10, b: "hello", c: undefined, d: null, e: e };
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyObjectLiteralStrictNullChecks.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyObjectLiteralStrictNullChecks.ts
new file mode 100644
index 0000000000000..0b0ab8cdd59a2
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredPropertyObjectLiteralStrictNullChecks.ts
@@ -0,0 +1,21 @@
+///
+
+// @strictNullChecks: true
+
+//// [|class A {
+//// constructor() {
+//// let e: any = 10;
+//// this.x = { a: 10, b: "hello", c: undefined, d: null, e: e };
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ x: { a: number; b: string; c: undefined; d: null; e: any; };
+
+ constructor() {
+ let e: any = 10;
+ this.x = { a: 10, b: "hello", c: undefined, d: null, e: e };
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
\ No newline at end of file
diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyThisType.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyThisType.ts
new file mode 100644
index 0000000000000..56f9b34d809ce
--- /dev/null
+++ b/tests/cases/fourslash/codeFixUndeclaredPropertyThisType.ts
@@ -0,0 +1,17 @@
+///
+
+//// [|class A {
+//// constructor() {
+//// this.mythis = this;
+//// }
+//// }|]
+
+verify.rangeAfterCodeFix(`
+class A {
+ mythis: this;
+
+ constructor() {
+ this.mythis = this;
+ }
+}
+`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index 661d7cba6a495..34afb71ec85ea 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -225,7 +225,7 @@ declare namespace FourSlashInterface {
noMatchingBracePositionInCurrentFile(bracePosition: number): void;
DocCommentTemplate(expectedText: string, expectedOffset: number, empty?: boolean): void;
noDocCommentTemplate(): void;
- rangeAfterCodeFix(expectedText: string, errorCode?: number, includeWhiteSpace?: boolean): void;
+ rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void;
importFixAtPosition(expectedTextArray: string[], errorCode?: number): void;
navigationBar(json: any): void;
diff --git a/tests/cases/fourslash/unusedImports2FS.ts b/tests/cases/fourslash/unusedImports2FS.ts
index 5387569336247..8865dea56a671 100644
--- a/tests/cases/fourslash/unusedImports2FS.ts
+++ b/tests/cases/fourslash/unusedImports2FS.ts
@@ -16,4 +16,4 @@
////
//// }
-verify.rangeAfterCodeFix(`import {Calculator} from "./file1"`, /*errorCode*/ undefined, /*includeWhiteSpace*/ true);
+verify.rangeAfterCodeFix(`import {Calculator} from "./file1"`, /*includeWhiteSpace*/ true, /*errorCode*/ undefined);
diff --git a/tests/cases/fourslash/unusedLocalsInFunction3.ts b/tests/cases/fourslash/unusedLocalsInFunction3.ts
index 7aa9b20a33ce3..0164873acd87e 100644
--- a/tests/cases/fourslash/unusedLocalsInFunction3.ts
+++ b/tests/cases/fourslash/unusedLocalsInFunction3.ts
@@ -7,4 +7,4 @@
//// z+1;
////}
-verify.rangeAfterCodeFix("var x,z = 1;", 6133);
+verify.rangeAfterCodeFix("var x,z = 1;", /*includeWhiteSpace*/ undefined, 6133);