This commit is contained in:
Arthur Ozga 2017-02-15 15:15:09 -08:00
parent 9110461294
commit cf3b4d6b00
3 changed files with 71 additions and 102 deletions

View file

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

View file

@ -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<number>(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<T>(this.x) where foo(x: SomeType<T>)
// * 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};`
}]
}]
}];
}
}

View file

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