]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-use-before-define.js
2 * @fileoverview Rule to flag use of variables before they are defined
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const SENTINEL_TYPE
= /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
13 const FOR_IN_OF_TYPE
= /^For(?:In|Of)Statement$/u;
16 * Parses a given value as options.
17 * @param {any} options A value to parse.
18 * @returns {Object} The parsed options.
20 function parseOptions(options
) {
24 let allowNamedExports
= false;
26 if (typeof options
=== "string") {
27 functions
= (options
!== "nofunc");
28 } else if (typeof options
=== "object" && options
!== null) {
29 functions
= options
.functions
!== false;
30 classes
= options
.classes
!== false;
31 variables
= options
.variables
!== false;
32 allowNamedExports
= !!options
.allowNamedExports
;
35 return { functions
, classes
, variables
, allowNamedExports
};
39 * Checks whether or not a given location is inside of the range of a given node.
40 * @param {ASTNode} node An node to check.
41 * @param {number} location A location to check.
42 * @returns {boolean} `true` if the location is inside of the range of the node.
44 function isInRange(node
, location
) {
45 return node
&& node
.range
[0] <= location
&& location
<= node
.range
[1];
49 * Checks whether or not a given location is inside of the range of a class static initializer.
50 * Static initializers are static blocks and initializers of static fields.
51 * @param {ASTNode} node `ClassBody` node to check static initializers.
52 * @param {number} location A location to check.
53 * @returns {boolean} `true` if the location is inside of a class static initializer.
55 function isInClassStaticInitializerRange(node
, location
) {
56 return node
.body
.some(classMember
=> (
58 classMember
.type
=== "StaticBlock" &&
59 isInRange(classMember
, location
)
62 classMember
.type
=== "PropertyDefinition" &&
65 isInRange(classMember
.value
, location
)
71 * Checks whether a given scope is the scope of a a class static initializer.
72 * Static initializers are static blocks and initializers of static fields.
73 * @param {eslint-scope.Scope} scope A scope to check.
74 * @returns {boolean} `true` if the scope is a class static initializer scope.
76 function isClassStaticInitializerScope(scope
) {
77 if (scope
.type
=== "class-static-block") {
81 if (scope
.type
=== "class-field-initializer") {
83 // `scope.block` is PropertyDefinition#value node
84 const propertyDefinition
= scope
.block
.parent
;
86 return propertyDefinition
.static;
93 * Checks whether a given reference is evaluated in an execution context
94 * that isn't the one where the variable it refers to is defined.
95 * Execution contexts are:
98 * - class field initializers (implicit functions)
99 * - class static blocks (implicit functions)
100 * Static class field initializers and class static blocks are automatically run during the class definition evaluation,
101 * and therefore we'll consider them as a part of the parent execution context.
106 * x; // returns `false`
107 * () => x; // returns `true`
110 * field = x; // returns `true`
111 * static field = x; // returns `false`
114 * x; // returns `true`
118 * x; // returns `true`
122 * x; // returns `false`
125 * @param {eslint-scope.Reference} reference A reference to check.
126 * @returns {boolean} `true` if the reference is from a separate execution context.
128 function isFromSeparateExecutionContext(reference
) {
129 const variable
= reference
.resolved
;
130 let scope
= reference
.from;
132 // Scope#variableScope represents execution context
133 while (variable
.scope
.variableScope
!== scope
.variableScope
) {
134 if (isClassStaticInitializerScope(scope
.variableScope
)) {
135 scope
= scope
.variableScope
.upper
;
145 * Checks whether or not a given reference is evaluated during the initialization of its variable.
147 * This returns `true` in the following cases:
152 * for (var a in a) {}
153 * for (var a of a) {}
154 * var C = class { [C]; };
155 * var C = class { static foo = C; };
156 * var C = class { static { foo = C; } };
157 * class C extends C {}
158 * class C extends (class { static foo = C; }) {}
160 * @param {Reference} reference A reference to check.
161 * @returns {boolean} `true` if the reference is evaluated during the initialization.
163 function isEvaluatedDuringInitialization(reference
) {
164 if (isFromSeparateExecutionContext(reference
)) {
167 * Even if the reference appears in the initializer, it isn't evaluated during the initialization.
168 * For example, `const x = () => x;` is valid.
173 const location
= reference
.identifier
.range
[1];
174 const definition
= reference
.resolved
.defs
[0];
176 if (definition
.type
=== "ClassName") {
178 // `ClassDeclaration` or `ClassExpression`
179 const classDefinition
= definition
.node
;
182 isInRange(classDefinition
, location
) &&
185 * Class binding is initialized before running static initializers.
186 * For example, `class C { static foo = C; static { bar = C; } }` is valid.
188 !isInClassStaticInitializerRange(classDefinition
.body
, location
)
192 let node
= definition
.name
.parent
;
195 if (node
.type
=== "VariableDeclarator") {
196 if (isInRange(node
.init
, location
)) {
199 if (FOR_IN_OF_TYPE
.test(node
.parent
.parent
.type
) &&
200 isInRange(node
.parent
.parent
.right
, location
)
205 } else if (node
.type
=== "AssignmentPattern") {
206 if (isInRange(node
.right
, location
)) {
209 } else if (SENTINEL_TYPE
.test(node
.type
)) {
219 //------------------------------------------------------------------------------
221 //------------------------------------------------------------------------------
223 /** @type {import('../shared/types').Rule} */
229 description
: "Disallow the use of variables before they are defined",
231 url
: "https://eslint.org/docs/rules/no-use-before-define"
243 functions
: { type
: "boolean" },
244 classes
: { type
: "boolean" },
245 variables
: { type
: "boolean" },
246 allowNamedExports
: { type
: "boolean" }
248 additionalProperties
: false
255 usedBeforeDefined
: "'{{name}}' was used before it was defined."
260 const options
= parseOptions(context
.options
[0]);
263 * Determines whether a given reference should be checked.
265 * Returns `false` if the reference is:
266 * - initialization's (e.g., `let a = 1`).
267 * - referring to an undefined variable (i.e., if it's an unresolved reference).
268 * - referring to a variable that is defined, but not in the given source code
269 * (e.g., global environment variable or `arguments` in functions).
270 * - allowed by options.
271 * @param {eslint-scope.Reference} reference The reference
272 * @returns {boolean} `true` if the reference should be checked
274 function shouldCheck(reference
) {
275 if (reference
.init
) {
279 const { identifier
} = reference
;
282 options
.allowNamedExports
&&
283 identifier
.parent
.type
=== "ExportSpecifier" &&
284 identifier
.parent
.local
=== identifier
289 const variable
= reference
.resolved
;
291 if (!variable
|| variable
.defs
.length
=== 0) {
295 const definitionType
= variable
.defs
[0].type
;
297 if (!options
.functions
&& definitionType
=== "FunctionName") {
303 !options
.variables
&& definitionType
=== "Variable" ||
304 !options
.classes
&& definitionType
=== "ClassName"
307 // don't skip checking the reference if it's in the same execution context, because of TDZ
308 isFromSeparateExecutionContext(reference
)
317 * Finds and validates all references in a given scope and its child scopes.
318 * @param {eslint-scope.Scope} scope The scope object.
321 function checkReferencesInScope(scope
) {
322 scope
.references
.filter(shouldCheck
).forEach(reference
=> {
323 const variable
= reference
.resolved
;
324 const definitionIdentifier
= variable
.defs
[0].name
;
327 reference
.identifier
.range
[1] < definitionIdentifier
.range
[1] ||
328 isEvaluatedDuringInitialization(reference
)
331 node
: reference
.identifier
,
332 messageId
: "usedBeforeDefined",
333 data
: reference
.identifier
338 scope
.childScopes
.forEach(checkReferencesInScope
);
343 checkReferencesInScope(context
.getScope());