From a4cf12efb764c8da2e943324b7544a5336756028 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 15 Feb 2017 15:15:09 -0800 Subject: [PATCH] cleanup --- src/harness/fourslash.ts | 2 +- src/services/codefixes/fixAddMissingMember.ts | 163 +++++++----------- src/services/codefixes/helpers.ts | 8 +- 3 files changed, 71 insertions(+), 102 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a722cb20724..49d1336c874 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2205,7 +2205,7 @@ namespace FourSlash { this.raiseError(`Should find at least ${index + 1} codefix(es), but ${actions ? actions.length : "none"} found.`); } } - + 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."); diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index fbb8b3a483b..ac03f606d59 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -1,98 +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 in the case of a class declaration - // or the class keyword token in the case of a class expression. - const token = getTokenAtPosition(sourceFile, start); - - const classDeclaration = getContainingClass(token); - if (!classDeclaration) { - return undefined; - } - - const startPos = classDeclaration.members.pos; - - if (!(token.parent && token.parent.kind === SyntaxKind.PropertyAccessExpression)) { - return undefined; - } - - if ((token.parent as PropertyAccessExpression).expression.kind !== SyntaxKind.ThisKeyword) { - return undefined; - } - - // if function call, synthesize function declaration - if(token.parent.parent.kind == SyntaxKind.CallExpression) { - const callExpression = token.parent.parent as CallExpression; - if(callExpression.typeArguments) { - /** - * We can't in general know which arguments should use the type of the expression - * or the type of the type argument in the declaration. Consider - * ``` - * class A { - * constructor(a: number){ - * this.foo(a,1,true); - * } - * } - * ``` - */ - return undefined; - } - - - } - - let typeString: string = 'any'; - - // if binary expression, try to infer type for LHS, else use any - if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { - const binaryExpression = token.parent.parent as BinaryExpression; - binaryExpression.operatorToken; - - const checker = context.program.getTypeChecker(); - const widenedType = checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right)); - typeString = checker.typeToString(widenedType); - } - - 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_accessor_for_missing_property_0), [token.getText()]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: `[name: string]: ${typeString};` - }] - }] - }]; - } - - // Want to infer type of x when possible. ie: - // * assignment, - // * function call argument: foo(this.x) where foo(x: SomeType) - // * expression with a type assertion: this.x as MyFavoriteType - // * access expression: this.x.push("asdf") ... probably an array? - // * - // What if there are multiple usages of this.x? Create intersection over all usages? - - // needs to be in a class - // inferred type might be error. then add any. - // either make indexable of the inferred type - // add named member of the inferred type. +/* @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.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_accessor_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/helpers.ts b/src/services/codefixes/helpers.ts index 60efdef1e81..3eab994f84c 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -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,8 +138,8 @@ namespace ts.codefix { } } - export function getStubbedMethod(visibility: string, name: string, signature: string = '()', newlineChar: string): string { - return `${visibility}${name}${signature}${getMethodBodyStub(newlineChar)}`; + export function getStubbedMethod(visibility: string, name: string, sigString = "()", newlineChar: string): string { + return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`; } function getMethodBodyStub(newlineChar: string) {