mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 20:14:01 -06:00
Merge pull request #14133 from aozgaa/MissingPropertyFix-2.2
Missing property fix 2.2
This commit is contained in:
commit
125a8fa2fe
@ -83,6 +83,8 @@ namespace ts {
|
||||
getSignaturesOfType,
|
||||
getIndexTypeOfType,
|
||||
getBaseTypes,
|
||||
getBaseTypeOfLiteralType,
|
||||
getWidenedType,
|
||||
getTypeFromTypeNode,
|
||||
getParameterType: getTypeAtPosition,
|
||||
getReturnTypeOfSignature,
|
||||
|
||||
@ -3303,10 +3303,18 @@
|
||||
"category": "Message",
|
||||
"code": 90014
|
||||
},
|
||||
"Add {0} to existing import declaration from {1}": {
|
||||
"Add {0} to existing import declaration from {1}.": {
|
||||
"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
|
||||
|
||||
@ -2382,6 +2382,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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
67
src/services/codefixes/fixAddMissingMember.ts
Normal file
67
src/services/codefixes/fixAddMissingMember.ts
Normal file
@ -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};`
|
||||
}]
|
||||
}]
|
||||
}];
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
/// <reference path="fixClassIncorrectlyImplementsInterface.ts" />
|
||||
/// <reference path="fixAddMissingMember.ts" />
|
||||
/// <reference path="fixClassDoesntImplementInheritedAbstractMember.ts" />
|
||||
/// <reference path="fixClassSuperMustPrecedeThisAccess.ts" />
|
||||
/// <reference path="fixConstructorForDerivedNeedSuperCall.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 ";
|
||||
}
|
||||
|
||||
@ -78,6 +78,7 @@
|
||||
"formatting/smartIndenter.ts",
|
||||
"formatting/tokenRange.ts",
|
||||
"codeFixProvider.ts",
|
||||
"codefixes/fixAddMissingMember.ts",
|
||||
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
|
||||
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
|
||||
"codefixes/fixClassDoesntImplementInheritedAbstractMember.ts",
|
||||
|
||||
22
tests/cases/fourslash/codeFixUndeclaredClassInstance.ts
Normal file
22
tests/cases/fourslash/codeFixUndeclaredClassInstance.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// 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);
|
||||
@ -0,0 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A<T> {
|
||||
//// a: number;
|
||||
//// b: string;
|
||||
//// constructor(public x: T) {}
|
||||
//// }
|
||||
//// [|class B {
|
||||
//// constructor() {
|
||||
//// this.x = new A(3);
|
||||
//// }
|
||||
//// }|]
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
class B {
|
||||
x: A<number>;
|
||||
|
||||
constructor() {
|
||||
this.x = new A(3);
|
||||
}
|
||||
}
|
||||
`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
|
||||
@ -0,0 +1,17 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// [|class A {
|
||||
//// constructor() {
|
||||
//// this.x = 10;
|
||||
//// }
|
||||
//// }|]
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
class A {
|
||||
[name: string]: number;
|
||||
|
||||
constructor() {
|
||||
this.x = 10;
|
||||
}
|
||||
}
|
||||
`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 1);
|
||||
@ -0,0 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// [|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);
|
||||
@ -0,0 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// [|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);
|
||||
@ -0,0 +1,17 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// [|class A {
|
||||
//// constructor() {
|
||||
//// this.x = 10;
|
||||
//// }
|
||||
//// }|]
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
class A {
|
||||
x: number;
|
||||
|
||||
constructor() {
|
||||
this.x = 10;
|
||||
}
|
||||
}
|
||||
`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
|
||||
@ -0,0 +1,19 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// [|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);
|
||||
@ -0,0 +1,21 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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);
|
||||
17
tests/cases/fourslash/codeFixUndeclaredPropertyThisType.ts
Normal file
17
tests/cases/fourslash/codeFixUndeclaredPropertyThisType.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// [|class A {
|
||||
//// constructor() {
|
||||
//// this.mythis = this;
|
||||
//// }
|
||||
//// }|]
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
class A {
|
||||
mythis: this;
|
||||
|
||||
constructor() {
|
||||
this.mythis = this;
|
||||
}
|
||||
}
|
||||
`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
|
||||
@ -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;
|
||||
|
||||
@ -16,4 +16,4 @@
|
||||
////
|
||||
//// }
|
||||
|
||||
verify.rangeAfterCodeFix(`import {Calculator} from "./file1"`, /*errorCode*/ undefined, /*includeWhiteSpace*/ true);
|
||||
verify.rangeAfterCodeFix(`import {Calculator} from "./file1"`, /*includeWhiteSpace*/ true, /*errorCode*/ undefined);
|
||||
|
||||
@ -7,4 +7,4 @@
|
||||
//// z+1;
|
||||
////}
|
||||
|
||||
verify.rangeAfterCodeFix("var x,z = 1;", 6133);
|
||||
verify.rangeAfterCodeFix("var x,z = 1;", /*includeWhiteSpace*/ undefined, 6133);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user