• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixId = "addMissingConst";
4    const errorCodes = [
5        Diagnostics.Cannot_find_name_0.code,
6        Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer.code
7    ];
8
9    registerCodeFix({
10        errorCodes,
11        getCodeActions: (context) => {
12            const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start, context.program));
13            if (changes.length > 0) {
14                return [createCodeFixAction(fixId, changes, Diagnostics.Add_const_to_unresolved_variable, fixId, Diagnostics.Add_const_to_all_unresolved_variables)];
15            }
16        },
17        fixIds: [fixId],
18        getAllCodeActions: context => {
19            const fixedNodes = new Set<Node>();
20            return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start, context.program, fixedNodes));
21        },
22    });
23
24    function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program, fixedNodes?: Set<Node>) {
25        const token = getTokenAtPosition(sourceFile, pos);
26        const forInitializer = findAncestor(token, node =>
27            isForInOrOfStatement(node.parent) ? node.parent.initializer === node :
28            isPossiblyPartOfDestructuring(node) ? false : "quit"
29        );
30        if (forInitializer) return applyChange(changeTracker, forInitializer, sourceFile, fixedNodes);
31
32        const parent = token.parent;
33        if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken && isExpressionStatement(parent.parent)) {
34            return applyChange(changeTracker, token, sourceFile, fixedNodes);
35        }
36
37        if (isArrayLiteralExpression(parent)) {
38            const checker = program.getTypeChecker();
39            if (!every(parent.elements, element => arrayElementCouldBeVariableDeclaration(element, checker))) {
40                return;
41            }
42
43            return applyChange(changeTracker, parent, sourceFile, fixedNodes);
44        }
45
46        const commaExpression = findAncestor(token, node =>
47            isExpressionStatement(node.parent) ? true :
48            isPossiblyPartOfCommaSeperatedInitializer(node) ? false : "quit"
49        );
50        if (commaExpression) {
51            const checker = program.getTypeChecker();
52            if (!expressionCouldBeVariableDeclaration(commaExpression, checker)) {
53                return;
54            }
55
56            return applyChange(changeTracker, commaExpression, sourceFile, fixedNodes);
57        }
58    }
59
60    function applyChange(changeTracker: textChanges.ChangeTracker, initializer: Node, sourceFile: SourceFile, fixedNodes?: Set<Node>) {
61        if (!fixedNodes || tryAddToSet(fixedNodes, initializer)) {
62            changeTracker.insertModifierBefore(sourceFile, SyntaxKind.ConstKeyword, initializer);
63        }
64    }
65
66    function isPossiblyPartOfDestructuring(node: Node): boolean {
67        switch (node.kind) {
68            case SyntaxKind.Identifier:
69            case SyntaxKind.ArrayLiteralExpression:
70            case SyntaxKind.ObjectLiteralExpression:
71            case SyntaxKind.PropertyAssignment:
72            case SyntaxKind.ShorthandPropertyAssignment:
73                return true;
74            default:
75                return false;
76        }
77    }
78
79    function arrayElementCouldBeVariableDeclaration(expression: Expression, checker: TypeChecker): boolean {
80        const identifier =
81            isIdentifier(expression) ? expression :
82            isAssignmentExpression(expression, /*excludeCompoundAssignment*/ true) && isIdentifier(expression.left) ? expression.left :
83            undefined;
84        return !!identifier && !checker.getSymbolAtLocation(identifier);
85    }
86
87    function isPossiblyPartOfCommaSeperatedInitializer(node: Node): boolean {
88        switch (node.kind) {
89            case SyntaxKind.Identifier:
90            case SyntaxKind.BinaryExpression:
91            case SyntaxKind.CommaToken:
92                return true;
93            default:
94                return false;
95        }
96    }
97
98    function expressionCouldBeVariableDeclaration(expression: Node, checker: TypeChecker): boolean {
99        if (!isBinaryExpression(expression)) {
100            return false;
101        }
102
103        if (expression.operatorToken.kind === SyntaxKind.CommaToken) {
104            return every([expression.left, expression.right], expression => expressionCouldBeVariableDeclaration(expression, checker));
105        }
106
107        return expression.operatorToken.kind === SyntaxKind.EqualsToken
108            && isIdentifier(expression.left)
109            && !checker.getSymbolAtLocation(expression.left);
110    }
111}
112