• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixId = "fixImplicitThis";
4    const errorCodes = [Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code];
5    registerCodeFix({
6        errorCodes,
7        getCodeActions: function getCodeActionsToFixImplicitThis(context) {
8            const { sourceFile, program, span } = context;
9            let diagnostic: DiagnosticAndArguments | undefined;
10            const changes = textChanges.ChangeTracker.with(context, t => {
11                diagnostic = doChange(t, sourceFile, span.start, program.getTypeChecker());
12            });
13            return diagnostic ? [createCodeFixAction(fixId, changes, diagnostic, fixId, Diagnostics.Fix_all_implicit_this_errors)] : emptyArray;
14        },
15        fixIds: [fixId],
16        getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
17            doChange(changes, diag.file, diag.start, context.program.getTypeChecker());
18        }),
19    });
20
21    function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, checker: TypeChecker): DiagnosticAndArguments | undefined {
22        const token = getTokenAtPosition(sourceFile, pos);
23        if (!isThis(token)) return undefined;
24
25        const fn = getThisContainer(token, /*includeArrowFunctions*/ false);
26        if (!isFunctionDeclaration(fn) && !isFunctionExpression(fn)) return undefined;
27
28        if (!isSourceFile(getThisContainer(fn, /*includeArrowFunctions*/ false))) { // 'this' is defined outside, convert to arrow function
29            const fnKeyword = Debug.checkDefined(findChildOfKind(fn, SyntaxKind.FunctionKeyword, sourceFile));
30            const { name } = fn;
31            const body = Debug.checkDefined(fn.body); // Should be defined because the function contained a 'this' expression
32            if (isFunctionExpression(fn)) {
33                if (name && FindAllReferences.Core.isSymbolReferencedInFile(name, checker, sourceFile, body)) {
34                    // Function expression references itself. To fix we would have to extract it to a const.
35                    return undefined;
36                }
37
38                // `function() {}` --> `() => {}`
39                changes.delete(sourceFile, fnKeyword);
40                if (name) {
41                    changes.delete(sourceFile, name);
42                }
43                changes.insertText(sourceFile, body.pos, " =>");
44                return [Diagnostics.Convert_function_expression_0_to_arrow_function, name ? name.text : ANONYMOUS];
45            }
46            else {
47                // `function f() {}` => `const f = () => {}`
48                // `name` should be defined because we only do this in inner contexts, and name is only undefined for `export default function() {}`.
49                changes.replaceNode(sourceFile, fnKeyword, factory.createToken(SyntaxKind.ConstKeyword));
50                changes.insertText(sourceFile, name!.end, " = ");
51                changes.insertText(sourceFile, body.pos, " =>");
52                return [Diagnostics.Convert_function_declaration_0_to_arrow_function, name!.text];
53            }
54        }
55    }
56}
57