Skip to content

Commit 21fc30f

Browse files
author
Arthur Ozga
committed
Add support across files and static, js methods
1 parent c125f08 commit 21fc30f

File tree

7 files changed

+137
-87
lines changed

7 files changed

+137
-87
lines changed

src/compiler/checker.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ namespace ts {
110110
getParameterType: getTypeAtPosition,
111111
getReturnTypeOfSignature,
112112
getNonNullableType,
113+
getBaseTypeVariableOfClass,
113114
typeToTypeNode: nodeBuilder.typeToTypeNode,
114115
indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration,
115116
signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration,
@@ -681,10 +682,6 @@ namespace ts {
681682
return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 });
682683
}
683684

684-
function getObjectFlags(type: Type): ObjectFlags {
685-
return type.flags & TypeFlags.Object ? (<ObjectType>type).objectFlags : 0;
686-
}
687-
688685
function isGlobalSourceFile(node: Node) {
689686
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
690687
}
@@ -2282,7 +2279,7 @@ namespace ts {
22822279

22832280
function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string {
22842281
const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName);
2285-
Debug.assert(typeNode !== undefined, "should always get typenode?");
2282+
Debug.assert(typeNode !== undefined, "should always get typenode");
22862283
const options = { removeComments: true };
22872284
const writer = createTextWriter("");
22882285
const printer = createPrinter(options, writer);

src/compiler/diagnosticMessages.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3555,11 +3555,11 @@
35553555
"category": "Message",
35563556
"code": 90015
35573557
},
3558-
"Add declaration for missing property '{0}'.": {
3558+
"Declare property '{0}'.": {
35593559
"category": "Message",
35603560
"code": 90016
35613561
},
3562-
"Add index signature for missing property '{0}'.": {
3562+
"Add index signature for property '{0}'.": {
35633563
"category": "Message",
35643564
"code": 90017
35653565
},
@@ -3583,10 +3583,14 @@
35833583
"category": "Message",
35843584
"code": 90022
35853585
},
3586-
"Add declaration for missing method '{0}'.": {
3586+
"Declare method '{0}'.": {
35873587
"category": "Message",
35883588
"code": 90023
35893589
},
3590+
"Declare static method '{0}'.": {
3591+
"category": "Message",
3592+
"code": 90024
3593+
},
35903594

35913595
"Convert function to an ES2015 class": {
35923596
"category": "Message",

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,6 +2530,7 @@ namespace ts {
25302530
*/
25312531
/* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type;
25322532
getNonNullableType(type: Type): Type;
2533+
/* @internal */ getBaseTypeVariableOfClass(symbol: Symbol): Type | undefined;
25332534

25342535
/** Note that the resulting nodes cannot be checked. */
25352536
typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode;

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,10 @@ namespace ts {
733733
return false;
734734
}
735735

736+
export function getObjectFlags(type: Type): ObjectFlags {
737+
return type.flags & TypeFlags.Object ? (<ObjectType>type).objectFlags : 0;
738+
}
739+
736740
export function isChildOfNodeWithKind(node: Node, kind: SyntaxKind): boolean {
737741
while (node) {
738742
if (node.kind === kind) {

src/services/codefixes/fixAddMissingMember.ts

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,116 +8,151 @@ namespace ts.codefix {
88

99
function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined {
1010

11-
const sourceFile = context.sourceFile;
11+
const tokenSourceFile = context.sourceFile;
1212
const start = context.span.start;
13-
// This is the identifier of the missing property. eg:
13+
// The identifier of the missing property. eg:
1414
// this.missing = 1;
1515
// ^^^^^^^
16-
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
16+
const token = getTokenAtPosition(tokenSourceFile, start, /*includeJsDocComment*/ false);
1717

1818
if (token.kind !== SyntaxKind.Identifier) {
1919
return undefined;
2020
}
2121

22-
if (!isPropertyAccessExpression(token.parent) || token.parent.expression.kind !== SyntaxKind.ThisKeyword) {
22+
if (!isPropertyAccessExpression(token.parent)) {
2323
return undefined;
2424
}
2525

26-
const classMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false);
27-
if (!isClassElement(classMemberDeclaration)) {
28-
return undefined;
26+
const tokenName = token.getText(tokenSourceFile);
27+
28+
let makeStatic = false;
29+
let classDeclaration: ClassLikeDeclaration;
30+
31+
if (token.parent.expression.kind === SyntaxKind.ThisKeyword) {
32+
const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false);
33+
if (!isClassElement(containingClassMemberDeclaration)) {
34+
return undefined;
35+
}
36+
37+
classDeclaration = <ClassLikeDeclaration>containingClassMemberDeclaration.parent;
38+
39+
// Property accesses on `this` in a static method are accesses of a static member.
40+
makeStatic = classDeclaration && hasModifier(containingClassMemberDeclaration, ModifierFlags.Static);
41+
}
42+
else {
43+
44+
const checker = context.program.getTypeChecker();
45+
const leftExpression = token.parent.expression;
46+
const leftExpressionType = checker.getTypeAtLocation(leftExpression);
47+
48+
if (leftExpressionType.flags & TypeFlags.Object) {
49+
const symbol = leftExpressionType.symbol;
50+
if (symbol.flags & SymbolFlags.Class) {
51+
classDeclaration = symbol.declarations && <ClassLikeDeclaration>symbol.declarations[0];
52+
if (getObjectFlags(leftExpressionType) & ObjectFlags.Anonymous && symbol.flags & SymbolFlags.Class && !checker.getBaseTypeVariableOfClass(symbol)) {
53+
makeStatic = true;
54+
}
55+
}
56+
}
2957
}
3058

31-
const classDeclaration = <ClassLikeDeclaration>classMemberDeclaration.parent;
3259
if (!classDeclaration || !isClassLike(classDeclaration)) {
3360
return undefined;
3461
}
3562

36-
const tokenName = token.getText(sourceFile);
37-
const isStatic = hasModifier(classMemberDeclaration, ModifierFlags.Static);
63+
const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration);
64+
const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile);
3865

39-
return isInJavaScriptFile(sourceFile) ? getActionsForAddMissingMemberInJavaScriptFile() : getActionsForAddMissingMemberInTypeScriptFile();
66+
return isInJavaScriptFile(classDeclarationSourceFile) ?
67+
getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) :
68+
getActionsForAddMissingMemberInTypeScriptFile(classDeclaration, makeStatic);
69+
70+
function getActionsForAddMissingMemberInJavaScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined {
71+
let actions: CodeAction[];
4072

41-
function getActionsForAddMissingMemberInJavaScriptFile(): CodeAction[] | undefined {
42-
if (isStatic) {
73+
const methodCodeAction = getActionForMethodDeclaration();
74+
if (methodCodeAction) {
75+
actions = [methodCodeAction];
76+
}
77+
78+
if (makeStatic) {
4379
if (classDeclaration.kind === SyntaxKind.ClassExpression) {
44-
return undefined;
80+
return actions;
4581
}
4682

4783
const className = classDeclaration.name.getText();
4884

49-
return [{
85+
const initializeStaticAction = {
5086
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [tokenName]),
5187
changes: [{
52-
fileName: sourceFile.fileName,
88+
fileName: classDeclarationSourceFile.fileName,
5389
textChanges: [{
5490
span: { start: classDeclaration.getEnd(), length: 0 },
5591
newText: `${context.newLineCharacter}${className}.${tokenName} = undefined;${context.newLineCharacter}`
5692
}]
5793
}]
58-
}];
94+
};
5995

96+
(actions || (actions = [])).push(initializeStaticAction);
97+
return actions;
6098
}
6199
else {
62100
const classConstructor = getFirstConstructorWithBody(classDeclaration);
63101
if (!classConstructor) {
64-
return undefined;
102+
return actions;
65103
}
66104

67-
return [{
105+
const initializeAction = {
68106
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]),
69107
changes: [{
70-
fileName: sourceFile.fileName,
108+
fileName: classDeclarationSourceFile.fileName,
71109
textChanges: [{
72110
span: { start: classConstructor.body.getEnd() - 1, length: 0 },
73111
newText: `this.${tokenName} = undefined;${context.newLineCharacter}`
74112
}]
75113
}]
76-
}];
114+
};
115+
116+
(actions || (actions = [])).push(initializeAction);
117+
return actions;
77118
}
78119
}
79120

80-
function getActionsForAddMissingMemberInTypeScriptFile(): CodeAction[] | undefined {
81-
const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile);
121+
function getActionsForAddMissingMemberInTypeScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined {
82122
let actions: CodeAction[];
83123

84-
if (token.parent.parent.kind === SyntaxKind.CallExpression) {
85-
const callExpression = <CallExpression>token.parent.parent;
86-
const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName);
87-
88-
const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
89-
methodDeclarationChangeTracker.insertNodeAfter(sourceFile, openBrace, methodDeclaration, { suffix: context.newLineCharacter });
90-
actions = [{
91-
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_method_0), [tokenName]),
92-
changes: methodDeclarationChangeTracker.getChanges()
93-
}];
124+
const methodCodeAction = getActionForMethodDeclaration();
125+
if (methodCodeAction) {
126+
actions = [methodCodeAction];
94127
}
95128

96129
let typeNode: TypeNode;
97130
if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
98131
const binaryExpression = token.parent.parent as BinaryExpression;
132+
const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left;
99133
const checker = context.program.getTypeChecker();
100-
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right)));
134+
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression)));
101135
typeNode = checker.typeToTypeNode(widenedType, classDeclaration);
102136
}
103137
typeNode = typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword);
104138

105139
const property = createProperty(
106140
/*decorators*/undefined,
107-
/*modifiers*/ isStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
141+
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
108142
tokenName,
109143
/*questionToken*/ undefined,
110144
typeNode,
111145
/*initializer*/ undefined);
112146
const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
113-
propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter });
147+
propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter });
114148

115149
(actions || (actions = [])).push({
116-
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [tokenName]),
150+
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Declare_property_0), [tokenName]),
117151
changes: propertyChangeTracker.getChanges()
118152
});
119153

120-
if (!isStatic) {
154+
if (!makeStatic) {
155+
// Index signatures cannot have the static modifier.
121156
const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword);
122157
const indexingParameter = createParameter(
123158
/*decorators*/ undefined,
@@ -134,15 +169,32 @@ namespace ts.codefix {
134169
typeNode);
135170

136171
const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
137-
indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { suffix: context.newLineCharacter });
172+
indexSignatureChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter });
138173

139174
actions.push({
140-
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [tokenName]),
175+
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]),
141176
changes: indexSignatureChangeTracker.getChanges()
142177
});
143178
}
144179

145180
return actions;
146181
}
182+
183+
function getActionForMethodDeclaration(): CodeAction | undefined {
184+
if (token.parent.parent.kind === SyntaxKind.CallExpression) {
185+
const callExpression = <CallExpression>token.parent.parent;
186+
const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, /*includeTypeScriptSyntax*/ true, makeStatic);
187+
188+
const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
189+
methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter });
190+
return {
191+
description: formatStringFromArgs(getLocaleSpecificMessage(makeStatic ?
192+
Diagnostics.Declare_method_0 :
193+
Diagnostics.Declare_static_method_0),
194+
[tokenName]),
195+
changes: methodDeclarationChangeTracker.getChanges()
196+
};
197+
}
198+
}
147199
}
148200
}

0 commit comments

Comments
 (0)