-
Notifications
You must be signed in to change notification settings - Fork 12.8k
/
Copy pathfixImplicitThis.ts
80 lines (75 loc) · 3.54 KB
/
fixImplicitThis.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import {
codeFixAll,
createCodeFixAction,
registerCodeFix,
} from "../_namespaces/ts.codefix.js";
import {
ANONYMOUS,
Debug,
DiagnosticOrDiagnosticAndArguments,
Diagnostics,
emptyArray,
factory,
FindAllReferences,
findChildOfKind,
getThisContainer,
getTokenAtPosition,
isFunctionDeclaration,
isFunctionExpression,
isSourceFile,
isThis,
SourceFile,
SyntaxKind,
textChanges,
TypeChecker,
} from "../_namespaces/ts.js";
const fixId = "fixImplicitThis";
const errorCodes = [Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code];
registerCodeFix({
errorCodes,
getCodeActions: function getCodeActionsToFixImplicitThis(context) {
const { sourceFile, program, span } = context;
let diagnostic: DiagnosticOrDiagnosticAndArguments | undefined;
const changes = textChanges.ChangeTracker.with(context, t => {
diagnostic = doChange(t, sourceFile, span.start, program.getTypeChecker());
});
return diagnostic ? [createCodeFixAction(fixId, changes, diagnostic, fixId, Diagnostics.Fix_all_implicit_this_errors)] : emptyArray;
},
fixIds: [fixId],
getAllCodeActions: context =>
codeFixAll(context, errorCodes, (changes, diag) => {
doChange(changes, diag.file, diag.start, context.program.getTypeChecker());
}),
});
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, checker: TypeChecker): DiagnosticOrDiagnosticAndArguments | undefined {
const token = getTokenAtPosition(sourceFile, pos);
if (!isThis(token)) return undefined;
const fn = getThisContainer(token, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
if (!isFunctionDeclaration(fn) && !isFunctionExpression(fn)) return undefined;
if (!isSourceFile(getThisContainer(fn, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false))) { // 'this' is defined outside, convert to arrow function
const fnKeyword = Debug.checkDefined(findChildOfKind(fn, SyntaxKind.FunctionKeyword, sourceFile));
const { name } = fn;
const body = Debug.checkDefined(fn.body); // Should be defined because the function contained a 'this' expression
if (isFunctionExpression(fn)) {
if (name && FindAllReferences.Core.isSymbolReferencedInFile(name, checker, sourceFile, body)) {
// Function expression references itself. To fix we would have to extract it to a const.
return undefined;
}
// `function() {}` --> `() => {}`
changes.delete(sourceFile, fnKeyword);
if (name) {
changes.delete(sourceFile, name);
}
changes.insertText(sourceFile, body.pos, " =>");
return [Diagnostics.Convert_function_expression_0_to_arrow_function, name ? name.text : ANONYMOUS];
}
else {
// `function f() {}` => `const f = () => {}`
// `name` should be defined because we only do this in inner contexts, and name is only undefined for `export default function() {}`.
changes.replaceNode(sourceFile, fnKeyword, factory.createToken(SyntaxKind.ConstKeyword));
changes.insertText(sourceFile, name!.end, " = ");
changes.insertText(sourceFile, body.pos, " =>");
return [Diagnostics.Convert_function_declaration_0_to_arrow_function, name!.text];
}
}
}