1 | /**
|
---|
2 | * @fileoverview Common utils for AST.
|
---|
3 | * @author Gyandeep Singh
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | //------------------------------------------------------------------------------
|
---|
9 | // Requirements
|
---|
10 | //------------------------------------------------------------------------------
|
---|
11 |
|
---|
12 | const { KEYS: eslintVisitorKeys } = require("eslint-visitor-keys");
|
---|
13 | const esutils = require("esutils");
|
---|
14 | const espree = require("espree");
|
---|
15 | const escapeRegExp = require("escape-string-regexp");
|
---|
16 | const {
|
---|
17 | breakableTypePattern,
|
---|
18 | createGlobalLinebreakMatcher,
|
---|
19 | lineBreakPattern,
|
---|
20 | shebangPattern
|
---|
21 | } = require("../../shared/ast-utils");
|
---|
22 |
|
---|
23 | //------------------------------------------------------------------------------
|
---|
24 | // Helpers
|
---|
25 | //------------------------------------------------------------------------------
|
---|
26 |
|
---|
27 | const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
|
---|
28 | const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u;
|
---|
29 | const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u;
|
---|
30 | const arrayOrTypedArrayPattern = /Array$/u;
|
---|
31 | const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u;
|
---|
32 | const thisTagPattern = /^[\s*]*@this/mu;
|
---|
33 |
|
---|
34 |
|
---|
35 | const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;
|
---|
36 | const ESLINT_DIRECTIVE_PATTERN = /^(?:eslint[- ]|(?:globals?|exported) )/u;
|
---|
37 | const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
|
---|
38 |
|
---|
39 | // A set of node types that can contain a list of statements
|
---|
40 | const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "StaticBlock", "SwitchCase"]);
|
---|
41 |
|
---|
42 | const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
|
---|
43 |
|
---|
44 | // Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
|
---|
45 | const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
|
---|
46 |
|
---|
47 | const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
|
---|
48 |
|
---|
49 | /**
|
---|
50 | * Checks reference if is non initializer and writable.
|
---|
51 | * @param {Reference} reference A reference to check.
|
---|
52 | * @param {int} index The index of the reference in the references.
|
---|
53 | * @param {Reference[]} references The array that the reference belongs to.
|
---|
54 | * @returns {boolean} Success/Failure
|
---|
55 | * @private
|
---|
56 | */
|
---|
57 | function isModifyingReference(reference, index, references) {
|
---|
58 | const identifier = reference.identifier;
|
---|
59 |
|
---|
60 | /*
|
---|
61 | * Destructuring assignments can have multiple default value, so
|
---|
62 | * possibly there are multiple writeable references for the same
|
---|
63 | * identifier.
|
---|
64 | */
|
---|
65 | const modifyingDifferentIdentifier = index === 0 ||
|
---|
66 | references[index - 1].identifier !== identifier;
|
---|
67 |
|
---|
68 | return (identifier &&
|
---|
69 | reference.init === false &&
|
---|
70 | reference.isWrite() &&
|
---|
71 | modifyingDifferentIdentifier
|
---|
72 | );
|
---|
73 | }
|
---|
74 |
|
---|
75 | /**
|
---|
76 | * Checks whether the given string starts with uppercase or not.
|
---|
77 | * @param {string} s The string to check.
|
---|
78 | * @returns {boolean} `true` if the string starts with uppercase.
|
---|
79 | */
|
---|
80 | function startsWithUpperCase(s) {
|
---|
81 | return s[0] !== s[0].toLocaleLowerCase();
|
---|
82 | }
|
---|
83 |
|
---|
84 | /**
|
---|
85 | * Checks whether or not a node is a constructor.
|
---|
86 | * @param {ASTNode} node A function node to check.
|
---|
87 | * @returns {boolean} Whether or not a node is a constructor.
|
---|
88 | */
|
---|
89 | function isES5Constructor(node) {
|
---|
90 | return (node.id && startsWithUpperCase(node.id.name));
|
---|
91 | }
|
---|
92 |
|
---|
93 | /**
|
---|
94 | * Finds a function node from ancestors of a node.
|
---|
95 | * @param {ASTNode} node A start node to find.
|
---|
96 | * @returns {Node|null} A found function node.
|
---|
97 | */
|
---|
98 | function getUpperFunction(node) {
|
---|
99 | for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
|
---|
100 | if (anyFunctionPattern.test(currentNode.type)) {
|
---|
101 | return currentNode;
|
---|
102 | }
|
---|
103 | }
|
---|
104 | return null;
|
---|
105 | }
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * Checks whether a given node is a function node or not.
|
---|
109 | * The following types are function nodes:
|
---|
110 | *
|
---|
111 | * - ArrowFunctionExpression
|
---|
112 | * - FunctionDeclaration
|
---|
113 | * - FunctionExpression
|
---|
114 | * @param {ASTNode|null} node A node to check.
|
---|
115 | * @returns {boolean} `true` if the node is a function node.
|
---|
116 | */
|
---|
117 | function isFunction(node) {
|
---|
118 | return Boolean(node && anyFunctionPattern.test(node.type));
|
---|
119 | }
|
---|
120 |
|
---|
121 | /**
|
---|
122 | * Checks whether a given node is a loop node or not.
|
---|
123 | * The following types are loop nodes:
|
---|
124 | *
|
---|
125 | * - DoWhileStatement
|
---|
126 | * - ForInStatement
|
---|
127 | * - ForOfStatement
|
---|
128 | * - ForStatement
|
---|
129 | * - WhileStatement
|
---|
130 | * @param {ASTNode|null} node A node to check.
|
---|
131 | * @returns {boolean} `true` if the node is a loop node.
|
---|
132 | */
|
---|
133 | function isLoop(node) {
|
---|
134 | return Boolean(node && anyLoopPattern.test(node.type));
|
---|
135 | }
|
---|
136 |
|
---|
137 | /**
|
---|
138 | * Checks whether the given node is in a loop or not.
|
---|
139 | * @param {ASTNode} node The node to check.
|
---|
140 | * @returns {boolean} `true` if the node is in a loop.
|
---|
141 | */
|
---|
142 | function isInLoop(node) {
|
---|
143 | for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) {
|
---|
144 | if (isLoop(currentNode)) {
|
---|
145 | return true;
|
---|
146 | }
|
---|
147 | }
|
---|
148 |
|
---|
149 | return false;
|
---|
150 | }
|
---|
151 |
|
---|
152 | /**
|
---|
153 | * Determines whether the given node is a `null` literal.
|
---|
154 | * @param {ASTNode} node The node to check
|
---|
155 | * @returns {boolean} `true` if the node is a `null` literal
|
---|
156 | */
|
---|
157 | function isNullLiteral(node) {
|
---|
158 |
|
---|
159 | /*
|
---|
160 | * Checking `node.value === null` does not guarantee that a literal is a null literal.
|
---|
161 | * When parsing values that cannot be represented in the current environment (e.g. unicode
|
---|
162 | * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
|
---|
163 | * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
|
---|
164 | * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
|
---|
165 | */
|
---|
166 | return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
|
---|
167 | }
|
---|
168 |
|
---|
169 | /**
|
---|
170 | * Checks whether or not a node is `null` or `undefined`.
|
---|
171 | * @param {ASTNode} node A node to check.
|
---|
172 | * @returns {boolean} Whether or not the node is a `null` or `undefined`.
|
---|
173 | * @public
|
---|
174 | */
|
---|
175 | function isNullOrUndefined(node) {
|
---|
176 | return (
|
---|
177 | isNullLiteral(node) ||
|
---|
178 | (node.type === "Identifier" && node.name === "undefined") ||
|
---|
179 | (node.type === "UnaryExpression" && node.operator === "void")
|
---|
180 | );
|
---|
181 | }
|
---|
182 |
|
---|
183 | /**
|
---|
184 | * Checks whether or not a node is callee.
|
---|
185 | * @param {ASTNode} node A node to check.
|
---|
186 | * @returns {boolean} Whether or not the node is callee.
|
---|
187 | */
|
---|
188 | function isCallee(node) {
|
---|
189 | return node.parent.type === "CallExpression" && node.parent.callee === node;
|
---|
190 | }
|
---|
191 |
|
---|
192 | /**
|
---|
193 | * Returns the result of the string conversion applied to the evaluated value of the given expression node,
|
---|
194 | * if it can be determined statically.
|
---|
195 | *
|
---|
196 | * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
|
---|
197 | * In all other cases, this function returns `null`.
|
---|
198 | * @param {ASTNode} node Expression node.
|
---|
199 | * @returns {string|null} String value if it can be determined. Otherwise, `null`.
|
---|
200 | */
|
---|
201 | function getStaticStringValue(node) {
|
---|
202 | switch (node.type) {
|
---|
203 | case "Literal":
|
---|
204 | if (node.value === null) {
|
---|
205 | if (isNullLiteral(node)) {
|
---|
206 | return String(node.value); // "null"
|
---|
207 | }
|
---|
208 | if (node.regex) {
|
---|
209 | return `/${node.regex.pattern}/${node.regex.flags}`;
|
---|
210 | }
|
---|
211 | if (node.bigint) {
|
---|
212 | return node.bigint;
|
---|
213 | }
|
---|
214 |
|
---|
215 | // Otherwise, this is an unknown literal. The function will return null.
|
---|
216 |
|
---|
217 | } else {
|
---|
218 | return String(node.value);
|
---|
219 | }
|
---|
220 | break;
|
---|
221 | case "TemplateLiteral":
|
---|
222 | if (node.expressions.length === 0 && node.quasis.length === 1) {
|
---|
223 | return node.quasis[0].value.cooked;
|
---|
224 | }
|
---|
225 | break;
|
---|
226 |
|
---|
227 | // no default
|
---|
228 | }
|
---|
229 |
|
---|
230 | return null;
|
---|
231 | }
|
---|
232 |
|
---|
233 | /**
|
---|
234 | * Gets the property name of a given node.
|
---|
235 | * The node can be a MemberExpression, a Property, or a MethodDefinition.
|
---|
236 | *
|
---|
237 | * If the name is dynamic, this returns `null`.
|
---|
238 | *
|
---|
239 | * For examples:
|
---|
240 | *
|
---|
241 | * a.b // => "b"
|
---|
242 | * a["b"] // => "b"
|
---|
243 | * a['b'] // => "b"
|
---|
244 | * a[`b`] // => "b"
|
---|
245 | * a[100] // => "100"
|
---|
246 | * a[b] // => null
|
---|
247 | * a["a" + "b"] // => null
|
---|
248 | * a[tag`b`] // => null
|
---|
249 | * a[`${b}`] // => null
|
---|
250 | *
|
---|
251 | * let a = {b: 1} // => "b"
|
---|
252 | * let a = {["b"]: 1} // => "b"
|
---|
253 | * let a = {['b']: 1} // => "b"
|
---|
254 | * let a = {[`b`]: 1} // => "b"
|
---|
255 | * let a = {[100]: 1} // => "100"
|
---|
256 | * let a = {[b]: 1} // => null
|
---|
257 | * let a = {["a" + "b"]: 1} // => null
|
---|
258 | * let a = {[tag`b`]: 1} // => null
|
---|
259 | * let a = {[`${b}`]: 1} // => null
|
---|
260 | * @param {ASTNode} node The node to get.
|
---|
261 | * @returns {string|null} The property name if static. Otherwise, null.
|
---|
262 | */
|
---|
263 | function getStaticPropertyName(node) {
|
---|
264 | let prop;
|
---|
265 |
|
---|
266 | switch (node && node.type) {
|
---|
267 | case "ChainExpression":
|
---|
268 | return getStaticPropertyName(node.expression);
|
---|
269 |
|
---|
270 | case "Property":
|
---|
271 | case "PropertyDefinition":
|
---|
272 | case "MethodDefinition":
|
---|
273 | prop = node.key;
|
---|
274 | break;
|
---|
275 |
|
---|
276 | case "MemberExpression":
|
---|
277 | prop = node.property;
|
---|
278 | break;
|
---|
279 |
|
---|
280 | // no default
|
---|
281 | }
|
---|
282 |
|
---|
283 | if (prop) {
|
---|
284 | if (prop.type === "Identifier" && !node.computed) {
|
---|
285 | return prop.name;
|
---|
286 | }
|
---|
287 |
|
---|
288 | return getStaticStringValue(prop);
|
---|
289 | }
|
---|
290 |
|
---|
291 | return null;
|
---|
292 | }
|
---|
293 |
|
---|
294 | /**
|
---|
295 | * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
|
---|
296 | * @param {ASTNode} node The node to address.
|
---|
297 | * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
|
---|
298 | */
|
---|
299 | function skipChainExpression(node) {
|
---|
300 | return node && node.type === "ChainExpression" ? node.expression : node;
|
---|
301 | }
|
---|
302 |
|
---|
303 | /**
|
---|
304 | * Check if the `actual` is an expected value.
|
---|
305 | * @param {string} actual The string value to check.
|
---|
306 | * @param {string | RegExp} expected The expected string value or pattern.
|
---|
307 | * @returns {boolean} `true` if the `actual` is an expected value.
|
---|
308 | */
|
---|
309 | function checkText(actual, expected) {
|
---|
310 | return typeof expected === "string"
|
---|
311 | ? actual === expected
|
---|
312 | : expected.test(actual);
|
---|
313 | }
|
---|
314 |
|
---|
315 | /**
|
---|
316 | * Check if a given node is an Identifier node with a given name.
|
---|
317 | * @param {ASTNode} node The node to check.
|
---|
318 | * @param {string | RegExp} name The expected name or the expected pattern of the object name.
|
---|
319 | * @returns {boolean} `true` if the node is an Identifier node with the name.
|
---|
320 | */
|
---|
321 | function isSpecificId(node, name) {
|
---|
322 | return node.type === "Identifier" && checkText(node.name, name);
|
---|
323 | }
|
---|
324 |
|
---|
325 | /**
|
---|
326 | * Check if a given node is member access with a given object name and property name pair.
|
---|
327 | * This is regardless of optional or not.
|
---|
328 | * @param {ASTNode} node The node to check.
|
---|
329 | * @param {string | RegExp | null} objectName The expected name or the expected pattern of the object name. If this is nullish, this method doesn't check object.
|
---|
330 | * @param {string | RegExp | null} propertyName The expected name or the expected pattern of the property name. If this is nullish, this method doesn't check property.
|
---|
331 | * @returns {boolean} `true` if the node is member access with the object name and property name pair.
|
---|
332 | * The node is a `MemberExpression` or `ChainExpression`.
|
---|
333 | */
|
---|
334 | function isSpecificMemberAccess(node, objectName, propertyName) {
|
---|
335 | const checkNode = skipChainExpression(node);
|
---|
336 |
|
---|
337 | if (checkNode.type !== "MemberExpression") {
|
---|
338 | return false;
|
---|
339 | }
|
---|
340 |
|
---|
341 | if (objectName && !isSpecificId(checkNode.object, objectName)) {
|
---|
342 | return false;
|
---|
343 | }
|
---|
344 |
|
---|
345 | if (propertyName) {
|
---|
346 | const actualPropertyName = getStaticPropertyName(checkNode);
|
---|
347 |
|
---|
348 | if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) {
|
---|
349 | return false;
|
---|
350 | }
|
---|
351 | }
|
---|
352 |
|
---|
353 | return true;
|
---|
354 | }
|
---|
355 |
|
---|
356 | /**
|
---|
357 | * Check if two literal nodes are the same value.
|
---|
358 | * @param {ASTNode} left The Literal node to compare.
|
---|
359 | * @param {ASTNode} right The other Literal node to compare.
|
---|
360 | * @returns {boolean} `true` if the two literal nodes are the same value.
|
---|
361 | */
|
---|
362 | function equalLiteralValue(left, right) {
|
---|
363 |
|
---|
364 | // RegExp literal.
|
---|
365 | if (left.regex || right.regex) {
|
---|
366 | return Boolean(
|
---|
367 | left.regex &&
|
---|
368 | right.regex &&
|
---|
369 | left.regex.pattern === right.regex.pattern &&
|
---|
370 | left.regex.flags === right.regex.flags
|
---|
371 | );
|
---|
372 | }
|
---|
373 |
|
---|
374 | // BigInt literal.
|
---|
375 | if (left.bigint || right.bigint) {
|
---|
376 | return left.bigint === right.bigint;
|
---|
377 | }
|
---|
378 |
|
---|
379 | return left.value === right.value;
|
---|
380 | }
|
---|
381 |
|
---|
382 | /**
|
---|
383 | * Check if two expressions reference the same value. For example:
|
---|
384 | * a = a
|
---|
385 | * a.b = a.b
|
---|
386 | * a[0] = a[0]
|
---|
387 | * a['b'] = a['b']
|
---|
388 | * @param {ASTNode} left The left side of the comparison.
|
---|
389 | * @param {ASTNode} right The right side of the comparison.
|
---|
390 | * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility.
|
---|
391 | * @returns {boolean} `true` if both sides match and reference the same value.
|
---|
392 | */
|
---|
393 | function isSameReference(left, right, disableStaticComputedKey = false) {
|
---|
394 | if (left.type !== right.type) {
|
---|
395 |
|
---|
396 | // Handle `a.b` and `a?.b` are samely.
|
---|
397 | if (left.type === "ChainExpression") {
|
---|
398 | return isSameReference(left.expression, right, disableStaticComputedKey);
|
---|
399 | }
|
---|
400 | if (right.type === "ChainExpression") {
|
---|
401 | return isSameReference(left, right.expression, disableStaticComputedKey);
|
---|
402 | }
|
---|
403 |
|
---|
404 | return false;
|
---|
405 | }
|
---|
406 |
|
---|
407 | switch (left.type) {
|
---|
408 | case "Super":
|
---|
409 | case "ThisExpression":
|
---|
410 | return true;
|
---|
411 |
|
---|
412 | case "Identifier":
|
---|
413 | case "PrivateIdentifier":
|
---|
414 | return left.name === right.name;
|
---|
415 | case "Literal":
|
---|
416 | return equalLiteralValue(left, right);
|
---|
417 |
|
---|
418 | case "ChainExpression":
|
---|
419 | return isSameReference(left.expression, right.expression, disableStaticComputedKey);
|
---|
420 |
|
---|
421 | case "MemberExpression": {
|
---|
422 | if (!disableStaticComputedKey) {
|
---|
423 | const nameA = getStaticPropertyName(left);
|
---|
424 |
|
---|
425 | // x.y = x["y"]
|
---|
426 | if (nameA !== null) {
|
---|
427 | return (
|
---|
428 | isSameReference(left.object, right.object, disableStaticComputedKey) &&
|
---|
429 | nameA === getStaticPropertyName(right)
|
---|
430 | );
|
---|
431 | }
|
---|
432 | }
|
---|
433 |
|
---|
434 | /*
|
---|
435 | * x[0] = x[0]
|
---|
436 | * x[y] = x[y]
|
---|
437 | * x.y = x.y
|
---|
438 | */
|
---|
439 | return (
|
---|
440 | left.computed === right.computed &&
|
---|
441 | isSameReference(left.object, right.object, disableStaticComputedKey) &&
|
---|
442 | isSameReference(left.property, right.property, disableStaticComputedKey)
|
---|
443 | );
|
---|
444 | }
|
---|
445 |
|
---|
446 | default:
|
---|
447 | return false;
|
---|
448 | }
|
---|
449 | }
|
---|
450 |
|
---|
451 | /**
|
---|
452 | * Checks whether or not a node is `Reflect.apply`.
|
---|
453 | * @param {ASTNode} node A node to check.
|
---|
454 | * @returns {boolean} Whether or not the node is a `Reflect.apply`.
|
---|
455 | */
|
---|
456 | function isReflectApply(node) {
|
---|
457 | return isSpecificMemberAccess(node, "Reflect", "apply");
|
---|
458 | }
|
---|
459 |
|
---|
460 | /**
|
---|
461 | * Checks whether or not a node is `Array.from`.
|
---|
462 | * @param {ASTNode} node A node to check.
|
---|
463 | * @returns {boolean} Whether or not the node is a `Array.from`.
|
---|
464 | */
|
---|
465 | function isArrayFromMethod(node) {
|
---|
466 | return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from");
|
---|
467 | }
|
---|
468 |
|
---|
469 | /**
|
---|
470 | * Checks whether or not a node is a method which expects a function as a first argument, and `thisArg` as a second argument.
|
---|
471 | * @param {ASTNode} node A node to check.
|
---|
472 | * @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument.
|
---|
473 | */
|
---|
474 | function isMethodWhichHasThisArg(node) {
|
---|
475 | return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern);
|
---|
476 | }
|
---|
477 |
|
---|
478 | /**
|
---|
479 | * Creates the negate function of the given function.
|
---|
480 | * @param {Function} f The function to negate.
|
---|
481 | * @returns {Function} Negated function.
|
---|
482 | */
|
---|
483 | function negate(f) {
|
---|
484 | return token => !f(token);
|
---|
485 | }
|
---|
486 |
|
---|
487 | /**
|
---|
488 | * Checks whether or not a node has a `@this` tag in its comments.
|
---|
489 | * @param {ASTNode} node A node to check.
|
---|
490 | * @param {SourceCode} sourceCode A SourceCode instance to get comments.
|
---|
491 | * @returns {boolean} Whether or not the node has a `@this` tag in its comments.
|
---|
492 | */
|
---|
493 | function hasJSDocThisTag(node, sourceCode) {
|
---|
494 | const jsdocComment = sourceCode.getJSDocComment(node);
|
---|
495 |
|
---|
496 | if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
|
---|
497 | return true;
|
---|
498 | }
|
---|
499 |
|
---|
500 | // Checks `@this` in its leading comments for callbacks,
|
---|
501 | // because callbacks don't have its JSDoc comment.
|
---|
502 | // e.g.
|
---|
503 | // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
|
---|
504 | return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value));
|
---|
505 | }
|
---|
506 |
|
---|
507 | /**
|
---|
508 | * Determines if a node is surrounded by parentheses.
|
---|
509 | * @param {SourceCode} sourceCode The ESLint source code object
|
---|
510 | * @param {ASTNode} node The node to be checked.
|
---|
511 | * @returns {boolean} True if the node is parenthesised.
|
---|
512 | * @private
|
---|
513 | */
|
---|
514 | function isParenthesised(sourceCode, node) {
|
---|
515 | const previousToken = sourceCode.getTokenBefore(node),
|
---|
516 | nextToken = sourceCode.getTokenAfter(node);
|
---|
517 |
|
---|
518 | return Boolean(previousToken && nextToken) &&
|
---|
519 | previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
---|
520 | nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
---|
521 | }
|
---|
522 |
|
---|
523 | /**
|
---|
524 | * Checks if the given token is a `=` token or not.
|
---|
525 | * @param {Token} token The token to check.
|
---|
526 | * @returns {boolean} `true` if the token is a `=` token.
|
---|
527 | */
|
---|
528 | function isEqToken(token) {
|
---|
529 | return token.value === "=" && token.type === "Punctuator";
|
---|
530 | }
|
---|
531 |
|
---|
532 | /**
|
---|
533 | * Checks if the given token is an arrow token or not.
|
---|
534 | * @param {Token} token The token to check.
|
---|
535 | * @returns {boolean} `true` if the token is an arrow token.
|
---|
536 | */
|
---|
537 | function isArrowToken(token) {
|
---|
538 | return token.value === "=>" && token.type === "Punctuator";
|
---|
539 | }
|
---|
540 |
|
---|
541 | /**
|
---|
542 | * Checks if the given token is a comma token or not.
|
---|
543 | * @param {Token} token The token to check.
|
---|
544 | * @returns {boolean} `true` if the token is a comma token.
|
---|
545 | */
|
---|
546 | function isCommaToken(token) {
|
---|
547 | return token.value === "," && token.type === "Punctuator";
|
---|
548 | }
|
---|
549 |
|
---|
550 | /**
|
---|
551 | * Checks if the given token is a dot token or not.
|
---|
552 | * @param {Token} token The token to check.
|
---|
553 | * @returns {boolean} `true` if the token is a dot token.
|
---|
554 | */
|
---|
555 | function isDotToken(token) {
|
---|
556 | return token.value === "." && token.type === "Punctuator";
|
---|
557 | }
|
---|
558 |
|
---|
559 | /**
|
---|
560 | * Checks if the given token is a `?.` token or not.
|
---|
561 | * @param {Token} token The token to check.
|
---|
562 | * @returns {boolean} `true` if the token is a `?.` token.
|
---|
563 | */
|
---|
564 | function isQuestionDotToken(token) {
|
---|
565 | return token.value === "?." && token.type === "Punctuator";
|
---|
566 | }
|
---|
567 |
|
---|
568 | /**
|
---|
569 | * Checks if the given token is a semicolon token or not.
|
---|
570 | * @param {Token} token The token to check.
|
---|
571 | * @returns {boolean} `true` if the token is a semicolon token.
|
---|
572 | */
|
---|
573 | function isSemicolonToken(token) {
|
---|
574 | return token.value === ";" && token.type === "Punctuator";
|
---|
575 | }
|
---|
576 |
|
---|
577 | /**
|
---|
578 | * Checks if the given token is a colon token or not.
|
---|
579 | * @param {Token} token The token to check.
|
---|
580 | * @returns {boolean} `true` if the token is a colon token.
|
---|
581 | */
|
---|
582 | function isColonToken(token) {
|
---|
583 | return token.value === ":" && token.type === "Punctuator";
|
---|
584 | }
|
---|
585 |
|
---|
586 | /**
|
---|
587 | * Checks if the given token is an opening parenthesis token or not.
|
---|
588 | * @param {Token} token The token to check.
|
---|
589 | * @returns {boolean} `true` if the token is an opening parenthesis token.
|
---|
590 | */
|
---|
591 | function isOpeningParenToken(token) {
|
---|
592 | return token.value === "(" && token.type === "Punctuator";
|
---|
593 | }
|
---|
594 |
|
---|
595 | /**
|
---|
596 | * Checks if the given token is a closing parenthesis token or not.
|
---|
597 | * @param {Token} token The token to check.
|
---|
598 | * @returns {boolean} `true` if the token is a closing parenthesis token.
|
---|
599 | */
|
---|
600 | function isClosingParenToken(token) {
|
---|
601 | return token.value === ")" && token.type === "Punctuator";
|
---|
602 | }
|
---|
603 |
|
---|
604 | /**
|
---|
605 | * Checks if the given token is an opening square bracket token or not.
|
---|
606 | * @param {Token} token The token to check.
|
---|
607 | * @returns {boolean} `true` if the token is an opening square bracket token.
|
---|
608 | */
|
---|
609 | function isOpeningBracketToken(token) {
|
---|
610 | return token.value === "[" && token.type === "Punctuator";
|
---|
611 | }
|
---|
612 |
|
---|
613 | /**
|
---|
614 | * Checks if the given token is a closing square bracket token or not.
|
---|
615 | * @param {Token} token The token to check.
|
---|
616 | * @returns {boolean} `true` if the token is a closing square bracket token.
|
---|
617 | */
|
---|
618 | function isClosingBracketToken(token) {
|
---|
619 | return token.value === "]" && token.type === "Punctuator";
|
---|
620 | }
|
---|
621 |
|
---|
622 | /**
|
---|
623 | * Checks if the given token is an opening brace token or not.
|
---|
624 | * @param {Token} token The token to check.
|
---|
625 | * @returns {boolean} `true` if the token is an opening brace token.
|
---|
626 | */
|
---|
627 | function isOpeningBraceToken(token) {
|
---|
628 | return token.value === "{" && token.type === "Punctuator";
|
---|
629 | }
|
---|
630 |
|
---|
631 | /**
|
---|
632 | * Checks if the given token is a closing brace token or not.
|
---|
633 | * @param {Token} token The token to check.
|
---|
634 | * @returns {boolean} `true` if the token is a closing brace token.
|
---|
635 | */
|
---|
636 | function isClosingBraceToken(token) {
|
---|
637 | return token.value === "}" && token.type === "Punctuator";
|
---|
638 | }
|
---|
639 |
|
---|
640 | /**
|
---|
641 | * Checks if the given token is a comment token or not.
|
---|
642 | * @param {Token} token The token to check.
|
---|
643 | * @returns {boolean} `true` if the token is a comment token.
|
---|
644 | */
|
---|
645 | function isCommentToken(token) {
|
---|
646 | return token.type === "Line" || token.type === "Block" || token.type === "Shebang";
|
---|
647 | }
|
---|
648 |
|
---|
649 | /**
|
---|
650 | * Checks if the given token is a keyword token or not.
|
---|
651 | * @param {Token} token The token to check.
|
---|
652 | * @returns {boolean} `true` if the token is a keyword token.
|
---|
653 | */
|
---|
654 | function isKeywordToken(token) {
|
---|
655 | return token.type === "Keyword";
|
---|
656 | }
|
---|
657 |
|
---|
658 | /**
|
---|
659 | * Gets the `(` token of the given function node.
|
---|
660 | * @param {ASTNode} node The function node to get.
|
---|
661 | * @param {SourceCode} sourceCode The source code object to get tokens.
|
---|
662 | * @returns {Token} `(` token.
|
---|
663 | */
|
---|
664 | function getOpeningParenOfParams(node, sourceCode) {
|
---|
665 |
|
---|
666 | // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param.
|
---|
667 | if (node.type === "ArrowFunctionExpression" && node.params.length === 1) {
|
---|
668 | const argToken = sourceCode.getFirstToken(node.params[0]);
|
---|
669 | const maybeParenToken = sourceCode.getTokenBefore(argToken);
|
---|
670 |
|
---|
671 | return isOpeningParenToken(maybeParenToken) ? maybeParenToken : argToken;
|
---|
672 | }
|
---|
673 |
|
---|
674 | // Otherwise, returns paren.
|
---|
675 | return node.id
|
---|
676 | ? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
|
---|
677 | : sourceCode.getFirstToken(node, isOpeningParenToken);
|
---|
678 | }
|
---|
679 |
|
---|
680 | /**
|
---|
681 | * Checks whether or not the tokens of two given nodes are same.
|
---|
682 | * @param {ASTNode} left A node 1 to compare.
|
---|
683 | * @param {ASTNode} right A node 2 to compare.
|
---|
684 | * @param {SourceCode} sourceCode The ESLint source code object.
|
---|
685 | * @returns {boolean} the source code for the given node.
|
---|
686 | */
|
---|
687 | function equalTokens(left, right, sourceCode) {
|
---|
688 | const tokensL = sourceCode.getTokens(left);
|
---|
689 | const tokensR = sourceCode.getTokens(right);
|
---|
690 |
|
---|
691 | if (tokensL.length !== tokensR.length) {
|
---|
692 | return false;
|
---|
693 | }
|
---|
694 | for (let i = 0; i < tokensL.length; ++i) {
|
---|
695 | if (tokensL[i].type !== tokensR[i].type ||
|
---|
696 | tokensL[i].value !== tokensR[i].value
|
---|
697 | ) {
|
---|
698 | return false;
|
---|
699 | }
|
---|
700 | }
|
---|
701 |
|
---|
702 | return true;
|
---|
703 | }
|
---|
704 |
|
---|
705 | /**
|
---|
706 | * Check if the given node is a true logical expression or not.
|
---|
707 | *
|
---|
708 | * The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
---|
709 | * coalesce (`??`) are known as `ShortCircuitExpression`.
|
---|
710 | * But ESTree represents those by `LogicalExpression` node.
|
---|
711 | *
|
---|
712 | * This function rejects coalesce expressions of `LogicalExpression` node.
|
---|
713 | * @param {ASTNode} node The node to check.
|
---|
714 | * @returns {boolean} `true` if the node is `&&` or `||`.
|
---|
715 | * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
|
---|
716 | */
|
---|
717 | function isLogicalExpression(node) {
|
---|
718 | return (
|
---|
719 | node.type === "LogicalExpression" &&
|
---|
720 | (node.operator === "&&" || node.operator === "||")
|
---|
721 | );
|
---|
722 | }
|
---|
723 |
|
---|
724 | /**
|
---|
725 | * Check if the given node is a nullish coalescing expression or not.
|
---|
726 | *
|
---|
727 | * The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
---|
728 | * coalesce (`??`) are known as `ShortCircuitExpression`.
|
---|
729 | * But ESTree represents those by `LogicalExpression` node.
|
---|
730 | *
|
---|
731 | * This function finds only coalesce expressions of `LogicalExpression` node.
|
---|
732 | * @param {ASTNode} node The node to check.
|
---|
733 | * @returns {boolean} `true` if the node is `??`.
|
---|
734 | */
|
---|
735 | function isCoalesceExpression(node) {
|
---|
736 | return node.type === "LogicalExpression" && node.operator === "??";
|
---|
737 | }
|
---|
738 |
|
---|
739 | /**
|
---|
740 | * Check if given two nodes are the pair of a logical expression and a coalesce expression.
|
---|
741 | * @param {ASTNode} left A node to check.
|
---|
742 | * @param {ASTNode} right Another node to check.
|
---|
743 | * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
|
---|
744 | */
|
---|
745 | function isMixedLogicalAndCoalesceExpressions(left, right) {
|
---|
746 | return (
|
---|
747 | (isLogicalExpression(left) && isCoalesceExpression(right)) ||
|
---|
748 | (isCoalesceExpression(left) && isLogicalExpression(right))
|
---|
749 | );
|
---|
750 | }
|
---|
751 |
|
---|
752 | /**
|
---|
753 | * Checks if the given operator is a logical assignment operator.
|
---|
754 | * @param {string} operator The operator to check.
|
---|
755 | * @returns {boolean} `true` if the operator is a logical assignment operator.
|
---|
756 | */
|
---|
757 | function isLogicalAssignmentOperator(operator) {
|
---|
758 | return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
|
---|
759 | }
|
---|
760 |
|
---|
761 | /**
|
---|
762 | * Get the colon token of the given SwitchCase node.
|
---|
763 | * @param {ASTNode} node The SwitchCase node to get.
|
---|
764 | * @param {SourceCode} sourceCode The source code object to get tokens.
|
---|
765 | * @returns {Token} The colon token of the node.
|
---|
766 | */
|
---|
767 | function getSwitchCaseColonToken(node, sourceCode) {
|
---|
768 | if (node.test) {
|
---|
769 | return sourceCode.getTokenAfter(node.test, isColonToken);
|
---|
770 | }
|
---|
771 | return sourceCode.getFirstToken(node, 1);
|
---|
772 | }
|
---|
773 |
|
---|
774 | /**
|
---|
775 | * Gets ESM module export name represented by the given node.
|
---|
776 | * @param {ASTNode} node `Identifier` or string `Literal` node in a position
|
---|
777 | * that represents a module export name:
|
---|
778 | * - `ImportSpecifier#imported`
|
---|
779 | * - `ExportSpecifier#local` (if it is a re-export from another module)
|
---|
780 | * - `ExportSpecifier#exported`
|
---|
781 | * - `ExportAllDeclaration#exported`
|
---|
782 | * @returns {string} The module export name.
|
---|
783 | */
|
---|
784 | function getModuleExportName(node) {
|
---|
785 | if (node.type === "Identifier") {
|
---|
786 | return node.name;
|
---|
787 | }
|
---|
788 |
|
---|
789 | // string literal
|
---|
790 | return node.value;
|
---|
791 | }
|
---|
792 |
|
---|
793 | /**
|
---|
794 | * Returns literal's value converted to the Boolean type
|
---|
795 | * @param {ASTNode} node any `Literal` node
|
---|
796 | * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
|
---|
797 | * `null` when it cannot be determined.
|
---|
798 | */
|
---|
799 | function getBooleanValue(node) {
|
---|
800 | if (node.value === null) {
|
---|
801 |
|
---|
802 | /*
|
---|
803 | * it might be a null literal or bigint/regex literal in unsupported environments .
|
---|
804 | * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
|
---|
805 | * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
|
---|
806 | */
|
---|
807 |
|
---|
808 | if (node.raw === "null") {
|
---|
809 | return false;
|
---|
810 | }
|
---|
811 |
|
---|
812 | // regex is always truthy
|
---|
813 | if (typeof node.regex === "object") {
|
---|
814 | return true;
|
---|
815 | }
|
---|
816 |
|
---|
817 | return null;
|
---|
818 | }
|
---|
819 |
|
---|
820 | return !!node.value;
|
---|
821 | }
|
---|
822 |
|
---|
823 | /**
|
---|
824 | * Checks if a branch node of LogicalExpression short circuits the whole condition
|
---|
825 | * @param {ASTNode} node The branch of main condition which needs to be checked
|
---|
826 | * @param {string} operator The operator of the main LogicalExpression.
|
---|
827 | * @returns {boolean} true when condition short circuits whole condition
|
---|
828 | */
|
---|
829 | function isLogicalIdentity(node, operator) {
|
---|
830 | switch (node.type) {
|
---|
831 | case "Literal":
|
---|
832 | return (operator === "||" && getBooleanValue(node) === true) ||
|
---|
833 | (operator === "&&" && getBooleanValue(node) === false);
|
---|
834 |
|
---|
835 | case "UnaryExpression":
|
---|
836 | return (operator === "&&" && node.operator === "void");
|
---|
837 |
|
---|
838 | case "LogicalExpression":
|
---|
839 |
|
---|
840 | /*
|
---|
841 | * handles `a && false || b`
|
---|
842 | * `false` is an identity element of `&&` but not `||`
|
---|
843 | */
|
---|
844 | return operator === node.operator &&
|
---|
845 | (
|
---|
846 | isLogicalIdentity(node.left, operator) ||
|
---|
847 | isLogicalIdentity(node.right, operator)
|
---|
848 | );
|
---|
849 |
|
---|
850 | case "AssignmentExpression":
|
---|
851 | return ["||=", "&&="].includes(node.operator) &&
|
---|
852 | operator === node.operator.slice(0, -1) &&
|
---|
853 | isLogicalIdentity(node.right, operator);
|
---|
854 |
|
---|
855 | // no default
|
---|
856 | }
|
---|
857 | return false;
|
---|
858 | }
|
---|
859 |
|
---|
860 | /**
|
---|
861 | * Checks if an identifier is a reference to a global variable.
|
---|
862 | * @param {Scope} scope The scope in which the identifier is referenced.
|
---|
863 | * @param {ASTNode} node An identifier node to check.
|
---|
864 | * @returns {boolean} `true` if the identifier is a reference to a global variable.
|
---|
865 | */
|
---|
866 | function isReferenceToGlobalVariable(scope, node) {
|
---|
867 | const reference = scope.references.find(ref => ref.identifier === node);
|
---|
868 |
|
---|
869 | return Boolean(
|
---|
870 | reference &&
|
---|
871 | reference.resolved &&
|
---|
872 | reference.resolved.scope.type === "global" &&
|
---|
873 | reference.resolved.defs.length === 0
|
---|
874 | );
|
---|
875 | }
|
---|
876 |
|
---|
877 |
|
---|
878 | /**
|
---|
879 | * Checks if a node has a constant truthiness value.
|
---|
880 | * @param {Scope} scope Scope in which the node appears.
|
---|
881 | * @param {ASTNode} node The AST node to check.
|
---|
882 | * @param {boolean} inBooleanPosition `true` if checking the test of a
|
---|
883 | * condition. `false` in all other cases. When `false`, checks if -- for
|
---|
884 | * both string and number -- if coerced to that type, the value will
|
---|
885 | * be constant.
|
---|
886 | * @returns {boolean} true when node's truthiness is constant
|
---|
887 | * @private
|
---|
888 | */
|
---|
889 | function isConstant(scope, node, inBooleanPosition) {
|
---|
890 |
|
---|
891 | // node.elements can return null values in the case of sparse arrays ex. [,]
|
---|
892 | if (!node) {
|
---|
893 | return true;
|
---|
894 | }
|
---|
895 | switch (node.type) {
|
---|
896 | case "Literal":
|
---|
897 | case "ArrowFunctionExpression":
|
---|
898 | case "FunctionExpression":
|
---|
899 | return true;
|
---|
900 | case "ClassExpression":
|
---|
901 | case "ObjectExpression":
|
---|
902 |
|
---|
903 | /**
|
---|
904 | * In theory objects like:
|
---|
905 | *
|
---|
906 | * `{toString: () => a}`
|
---|
907 | * `{valueOf: () => a}`
|
---|
908 | *
|
---|
909 | * Or a classes like:
|
---|
910 | *
|
---|
911 | * `class { static toString() { return a } }`
|
---|
912 | * `class { static valueOf() { return a } }`
|
---|
913 | *
|
---|
914 | * Are not constant verifiably when `inBooleanPosition` is
|
---|
915 | * false, but it's an edge case we've opted not to handle.
|
---|
916 | */
|
---|
917 | return true;
|
---|
918 | case "TemplateLiteral":
|
---|
919 | return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
|
---|
920 | node.expressions.every(exp => isConstant(scope, exp, false));
|
---|
921 |
|
---|
922 | case "ArrayExpression": {
|
---|
923 | if (!inBooleanPosition) {
|
---|
924 | return node.elements.every(element => isConstant(scope, element, false));
|
---|
925 | }
|
---|
926 | return true;
|
---|
927 | }
|
---|
928 |
|
---|
929 | case "UnaryExpression":
|
---|
930 | if (
|
---|
931 | node.operator === "void" ||
|
---|
932 | node.operator === "typeof" && inBooleanPosition
|
---|
933 | ) {
|
---|
934 | return true;
|
---|
935 | }
|
---|
936 |
|
---|
937 | if (node.operator === "!") {
|
---|
938 | return isConstant(scope, node.argument, true);
|
---|
939 | }
|
---|
940 |
|
---|
941 | return isConstant(scope, node.argument, false);
|
---|
942 |
|
---|
943 | case "BinaryExpression":
|
---|
944 | return isConstant(scope, node.left, false) &&
|
---|
945 | isConstant(scope, node.right, false) &&
|
---|
946 | node.operator !== "in";
|
---|
947 |
|
---|
948 | case "LogicalExpression": {
|
---|
949 | const isLeftConstant = isConstant(scope, node.left, inBooleanPosition);
|
---|
950 | const isRightConstant = isConstant(scope, node.right, inBooleanPosition);
|
---|
951 | const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
|
---|
952 | const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
|
---|
953 |
|
---|
954 | return (isLeftConstant && isRightConstant) ||
|
---|
955 | isLeftShortCircuit ||
|
---|
956 | isRightShortCircuit;
|
---|
957 | }
|
---|
958 | case "NewExpression":
|
---|
959 | return inBooleanPosition;
|
---|
960 | case "AssignmentExpression":
|
---|
961 | if (node.operator === "=") {
|
---|
962 | return isConstant(scope, node.right, inBooleanPosition);
|
---|
963 | }
|
---|
964 |
|
---|
965 | if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
|
---|
966 | return isLogicalIdentity(node.right, node.operator.slice(0, -1));
|
---|
967 | }
|
---|
968 |
|
---|
969 | return false;
|
---|
970 |
|
---|
971 | case "SequenceExpression":
|
---|
972 | return isConstant(scope, node.expressions[node.expressions.length - 1], inBooleanPosition);
|
---|
973 | case "SpreadElement":
|
---|
974 | return isConstant(scope, node.argument, inBooleanPosition);
|
---|
975 | case "CallExpression":
|
---|
976 | if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
|
---|
977 | if (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)) {
|
---|
978 | return isReferenceToGlobalVariable(scope, node.callee);
|
---|
979 | }
|
---|
980 | }
|
---|
981 | return false;
|
---|
982 | case "Identifier":
|
---|
983 | return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
|
---|
984 |
|
---|
985 | // no default
|
---|
986 | }
|
---|
987 | return false;
|
---|
988 | }
|
---|
989 |
|
---|
990 | /**
|
---|
991 | * Checks whether a node is an ExpressionStatement at the top level of a file or function body.
|
---|
992 | * A top-level ExpressionStatement node is a directive if it contains a single unparenthesized
|
---|
993 | * string literal and if it occurs either as the first sibling or immediately after another
|
---|
994 | * directive.
|
---|
995 | * @param {ASTNode} node The node to check.
|
---|
996 | * @returns {boolean} Whether or not the node is an ExpressionStatement at the top level of a
|
---|
997 | * file or function body.
|
---|
998 | */
|
---|
999 | function isTopLevelExpressionStatement(node) {
|
---|
1000 | if (node.type !== "ExpressionStatement") {
|
---|
1001 | return false;
|
---|
1002 | }
|
---|
1003 | const parent = node.parent;
|
---|
1004 |
|
---|
1005 | return parent.type === "Program" || (parent.type === "BlockStatement" && isFunction(parent.parent));
|
---|
1006 |
|
---|
1007 | }
|
---|
1008 |
|
---|
1009 | /**
|
---|
1010 | * Check whether the given node is a part of a directive prologue or not.
|
---|
1011 | * @param {ASTNode} node The node to check.
|
---|
1012 | * @returns {boolean} `true` if the node is a part of directive prologue.
|
---|
1013 | */
|
---|
1014 | function isDirective(node) {
|
---|
1015 | return node.type === "ExpressionStatement" && typeof node.directive === "string";
|
---|
1016 | }
|
---|
1017 |
|
---|
1018 | /**
|
---|
1019 | * Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
|
---|
1020 | * @param {ASTNode} node The node to check.
|
---|
1021 | * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
|
---|
1022 | */
|
---|
1023 | function isStartOfExpressionStatement(node) {
|
---|
1024 | const start = node.range[0];
|
---|
1025 | let ancestor = node;
|
---|
1026 |
|
---|
1027 | while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
|
---|
1028 | if (ancestor.type === "ExpressionStatement") {
|
---|
1029 | return true;
|
---|
1030 | }
|
---|
1031 | }
|
---|
1032 | return false;
|
---|
1033 | }
|
---|
1034 |
|
---|
1035 | /**
|
---|
1036 | * Determines whether an opening parenthesis `(`, bracket `[` or backtick ``` ` ``` needs to be preceded by a semicolon.
|
---|
1037 | * This opening parenthesis or bracket should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
|
---|
1038 | * @type {(sourceCode: SourceCode, node: ASTNode) => boolean}
|
---|
1039 | * @param {SourceCode} sourceCode The source code object.
|
---|
1040 | * @param {ASTNode} node A node at the position where an opening parenthesis or bracket will be inserted.
|
---|
1041 | * @returns {boolean} Whether a semicolon is required before the opening parenthesis or braket.
|
---|
1042 | */
|
---|
1043 | let needsPrecedingSemicolon;
|
---|
1044 |
|
---|
1045 | {
|
---|
1046 | const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
|
---|
1047 |
|
---|
1048 | // Declaration types that must contain a string Literal node at the end.
|
---|
1049 | const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
|
---|
1050 |
|
---|
1051 | const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
|
---|
1052 |
|
---|
1053 | // Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
|
---|
1054 | const NODE_TYPES_BY_KEYWORD = {
|
---|
1055 | __proto__: null,
|
---|
1056 | break: "BreakStatement",
|
---|
1057 | continue: "ContinueStatement",
|
---|
1058 | debugger: "DebuggerStatement",
|
---|
1059 | do: "DoWhileStatement",
|
---|
1060 | else: "IfStatement",
|
---|
1061 | return: "ReturnStatement",
|
---|
1062 | yield: "YieldExpression"
|
---|
1063 | };
|
---|
1064 |
|
---|
1065 | /*
|
---|
1066 | * Before an opening parenthesis, postfix `++` and `--` always trigger ASI;
|
---|
1067 | * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
|
---|
1068 | */
|
---|
1069 | const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]);
|
---|
1070 |
|
---|
1071 | /*
|
---|
1072 | * Statements that can contain an `ExpressionStatement` after a closing parenthesis.
|
---|
1073 | * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
|
---|
1074 | */
|
---|
1075 | const STATEMENTS = new Set([
|
---|
1076 | "DoWhileStatement",
|
---|
1077 | "ForInStatement",
|
---|
1078 | "ForOfStatement",
|
---|
1079 | "ForStatement",
|
---|
1080 | "IfStatement",
|
---|
1081 | "WhileStatement",
|
---|
1082 | "WithStatement"
|
---|
1083 | ]);
|
---|
1084 |
|
---|
1085 | needsPrecedingSemicolon =
|
---|
1086 | function(sourceCode, node) {
|
---|
1087 | const prevToken = sourceCode.getTokenBefore(node);
|
---|
1088 |
|
---|
1089 | if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
|
---|
1090 | return false;
|
---|
1091 | }
|
---|
1092 |
|
---|
1093 | const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
|
---|
1094 |
|
---|
1095 | if (isClosingParenToken(prevToken)) {
|
---|
1096 | return !STATEMENTS.has(prevNode.type);
|
---|
1097 | }
|
---|
1098 |
|
---|
1099 | if (isClosingBraceToken(prevToken)) {
|
---|
1100 | return (
|
---|
1101 | prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
|
---|
1102 | prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
|
---|
1103 | prevNode.type === "ObjectExpression"
|
---|
1104 | );
|
---|
1105 | }
|
---|
1106 |
|
---|
1107 | if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
|
---|
1108 | if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
|
---|
1109 | return false;
|
---|
1110 | }
|
---|
1111 |
|
---|
1112 | const keyword = prevToken.value;
|
---|
1113 | const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
|
---|
1114 |
|
---|
1115 | return prevNode.type !== nodeType;
|
---|
1116 | }
|
---|
1117 |
|
---|
1118 | if (prevToken.type === "String") {
|
---|
1119 | return !DECLARATIONS.has(prevNode.parent.type);
|
---|
1120 | }
|
---|
1121 |
|
---|
1122 | return true;
|
---|
1123 | };
|
---|
1124 | }
|
---|
1125 |
|
---|
1126 | //------------------------------------------------------------------------------
|
---|
1127 | // Public Interface
|
---|
1128 | //------------------------------------------------------------------------------
|
---|
1129 |
|
---|
1130 | module.exports = {
|
---|
1131 | COMMENTS_IGNORE_PATTERN,
|
---|
1132 | LINEBREAKS,
|
---|
1133 | LINEBREAK_MATCHER: lineBreakPattern,
|
---|
1134 | SHEBANG_MATCHER: shebangPattern,
|
---|
1135 | STATEMENT_LIST_PARENTS,
|
---|
1136 |
|
---|
1137 | /**
|
---|
1138 | * Determines whether two adjacent tokens are on the same line.
|
---|
1139 | * @param {Object} left The left token object.
|
---|
1140 | * @param {Object} right The right token object.
|
---|
1141 | * @returns {boolean} Whether or not the tokens are on the same line.
|
---|
1142 | * @public
|
---|
1143 | */
|
---|
1144 | isTokenOnSameLine(left, right) {
|
---|
1145 | return left.loc.end.line === right.loc.start.line;
|
---|
1146 | },
|
---|
1147 |
|
---|
1148 | isNullOrUndefined,
|
---|
1149 | isCallee,
|
---|
1150 | isES5Constructor,
|
---|
1151 | getUpperFunction,
|
---|
1152 | isFunction,
|
---|
1153 | isLoop,
|
---|
1154 | isInLoop,
|
---|
1155 | isArrayFromMethod,
|
---|
1156 | isParenthesised,
|
---|
1157 | createGlobalLinebreakMatcher,
|
---|
1158 | equalTokens,
|
---|
1159 |
|
---|
1160 | isArrowToken,
|
---|
1161 | isClosingBraceToken,
|
---|
1162 | isClosingBracketToken,
|
---|
1163 | isClosingParenToken,
|
---|
1164 | isColonToken,
|
---|
1165 | isCommaToken,
|
---|
1166 | isCommentToken,
|
---|
1167 | isDotToken,
|
---|
1168 | isQuestionDotToken,
|
---|
1169 | isKeywordToken,
|
---|
1170 | isNotClosingBraceToken: negate(isClosingBraceToken),
|
---|
1171 | isNotClosingBracketToken: negate(isClosingBracketToken),
|
---|
1172 | isNotClosingParenToken: negate(isClosingParenToken),
|
---|
1173 | isNotColonToken: negate(isColonToken),
|
---|
1174 | isNotCommaToken: negate(isCommaToken),
|
---|
1175 | isNotDotToken: negate(isDotToken),
|
---|
1176 | isNotQuestionDotToken: negate(isQuestionDotToken),
|
---|
1177 | isNotOpeningBraceToken: negate(isOpeningBraceToken),
|
---|
1178 | isNotOpeningBracketToken: negate(isOpeningBracketToken),
|
---|
1179 | isNotOpeningParenToken: negate(isOpeningParenToken),
|
---|
1180 | isNotSemicolonToken: negate(isSemicolonToken),
|
---|
1181 | isOpeningBraceToken,
|
---|
1182 | isOpeningBracketToken,
|
---|
1183 | isOpeningParenToken,
|
---|
1184 | isSemicolonToken,
|
---|
1185 | isEqToken,
|
---|
1186 |
|
---|
1187 | /**
|
---|
1188 | * Checks whether or not a given node is a string literal.
|
---|
1189 | * @param {ASTNode} node A node to check.
|
---|
1190 | * @returns {boolean} `true` if the node is a string literal.
|
---|
1191 | */
|
---|
1192 | isStringLiteral(node) {
|
---|
1193 | return (
|
---|
1194 | (node.type === "Literal" && typeof node.value === "string") ||
|
---|
1195 | node.type === "TemplateLiteral"
|
---|
1196 | );
|
---|
1197 | },
|
---|
1198 |
|
---|
1199 | /**
|
---|
1200 | * Checks whether a given node is a breakable statement or not.
|
---|
1201 | * The node is breakable if the node is one of the following type:
|
---|
1202 | *
|
---|
1203 | * - DoWhileStatement
|
---|
1204 | * - ForInStatement
|
---|
1205 | * - ForOfStatement
|
---|
1206 | * - ForStatement
|
---|
1207 | * - SwitchStatement
|
---|
1208 | * - WhileStatement
|
---|
1209 | * @param {ASTNode} node A node to check.
|
---|
1210 | * @returns {boolean} `true` if the node is breakable.
|
---|
1211 | */
|
---|
1212 | isBreakableStatement(node) {
|
---|
1213 | return breakableTypePattern.test(node.type);
|
---|
1214 | },
|
---|
1215 |
|
---|
1216 | /**
|
---|
1217 | * Gets references which are non initializer and writable.
|
---|
1218 | * @param {Reference[]} references An array of references.
|
---|
1219 | * @returns {Reference[]} An array of only references which are non initializer and writable.
|
---|
1220 | * @public
|
---|
1221 | */
|
---|
1222 | getModifyingReferences(references) {
|
---|
1223 | return references.filter(isModifyingReference);
|
---|
1224 | },
|
---|
1225 |
|
---|
1226 | /**
|
---|
1227 | * Validate that a string passed in is surrounded by the specified character
|
---|
1228 | * @param {string} val The text to check.
|
---|
1229 | * @param {string} character The character to see if it's surrounded by.
|
---|
1230 | * @returns {boolean} True if the text is surrounded by the character, false if not.
|
---|
1231 | * @private
|
---|
1232 | */
|
---|
1233 | isSurroundedBy(val, character) {
|
---|
1234 | return val[0] === character && val[val.length - 1] === character;
|
---|
1235 | },
|
---|
1236 |
|
---|
1237 | /**
|
---|
1238 | * Returns whether the provided node is an ESLint directive comment or not
|
---|
1239 | * @param {Line|Block} node The comment token to be checked
|
---|
1240 | * @returns {boolean} `true` if the node is an ESLint directive comment
|
---|
1241 | */
|
---|
1242 | isDirectiveComment(node) {
|
---|
1243 | const comment = node.value.trim();
|
---|
1244 |
|
---|
1245 | return (
|
---|
1246 | node.type === "Line" && comment.startsWith("eslint-") ||
|
---|
1247 | node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment)
|
---|
1248 | );
|
---|
1249 | },
|
---|
1250 |
|
---|
1251 | /**
|
---|
1252 | * Gets the trailing statement of a given node.
|
---|
1253 | *
|
---|
1254 | * if (code)
|
---|
1255 | * consequent;
|
---|
1256 | *
|
---|
1257 | * When taking this `IfStatement`, returns `consequent;` statement.
|
---|
1258 | * @param {ASTNode} A node to get.
|
---|
1259 | * @returns {ASTNode|null} The trailing statement's node.
|
---|
1260 | */
|
---|
1261 | getTrailingStatement: esutils.ast.trailingStatement,
|
---|
1262 |
|
---|
1263 | /**
|
---|
1264 | * Finds the variable by a given name in a given scope and its upper scopes.
|
---|
1265 | * @param {eslint-scope.Scope} initScope A scope to start find.
|
---|
1266 | * @param {string} name A variable name to find.
|
---|
1267 | * @returns {eslint-scope.Variable|null} A found variable or `null`.
|
---|
1268 | */
|
---|
1269 | getVariableByName(initScope, name) {
|
---|
1270 | let scope = initScope;
|
---|
1271 |
|
---|
1272 | while (scope) {
|
---|
1273 | const variable = scope.set.get(name);
|
---|
1274 |
|
---|
1275 | if (variable) {
|
---|
1276 | return variable;
|
---|
1277 | }
|
---|
1278 |
|
---|
1279 | scope = scope.upper;
|
---|
1280 | }
|
---|
1281 |
|
---|
1282 | return null;
|
---|
1283 | },
|
---|
1284 |
|
---|
1285 | /**
|
---|
1286 | * Checks whether or not a given function node is the default `this` binding.
|
---|
1287 | *
|
---|
1288 | * First, this checks the node:
|
---|
1289 | *
|
---|
1290 | * - The given node is not in `PropertyDefinition#value` position.
|
---|
1291 | * - The given node is not `StaticBlock`.
|
---|
1292 | * - The function name does not start with uppercase. It's a convention to capitalize the names
|
---|
1293 | * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`.
|
---|
1294 | * - The function does not have a JSDoc comment that has a @this tag.
|
---|
1295 | *
|
---|
1296 | * Next, this checks the location of the node.
|
---|
1297 | * If the location is below, this judges `this` is valid.
|
---|
1298 | *
|
---|
1299 | * - The location is not on an object literal.
|
---|
1300 | * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous
|
---|
1301 | * functions only, as the name of the variable is considered to be the name of the function in this case.
|
---|
1302 | * This check is not performed if `capIsConstructor` is set to `false`.
|
---|
1303 | * - The location is not on an ES2015 class.
|
---|
1304 | * - Its `bind`/`call`/`apply` method is not called directly.
|
---|
1305 | * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given.
|
---|
1306 | * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock`
|
---|
1307 | * or any expression that is `PropertyDefinition#value` node.
|
---|
1308 | * @param {SourceCode} sourceCode A SourceCode instance to get comments.
|
---|
1309 | * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts
|
---|
1310 | * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors.
|
---|
1311 | * @returns {boolean} The function node is the default `this` binding.
|
---|
1312 | */
|
---|
1313 | isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) {
|
---|
1314 |
|
---|
1315 | /*
|
---|
1316 | * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers.
|
---|
1317 | * Therefore, A expression node at `PropertyDefinition#value` is a function.
|
---|
1318 | * In this case, `this` is always not default binding.
|
---|
1319 | */
|
---|
1320 | if (node.parent.type === "PropertyDefinition" && node.parent.value === node) {
|
---|
1321 | return false;
|
---|
1322 | }
|
---|
1323 |
|
---|
1324 | // Class static blocks are implicit functions. In this case, `this` is always not default binding.
|
---|
1325 | if (node.type === "StaticBlock") {
|
---|
1326 | return false;
|
---|
1327 | }
|
---|
1328 |
|
---|
1329 | if (
|
---|
1330 | (capIsConstructor && isES5Constructor(node)) ||
|
---|
1331 | hasJSDocThisTag(node, sourceCode)
|
---|
1332 | ) {
|
---|
1333 | return false;
|
---|
1334 | }
|
---|
1335 | const isAnonymous = node.id === null;
|
---|
1336 | let currentNode = node;
|
---|
1337 |
|
---|
1338 | while (currentNode) {
|
---|
1339 | const parent = currentNode.parent;
|
---|
1340 |
|
---|
1341 | switch (parent.type) {
|
---|
1342 |
|
---|
1343 | /*
|
---|
1344 | * Looks up the destination.
|
---|
1345 | * e.g., obj.foo = nativeFoo || function foo() { ... };
|
---|
1346 | */
|
---|
1347 | case "LogicalExpression":
|
---|
1348 | case "ConditionalExpression":
|
---|
1349 | case "ChainExpression":
|
---|
1350 | currentNode = parent;
|
---|
1351 | break;
|
---|
1352 |
|
---|
1353 | /*
|
---|
1354 | * If the upper function is IIFE, checks the destination of the return value.
|
---|
1355 | * e.g.
|
---|
1356 | * obj.foo = (function() {
|
---|
1357 | * // setup...
|
---|
1358 | * return function foo() { ... };
|
---|
1359 | * })();
|
---|
1360 | * obj.foo = (() =>
|
---|
1361 | * function foo() { ... }
|
---|
1362 | * )();
|
---|
1363 | */
|
---|
1364 | case "ReturnStatement": {
|
---|
1365 | const func = getUpperFunction(parent);
|
---|
1366 |
|
---|
1367 | if (func === null || !isCallee(func)) {
|
---|
1368 | return true;
|
---|
1369 | }
|
---|
1370 | currentNode = func.parent;
|
---|
1371 | break;
|
---|
1372 | }
|
---|
1373 | case "ArrowFunctionExpression":
|
---|
1374 | if (currentNode !== parent.body || !isCallee(parent)) {
|
---|
1375 | return true;
|
---|
1376 | }
|
---|
1377 | currentNode = parent.parent;
|
---|
1378 | break;
|
---|
1379 |
|
---|
1380 | /*
|
---|
1381 | * e.g.
|
---|
1382 | * var obj = { foo() { ... } };
|
---|
1383 | * var obj = { foo: function() { ... } };
|
---|
1384 | * class A { constructor() { ... } }
|
---|
1385 | * class A { foo() { ... } }
|
---|
1386 | * class A { get foo() { ... } }
|
---|
1387 | * class A { set foo() { ... } }
|
---|
1388 | * class A { static foo() { ... } }
|
---|
1389 | * class A { foo = function() { ... } }
|
---|
1390 | */
|
---|
1391 | case "Property":
|
---|
1392 | case "PropertyDefinition":
|
---|
1393 | case "MethodDefinition":
|
---|
1394 | return parent.value !== currentNode;
|
---|
1395 |
|
---|
1396 | /*
|
---|
1397 | * e.g.
|
---|
1398 | * obj.foo = function foo() { ... };
|
---|
1399 | * Foo = function() { ... };
|
---|
1400 | * [obj.foo = function foo() { ... }] = a;
|
---|
1401 | * [Foo = function() { ... }] = a;
|
---|
1402 | */
|
---|
1403 | case "AssignmentExpression":
|
---|
1404 | case "AssignmentPattern":
|
---|
1405 | if (parent.left.type === "MemberExpression") {
|
---|
1406 | return false;
|
---|
1407 | }
|
---|
1408 | if (
|
---|
1409 | capIsConstructor &&
|
---|
1410 | isAnonymous &&
|
---|
1411 | parent.left.type === "Identifier" &&
|
---|
1412 | startsWithUpperCase(parent.left.name)
|
---|
1413 | ) {
|
---|
1414 | return false;
|
---|
1415 | }
|
---|
1416 | return true;
|
---|
1417 |
|
---|
1418 | /*
|
---|
1419 | * e.g.
|
---|
1420 | * var Foo = function() { ... };
|
---|
1421 | */
|
---|
1422 | case "VariableDeclarator":
|
---|
1423 | return !(
|
---|
1424 | capIsConstructor &&
|
---|
1425 | isAnonymous &&
|
---|
1426 | parent.init === currentNode &&
|
---|
1427 | parent.id.type === "Identifier" &&
|
---|
1428 | startsWithUpperCase(parent.id.name)
|
---|
1429 | );
|
---|
1430 |
|
---|
1431 | /*
|
---|
1432 | * e.g.
|
---|
1433 | * var foo = function foo() { ... }.bind(obj);
|
---|
1434 | * (function foo() { ... }).call(obj);
|
---|
1435 | * (function foo() { ... }).apply(obj, []);
|
---|
1436 | */
|
---|
1437 | case "MemberExpression":
|
---|
1438 | if (
|
---|
1439 | parent.object === currentNode &&
|
---|
1440 | isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern)
|
---|
1441 | ) {
|
---|
1442 | const maybeCalleeNode = parent.parent.type === "ChainExpression"
|
---|
1443 | ? parent.parent
|
---|
1444 | : parent;
|
---|
1445 |
|
---|
1446 | return !(
|
---|
1447 | isCallee(maybeCalleeNode) &&
|
---|
1448 | maybeCalleeNode.parent.arguments.length >= 1 &&
|
---|
1449 | !isNullOrUndefined(maybeCalleeNode.parent.arguments[0])
|
---|
1450 | );
|
---|
1451 | }
|
---|
1452 | return true;
|
---|
1453 |
|
---|
1454 | /*
|
---|
1455 | * e.g.
|
---|
1456 | * Reflect.apply(function() {}, obj, []);
|
---|
1457 | * Array.from([], function() {}, obj);
|
---|
1458 | * list.forEach(function() {}, obj);
|
---|
1459 | */
|
---|
1460 | case "CallExpression":
|
---|
1461 | if (isReflectApply(parent.callee)) {
|
---|
1462 | return (
|
---|
1463 | parent.arguments.length !== 3 ||
|
---|
1464 | parent.arguments[0] !== currentNode ||
|
---|
1465 | isNullOrUndefined(parent.arguments[1])
|
---|
1466 | );
|
---|
1467 | }
|
---|
1468 | if (isArrayFromMethod(parent.callee)) {
|
---|
1469 | return (
|
---|
1470 | parent.arguments.length !== 3 ||
|
---|
1471 | parent.arguments[1] !== currentNode ||
|
---|
1472 | isNullOrUndefined(parent.arguments[2])
|
---|
1473 | );
|
---|
1474 | }
|
---|
1475 | if (isMethodWhichHasThisArg(parent.callee)) {
|
---|
1476 | return (
|
---|
1477 | parent.arguments.length !== 2 ||
|
---|
1478 | parent.arguments[0] !== currentNode ||
|
---|
1479 | isNullOrUndefined(parent.arguments[1])
|
---|
1480 | );
|
---|
1481 | }
|
---|
1482 | return true;
|
---|
1483 |
|
---|
1484 | // Otherwise `this` is default.
|
---|
1485 | default:
|
---|
1486 | return true;
|
---|
1487 | }
|
---|
1488 | }
|
---|
1489 |
|
---|
1490 | /* c8 ignore next */
|
---|
1491 | return true;
|
---|
1492 | },
|
---|
1493 |
|
---|
1494 | /**
|
---|
1495 | * Get the precedence level based on the node type
|
---|
1496 | * @param {ASTNode} node node to evaluate
|
---|
1497 | * @returns {int} precedence level
|
---|
1498 | * @private
|
---|
1499 | */
|
---|
1500 | getPrecedence(node) {
|
---|
1501 | switch (node.type) {
|
---|
1502 | case "SequenceExpression":
|
---|
1503 | return 0;
|
---|
1504 |
|
---|
1505 | case "AssignmentExpression":
|
---|
1506 | case "ArrowFunctionExpression":
|
---|
1507 | case "YieldExpression":
|
---|
1508 | return 1;
|
---|
1509 |
|
---|
1510 | case "ConditionalExpression":
|
---|
1511 | return 3;
|
---|
1512 |
|
---|
1513 | case "LogicalExpression":
|
---|
1514 | switch (node.operator) {
|
---|
1515 | case "||":
|
---|
1516 | case "??":
|
---|
1517 | return 4;
|
---|
1518 | case "&&":
|
---|
1519 | return 5;
|
---|
1520 |
|
---|
1521 | // no default
|
---|
1522 | }
|
---|
1523 |
|
---|
1524 | /* falls through */
|
---|
1525 |
|
---|
1526 | case "BinaryExpression":
|
---|
1527 |
|
---|
1528 | switch (node.operator) {
|
---|
1529 | case "|":
|
---|
1530 | return 6;
|
---|
1531 | case "^":
|
---|
1532 | return 7;
|
---|
1533 | case "&":
|
---|
1534 | return 8;
|
---|
1535 | case "==":
|
---|
1536 | case "!=":
|
---|
1537 | case "===":
|
---|
1538 | case "!==":
|
---|
1539 | return 9;
|
---|
1540 | case "<":
|
---|
1541 | case "<=":
|
---|
1542 | case ">":
|
---|
1543 | case ">=":
|
---|
1544 | case "in":
|
---|
1545 | case "instanceof":
|
---|
1546 | return 10;
|
---|
1547 | case "<<":
|
---|
1548 | case ">>":
|
---|
1549 | case ">>>":
|
---|
1550 | return 11;
|
---|
1551 | case "+":
|
---|
1552 | case "-":
|
---|
1553 | return 12;
|
---|
1554 | case "*":
|
---|
1555 | case "/":
|
---|
1556 | case "%":
|
---|
1557 | return 13;
|
---|
1558 | case "**":
|
---|
1559 | return 15;
|
---|
1560 |
|
---|
1561 | // no default
|
---|
1562 | }
|
---|
1563 |
|
---|
1564 | /* falls through */
|
---|
1565 |
|
---|
1566 | case "UnaryExpression":
|
---|
1567 | case "AwaitExpression":
|
---|
1568 | return 16;
|
---|
1569 |
|
---|
1570 | case "UpdateExpression":
|
---|
1571 | return 17;
|
---|
1572 |
|
---|
1573 | case "CallExpression":
|
---|
1574 | case "ChainExpression":
|
---|
1575 | case "ImportExpression":
|
---|
1576 | return 18;
|
---|
1577 |
|
---|
1578 | case "NewExpression":
|
---|
1579 | return 19;
|
---|
1580 |
|
---|
1581 | default:
|
---|
1582 | if (node.type in eslintVisitorKeys) {
|
---|
1583 | return 20;
|
---|
1584 | }
|
---|
1585 |
|
---|
1586 | /*
|
---|
1587 | * if the node is not a standard node that we know about, then assume it has the lowest precedence
|
---|
1588 | * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of
|
---|
1589 | * unwrapping them and potentially changing the meaning of the code or introducing a syntax error.
|
---|
1590 | */
|
---|
1591 | return -1;
|
---|
1592 | }
|
---|
1593 | },
|
---|
1594 |
|
---|
1595 | /**
|
---|
1596 | * Checks whether the given node is an empty block node or not.
|
---|
1597 | * @param {ASTNode|null} node The node to check.
|
---|
1598 | * @returns {boolean} `true` if the node is an empty block.
|
---|
1599 | */
|
---|
1600 | isEmptyBlock(node) {
|
---|
1601 | return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
|
---|
1602 | },
|
---|
1603 |
|
---|
1604 | /**
|
---|
1605 | * Checks whether the given node is an empty function node or not.
|
---|
1606 | * @param {ASTNode|null} node The node to check.
|
---|
1607 | * @returns {boolean} `true` if the node is an empty function.
|
---|
1608 | */
|
---|
1609 | isEmptyFunction(node) {
|
---|
1610 | return isFunction(node) && module.exports.isEmptyBlock(node.body);
|
---|
1611 | },
|
---|
1612 |
|
---|
1613 | /**
|
---|
1614 | * Get directives from directive prologue of a Program or Function node.
|
---|
1615 | * @param {ASTNode} node The node to check.
|
---|
1616 | * @returns {ASTNode[]} The directives found in the directive prologue.
|
---|
1617 | */
|
---|
1618 | getDirectivePrologue(node) {
|
---|
1619 | const directives = [];
|
---|
1620 |
|
---|
1621 | // Directive prologues only occur at the top of files or functions.
|
---|
1622 | if (
|
---|
1623 | node.type === "Program" ||
|
---|
1624 | node.type === "FunctionDeclaration" ||
|
---|
1625 | node.type === "FunctionExpression" ||
|
---|
1626 |
|
---|
1627 | /*
|
---|
1628 | * Do not check arrow functions with implicit return.
|
---|
1629 | * `() => "use strict";` returns the string `"use strict"`.
|
---|
1630 | */
|
---|
1631 | (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement")
|
---|
1632 | ) {
|
---|
1633 | const statements = node.type === "Program" ? node.body : node.body.body;
|
---|
1634 |
|
---|
1635 | for (const statement of statements) {
|
---|
1636 | if (
|
---|
1637 | statement.type === "ExpressionStatement" &&
|
---|
1638 | statement.expression.type === "Literal"
|
---|
1639 | ) {
|
---|
1640 | directives.push(statement);
|
---|
1641 | } else {
|
---|
1642 | break;
|
---|
1643 | }
|
---|
1644 | }
|
---|
1645 | }
|
---|
1646 |
|
---|
1647 | return directives;
|
---|
1648 | },
|
---|
1649 |
|
---|
1650 | /**
|
---|
1651 | * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added
|
---|
1652 | * after the node will be parsed as a decimal point, rather than a property-access dot.
|
---|
1653 | * @param {ASTNode} node The node to check.
|
---|
1654 | * @returns {boolean} `true` if this node is a decimal integer.
|
---|
1655 | * @example
|
---|
1656 | *
|
---|
1657 | * 0 // true
|
---|
1658 | * 5 // true
|
---|
1659 | * 50 // true
|
---|
1660 | * 5_000 // true
|
---|
1661 | * 1_234_56 // true
|
---|
1662 | * 08 // true
|
---|
1663 | * 0192 // true
|
---|
1664 | * 5. // false
|
---|
1665 | * .5 // false
|
---|
1666 | * 5.0 // false
|
---|
1667 | * 5.00_00 // false
|
---|
1668 | * 05 // false
|
---|
1669 | * 0x5 // false
|
---|
1670 | * 0b101 // false
|
---|
1671 | * 0b11_01 // false
|
---|
1672 | * 0o5 // false
|
---|
1673 | * 5e0 // false
|
---|
1674 | * 5e1_000 // false
|
---|
1675 | * 5n // false
|
---|
1676 | * 1_000n // false
|
---|
1677 | * "5" // false
|
---|
1678 | *
|
---|
1679 | */
|
---|
1680 | isDecimalInteger(node) {
|
---|
1681 | return node.type === "Literal" && typeof node.value === "number" &&
|
---|
1682 | DECIMAL_INTEGER_PATTERN.test(node.raw);
|
---|
1683 | },
|
---|
1684 |
|
---|
1685 | /**
|
---|
1686 | * Determines whether this token is a decimal integer numeric token.
|
---|
1687 | * This is similar to isDecimalInteger(), but for tokens.
|
---|
1688 | * @param {Token} token The token to check.
|
---|
1689 | * @returns {boolean} `true` if this token is a decimal integer.
|
---|
1690 | */
|
---|
1691 | isDecimalIntegerNumericToken(token) {
|
---|
1692 | return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value);
|
---|
1693 | },
|
---|
1694 |
|
---|
1695 | /**
|
---|
1696 | * Gets the name and kind of the given function node.
|
---|
1697 | *
|
---|
1698 | * - `function foo() {}` .................... `function 'foo'`
|
---|
1699 | * - `(function foo() {})` .................. `function 'foo'`
|
---|
1700 | * - `(function() {})` ...................... `function`
|
---|
1701 | * - `function* foo() {}` ................... `generator function 'foo'`
|
---|
1702 | * - `(function* foo() {})` ................. `generator function 'foo'`
|
---|
1703 | * - `(function*() {})` ..................... `generator function`
|
---|
1704 | * - `() => {}` ............................. `arrow function`
|
---|
1705 | * - `async () => {}` ....................... `async arrow function`
|
---|
1706 | * - `({ foo: function foo() {} })` ......... `method 'foo'`
|
---|
1707 | * - `({ foo: function() {} })` ............. `method 'foo'`
|
---|
1708 | * - `({ ['foo']: function() {} })` ......... `method 'foo'`
|
---|
1709 | * - `({ [foo]: function() {} })` ........... `method`
|
---|
1710 | * - `({ foo() {} })` ....................... `method 'foo'`
|
---|
1711 | * - `({ foo: function* foo() {} })` ........ `generator method 'foo'`
|
---|
1712 | * - `({ foo: function*() {} })` ............ `generator method 'foo'`
|
---|
1713 | * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'`
|
---|
1714 | * - `({ [foo]: function*() {} })` .......... `generator method`
|
---|
1715 | * - `({ *foo() {} })` ...................... `generator method 'foo'`
|
---|
1716 | * - `({ foo: async function foo() {} })` ... `async method 'foo'`
|
---|
1717 | * - `({ foo: async function() {} })` ....... `async method 'foo'`
|
---|
1718 | * - `({ ['foo']: async function() {} })` ... `async method 'foo'`
|
---|
1719 | * - `({ [foo]: async function() {} })` ..... `async method`
|
---|
1720 | * - `({ async foo() {} })` ................. `async method 'foo'`
|
---|
1721 | * - `({ get foo() {} })` ................... `getter 'foo'`
|
---|
1722 | * - `({ set foo(a) {} })` .................. `setter 'foo'`
|
---|
1723 | * - `class A { constructor() {} }` ......... `constructor`
|
---|
1724 | * - `class A { foo() {} }` ................. `method 'foo'`
|
---|
1725 | * - `class A { *foo() {} }` ................ `generator method 'foo'`
|
---|
1726 | * - `class A { async foo() {} }` ........... `async method 'foo'`
|
---|
1727 | * - `class A { ['foo']() {} }` ............. `method 'foo'`
|
---|
1728 | * - `class A { *['foo']() {} }` ............ `generator method 'foo'`
|
---|
1729 | * - `class A { async ['foo']() {} }` ....... `async method 'foo'`
|
---|
1730 | * - `class A { [foo]() {} }` ............... `method`
|
---|
1731 | * - `class A { *[foo]() {} }` .............. `generator method`
|
---|
1732 | * - `class A { async [foo]() {} }` ......... `async method`
|
---|
1733 | * - `class A { get foo() {} }` ............. `getter 'foo'`
|
---|
1734 | * - `class A { set foo(a) {} }` ............ `setter 'foo'`
|
---|
1735 | * - `class A { static foo() {} }` .......... `static method 'foo'`
|
---|
1736 | * - `class A { static *foo() {} }` ......... `static generator method 'foo'`
|
---|
1737 | * - `class A { static async foo() {} }` .... `static async method 'foo'`
|
---|
1738 | * - `class A { static get foo() {} }` ...... `static getter 'foo'`
|
---|
1739 | * - `class A { static set foo(a) {} }` ..... `static setter 'foo'`
|
---|
1740 | * - `class A { foo = () => {}; }` .......... `method 'foo'`
|
---|
1741 | * - `class A { foo = function() {}; }` ..... `method 'foo'`
|
---|
1742 | * - `class A { foo = function bar() {}; }` . `method 'foo'`
|
---|
1743 | * - `class A { static foo = () => {}; }` ... `static method 'foo'`
|
---|
1744 | * - `class A { '#foo' = () => {}; }` ....... `method '#foo'`
|
---|
1745 | * - `class A { #foo = () => {}; }` ......... `private method #foo`
|
---|
1746 | * - `class A { static #foo = () => {}; }` .. `static private method #foo`
|
---|
1747 | * - `class A { '#foo'() {} }` .............. `method '#foo'`
|
---|
1748 | * - `class A { #foo() {} }` ................ `private method #foo`
|
---|
1749 | * - `class A { static #foo() {} }` ......... `static private method #foo`
|
---|
1750 | * @param {ASTNode} node The function node to get.
|
---|
1751 | * @returns {string} The name and kind of the function node.
|
---|
1752 | */
|
---|
1753 | getFunctionNameWithKind(node) {
|
---|
1754 | const parent = node.parent;
|
---|
1755 | const tokens = [];
|
---|
1756 |
|
---|
1757 | if (parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
|
---|
1758 |
|
---|
1759 | // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features
|
---|
1760 | if (parent.static) {
|
---|
1761 | tokens.push("static");
|
---|
1762 | }
|
---|
1763 | if (!parent.computed && parent.key.type === "PrivateIdentifier") {
|
---|
1764 | tokens.push("private");
|
---|
1765 | }
|
---|
1766 | }
|
---|
1767 | if (node.async) {
|
---|
1768 | tokens.push("async");
|
---|
1769 | }
|
---|
1770 | if (node.generator) {
|
---|
1771 | tokens.push("generator");
|
---|
1772 | }
|
---|
1773 |
|
---|
1774 | if (parent.type === "Property" || parent.type === "MethodDefinition") {
|
---|
1775 | if (parent.kind === "constructor") {
|
---|
1776 | return "constructor";
|
---|
1777 | }
|
---|
1778 | if (parent.kind === "get") {
|
---|
1779 | tokens.push("getter");
|
---|
1780 | } else if (parent.kind === "set") {
|
---|
1781 | tokens.push("setter");
|
---|
1782 | } else {
|
---|
1783 | tokens.push("method");
|
---|
1784 | }
|
---|
1785 | } else if (parent.type === "PropertyDefinition") {
|
---|
1786 | tokens.push("method");
|
---|
1787 | } else {
|
---|
1788 | if (node.type === "ArrowFunctionExpression") {
|
---|
1789 | tokens.push("arrow");
|
---|
1790 | }
|
---|
1791 | tokens.push("function");
|
---|
1792 | }
|
---|
1793 |
|
---|
1794 | if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
|
---|
1795 | if (!parent.computed && parent.key.type === "PrivateIdentifier") {
|
---|
1796 | tokens.push(`#${parent.key.name}`);
|
---|
1797 | } else {
|
---|
1798 | const name = getStaticPropertyName(parent);
|
---|
1799 |
|
---|
1800 | if (name !== null) {
|
---|
1801 | tokens.push(`'${name}'`);
|
---|
1802 | } else if (node.id) {
|
---|
1803 | tokens.push(`'${node.id.name}'`);
|
---|
1804 | }
|
---|
1805 | }
|
---|
1806 | } else if (node.id) {
|
---|
1807 | tokens.push(`'${node.id.name}'`);
|
---|
1808 | }
|
---|
1809 |
|
---|
1810 | return tokens.join(" ");
|
---|
1811 | },
|
---|
1812 |
|
---|
1813 | /**
|
---|
1814 | * Gets the location of the given function node for reporting.
|
---|
1815 | *
|
---|
1816 | * - `function foo() {}`
|
---|
1817 | * ^^^^^^^^^^^^
|
---|
1818 | * - `(function foo() {})`
|
---|
1819 | * ^^^^^^^^^^^^
|
---|
1820 | * - `(function() {})`
|
---|
1821 | * ^^^^^^^^
|
---|
1822 | * - `function* foo() {}`
|
---|
1823 | * ^^^^^^^^^^^^^
|
---|
1824 | * - `(function* foo() {})`
|
---|
1825 | * ^^^^^^^^^^^^^
|
---|
1826 | * - `(function*() {})`
|
---|
1827 | * ^^^^^^^^^
|
---|
1828 | * - `() => {}`
|
---|
1829 | * ^^
|
---|
1830 | * - `async () => {}`
|
---|
1831 | * ^^
|
---|
1832 | * - `({ foo: function foo() {} })`
|
---|
1833 | * ^^^^^^^^^^^^^^^^^
|
---|
1834 | * - `({ foo: function() {} })`
|
---|
1835 | * ^^^^^^^^^^^^^
|
---|
1836 | * - `({ ['foo']: function() {} })`
|
---|
1837 | * ^^^^^^^^^^^^^^^^^
|
---|
1838 | * - `({ [foo]: function() {} })`
|
---|
1839 | * ^^^^^^^^^^^^^^^
|
---|
1840 | * - `({ foo() {} })`
|
---|
1841 | * ^^^
|
---|
1842 | * - `({ foo: function* foo() {} })`
|
---|
1843 | * ^^^^^^^^^^^^^^^^^^
|
---|
1844 | * - `({ foo: function*() {} })`
|
---|
1845 | * ^^^^^^^^^^^^^^
|
---|
1846 | * - `({ ['foo']: function*() {} })`
|
---|
1847 | * ^^^^^^^^^^^^^^^^^^
|
---|
1848 | * - `({ [foo]: function*() {} })`
|
---|
1849 | * ^^^^^^^^^^^^^^^^
|
---|
1850 | * - `({ *foo() {} })`
|
---|
1851 | * ^^^^
|
---|
1852 | * - `({ foo: async function foo() {} })`
|
---|
1853 | * ^^^^^^^^^^^^^^^^^^^^^^^
|
---|
1854 | * - `({ foo: async function() {} })`
|
---|
1855 | * ^^^^^^^^^^^^^^^^^^^
|
---|
1856 | * - `({ ['foo']: async function() {} })`
|
---|
1857 | * ^^^^^^^^^^^^^^^^^^^^^^^
|
---|
1858 | * - `({ [foo]: async function() {} })`
|
---|
1859 | * ^^^^^^^^^^^^^^^^^^^^^
|
---|
1860 | * - `({ async foo() {} })`
|
---|
1861 | * ^^^^^^^^^
|
---|
1862 | * - `({ get foo() {} })`
|
---|
1863 | * ^^^^^^^
|
---|
1864 | * - `({ set foo(a) {} })`
|
---|
1865 | * ^^^^^^^
|
---|
1866 | * - `class A { constructor() {} }`
|
---|
1867 | * ^^^^^^^^^^^
|
---|
1868 | * - `class A { foo() {} }`
|
---|
1869 | * ^^^
|
---|
1870 | * - `class A { *foo() {} }`
|
---|
1871 | * ^^^^
|
---|
1872 | * - `class A { async foo() {} }`
|
---|
1873 | * ^^^^^^^^^
|
---|
1874 | * - `class A { ['foo']() {} }`
|
---|
1875 | * ^^^^^^^
|
---|
1876 | * - `class A { *['foo']() {} }`
|
---|
1877 | * ^^^^^^^^
|
---|
1878 | * - `class A { async ['foo']() {} }`
|
---|
1879 | * ^^^^^^^^^^^^^
|
---|
1880 | * - `class A { [foo]() {} }`
|
---|
1881 | * ^^^^^
|
---|
1882 | * - `class A { *[foo]() {} }`
|
---|
1883 | * ^^^^^^
|
---|
1884 | * - `class A { async [foo]() {} }`
|
---|
1885 | * ^^^^^^^^^^^
|
---|
1886 | * - `class A { get foo() {} }`
|
---|
1887 | * ^^^^^^^
|
---|
1888 | * - `class A { set foo(a) {} }`
|
---|
1889 | * ^^^^^^^
|
---|
1890 | * - `class A { static foo() {} }`
|
---|
1891 | * ^^^^^^^^^^
|
---|
1892 | * - `class A { static *foo() {} }`
|
---|
1893 | * ^^^^^^^^^^^
|
---|
1894 | * - `class A { static async foo() {} }`
|
---|
1895 | * ^^^^^^^^^^^^^^^^
|
---|
1896 | * - `class A { static get foo() {} }`
|
---|
1897 | * ^^^^^^^^^^^^^^
|
---|
1898 | * - `class A { static set foo(a) {} }`
|
---|
1899 | * ^^^^^^^^^^^^^^
|
---|
1900 | * - `class A { foo = function() {} }`
|
---|
1901 | * ^^^^^^^^^^^^^^
|
---|
1902 | * - `class A { static foo = function() {} }`
|
---|
1903 | * ^^^^^^^^^^^^^^^^^^^^^
|
---|
1904 | * - `class A { foo = (a, b) => {} }`
|
---|
1905 | * ^^^^^^
|
---|
1906 | * @param {ASTNode} node The function node to get.
|
---|
1907 | * @param {SourceCode} sourceCode The source code object to get tokens.
|
---|
1908 | * @returns {string} The location of the function node for reporting.
|
---|
1909 | */
|
---|
1910 | getFunctionHeadLoc(node, sourceCode) {
|
---|
1911 | const parent = node.parent;
|
---|
1912 | let start = null;
|
---|
1913 | let end = null;
|
---|
1914 |
|
---|
1915 | if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
|
---|
1916 | start = parent.loc.start;
|
---|
1917 | end = getOpeningParenOfParams(node, sourceCode).loc.start;
|
---|
1918 | } else if (node.type === "ArrowFunctionExpression") {
|
---|
1919 | const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
|
---|
1920 |
|
---|
1921 | start = arrowToken.loc.start;
|
---|
1922 | end = arrowToken.loc.end;
|
---|
1923 | } else {
|
---|
1924 | start = node.loc.start;
|
---|
1925 | end = getOpeningParenOfParams(node, sourceCode).loc.start;
|
---|
1926 | }
|
---|
1927 |
|
---|
1928 | return {
|
---|
1929 | start: Object.assign({}, start),
|
---|
1930 | end: Object.assign({}, end)
|
---|
1931 | };
|
---|
1932 | },
|
---|
1933 |
|
---|
1934 | /**
|
---|
1935 | * Gets next location when the result is not out of bound, otherwise returns null.
|
---|
1936 | *
|
---|
1937 | * Assumptions:
|
---|
1938 | *
|
---|
1939 | * - The given location represents a valid location in the given source code.
|
---|
1940 | * - Columns are 0-based.
|
---|
1941 | * - Lines are 1-based.
|
---|
1942 | * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location.
|
---|
1943 | * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end.
|
---|
1944 | * The start (column 0) of that extra line is considered to be a valid location.
|
---|
1945 | *
|
---|
1946 | * Examples of successive locations (line, column):
|
---|
1947 | *
|
---|
1948 | * code: foo
|
---|
1949 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null
|
---|
1950 | *
|
---|
1951 | * code: foo<LF>
|
---|
1952 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
|
---|
1953 | *
|
---|
1954 | * code: foo<CR><LF>
|
---|
1955 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
|
---|
1956 | *
|
---|
1957 | * code: a<LF>b
|
---|
1958 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null
|
---|
1959 | *
|
---|
1960 | * code: a<LF>b<LF>
|
---|
1961 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
|
---|
1962 | *
|
---|
1963 | * code: a<CR><LF>b<CR><LF>
|
---|
1964 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
|
---|
1965 | *
|
---|
1966 | * code: a<LF><LF>
|
---|
1967 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null
|
---|
1968 | *
|
---|
1969 | * code: <LF>
|
---|
1970 | * locations: (1, 0) -> (2, 0) -> null
|
---|
1971 | *
|
---|
1972 | * code:
|
---|
1973 | * locations: (1, 0) -> null
|
---|
1974 | * @param {SourceCode} sourceCode The sourceCode
|
---|
1975 | * @param {{line: number, column: number}} location The location
|
---|
1976 | * @returns {{line: number, column: number} | null} Next location
|
---|
1977 | */
|
---|
1978 | getNextLocation(sourceCode, { line, column }) {
|
---|
1979 | if (column < sourceCode.lines[line - 1].length) {
|
---|
1980 | return {
|
---|
1981 | line,
|
---|
1982 | column: column + 1
|
---|
1983 | };
|
---|
1984 | }
|
---|
1985 |
|
---|
1986 | if (line < sourceCode.lines.length) {
|
---|
1987 | return {
|
---|
1988 | line: line + 1,
|
---|
1989 | column: 0
|
---|
1990 | };
|
---|
1991 | }
|
---|
1992 |
|
---|
1993 | return null;
|
---|
1994 | },
|
---|
1995 |
|
---|
1996 | /**
|
---|
1997 | * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
|
---|
1998 | * surrounding the node.
|
---|
1999 | * @param {SourceCode} sourceCode The source code object
|
---|
2000 | * @param {ASTNode} node An expression node
|
---|
2001 | * @returns {string} The text representing the node, with all surrounding parentheses included
|
---|
2002 | */
|
---|
2003 | getParenthesisedText(sourceCode, node) {
|
---|
2004 | let leftToken = sourceCode.getFirstToken(node);
|
---|
2005 | let rightToken = sourceCode.getLastToken(node);
|
---|
2006 |
|
---|
2007 | while (
|
---|
2008 | sourceCode.getTokenBefore(leftToken) &&
|
---|
2009 | sourceCode.getTokenBefore(leftToken).type === "Punctuator" &&
|
---|
2010 | sourceCode.getTokenBefore(leftToken).value === "(" &&
|
---|
2011 | sourceCode.getTokenAfter(rightToken) &&
|
---|
2012 | sourceCode.getTokenAfter(rightToken).type === "Punctuator" &&
|
---|
2013 | sourceCode.getTokenAfter(rightToken).value === ")"
|
---|
2014 | ) {
|
---|
2015 | leftToken = sourceCode.getTokenBefore(leftToken);
|
---|
2016 | rightToken = sourceCode.getTokenAfter(rightToken);
|
---|
2017 | }
|
---|
2018 |
|
---|
2019 | return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
|
---|
2020 | },
|
---|
2021 |
|
---|
2022 | /**
|
---|
2023 | * Determine if a node has a possibility to be an Error object
|
---|
2024 | * @param {ASTNode} node ASTNode to check
|
---|
2025 | * @returns {boolean} True if there is a chance it contains an Error obj
|
---|
2026 | */
|
---|
2027 | couldBeError(node) {
|
---|
2028 | switch (node.type) {
|
---|
2029 | case "Identifier":
|
---|
2030 | case "CallExpression":
|
---|
2031 | case "NewExpression":
|
---|
2032 | case "MemberExpression":
|
---|
2033 | case "TaggedTemplateExpression":
|
---|
2034 | case "YieldExpression":
|
---|
2035 | case "AwaitExpression":
|
---|
2036 | case "ChainExpression":
|
---|
2037 | return true; // possibly an error object.
|
---|
2038 |
|
---|
2039 | case "AssignmentExpression":
|
---|
2040 | if (["=", "&&="].includes(node.operator)) {
|
---|
2041 | return module.exports.couldBeError(node.right);
|
---|
2042 | }
|
---|
2043 |
|
---|
2044 | if (["||=", "??="].includes(node.operator)) {
|
---|
2045 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
|
---|
2046 | }
|
---|
2047 |
|
---|
2048 | /**
|
---|
2049 | * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
|
---|
2050 | * An assignment expression with a mathematical operator can either evaluate to a primitive value,
|
---|
2051 | * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object.
|
---|
2052 | */
|
---|
2053 | return false;
|
---|
2054 |
|
---|
2055 | case "SequenceExpression": {
|
---|
2056 | const exprs = node.expressions;
|
---|
2057 |
|
---|
2058 | return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]);
|
---|
2059 | }
|
---|
2060 |
|
---|
2061 | case "LogicalExpression":
|
---|
2062 |
|
---|
2063 | /*
|
---|
2064 | * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it
|
---|
2065 | * doesn't short-circuit, it takes the value from the right side, so the right side must always be
|
---|
2066 | * a plausible error. A future improvement could verify that the left side could be truthy by
|
---|
2067 | * excluding falsy literals.
|
---|
2068 | */
|
---|
2069 | if (node.operator === "&&") {
|
---|
2070 | return module.exports.couldBeError(node.right);
|
---|
2071 | }
|
---|
2072 |
|
---|
2073 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
|
---|
2074 |
|
---|
2075 | case "ConditionalExpression":
|
---|
2076 | return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate);
|
---|
2077 |
|
---|
2078 | default:
|
---|
2079 | return false;
|
---|
2080 | }
|
---|
2081 | },
|
---|
2082 |
|
---|
2083 | /**
|
---|
2084 | * Check if a given node is a numeric literal or not.
|
---|
2085 | * @param {ASTNode} node The node to check.
|
---|
2086 | * @returns {boolean} `true` if the node is a number or bigint literal.
|
---|
2087 | */
|
---|
2088 | isNumericLiteral(node) {
|
---|
2089 | return (
|
---|
2090 | node.type === "Literal" &&
|
---|
2091 | (typeof node.value === "number" || Boolean(node.bigint))
|
---|
2092 | );
|
---|
2093 | },
|
---|
2094 |
|
---|
2095 | /**
|
---|
2096 | * Determines whether two tokens can safely be placed next to each other without merging into a single token
|
---|
2097 | * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used.
|
---|
2098 | * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used.
|
---|
2099 | * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed
|
---|
2100 | * next to each other, behavior is undefined (although it should return `true` in most cases).
|
---|
2101 | */
|
---|
2102 | canTokensBeAdjacent(leftValue, rightValue) {
|
---|
2103 | const espreeOptions = {
|
---|
2104 | ecmaVersion: espree.latestEcmaVersion,
|
---|
2105 | comment: true,
|
---|
2106 | range: true
|
---|
2107 | };
|
---|
2108 |
|
---|
2109 | let leftToken;
|
---|
2110 |
|
---|
2111 | if (typeof leftValue === "string") {
|
---|
2112 | let tokens;
|
---|
2113 |
|
---|
2114 | try {
|
---|
2115 | tokens = espree.tokenize(leftValue, espreeOptions);
|
---|
2116 | } catch {
|
---|
2117 | return false;
|
---|
2118 | }
|
---|
2119 |
|
---|
2120 | const comments = tokens.comments;
|
---|
2121 |
|
---|
2122 | leftToken = tokens[tokens.length - 1];
|
---|
2123 | if (comments.length) {
|
---|
2124 | const lastComment = comments[comments.length - 1];
|
---|
2125 |
|
---|
2126 | if (!leftToken || lastComment.range[0] > leftToken.range[0]) {
|
---|
2127 | leftToken = lastComment;
|
---|
2128 | }
|
---|
2129 | }
|
---|
2130 | } else {
|
---|
2131 | leftToken = leftValue;
|
---|
2132 | }
|
---|
2133 |
|
---|
2134 | /*
|
---|
2135 | * If a hashbang comment was passed as a token object from SourceCode,
|
---|
2136 | * its type will be "Shebang" because of the way ESLint itself handles hashbangs.
|
---|
2137 | * If a hashbang comment was passed in a string and then tokenized in this function,
|
---|
2138 | * its type will be "Hashbang" because of the way Espree tokenizes hashbangs.
|
---|
2139 | */
|
---|
2140 | if (leftToken.type === "Shebang" || leftToken.type === "Hashbang") {
|
---|
2141 | return false;
|
---|
2142 | }
|
---|
2143 |
|
---|
2144 | let rightToken;
|
---|
2145 |
|
---|
2146 | if (typeof rightValue === "string") {
|
---|
2147 | let tokens;
|
---|
2148 |
|
---|
2149 | try {
|
---|
2150 | tokens = espree.tokenize(rightValue, espreeOptions);
|
---|
2151 | } catch {
|
---|
2152 | return false;
|
---|
2153 | }
|
---|
2154 |
|
---|
2155 | const comments = tokens.comments;
|
---|
2156 |
|
---|
2157 | rightToken = tokens[0];
|
---|
2158 | if (comments.length) {
|
---|
2159 | const firstComment = comments[0];
|
---|
2160 |
|
---|
2161 | if (!rightToken || firstComment.range[0] < rightToken.range[0]) {
|
---|
2162 | rightToken = firstComment;
|
---|
2163 | }
|
---|
2164 | }
|
---|
2165 | } else {
|
---|
2166 | rightToken = rightValue;
|
---|
2167 | }
|
---|
2168 |
|
---|
2169 | if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") {
|
---|
2170 | if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") {
|
---|
2171 | const PLUS_TOKENS = new Set(["+", "++"]);
|
---|
2172 | const MINUS_TOKENS = new Set(["-", "--"]);
|
---|
2173 |
|
---|
2174 | return !(
|
---|
2175 | PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) ||
|
---|
2176 | MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)
|
---|
2177 | );
|
---|
2178 | }
|
---|
2179 | if (leftToken.type === "Punctuator" && leftToken.value === "/") {
|
---|
2180 | return !["Block", "Line", "RegularExpression"].includes(rightToken.type);
|
---|
2181 | }
|
---|
2182 | return true;
|
---|
2183 | }
|
---|
2184 |
|
---|
2185 | if (
|
---|
2186 | leftToken.type === "String" || rightToken.type === "String" ||
|
---|
2187 | leftToken.type === "Template" || rightToken.type === "Template"
|
---|
2188 | ) {
|
---|
2189 | return true;
|
---|
2190 | }
|
---|
2191 |
|
---|
2192 | if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) {
|
---|
2193 | return true;
|
---|
2194 | }
|
---|
2195 |
|
---|
2196 | if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") {
|
---|
2197 | return true;
|
---|
2198 | }
|
---|
2199 |
|
---|
2200 | if (rightToken.type === "PrivateIdentifier") {
|
---|
2201 | return true;
|
---|
2202 | }
|
---|
2203 |
|
---|
2204 | return false;
|
---|
2205 | },
|
---|
2206 |
|
---|
2207 | /**
|
---|
2208 | * Get the `loc` object of a given name in a `/*globals` directive comment.
|
---|
2209 | * @param {SourceCode} sourceCode The source code to convert index to loc.
|
---|
2210 | * @param {Comment} comment The `/*globals` directive comment which include the name.
|
---|
2211 | * @param {string} name The name to find.
|
---|
2212 | * @returns {SourceLocation} The `loc` object.
|
---|
2213 | */
|
---|
2214 | getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
|
---|
2215 | const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
|
---|
2216 |
|
---|
2217 | // To ignore the first text "global".
|
---|
2218 | namePattern.lastIndex = comment.value.indexOf("global") + 6;
|
---|
2219 |
|
---|
2220 | // Search a given variable name.
|
---|
2221 | const match = namePattern.exec(comment.value);
|
---|
2222 |
|
---|
2223 | // Convert the index to loc.
|
---|
2224 | const start = sourceCode.getLocFromIndex(
|
---|
2225 | comment.range[0] +
|
---|
2226 | "/*".length +
|
---|
2227 | (match ? match.index + 1 : 0)
|
---|
2228 | );
|
---|
2229 | const end = {
|
---|
2230 | line: start.line,
|
---|
2231 | column: start.column + (match ? name.length : 1)
|
---|
2232 | };
|
---|
2233 |
|
---|
2234 | return { start, end };
|
---|
2235 | },
|
---|
2236 |
|
---|
2237 | /**
|
---|
2238 | * Determines whether the given raw string contains an octal escape sequence
|
---|
2239 | * or a non-octal decimal escape sequence ("\8", "\9").
|
---|
2240 | *
|
---|
2241 | * "\1", "\2" ... "\7", "\8", "\9"
|
---|
2242 | * "\00", "\01" ... "\07", "\08", "\09"
|
---|
2243 | *
|
---|
2244 | * "\0", when not followed by a digit, is not an octal escape sequence.
|
---|
2245 | * @param {string} rawString A string in its raw representation.
|
---|
2246 | * @returns {boolean} `true` if the string contains at least one octal escape sequence
|
---|
2247 | * or at least one non-octal decimal escape sequence.
|
---|
2248 | */
|
---|
2249 | hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
|
---|
2250 | return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
|
---|
2251 | },
|
---|
2252 |
|
---|
2253 | /**
|
---|
2254 | * Determines whether the given node is a template literal without expressions.
|
---|
2255 | * @param {ASTNode} node Node to check.
|
---|
2256 | * @returns {boolean} True if the node is a template literal without expressions.
|
---|
2257 | */
|
---|
2258 | isStaticTemplateLiteral(node) {
|
---|
2259 | return node.type === "TemplateLiteral" && node.expressions.length === 0;
|
---|
2260 | },
|
---|
2261 |
|
---|
2262 | isReferenceToGlobalVariable,
|
---|
2263 | isLogicalExpression,
|
---|
2264 | isCoalesceExpression,
|
---|
2265 | isMixedLogicalAndCoalesceExpressions,
|
---|
2266 | isNullLiteral,
|
---|
2267 | getStaticStringValue,
|
---|
2268 | getStaticPropertyName,
|
---|
2269 | skipChainExpression,
|
---|
2270 | isSpecificId,
|
---|
2271 | isSpecificMemberAccess,
|
---|
2272 | equalLiteralValue,
|
---|
2273 | isSameReference,
|
---|
2274 | isLogicalAssignmentOperator,
|
---|
2275 | getSwitchCaseColonToken,
|
---|
2276 | getModuleExportName,
|
---|
2277 | isConstant,
|
---|
2278 | isTopLevelExpressionStatement,
|
---|
2279 | isDirective,
|
---|
2280 | isStartOfExpressionStatement,
|
---|
2281 | needsPrecedingSemicolon
|
---|
2282 | };
|
---|