mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-17 00:34:47 -05:00
Add support across files and static, js methods
This commit is contained in:
@@ -8,116 +8,151 @@ namespace ts.codefix {
|
||||
|
||||
function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined {
|
||||
|
||||
const sourceFile = context.sourceFile;
|
||||
const tokenSourceFile = context.sourceFile;
|
||||
const start = context.span.start;
|
||||
// This is the identifier of the missing property. eg:
|
||||
// The identifier of the missing property. eg:
|
||||
// this.missing = 1;
|
||||
// ^^^^^^^
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const token = getTokenAtPosition(tokenSourceFile, start, /*includeJsDocComment*/ false);
|
||||
|
||||
if (token.kind !== SyntaxKind.Identifier) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!isPropertyAccessExpression(token.parent) || token.parent.expression.kind !== SyntaxKind.ThisKeyword) {
|
||||
if (!isPropertyAccessExpression(token.parent)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const classMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false);
|
||||
if (!isClassElement(classMemberDeclaration)) {
|
||||
return undefined;
|
||||
const tokenName = token.getText(tokenSourceFile);
|
||||
|
||||
let makeStatic = false;
|
||||
let classDeclaration: ClassLikeDeclaration;
|
||||
|
||||
if (token.parent.expression.kind === SyntaxKind.ThisKeyword) {
|
||||
const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false);
|
||||
if (!isClassElement(containingClassMemberDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
classDeclaration = <ClassLikeDeclaration>containingClassMemberDeclaration.parent;
|
||||
|
||||
// Property accesses on `this` in a static method are accesses of a static member.
|
||||
makeStatic = classDeclaration && hasModifier(containingClassMemberDeclaration, ModifierFlags.Static);
|
||||
}
|
||||
else {
|
||||
|
||||
const checker = context.program.getTypeChecker();
|
||||
const leftExpression = token.parent.expression;
|
||||
const leftExpressionType = checker.getTypeAtLocation(leftExpression);
|
||||
|
||||
if (leftExpressionType.flags & TypeFlags.Object) {
|
||||
const symbol = leftExpressionType.symbol;
|
||||
if (symbol.flags & SymbolFlags.Class) {
|
||||
classDeclaration = symbol.declarations && <ClassLikeDeclaration>symbol.declarations[0];
|
||||
if (getObjectFlags(leftExpressionType) & ObjectFlags.Anonymous && symbol.flags & SymbolFlags.Class && !checker.getBaseTypeVariableOfClass(symbol)) {
|
||||
makeStatic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const classDeclaration = <ClassLikeDeclaration>classMemberDeclaration.parent;
|
||||
if (!classDeclaration || !isClassLike(classDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tokenName = token.getText(sourceFile);
|
||||
const isStatic = hasModifier(classMemberDeclaration, ModifierFlags.Static);
|
||||
const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration);
|
||||
const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile);
|
||||
|
||||
return isInJavaScriptFile(sourceFile) ? getActionsForAddMissingMemberInJavaScriptFile() : getActionsForAddMissingMemberInTypeScriptFile();
|
||||
return isInJavaScriptFile(classDeclarationSourceFile) ?
|
||||
getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) :
|
||||
getActionsForAddMissingMemberInTypeScriptFile(classDeclaration, makeStatic);
|
||||
|
||||
function getActionsForAddMissingMemberInJavaScriptFile(): CodeAction[] | undefined {
|
||||
if (isStatic) {
|
||||
function getActionsForAddMissingMemberInJavaScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined {
|
||||
let actions: CodeAction[];
|
||||
|
||||
const methodCodeAction = getActionForMethodDeclaration();
|
||||
if (methodCodeAction) {
|
||||
actions = [methodCodeAction];
|
||||
}
|
||||
|
||||
if (makeStatic) {
|
||||
if (classDeclaration.kind === SyntaxKind.ClassExpression) {
|
||||
return undefined;
|
||||
return actions;
|
||||
}
|
||||
|
||||
const className = classDeclaration.name.getText();
|
||||
|
||||
return [{
|
||||
const initializeStaticAction = {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [tokenName]),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
fileName: classDeclarationSourceFile.fileName,
|
||||
textChanges: [{
|
||||
span: { start: classDeclaration.getEnd(), length: 0 },
|
||||
newText: `${context.newLineCharacter}${className}.${tokenName} = undefined;${context.newLineCharacter}`
|
||||
}]
|
||||
}]
|
||||
}];
|
||||
};
|
||||
|
||||
(actions || (actions = [])).push(initializeStaticAction);
|
||||
return actions;
|
||||
}
|
||||
else {
|
||||
const classConstructor = getFirstConstructorWithBody(classDeclaration);
|
||||
if (!classConstructor) {
|
||||
return undefined;
|
||||
return actions;
|
||||
}
|
||||
|
||||
return [{
|
||||
const initializeAction = {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
fileName: classDeclarationSourceFile.fileName,
|
||||
textChanges: [{
|
||||
span: { start: classConstructor.body.getEnd() - 1, length: 0 },
|
||||
newText: `this.${tokenName} = undefined;${context.newLineCharacter}`
|
||||
}]
|
||||
}]
|
||||
}];
|
||||
};
|
||||
|
||||
(actions || (actions = [])).push(initializeAction);
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
function getActionsForAddMissingMemberInTypeScriptFile(): CodeAction[] | undefined {
|
||||
const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile);
|
||||
function getActionsForAddMissingMemberInTypeScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined {
|
||||
let actions: CodeAction[];
|
||||
|
||||
if (token.parent.parent.kind === SyntaxKind.CallExpression) {
|
||||
const callExpression = <CallExpression>token.parent.parent;
|
||||
const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName);
|
||||
|
||||
const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
|
||||
methodDeclarationChangeTracker.insertNodeAfter(sourceFile, openBrace, methodDeclaration, { suffix: context.newLineCharacter });
|
||||
actions = [{
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_method_0), [tokenName]),
|
||||
changes: methodDeclarationChangeTracker.getChanges()
|
||||
}];
|
||||
const methodCodeAction = getActionForMethodDeclaration();
|
||||
if (methodCodeAction) {
|
||||
actions = [methodCodeAction];
|
||||
}
|
||||
|
||||
let typeNode: TypeNode;
|
||||
if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
|
||||
const binaryExpression = token.parent.parent as BinaryExpression;
|
||||
const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left;
|
||||
const checker = context.program.getTypeChecker();
|
||||
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right)));
|
||||
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression)));
|
||||
typeNode = checker.typeToTypeNode(widenedType, classDeclaration);
|
||||
}
|
||||
typeNode = typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
|
||||
const property = createProperty(
|
||||
/*decorators*/undefined,
|
||||
/*modifiers*/ isStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
tokenName,
|
||||
/*questionToken*/ undefined,
|
||||
typeNode,
|
||||
/*initializer*/ undefined);
|
||||
const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
|
||||
propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter });
|
||||
propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter });
|
||||
|
||||
(actions || (actions = [])).push({
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [tokenName]),
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Declare_property_0), [tokenName]),
|
||||
changes: propertyChangeTracker.getChanges()
|
||||
});
|
||||
|
||||
if (!isStatic) {
|
||||
if (!makeStatic) {
|
||||
// Index signatures cannot have the static modifier.
|
||||
const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword);
|
||||
const indexingParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
@@ -134,15 +169,32 @@ namespace ts.codefix {
|
||||
typeNode);
|
||||
|
||||
const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
|
||||
indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { suffix: context.newLineCharacter });
|
||||
indexSignatureChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter });
|
||||
|
||||
actions.push({
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [tokenName]),
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]),
|
||||
changes: indexSignatureChangeTracker.getChanges()
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function getActionForMethodDeclaration(): CodeAction | undefined {
|
||||
if (token.parent.parent.kind === SyntaxKind.CallExpression) {
|
||||
const callExpression = <CallExpression>token.parent.parent;
|
||||
const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, /*includeTypeScriptSyntax*/ true, makeStatic);
|
||||
|
||||
const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
|
||||
methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter });
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(makeStatic ?
|
||||
Diagnostics.Declare_method_0 :
|
||||
Diagnostics.Declare_static_method_0),
|
||||
[tokenName]),
|
||||
changes: methodDeclarationChangeTracker.getChanges()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,46 +142,50 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMethodFromCallExpression(callExpression: CallExpression, methodName: string): MethodDeclaration {
|
||||
const argCount = callExpression.arguments.length;
|
||||
const parameters: ParameterDeclaration[] = [];
|
||||
for (let i = 0; i < argCount; i++) {
|
||||
const typeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
const newParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
`arg${i}`,
|
||||
/*questionToken*/ undefined,
|
||||
typeNode,
|
||||
/*initializer*/ undefined);
|
||||
parameters.push(newParameter);
|
||||
}
|
||||
export function createMethodFromCallExpression(callExpression: CallExpression, methodName: string, includeTypeScriptSyntax: boolean, makeStatic: boolean): MethodDeclaration {
|
||||
const parameters = createDummyParameters(callExpression.arguments.length, /*names*/ undefined, /*minArgumentCount*/ undefined, includeTypeScriptSyntax);
|
||||
|
||||
const typeArgCount = callExpression.typeArguments ? callExpression.typeArguments.length : 0;
|
||||
let typeParameters: TypeParameterDeclaration[];
|
||||
for (let i = 0; i < typeArgCount; i++) {
|
||||
const name = typeArgCount < 8 ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`;
|
||||
const typeParameter = createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined);
|
||||
|
||||
(typeParameters ? typeParameters : typeParameters = []).push(typeParameter);
|
||||
if (includeTypeScriptSyntax) {
|
||||
const typeArgCount = callExpression.typeArguments ? callExpression.typeArguments.length : 0;
|
||||
for (let i = 0; i < typeArgCount; i++) {
|
||||
const name = typeArgCount < 8 ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`;
|
||||
const typeParameter = createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined);
|
||||
(typeParameters ? typeParameters : typeParameters = []).push(typeParameter);
|
||||
}
|
||||
}
|
||||
|
||||
const newMethod = createMethod(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
/*asteriskToken*/ undefined,
|
||||
methodName,
|
||||
/*questionToken*/ undefined,
|
||||
typeParameters,
|
||||
parameters,
|
||||
/*type*/ undefined,
|
||||
/*type*/ includeTypeScriptSyntax ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined,
|
||||
createStubbedMethodBody()
|
||||
);
|
||||
|
||||
return newMethod;
|
||||
}
|
||||
|
||||
function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, addAnyType: boolean) {
|
||||
const parameters: ParameterDeclaration[] = [];
|
||||
for (let i = 0; i < argCount; i++) {
|
||||
const newParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
/*name*/ names && names[i] || `arg${i}`,
|
||||
/*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined,
|
||||
/*type*/ addAnyType ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined,
|
||||
/*initializer*/ undefined);
|
||||
parameters.push(newParameter);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, optional: boolean, modifiers: Modifier[] | undefined): MethodDeclaration {
|
||||
/** This is *a* signature with the maximal number of arguments,
|
||||
* such that if there is a "maximal" signature without rest arguments,
|
||||
@@ -203,19 +207,7 @@ namespace ts.codefix {
|
||||
const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0);
|
||||
const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.getName());
|
||||
|
||||
const parameters: ParameterDeclaration[] = [];
|
||||
for (let i = 0; i < maxNonRestArgs; i++) {
|
||||
const anyType = createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
const newParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
maxArgsParameterSymbolNames[i],
|
||||
/*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined,
|
||||
anyType,
|
||||
/*initializer*/ undefined);
|
||||
parameters.push(newParameter);
|
||||
}
|
||||
const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*addAnyType*/ true);
|
||||
|
||||
if (someSigHasRestParameter) {
|
||||
const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword));
|
||||
|
||||
Reference in New Issue
Block a user