diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2a8b74d64bd..bf8fcdc4f84 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3689,6 +3689,10 @@ "category": "Message", "code": 90026 }, + "Declare static property '{0}'.": { + "category": "Message", + "code": 90027 + }, "Convert function to an ES2015 class": { "category": "Message", diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index cb7988fb745..13db228e889 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2337,6 +2337,39 @@ namespace FourSlash { } } + public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) { + const fileName = this.activeFile.fileName; + const actions = this.getCodeFixActions(fileName, options.errorCode); + let index = options.index; + if (index === undefined) { + if (!(actions && actions.length === 1)) { + this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`); + } + 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 action = actions[index]; + + assert.equal(action.description, options.description); + + for (const change of action.changes) { + this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); + } + + if (options.newFileContent) { + assert(!options.newRangeContent); + this.verifyCurrentFileContent(options.newFileContent); + } + else { + this.verifyRangeIs(options.newRangeContent, /*includeWhitespace*/ true); + } + } + /** * Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found. * @param fileName Path to file where error should be retrieved from. @@ -3716,6 +3749,10 @@ namespace FourSlashInterface { this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges); } + public codeFix(options: FourSlashInterface.VerifyCodeFixOptions) { + this.state.verifyCodeFix(options); + } + public codeFixAvailable() { this.state.verifyCodeFixAvailable(this.negative); } @@ -4359,4 +4396,13 @@ namespace FourSlashInterface { export interface CompletionsAtOptions { isNewIdentifierLocation?: boolean; } + + export interface VerifyCodeFixOptions { + description: string; + // One of these should be defined. + newFileContent?: string; + newRangeContent?: string; + errorCode?: number; + index?: number; + } } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 97e209fe89a..a9583106fc6 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -156,8 +156,9 @@ namespace ts.codefix { const propertyChangeTracker = textChanges.ChangeTracker.fromContext(context); propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter }); - (actions || (actions = [])).push({ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Declare_property_0), [tokenName]), + const diag = makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0; + actions = append(actions, { + description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), changes: propertyChangeTracker.getChanges() }); @@ -197,11 +198,9 @@ namespace ts.codefix { const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromContext(context); methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter }); + const diag = makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0; return { - description: formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? - Diagnostics.Declare_method_0 : - Diagnostics.Declare_static_method_0), - [tokenName]), + description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), changes: methodDeclarationChangeTracker.getChanges() }; } diff --git a/tests/cases/fourslash/codeFixAddForgottenThis01.ts b/tests/cases/fourslash/codeFixAddForgottenThis01.ts index 07f06348212..5dc5dfa4866 100644 --- a/tests/cases/fourslash/codeFixAddForgottenThis01.ts +++ b/tests/cases/fourslash/codeFixAddForgottenThis01.ts @@ -7,6 +7,9 @@ //// |]} ////} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Add 'this.' to unresolved variable.", + newRangeContent: ` this.foo = 10; - `, /*includeWhitespace*/ true); \ No newline at end of file + ` +}); diff --git a/tests/cases/fourslash/codeFixAddForgottenThis02.ts b/tests/cases/fourslash/codeFixAddForgottenThis02.ts index b387f349073..349df613452 100644 --- a/tests/cases/fourslash/codeFixAddForgottenThis02.ts +++ b/tests/cases/fourslash/codeFixAddForgottenThis02.ts @@ -6,4 +6,7 @@ //// bar() { [|foo = 10|] }; ////} -verify.rangeAfterCodeFix("this.foo = 10"); \ No newline at end of file +verify.codeFix({ + description: "Add 'this.' to unresolved variable.", + newRangeContent: "this.foo = 10", +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember.ts b/tests/cases/fourslash/codeFixAddMissingMember.ts index 43c0bc3b765..a2ad3bd636e 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember.ts @@ -1,14 +1,19 @@ /// -////[|class C { +////class C { //// method() { //// this.foo = 10; //// } -////}|] +////} -verify.rangeAfterCodeFix(`class C { - foo: number; +verify.codeFix({ + description: "Declare property 'foo'.", + index: 0, + // TODO: GH#18445 + newFileContent: `class C { + foo: number;\r 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 index 7b64fde8c01..68c7035afcb 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember2.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember2.ts @@ -1,14 +1,19 @@ /// -////[|class C { +////class C { //// method() { //// this.foo = 10; //// } -////}|] +////} -verify.rangeAfterCodeFix(`class C { - [x:string]: number; +verify.codeFix({ + description: "Add index signature for property 'foo'.", + index: 1, + // TODO: GH#18445 + newFileContent: `class C { + [x: string]: number;\r 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 index 47cacd6753a..1e3ff32f3fa 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember3.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember3.ts @@ -1,14 +1,19 @@ /// -////[|class C { +////class C { //// static method() { //// this.foo = 10; //// } -////}|] +////} -verify.rangeAfterCodeFix(`class C { - static foo: number; +verify.codeFix({ + description: "Declare static property 'foo'.", + index: 0, + // TODO: GH#18445 + newFileContent: `class C { + static foo: number;\r static method() { this.foo = 10; } -}`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0); +}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember4.ts b/tests/cases/fourslash/codeFixAddMissingMember4.ts index 08df9ccabc2..cfbec8977f6 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember4.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember4.ts @@ -4,19 +4,25 @@ // @allowJs: true // @Filename: a.js -////[|class C { +////class C { //// constructor() { //// } //// method() { //// this.foo === 10; //// } -////}|] +////} -verify.rangeAfterCodeFix(`class C { +verify.codeFix({ + description: "Initialize property 'foo' in the constructor.", + index: 0, + // TODO: GH#18741 and GH#18445 + newFileContent: `class C { constructor() { - this.foo = undefined; + \r +this.foo = undefined;\r } 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 index 44b11c5141b..804a3910a8c 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember5.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember5.ts @@ -4,17 +4,20 @@ // @allowJs: true // @Filename: a.js -////[|class C { +////class C { //// static method() { //// ()=>{ this.foo === 10 }; //// } ////} -////|] -verify.getAndApplyCodeFix(/*errorCode*/ undefined, /*index*/ 0); -verify.currentFileContentIs(`class C { +verify.codeFix({ + description: "Initialize static property 'foo'.", + index: 0, + // TODO: GH#18743 and GH#18445 + newFileContent: `class C { static method() { ()=>{ this.foo === 10 }; } -} -C.foo = undefined;` + "\r\n"); // TODO: GH#18445 +}C.foo = undefined;\r +` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember6.ts b/tests/cases/fourslash/codeFixAddMissingMember6.ts index 2f3911d779e..2598014dde5 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember6.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember6.ts @@ -4,15 +4,21 @@ // @allowJs: true // @Filename: a.js -////[|class C { +////class C { //// constructor() { //// } //// prop = ()=>{ this.foo === 10 }; -////}|] +////} -verify.rangeAfterCodeFix(`class C { +verify.codeFix({ + description: "Initialize property 'foo' in the constructor.", + index: 0, + // TODO: GH#18741 and GH#18445 + newFileContent: `class C { constructor() { - this.foo = undefined; - } + \r +this.foo = undefined;\r +} 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 index 7690023815c..4ed9c9293d7 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember7.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember7.ts @@ -4,13 +4,16 @@ // @allowJs: true // @Filename: a.js -////[|class C { +////class C { //// static p = ()=>{ this.foo === 10 }; ////} -////|] -verify.getAndApplyCodeFix(/*errorCode*/ undefined, /*index*/ 2) -verify.currentFileContentIs(`class C { +verify.codeFix({ + description: "Initialize static property 'foo'.", + index: 2, + // TODO: GH#18743 and GH#18445 + newFileContent: `class C { static p = ()=>{ this.foo === 10 }; -} -C.foo = undefined;` + "\r\n"); // TODO: GH#18445 +}C.foo = undefined;\r +` +}); diff --git a/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts b/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts index 75be5420e43..e37725c01ed 100644 --- a/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts +++ b/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts @@ -9,18 +9,55 @@ //// } //// } -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.codeFix({ + description: "Declare static method 'm1'.", + index: 0, + // TODO: GH#18445 + newRangeContent: ` + static m1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + `, +}); -verify.rangeIs(` - static prop2: string; - static prop1: number; - static m2(arg0: any, arg1: any): any { - throw new Error("Method not implemented."); - } - static m1(arg0: any, arg1: any, arg2: any): any { - throw new Error("Method not implemented."); - } -`); +verify.codeFix({ + description: "Declare static method 'm2'.", + index: 0, + newRangeContent: ` + static m2(arg0: any, arg1: any): any {\r + throw new Error("Method not implemented.");\r + }\r + static m1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + `, +}); + +verify.codeFix({ + description: "Declare static property 'prop1'.", + index: 0, + newRangeContent: ` + static prop1: number;\r + static m2(arg0: any, arg1: any): any {\r + throw new Error("Method not implemented.");\r + }\r + static m1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + `, +}); + +verify.codeFix({ + description: "Declare static property 'prop2'.", + index: 0, + newRangeContent: ` + static prop2: string;\r + static prop1: number;\r + static m2(arg0: any, arg1: any): any {\r + throw new Error("Method not implemented.");\r + }\r + static m1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + `, +}); diff --git a/tests/cases/fourslash/codeFixUndeclaredMethod.ts b/tests/cases/fourslash/codeFixUndeclaredMethod.ts index 6a61f8421aa..3aace334b36 100644 --- a/tests/cases/fourslash/codeFixUndeclaredMethod.ts +++ b/tests/cases/fourslash/codeFixUndeclaredMethod.ts @@ -10,18 +10,42 @@ //// } //// } -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); -verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.codeFix({ + description: "Declare method 'foo1'.", + index: 0, + // TODO: GH#18445 + newRangeContent: ` + foo1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + `, +}); -verify.rangeIs(` - foo3(): any { - throw new Error("Method not implemented."); - } - foo2(): any { - throw new Error("Method not implemented."); - } - foo1(arg0: any, arg1: any, arg2: any): any { - throw new Error("Method not implemented."); - } -`); \ No newline at end of file +verify.codeFix({ + description: "Declare method 'foo2'.", + index: 0, + newRangeContent: ` + foo2(): any {\r + throw new Error("Method not implemented.");\r + }\r + foo1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + ` +}); + +verify.codeFix({ + description: "Declare method 'foo3'.", + index: 0, + newRangeContent:` + foo3(): any {\r + throw new Error("Method not implemented.");\r + }\r + foo2(): any {\r + throw new Error("Method not implemented.");\r + }\r + foo1(arg0: any, arg1: any, arg2: any): any {\r + throw new Error("Method not implemented.");\r + }\r + ` +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index b3d29456569..f4d47abc9c9 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -155,6 +155,13 @@ declare namespace FourSlashInterface { implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void; + codeFix(options: { + description: string, + newFileContent?: string, + newRangeContent?: string, + errorCode?: number, + index?: number, + }); codeFixAvailable(): void; applicableRefactorAvailableAtMarker(markerName: string): void; codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;