source: imaps-frontend/node_modules/eslint/lib/rules/no-use-before-define.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 10.9 KB
Line 
1/**
2 * @fileoverview Rule to flag use of variables before they are defined
3 * @author Ilya Volodin
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Helpers
10//------------------------------------------------------------------------------
11
12const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
13const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
14
15/**
16 * Parses a given value as options.
17 * @param {any} options A value to parse.
18 * @returns {Object} The parsed options.
19 */
20function parseOptions(options) {
21 let functions = true;
22 let classes = true;
23 let variables = true;
24 let allowNamedExports = false;
25
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;
33 }
34
35 return { functions, classes, variables, allowNamedExports };
36}
37
38/**
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.
43 */
44function isInRange(node, location) {
45 return node && node.range[0] <= location && location <= node.range[1];
46}
47
48/**
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.
54 */
55function isInClassStaticInitializerRange(node, location) {
56 return node.body.some(classMember => (
57 (
58 classMember.type === "StaticBlock" &&
59 isInRange(classMember, location)
60 ) ||
61 (
62 classMember.type === "PropertyDefinition" &&
63 classMember.static &&
64 classMember.value &&
65 isInRange(classMember.value, location)
66 )
67 ));
68}
69
70/**
71 * Checks whether a given scope is the scope of 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.
75 */
76function isClassStaticInitializerScope(scope) {
77 if (scope.type === "class-static-block") {
78 return true;
79 }
80
81 if (scope.type === "class-field-initializer") {
82
83 // `scope.block` is PropertyDefinition#value node
84 const propertyDefinition = scope.block.parent;
85
86 return propertyDefinition.static;
87 }
88
89 return false;
90}
91
92/**
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:
96 * - top-level
97 * - functions
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.
102 * Example:
103 *
104 * const x = 1;
105 *
106 * x; // returns `false`
107 * () => x; // returns `true`
108 *
109 * class C {
110 * field = x; // returns `true`
111 * static field = x; // returns `false`
112 *
113 * method() {
114 * x; // returns `true`
115 * }
116 *
117 * static method() {
118 * x; // returns `true`
119 * }
120 *
121 * static {
122 * x; // returns `false`
123 * }
124 * }
125 * @param {eslint-scope.Reference} reference A reference to check.
126 * @returns {boolean} `true` if the reference is from a separate execution context.
127 */
128function isFromSeparateExecutionContext(reference) {
129 const variable = reference.resolved;
130 let scope = reference.from;
131
132 // Scope#variableScope represents execution context
133 while (variable.scope.variableScope !== scope.variableScope) {
134 if (isClassStaticInitializerScope(scope.variableScope)) {
135 scope = scope.variableScope.upper;
136 } else {
137 return true;
138 }
139 }
140
141 return false;
142}
143
144/**
145 * Checks whether or not a given reference is evaluated during the initialization of its variable.
146 *
147 * This returns `true` in the following cases:
148 *
149 * var a = a
150 * var [a = a] = list
151 * var {a = a} = obj
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; }) {}
159 * class C { [C]; }
160 * @param {Reference} reference A reference to check.
161 * @returns {boolean} `true` if the reference is evaluated during the initialization.
162 */
163function isEvaluatedDuringInitialization(reference) {
164 if (isFromSeparateExecutionContext(reference)) {
165
166 /*
167 * Even if the reference appears in the initializer, it isn't evaluated during the initialization.
168 * For example, `const x = () => x;` is valid.
169 */
170 return false;
171 }
172
173 const location = reference.identifier.range[1];
174 const definition = reference.resolved.defs[0];
175
176 if (definition.type === "ClassName") {
177
178 // `ClassDeclaration` or `ClassExpression`
179 const classDefinition = definition.node;
180
181 return (
182 isInRange(classDefinition, location) &&
183
184 /*
185 * Class binding is initialized before running static initializers.
186 * For example, `class C { static foo = C; static { bar = C; } }` is valid.
187 */
188 !isInClassStaticInitializerRange(classDefinition.body, location)
189 );
190 }
191
192 let node = definition.name.parent;
193
194 while (node) {
195 if (node.type === "VariableDeclarator") {
196 if (isInRange(node.init, location)) {
197 return true;
198 }
199 if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
200 isInRange(node.parent.parent.right, location)
201 ) {
202 return true;
203 }
204 break;
205 } else if (node.type === "AssignmentPattern") {
206 if (isInRange(node.right, location)) {
207 return true;
208 }
209 } else if (SENTINEL_TYPE.test(node.type)) {
210 break;
211 }
212
213 node = node.parent;
214 }
215
216 return false;
217}
218
219//------------------------------------------------------------------------------
220// Rule Definition
221//------------------------------------------------------------------------------
222
223/** @type {import('../shared/types').Rule} */
224module.exports = {
225 meta: {
226 type: "problem",
227
228 docs: {
229 description: "Disallow the use of variables before they are defined",
230 recommended: false,
231 url: "https://eslint.org/docs/latest/rules/no-use-before-define"
232 },
233
234 schema: [
235 {
236 oneOf: [
237 {
238 enum: ["nofunc"]
239 },
240 {
241 type: "object",
242 properties: {
243 functions: { type: "boolean" },
244 classes: { type: "boolean" },
245 variables: { type: "boolean" },
246 allowNamedExports: { type: "boolean" }
247 },
248 additionalProperties: false
249 }
250 ]
251 }
252 ],
253
254 messages: {
255 usedBeforeDefined: "'{{name}}' was used before it was defined."
256 }
257 },
258
259 create(context) {
260 const options = parseOptions(context.options[0]);
261 const sourceCode = context.sourceCode;
262
263 /**
264 * Determines whether a given reference should be checked.
265 *
266 * Returns `false` if the reference is:
267 * - initialization's (e.g., `let a = 1`).
268 * - referring to an undefined variable (i.e., if it's an unresolved reference).
269 * - referring to a variable that is defined, but not in the given source code
270 * (e.g., global environment variable or `arguments` in functions).
271 * - allowed by options.
272 * @param {eslint-scope.Reference} reference The reference
273 * @returns {boolean} `true` if the reference should be checked
274 */
275 function shouldCheck(reference) {
276 if (reference.init) {
277 return false;
278 }
279
280 const { identifier } = reference;
281
282 if (
283 options.allowNamedExports &&
284 identifier.parent.type === "ExportSpecifier" &&
285 identifier.parent.local === identifier
286 ) {
287 return false;
288 }
289
290 const variable = reference.resolved;
291
292 if (!variable || variable.defs.length === 0) {
293 return false;
294 }
295
296 const definitionType = variable.defs[0].type;
297
298 if (!options.functions && definitionType === "FunctionName") {
299 return false;
300 }
301
302 if (
303 (
304 !options.variables && definitionType === "Variable" ||
305 !options.classes && definitionType === "ClassName"
306 ) &&
307
308 // don't skip checking the reference if it's in the same execution context, because of TDZ
309 isFromSeparateExecutionContext(reference)
310 ) {
311 return false;
312 }
313
314 return true;
315 }
316
317 /**
318 * Finds and validates all references in a given scope and its child scopes.
319 * @param {eslint-scope.Scope} scope The scope object.
320 * @returns {void}
321 */
322 function checkReferencesInScope(scope) {
323 scope.references.filter(shouldCheck).forEach(reference => {
324 const variable = reference.resolved;
325 const definitionIdentifier = variable.defs[0].name;
326
327 if (
328 reference.identifier.range[1] < definitionIdentifier.range[1] ||
329 isEvaluatedDuringInitialization(reference)
330 ) {
331 context.report({
332 node: reference.identifier,
333 messageId: "usedBeforeDefined",
334 data: reference.identifier
335 });
336 }
337 });
338
339 scope.childScopes.forEach(checkReferencesInScope);
340 }
341
342 return {
343 Program(node) {
344 checkReferencesInScope(sourceCode.getScope(node));
345 }
346 };
347 }
348};
Note: See TracBrowser for help on using the repository browser.