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: function getCodeActionsToAddMissingConst(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