|
| 1 | +import type { Scope } from '@typescript-eslint/scope-manager'; |
1 | 2 | import type { TSESTree } from '@typescript-eslint/utils';
|
2 | 3 | import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';
|
3 | 4 | import * as tsutils from 'ts-api-utils';
|
@@ -80,33 +81,66 @@ export default createRule<Options, MessageIds>({
|
80 | 81 | ) &&
|
81 | 82 | // ignore class properties as they are compile time guarded
|
82 | 83 | // also ignore function arguments as they can't be used before defined
|
83 |
| - ts.isVariableDeclaration(declaration) && |
84 |
| - // is it `const x!: number` |
85 |
| - declaration.initializer === undefined && |
86 |
| - declaration.exclamationToken === undefined && |
87 |
| - declaration.type !== undefined |
| 84 | + ts.isVariableDeclaration(declaration) |
88 | 85 | ) {
|
89 |
| - // check if the defined variable type has changed since assignment |
90 |
| - const declarationType = checker.getTypeFromTypeNode(declaration.type); |
91 |
| - const type = getConstrainedTypeAtLocation(services, node); |
| 86 | + // For var declarations, we need to check whether the node |
| 87 | + // is actually in a descendant of its declaration or not. If not, |
| 88 | + // it may be used before defined. |
| 89 | + |
| 90 | + // eg |
| 91 | + // if (Math.random() < 0.5) { |
| 92 | + // var x: number = 2; |
| 93 | + // } else { |
| 94 | + // x!.toFixed(); |
| 95 | + // } |
92 | 96 | if (
|
93 |
| - declarationType === type && |
94 |
| - // `declare`s are never narrowed, so never skip them |
95 |
| - !( |
96 |
| - ts.isVariableDeclarationList(declaration.parent) && |
97 |
| - ts.isVariableStatement(declaration.parent.parent) && |
98 |
| - tsutils.includesModifier( |
99 |
| - getModifiers(declaration.parent.parent), |
100 |
| - ts.SyntaxKind.DeclareKeyword, |
101 |
| - ) |
102 |
| - ) |
| 97 | + ts.isVariableDeclarationList(declaration.parent) && |
| 98 | + // var |
| 99 | + declaration.parent.flags === ts.NodeFlags.None && |
| 100 | + // If they are not in the same file it will not exist. |
| 101 | + // This situation must not occur using before defined. |
| 102 | + services.tsNodeToESTreeNodeMap.has(declaration) |
103 | 103 | ) {
|
104 |
| - // possibly used before assigned, so just skip it |
105 |
| - // better to false negative and skip it, than false positive and fix to compile erroring code |
106 |
| - // |
107 |
| - // no better way to figure this out right now |
108 |
| - // https://github.com/Microsoft/TypeScript/issues/31124 |
109 |
| - return true; |
| 104 | + const declaratorNode: TSESTree.VariableDeclaration = |
| 105 | + services.tsNodeToESTreeNodeMap.get(declaration); |
| 106 | + const scope = context.sourceCode.getScope(node); |
| 107 | + const declaratorScope = context.sourceCode.getScope(declaratorNode); |
| 108 | + let parentScope: Scope | null = declaratorScope; |
| 109 | + while ((parentScope = parentScope.upper)) { |
| 110 | + if (parentScope === scope) { |
| 111 | + return true; |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 |
8000
+ |
| 116 | + if ( |
| 117 | + // is it `const x!: number` |
| 118 | + declaration.initializer === undefined && |
| 119 | + declaration.exclamationToken === undefined && |
| 120 | + declaration.type !== undefined |
| 121 | + ) { |
| 122 | + // check if the defined variable type has changed since assignment |
| 123 | + const declarationType = checker.getTypeFromTypeNode(declaration.type); |
| 124 | + const type = getConstrainedTypeAtLocation(services, node); |
| 125 | + if ( |
| 126 | + declarationType === type && |
| 127 | + // `declare`s are never narrowed, so never skip them |
| 128 | + !( |
| 129 | + ts.isVariableDeclarationList(declaration.parent) && |
| 130 | + ts.isVariableStatement(declaration.parent.parent) && |
| 131 | + tsutils.includesModifier( |
| 132 | + getModifiers(declaration.parent.parent), |
| 133 | + ts.SyntaxKind.DeclareKeyword, |
| 134 | + ) |
| 135 | + ) |
| 136 | + ) { |
| 137 | + // possibly used before assigned, so just skip it |
| 138 | + // better to false negative and skip it, than false positive and fix to compile erroring code |
| 139 | + // |
| 140 | + // no better way to figure this out right now |
| 141 | + // https://github.com/Microsoft/TypeScript/issues/31124 |
| 142 | + return true; |
| 143 | + } |
110 | 144 | }
|
111 | 145 | }
|
112 | 146 | return false;
|
|
0 commit comments