Merge pull request #14133 from aozgaa/MissingPropertyFix-2.2

Missing property fix 2.2
This commit is contained in:
Arthur Ozga 2017-02-16 14:55:09 -08:00 committed by GitHub
commit 125a8fa2fe
20 changed files with 289 additions and 19 deletions

View File

@ -83,6 +83,8 @@ namespace ts {
getSignaturesOfType,
getIndexTypeOfType,
getBaseTypes,
getBaseTypeOfLiteralType,
getWidenedType,
getTypeFromTypeNode,
getParameterType: getTypeAtPosition,
getReturnTypeOfSignature,

View File

@ -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

View File

@ -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.

View File

@ -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 {

View 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};`
}]
}]
}];
}
}

View File

@ -1,4 +1,5 @@
/// <reference path="fixClassIncorrectlyImplementsInterface.ts" />
/// <reference path="fixAddMissingMember.ts" />
/// <reference path="fixClassDoesntImplementInheritedAbstractMember.ts" />
/// <reference path="fixClassSuperMustPrecedeThisAccess.ts" />
/// <reference path="fixConstructorForDerivedNeedSuperCall.ts" />

View File

@ -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 ";
}

View File

@ -78,6 +78,7 @@
"formatting/smartIndenter.ts",
"formatting/tokenRange.ts",
"codeFixProvider.ts",
"codefixes/fixAddMissingMember.ts",
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
"codefixes/fixClassDoesntImplementInheritedAbstractMember.ts",

View 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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View 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);

View File

@ -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;

View File

@ -16,4 +16,4 @@
////
//// }
verify.rangeAfterCodeFix(`import {Calculator} from "./file1"`, /*errorCode*/ undefined, /*includeWhiteSpace*/ true);
verify.rangeAfterCodeFix(`import {Calculator} from "./file1"`, /*includeWhiteSpace*/ true, /*errorCode*/ undefined);

View File

@ -7,4 +7,4 @@
//// z+1;
////}
verify.rangeAfterCodeFix("var x,z = 1;", 6133);
verify.rangeAfterCodeFix("var x,z = 1;", /*includeWhiteSpace*/ undefined, 6133);