]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Common utils for AST. | |
3 | * @author Gyandeep Singh | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Requirements | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const esutils = require("esutils"); | |
13 | const espree = require("espree"); | |
14 | const lodash = require("lodash"); | |
15 | const { | |
16 | breakableTypePattern, | |
17 | createGlobalLinebreakMatcher, | |
18 | lineBreakPattern, | |
19 | shebangPattern | |
20 | } = require("../../shared/ast-utils"); | |
21 | ||
22 | //------------------------------------------------------------------------------ | |
23 | // Helpers | |
24 | //------------------------------------------------------------------------------ | |
25 | ||
26 | const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u; | |
27 | const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u; | |
28 | const arrayOrTypedArrayPattern = /Array$/u; | |
29 | const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u; | |
30 | const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u; | |
31 | const thisTagPattern = /^[\s*]*@this/mu; | |
32 | ||
33 | ||
34 | const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u; | |
35 | const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); | |
36 | ||
37 | // A set of node types that can contain a list of statements | |
38 | const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]); | |
39 | ||
6f036462 TL |
40 | const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u; |
41 | ||
42 | // Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string | |
43 | const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su; | |
44 | ||
45 | const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]); | |
eb39fafa DC |
46 | |
47 | /** | |
48 | * Checks reference if is non initializer and writable. | |
49 | * @param {Reference} reference A reference to check. | |
50 | * @param {int} index The index of the reference in the references. | |
51 | * @param {Reference[]} references The array that the reference belongs to. | |
52 | * @returns {boolean} Success/Failure | |
53 | * @private | |
54 | */ | |
55 | function isModifyingReference(reference, index, references) { | |
56 | const identifier = reference.identifier; | |
57 | ||
58 | /* | |
59 | * Destructuring assignments can have multiple default value, so | |
60 | * possibly there are multiple writeable references for the same | |
61 | * identifier. | |
62 | */ | |
63 | const modifyingDifferentIdentifier = index === 0 || | |
64 | references[index - 1].identifier !== identifier; | |
65 | ||
66 | return (identifier && | |
67 | reference.init === false && | |
68 | reference.isWrite() && | |
69 | modifyingDifferentIdentifier | |
70 | ); | |
71 | } | |
72 | ||
73 | /** | |
74 | * Checks whether the given string starts with uppercase or not. | |
75 | * @param {string} s The string to check. | |
76 | * @returns {boolean} `true` if the string starts with uppercase. | |
77 | */ | |
78 | function startsWithUpperCase(s) { | |
79 | return s[0] !== s[0].toLocaleLowerCase(); | |
80 | } | |
81 | ||
82 | /** | |
83 | * Checks whether or not a node is a constructor. | |
84 | * @param {ASTNode} node A function node to check. | |
85 | * @returns {boolean} Wehether or not a node is a constructor. | |
86 | */ | |
87 | function isES5Constructor(node) { | |
88 | return (node.id && startsWithUpperCase(node.id.name)); | |
89 | } | |
90 | ||
91 | /** | |
92 | * Finds a function node from ancestors of a node. | |
93 | * @param {ASTNode} node A start node to find. | |
94 | * @returns {Node|null} A found function node. | |
95 | */ | |
96 | function getUpperFunction(node) { | |
97 | for (let currentNode = node; currentNode; currentNode = currentNode.parent) { | |
98 | if (anyFunctionPattern.test(currentNode.type)) { | |
99 | return currentNode; | |
100 | } | |
101 | } | |
102 | return null; | |
103 | } | |
104 | ||
105 | /** | |
106 | * Checks whether a given node is a function node or not. | |
107 | * The following types are function nodes: | |
108 | * | |
109 | * - ArrowFunctionExpression | |
110 | * - FunctionDeclaration | |
111 | * - FunctionExpression | |
112 | * @param {ASTNode|null} node A node to check. | |
113 | * @returns {boolean} `true` if the node is a function node. | |
114 | */ | |
115 | function isFunction(node) { | |
116 | return Boolean(node && anyFunctionPattern.test(node.type)); | |
117 | } | |
118 | ||
119 | /** | |
120 | * Checks whether a given node is a loop node or not. | |
121 | * The following types are loop nodes: | |
122 | * | |
123 | * - DoWhileStatement | |
124 | * - ForInStatement | |
125 | * - ForOfStatement | |
126 | * - ForStatement | |
127 | * - WhileStatement | |
128 | * @param {ASTNode|null} node A node to check. | |
129 | * @returns {boolean} `true` if the node is a loop node. | |
130 | */ | |
131 | function isLoop(node) { | |
132 | return Boolean(node && anyLoopPattern.test(node.type)); | |
133 | } | |
134 | ||
135 | /** | |
136 | * Checks whether the given node is in a loop or not. | |
137 | * @param {ASTNode} node The node to check. | |
138 | * @returns {boolean} `true` if the node is in a loop. | |
139 | */ | |
140 | function isInLoop(node) { | |
141 | for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) { | |
142 | if (isLoop(currentNode)) { | |
143 | return true; | |
144 | } | |
145 | } | |
146 | ||
147 | return false; | |
148 | } | |
149 | ||
6f036462 TL |
150 | /** |
151 | * Determines whether the given node is a `null` literal. | |
152 | * @param {ASTNode} node The node to check | |
153 | * @returns {boolean} `true` if the node is a `null` literal | |
154 | */ | |
155 | function isNullLiteral(node) { | |
156 | ||
157 | /* | |
158 | * Checking `node.value === null` does not guarantee that a literal is a null literal. | |
159 | * When parsing values that cannot be represented in the current environment (e.g. unicode | |
160 | * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to | |
161 | * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check | |
162 | * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020 | |
163 | */ | |
164 | return node.type === "Literal" && node.value === null && !node.regex && !node.bigint; | |
165 | } | |
166 | ||
eb39fafa DC |
167 | /** |
168 | * Checks whether or not a node is `null` or `undefined`. | |
169 | * @param {ASTNode} node A node to check. | |
170 | * @returns {boolean} Whether or not the node is a `null` or `undefined`. | |
171 | * @public | |
172 | */ | |
173 | function isNullOrUndefined(node) { | |
174 | return ( | |
6f036462 | 175 | isNullLiteral(node) || |
eb39fafa DC |
176 | (node.type === "Identifier" && node.name === "undefined") || |
177 | (node.type === "UnaryExpression" && node.operator === "void") | |
178 | ); | |
179 | } | |
180 | ||
181 | /** | |
182 | * Checks whether or not a node is callee. | |
183 | * @param {ASTNode} node A node to check. | |
184 | * @returns {boolean} Whether or not the node is callee. | |
185 | */ | |
186 | function isCallee(node) { | |
187 | return node.parent.type === "CallExpression" && node.parent.callee === node; | |
188 | } | |
189 | ||
6f036462 TL |
190 | /** |
191 | * Returns the result of the string conversion applied to the evaluated value of the given expression node, | |
192 | * if it can be determined statically. | |
193 | * | |
194 | * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only. | |
195 | * In all other cases, this function returns `null`. | |
196 | * @param {ASTNode} node Expression node. | |
197 | * @returns {string|null} String value if it can be determined. Otherwise, `null`. | |
198 | */ | |
199 | function getStaticStringValue(node) { | |
200 | switch (node.type) { | |
201 | case "Literal": | |
202 | if (node.value === null) { | |
203 | if (isNullLiteral(node)) { | |
204 | return String(node.value); // "null" | |
205 | } | |
206 | if (node.regex) { | |
207 | return `/${node.regex.pattern}/${node.regex.flags}`; | |
208 | } | |
209 | if (node.bigint) { | |
210 | return node.bigint; | |
211 | } | |
212 | ||
213 | // Otherwise, this is an unknown literal. The function will return null. | |
214 | ||
215 | } else { | |
216 | return String(node.value); | |
217 | } | |
218 | break; | |
219 | case "TemplateLiteral": | |
220 | if (node.expressions.length === 0 && node.quasis.length === 1) { | |
221 | return node.quasis[0].value.cooked; | |
222 | } | |
223 | break; | |
224 | ||
225 | // no default | |
226 | } | |
227 | ||
228 | return null; | |
229 | } | |
230 | ||
231 | /** | |
232 | * Gets the property name of a given node. | |
233 | * The node can be a MemberExpression, a Property, or a MethodDefinition. | |
234 | * | |
235 | * If the name is dynamic, this returns `null`. | |
236 | * | |
237 | * For examples: | |
238 | * | |
239 | * a.b // => "b" | |
240 | * a["b"] // => "b" | |
241 | * a['b'] // => "b" | |
242 | * a[`b`] // => "b" | |
243 | * a[100] // => "100" | |
244 | * a[b] // => null | |
245 | * a["a" + "b"] // => null | |
246 | * a[tag`b`] // => null | |
247 | * a[`${b}`] // => null | |
248 | * | |
249 | * let a = {b: 1} // => "b" | |
250 | * let a = {["b"]: 1} // => "b" | |
251 | * let a = {['b']: 1} // => "b" | |
252 | * let a = {[`b`]: 1} // => "b" | |
253 | * let a = {[100]: 1} // => "100" | |
254 | * let a = {[b]: 1} // => null | |
255 | * let a = {["a" + "b"]: 1} // => null | |
256 | * let a = {[tag`b`]: 1} // => null | |
257 | * let a = {[`${b}`]: 1} // => null | |
258 | * @param {ASTNode} node The node to get. | |
259 | * @returns {string|null} The property name if static. Otherwise, null. | |
260 | */ | |
261 | function getStaticPropertyName(node) { | |
262 | let prop; | |
263 | ||
264 | switch (node && node.type) { | |
265 | case "ChainExpression": | |
266 | return getStaticPropertyName(node.expression); | |
267 | ||
268 | case "Property": | |
269 | case "MethodDefinition": | |
270 | prop = node.key; | |
271 | break; | |
272 | ||
273 | case "MemberExpression": | |
274 | prop = node.property; | |
275 | break; | |
276 | ||
277 | // no default | |
278 | } | |
279 | ||
280 | if (prop) { | |
281 | if (prop.type === "Identifier" && !node.computed) { | |
282 | return prop.name; | |
283 | } | |
284 | ||
285 | return getStaticStringValue(prop); | |
286 | } | |
287 | ||
288 | return null; | |
289 | } | |
290 | ||
291 | /** | |
292 | * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it. | |
293 | * @param {ASTNode} node The node to address. | |
294 | * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node. | |
295 | */ | |
296 | function skipChainExpression(node) { | |
297 | return node && node.type === "ChainExpression" ? node.expression : node; | |
298 | } | |
299 | ||
300 | /** | |
301 | * Check if the `actual` is an expected value. | |
302 | * @param {string} actual The string value to check. | |
303 | * @param {string | RegExp} expected The expected string value or pattern. | |
304 | * @returns {boolean} `true` if the `actual` is an expected value. | |
305 | */ | |
306 | function checkText(actual, expected) { | |
307 | return typeof expected === "string" | |
308 | ? actual === expected | |
309 | : expected.test(actual); | |
310 | } | |
311 | ||
312 | /** | |
313 | * Check if a given node is an Identifier node with a given name. | |
314 | * @param {ASTNode} node The node to check. | |
315 | * @param {string | RegExp} name The expected name or the expected pattern of the object name. | |
316 | * @returns {boolean} `true` if the node is an Identifier node with the name. | |
317 | */ | |
318 | function isSpecificId(node, name) { | |
319 | return node.type === "Identifier" && checkText(node.name, name); | |
320 | } | |
321 | ||
322 | /** | |
323 | * Check if a given node is member access with a given object name and property name pair. | |
324 | * This is regardless of optional or not. | |
325 | * @param {ASTNode} node The node to check. | |
326 | * @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. | |
327 | * @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. | |
328 | * @returns {boolean} `true` if the node is member access with the object name and property name pair. | |
329 | * The node is a `MemberExpression` or `ChainExpression`. | |
330 | */ | |
331 | function isSpecificMemberAccess(node, objectName, propertyName) { | |
332 | const checkNode = skipChainExpression(node); | |
333 | ||
334 | if (checkNode.type !== "MemberExpression") { | |
335 | return false; | |
336 | } | |
337 | ||
338 | if (objectName && !isSpecificId(checkNode.object, objectName)) { | |
339 | return false; | |
340 | } | |
341 | ||
342 | if (propertyName) { | |
343 | const actualPropertyName = getStaticPropertyName(checkNode); | |
344 | ||
345 | if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) { | |
346 | return false; | |
347 | } | |
348 | } | |
349 | ||
350 | return true; | |
351 | } | |
352 | ||
353 | /** | |
354 | * Check if two literal nodes are the same value. | |
355 | * @param {ASTNode} left The Literal node to compare. | |
356 | * @param {ASTNode} right The other Literal node to compare. | |
357 | * @returns {boolean} `true` if the two literal nodes are the same value. | |
358 | */ | |
359 | function equalLiteralValue(left, right) { | |
360 | ||
361 | // RegExp literal. | |
362 | if (left.regex || right.regex) { | |
363 | return Boolean( | |
364 | left.regex && | |
365 | right.regex && | |
366 | left.regex.pattern === right.regex.pattern && | |
367 | left.regex.flags === right.regex.flags | |
368 | ); | |
369 | } | |
370 | ||
371 | // BigInt literal. | |
372 | if (left.bigint || right.bigint) { | |
373 | return left.bigint === right.bigint; | |
374 | } | |
375 | ||
376 | return left.value === right.value; | |
377 | } | |
378 | ||
379 | /** | |
380 | * Check if two expressions reference the same value. For example: | |
381 | * a = a | |
382 | * a.b = a.b | |
383 | * a[0] = a[0] | |
384 | * a['b'] = a['b'] | |
385 | * @param {ASTNode} left The left side of the comparison. | |
386 | * @param {ASTNode} right The right side of the comparison. | |
387 | * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility. | |
388 | * @returns {boolean} `true` if both sides match and reference the same value. | |
389 | */ | |
390 | function isSameReference(left, right, disableStaticComputedKey = false) { | |
391 | if (left.type !== right.type) { | |
392 | ||
393 | // Handle `a.b` and `a?.b` are samely. | |
394 | if (left.type === "ChainExpression") { | |
395 | return isSameReference(left.expression, right, disableStaticComputedKey); | |
396 | } | |
397 | if (right.type === "ChainExpression") { | |
398 | return isSameReference(left, right.expression, disableStaticComputedKey); | |
399 | } | |
400 | ||
401 | return false; | |
402 | } | |
403 | ||
404 | switch (left.type) { | |
405 | case "Super": | |
406 | case "ThisExpression": | |
407 | return true; | |
408 | ||
409 | case "Identifier": | |
410 | return left.name === right.name; | |
411 | case "Literal": | |
412 | return equalLiteralValue(left, right); | |
413 | ||
414 | case "ChainExpression": | |
415 | return isSameReference(left.expression, right.expression, disableStaticComputedKey); | |
416 | ||
417 | case "MemberExpression": { | |
418 | if (!disableStaticComputedKey) { | |
419 | const nameA = getStaticPropertyName(left); | |
420 | ||
421 | // x.y = x["y"] | |
422 | if (nameA !== null) { | |
423 | return ( | |
424 | isSameReference(left.object, right.object, disableStaticComputedKey) && | |
425 | nameA === getStaticPropertyName(right) | |
426 | ); | |
427 | } | |
428 | } | |
429 | ||
430 | /* | |
431 | * x[0] = x[0] | |
432 | * x[y] = x[y] | |
433 | * x.y = x.y | |
434 | */ | |
435 | return ( | |
436 | left.computed === right.computed && | |
437 | isSameReference(left.object, right.object, disableStaticComputedKey) && | |
438 | isSameReference(left.property, right.property, disableStaticComputedKey) | |
439 | ); | |
440 | } | |
441 | ||
442 | default: | |
443 | return false; | |
444 | } | |
445 | } | |
446 | ||
eb39fafa DC |
447 | /** |
448 | * Checks whether or not a node is `Reflect.apply`. | |
449 | * @param {ASTNode} node A node to check. | |
450 | * @returns {boolean} Whether or not the node is a `Reflect.apply`. | |
451 | */ | |
452 | function isReflectApply(node) { | |
6f036462 | 453 | return isSpecificMemberAccess(node, "Reflect", "apply"); |
eb39fafa DC |
454 | } |
455 | ||
456 | /** | |
457 | * Checks whether or not a node is `Array.from`. | |
458 | * @param {ASTNode} node A node to check. | |
459 | * @returns {boolean} Whether or not the node is a `Array.from`. | |
460 | */ | |
461 | function isArrayFromMethod(node) { | |
6f036462 | 462 | return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from"); |
eb39fafa DC |
463 | } |
464 | ||
465 | /** | |
466 | * Checks whether or not a node is a method which has `thisArg`. | |
467 | * @param {ASTNode} node A node to check. | |
468 | * @returns {boolean} Whether or not the node is a method which has `thisArg`. | |
469 | */ | |
470 | function isMethodWhichHasThisArg(node) { | |
6f036462 | 471 | return isSpecificMemberAccess(node, null, arrayMethodPattern); |
eb39fafa DC |
472 | } |
473 | ||
474 | /** | |
475 | * Creates the negate function of the given function. | |
476 | * @param {Function} f The function to negate. | |
477 | * @returns {Function} Negated function. | |
478 | */ | |
479 | function negate(f) { | |
480 | return token => !f(token); | |
481 | } | |
482 | ||
483 | /** | |
484 | * Checks whether or not a node has a `@this` tag in its comments. | |
485 | * @param {ASTNode} node A node to check. | |
486 | * @param {SourceCode} sourceCode A SourceCode instance to get comments. | |
487 | * @returns {boolean} Whether or not the node has a `@this` tag in its comments. | |
488 | */ | |
489 | function hasJSDocThisTag(node, sourceCode) { | |
490 | const jsdocComment = sourceCode.getJSDocComment(node); | |
491 | ||
492 | if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { | |
493 | return true; | |
494 | } | |
495 | ||
496 | // Checks `@this` in its leading comments for callbacks, | |
497 | // because callbacks don't have its JSDoc comment. | |
498 | // e.g. | |
499 | // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); | |
500 | return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value)); | |
501 | } | |
502 | ||
503 | /** | |
504 | * Determines if a node is surrounded by parentheses. | |
505 | * @param {SourceCode} sourceCode The ESLint source code object | |
506 | * @param {ASTNode} node The node to be checked. | |
507 | * @returns {boolean} True if the node is parenthesised. | |
508 | * @private | |
509 | */ | |
510 | function isParenthesised(sourceCode, node) { | |
511 | const previousToken = sourceCode.getTokenBefore(node), | |
512 | nextToken = sourceCode.getTokenAfter(node); | |
513 | ||
514 | return Boolean(previousToken && nextToken) && | |
515 | previousToken.value === "(" && previousToken.range[1] <= node.range[0] && | |
516 | nextToken.value === ")" && nextToken.range[0] >= node.range[1]; | |
517 | } | |
518 | ||
519 | /** | |
520 | * Checks if the given token is an arrow token or not. | |
521 | * @param {Token} token The token to check. | |
522 | * @returns {boolean} `true` if the token is an arrow token. | |
523 | */ | |
524 | function isArrowToken(token) { | |
525 | return token.value === "=>" && token.type === "Punctuator"; | |
526 | } | |
527 | ||
528 | /** | |
529 | * Checks if the given token is a comma token or not. | |
530 | * @param {Token} token The token to check. | |
531 | * @returns {boolean} `true` if the token is a comma token. | |
532 | */ | |
533 | function isCommaToken(token) { | |
534 | return token.value === "," && token.type === "Punctuator"; | |
535 | } | |
536 | ||
537 | /** | |
538 | * Checks if the given token is a dot token or not. | |
539 | * @param {Token} token The token to check. | |
540 | * @returns {boolean} `true` if the token is a dot token. | |
541 | */ | |
542 | function isDotToken(token) { | |
543 | return token.value === "." && token.type === "Punctuator"; | |
544 | } | |
545 | ||
6f036462 TL |
546 | /** |
547 | * Checks if the given token is a `?.` token or not. | |
548 | * @param {Token} token The token to check. | |
549 | * @returns {boolean} `true` if the token is a `?.` token. | |
550 | */ | |
551 | function isQuestionDotToken(token) { | |
552 | return token.value === "?." && token.type === "Punctuator"; | |
553 | } | |
554 | ||
eb39fafa DC |
555 | /** |
556 | * Checks if the given token is a semicolon token or not. | |
557 | * @param {Token} token The token to check. | |
558 | * @returns {boolean} `true` if the token is a semicolon token. | |
559 | */ | |
560 | function isSemicolonToken(token) { | |
561 | return token.value === ";" && token.type === "Punctuator"; | |
562 | } | |
563 | ||
564 | /** | |
565 | * Checks if the given token is a colon token or not. | |
566 | * @param {Token} token The token to check. | |
567 | * @returns {boolean} `true` if the token is a colon token. | |
568 | */ | |
569 | function isColonToken(token) { | |
570 | return token.value === ":" && token.type === "Punctuator"; | |
571 | } | |
572 | ||
573 | /** | |
574 | * Checks if the given token is an opening parenthesis token or not. | |
575 | * @param {Token} token The token to check. | |
576 | * @returns {boolean} `true` if the token is an opening parenthesis token. | |
577 | */ | |
578 | function isOpeningParenToken(token) { | |
579 | return token.value === "(" && token.type === "Punctuator"; | |
580 | } | |
581 | ||
582 | /** | |
583 | * Checks if the given token is a closing parenthesis token or not. | |
584 | * @param {Token} token The token to check. | |
585 | * @returns {boolean} `true` if the token is a closing parenthesis token. | |
586 | */ | |
587 | function isClosingParenToken(token) { | |
588 | return token.value === ")" && token.type === "Punctuator"; | |
589 | } | |
590 | ||
591 | /** | |
592 | * Checks if the given token is an opening square bracket token or not. | |
593 | * @param {Token} token The token to check. | |
594 | * @returns {boolean} `true` if the token is an opening square bracket token. | |
595 | */ | |
596 | function isOpeningBracketToken(token) { | |
597 | return token.value === "[" && token.type === "Punctuator"; | |
598 | } | |
599 | ||
600 | /** | |
601 | * Checks if the given token is a closing square bracket token or not. | |
602 | * @param {Token} token The token to check. | |
603 | * @returns {boolean} `true` if the token is a closing square bracket token. | |
604 | */ | |
605 | function isClosingBracketToken(token) { | |
606 | return token.value === "]" && token.type === "Punctuator"; | |
607 | } | |
608 | ||
609 | /** | |
610 | * Checks if the given token is an opening brace token or not. | |
611 | * @param {Token} token The token to check. | |
612 | * @returns {boolean} `true` if the token is an opening brace token. | |
613 | */ | |
614 | function isOpeningBraceToken(token) { | |
615 | return token.value === "{" && token.type === "Punctuator"; | |
616 | } | |
617 | ||
618 | /** | |
619 | * Checks if the given token is a closing brace token or not. | |
620 | * @param {Token} token The token to check. | |
621 | * @returns {boolean} `true` if the token is a closing brace token. | |
622 | */ | |
623 | function isClosingBraceToken(token) { | |
624 | return token.value === "}" && token.type === "Punctuator"; | |
625 | } | |
626 | ||
627 | /** | |
628 | * Checks if the given token is a comment token or not. | |
629 | * @param {Token} token The token to check. | |
630 | * @returns {boolean} `true` if the token is a comment token. | |
631 | */ | |
632 | function isCommentToken(token) { | |
633 | return token.type === "Line" || token.type === "Block" || token.type === "Shebang"; | |
634 | } | |
635 | ||
636 | /** | |
637 | * Checks if the given token is a keyword token or not. | |
638 | * @param {Token} token The token to check. | |
639 | * @returns {boolean} `true` if the token is a keyword token. | |
640 | */ | |
641 | function isKeywordToken(token) { | |
642 | return token.type === "Keyword"; | |
643 | } | |
644 | ||
645 | /** | |
646 | * Gets the `(` token of the given function node. | |
647 | * @param {ASTNode} node The function node to get. | |
648 | * @param {SourceCode} sourceCode The source code object to get tokens. | |
649 | * @returns {Token} `(` token. | |
650 | */ | |
651 | function getOpeningParenOfParams(node, sourceCode) { | |
652 | return node.id | |
653 | ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) | |
654 | : sourceCode.getFirstToken(node, isOpeningParenToken); | |
655 | } | |
656 | ||
657 | /** | |
658 | * Checks whether or not the tokens of two given nodes are same. | |
659 | * @param {ASTNode} left A node 1 to compare. | |
660 | * @param {ASTNode} right A node 2 to compare. | |
661 | * @param {SourceCode} sourceCode The ESLint source code object. | |
662 | * @returns {boolean} the source code for the given node. | |
663 | */ | |
664 | function equalTokens(left, right, sourceCode) { | |
665 | const tokensL = sourceCode.getTokens(left); | |
666 | const tokensR = sourceCode.getTokens(right); | |
667 | ||
668 | if (tokensL.length !== tokensR.length) { | |
669 | return false; | |
670 | } | |
671 | for (let i = 0; i < tokensL.length; ++i) { | |
672 | if (tokensL[i].type !== tokensR[i].type || | |
673 | tokensL[i].value !== tokensR[i].value | |
674 | ) { | |
675 | return false; | |
676 | } | |
677 | } | |
678 | ||
679 | return true; | |
680 | } | |
681 | ||
d3726936 TL |
682 | /** |
683 | * Check if the given node is a true logical expression or not. | |
684 | * | |
685 | * The three binary expressions logical-or (`||`), logical-and (`&&`), and | |
686 | * coalesce (`??`) are known as `ShortCircuitExpression`. | |
687 | * But ESTree represents those by `LogicalExpression` node. | |
688 | * | |
689 | * This function rejects coalesce expressions of `LogicalExpression` node. | |
690 | * @param {ASTNode} node The node to check. | |
691 | * @returns {boolean} `true` if the node is `&&` or `||`. | |
692 | * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression | |
693 | */ | |
694 | function isLogicalExpression(node) { | |
695 | return ( | |
696 | node.type === "LogicalExpression" && | |
697 | (node.operator === "&&" || node.operator === "||") | |
698 | ); | |
699 | } | |
700 | ||
701 | /** | |
702 | * Check if the given node is a nullish coalescing expression or not. | |
703 | * | |
704 | * The three binary expressions logical-or (`||`), logical-and (`&&`), and | |
705 | * coalesce (`??`) are known as `ShortCircuitExpression`. | |
706 | * But ESTree represents those by `LogicalExpression` node. | |
707 | * | |
708 | * This function finds only coalesce expressions of `LogicalExpression` node. | |
709 | * @param {ASTNode} node The node to check. | |
710 | * @returns {boolean} `true` if the node is `??`. | |
711 | */ | |
712 | function isCoalesceExpression(node) { | |
713 | return node.type === "LogicalExpression" && node.operator === "??"; | |
714 | } | |
715 | ||
716 | /** | |
717 | * Check if given two nodes are the pair of a logical expression and a coalesce expression. | |
718 | * @param {ASTNode} left A node to check. | |
719 | * @param {ASTNode} right Another node to check. | |
720 | * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression. | |
721 | */ | |
722 | function isMixedLogicalAndCoalesceExpressions(left, right) { | |
723 | return ( | |
724 | (isLogicalExpression(left) && isCoalesceExpression(right)) || | |
725 | (isCoalesceExpression(left) && isLogicalExpression(right)) | |
726 | ); | |
727 | } | |
728 | ||
6f036462 TL |
729 | /** |
730 | * Checks if the given operator is a logical assignment operator. | |
731 | * @param {string} operator The operator to check. | |
732 | * @returns {boolean} `true` if the operator is a logical assignment operator. | |
733 | */ | |
734 | function isLogicalAssignmentOperator(operator) { | |
735 | return LOGICAL_ASSIGNMENT_OPERATORS.has(operator); | |
736 | } | |
737 | ||
eb39fafa DC |
738 | //------------------------------------------------------------------------------ |
739 | // Public Interface | |
740 | //------------------------------------------------------------------------------ | |
741 | ||
742 | module.exports = { | |
743 | COMMENTS_IGNORE_PATTERN, | |
744 | LINEBREAKS, | |
745 | LINEBREAK_MATCHER: lineBreakPattern, | |
746 | SHEBANG_MATCHER: shebangPattern, | |
747 | STATEMENT_LIST_PARENTS, | |
748 | ||
749 | /** | |
750 | * Determines whether two adjacent tokens are on the same line. | |
751 | * @param {Object} left The left token object. | |
752 | * @param {Object} right The right token object. | |
753 | * @returns {boolean} Whether or not the tokens are on the same line. | |
754 | * @public | |
755 | */ | |
756 | isTokenOnSameLine(left, right) { | |
757 | return left.loc.end.line === right.loc.start.line; | |
758 | }, | |
759 | ||
760 | isNullOrUndefined, | |
761 | isCallee, | |
762 | isES5Constructor, | |
763 | getUpperFunction, | |
764 | isFunction, | |
765 | isLoop, | |
766 | isInLoop, | |
767 | isArrayFromMethod, | |
768 | isParenthesised, | |
769 | createGlobalLinebreakMatcher, | |
770 | equalTokens, | |
771 | ||
772 | isArrowToken, | |
773 | isClosingBraceToken, | |
774 | isClosingBracketToken, | |
775 | isClosingParenToken, | |
776 | isColonToken, | |
777 | isCommaToken, | |
778 | isCommentToken, | |
779 | isDotToken, | |
6f036462 | 780 | isQuestionDotToken, |
eb39fafa DC |
781 | isKeywordToken, |
782 | isNotClosingBraceToken: negate(isClosingBraceToken), | |
783 | isNotClosingBracketToken: negate(isClosingBracketToken), | |
784 | isNotClosingParenToken: negate(isClosingParenToken), | |
785 | isNotColonToken: negate(isColonToken), | |
786 | isNotCommaToken: negate(isCommaToken), | |
787 | isNotDotToken: negate(isDotToken), | |
6f036462 | 788 | isNotQuestionDotToken: negate(isQuestionDotToken), |
eb39fafa DC |
789 | isNotOpeningBraceToken: negate(isOpeningBraceToken), |
790 | isNotOpeningBracketToken: negate(isOpeningBracketToken), | |
791 | isNotOpeningParenToken: negate(isOpeningParenToken), | |
792 | isNotSemicolonToken: negate(isSemicolonToken), | |
793 | isOpeningBraceToken, | |
794 | isOpeningBracketToken, | |
795 | isOpeningParenToken, | |
796 | isSemicolonToken, | |
797 | ||
798 | /** | |
799 | * Checks whether or not a given node is a string literal. | |
800 | * @param {ASTNode} node A node to check. | |
801 | * @returns {boolean} `true` if the node is a string literal. | |
802 | */ | |
803 | isStringLiteral(node) { | |
804 | return ( | |
805 | (node.type === "Literal" && typeof node.value === "string") || | |
806 | node.type === "TemplateLiteral" | |
807 | ); | |
808 | }, | |
809 | ||
810 | /** | |
811 | * Checks whether a given node is a breakable statement or not. | |
812 | * The node is breakable if the node is one of the following type: | |
813 | * | |
814 | * - DoWhileStatement | |
815 | * - ForInStatement | |
816 | * - ForOfStatement | |
817 | * - ForStatement | |
818 | * - SwitchStatement | |
819 | * - WhileStatement | |
820 | * @param {ASTNode} node A node to check. | |
821 | * @returns {boolean} `true` if the node is breakable. | |
822 | */ | |
823 | isBreakableStatement(node) { | |
824 | return breakableTypePattern.test(node.type); | |
825 | }, | |
826 | ||
827 | /** | |
828 | * Gets references which are non initializer and writable. | |
829 | * @param {Reference[]} references An array of references. | |
830 | * @returns {Reference[]} An array of only references which are non initializer and writable. | |
831 | * @public | |
832 | */ | |
833 | getModifyingReferences(references) { | |
834 | return references.filter(isModifyingReference); | |
835 | }, | |
836 | ||
837 | /** | |
838 | * Validate that a string passed in is surrounded by the specified character | |
839 | * @param {string} val The text to check. | |
840 | * @param {string} character The character to see if it's surrounded by. | |
841 | * @returns {boolean} True if the text is surrounded by the character, false if not. | |
842 | * @private | |
843 | */ | |
844 | isSurroundedBy(val, character) { | |
845 | return val[0] === character && val[val.length - 1] === character; | |
846 | }, | |
847 | ||
848 | /** | |
849 | * Returns whether the provided node is an ESLint directive comment or not | |
850 | * @param {Line|Block} node The comment token to be checked | |
851 | * @returns {boolean} `true` if the node is an ESLint directive comment | |
852 | */ | |
853 | isDirectiveComment(node) { | |
854 | const comment = node.value.trim(); | |
855 | ||
856 | return ( | |
857 | node.type === "Line" && comment.indexOf("eslint-") === 0 || | |
858 | node.type === "Block" && ( | |
859 | comment.indexOf("global ") === 0 || | |
860 | comment.indexOf("eslint ") === 0 || | |
861 | comment.indexOf("eslint-") === 0 | |
862 | ) | |
863 | ); | |
864 | }, | |
865 | ||
866 | /** | |
867 | * Gets the trailing statement of a given node. | |
868 | * | |
869 | * if (code) | |
870 | * consequent; | |
871 | * | |
872 | * When taking this `IfStatement`, returns `consequent;` statement. | |
873 | * @param {ASTNode} A node to get. | |
874 | * @returns {ASTNode|null} The trailing statement's node. | |
875 | */ | |
876 | getTrailingStatement: esutils.ast.trailingStatement, | |
877 | ||
878 | /** | |
879 | * Finds the variable by a given name in a given scope and its upper scopes. | |
880 | * @param {eslint-scope.Scope} initScope A scope to start find. | |
881 | * @param {string} name A variable name to find. | |
882 | * @returns {eslint-scope.Variable|null} A found variable or `null`. | |
883 | */ | |
884 | getVariableByName(initScope, name) { | |
885 | let scope = initScope; | |
886 | ||
887 | while (scope) { | |
888 | const variable = scope.set.get(name); | |
889 | ||
890 | if (variable) { | |
891 | return variable; | |
892 | } | |
893 | ||
894 | scope = scope.upper; | |
895 | } | |
896 | ||
897 | return null; | |
898 | }, | |
899 | ||
900 | /** | |
901 | * Checks whether or not a given function node is the default `this` binding. | |
902 | * | |
903 | * First, this checks the node: | |
904 | * | |
905 | * - The function name does not start with uppercase. It's a convention to capitalize the names | |
906 | * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`. | |
907 | * - The function does not have a JSDoc comment that has a @this tag. | |
908 | * | |
909 | * Next, this checks the location of the node. | |
910 | * If the location is below, this judges `this` is valid. | |
911 | * | |
912 | * - The location is not on an object literal. | |
913 | * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous | |
914 | * functions only, as the name of the variable is considered to be the name of the function in this case. | |
915 | * This check is not performed if `capIsConstructor` is set to `false`. | |
916 | * - The location is not on an ES2015 class. | |
917 | * - Its `bind`/`call`/`apply` method is not called directly. | |
918 | * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. | |
919 | * @param {ASTNode} node A function node to check. | |
920 | * @param {SourceCode} sourceCode A SourceCode instance to get comments. | |
921 | * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts | |
922 | * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors. | |
923 | * @returns {boolean} The function node is the default `this` binding. | |
924 | */ | |
925 | isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { | |
926 | if ( | |
927 | (capIsConstructor && isES5Constructor(node)) || | |
928 | hasJSDocThisTag(node, sourceCode) | |
929 | ) { | |
930 | return false; | |
931 | } | |
932 | const isAnonymous = node.id === null; | |
933 | let currentNode = node; | |
934 | ||
935 | while (currentNode) { | |
936 | const parent = currentNode.parent; | |
937 | ||
938 | switch (parent.type) { | |
939 | ||
940 | /* | |
941 | * Looks up the destination. | |
942 | * e.g., obj.foo = nativeFoo || function foo() { ... }; | |
943 | */ | |
944 | case "LogicalExpression": | |
945 | case "ConditionalExpression": | |
6f036462 | 946 | case "ChainExpression": |
eb39fafa DC |
947 | currentNode = parent; |
948 | break; | |
949 | ||
950 | /* | |
951 | * If the upper function is IIFE, checks the destination of the return value. | |
952 | * e.g. | |
953 | * obj.foo = (function() { | |
954 | * // setup... | |
955 | * return function foo() { ... }; | |
956 | * })(); | |
957 | * obj.foo = (() => | |
958 | * function foo() { ... } | |
959 | * )(); | |
960 | */ | |
961 | case "ReturnStatement": { | |
962 | const func = getUpperFunction(parent); | |
963 | ||
964 | if (func === null || !isCallee(func)) { | |
965 | return true; | |
966 | } | |
967 | currentNode = func.parent; | |
968 | break; | |
969 | } | |
970 | case "ArrowFunctionExpression": | |
971 | if (currentNode !== parent.body || !isCallee(parent)) { | |
972 | return true; | |
973 | } | |
974 | currentNode = parent.parent; | |
975 | break; | |
976 | ||
977 | /* | |
978 | * e.g. | |
979 | * var obj = { foo() { ... } }; | |
980 | * var obj = { foo: function() { ... } }; | |
981 | * class A { constructor() { ... } } | |
982 | * class A { foo() { ... } } | |
983 | * class A { get foo() { ... } } | |
984 | * class A { set foo() { ... } } | |
985 | * class A { static foo() { ... } } | |
986 | */ | |
987 | case "Property": | |
988 | case "MethodDefinition": | |
989 | return parent.value !== currentNode; | |
990 | ||
991 | /* | |
992 | * e.g. | |
993 | * obj.foo = function foo() { ... }; | |
994 | * Foo = function() { ... }; | |
995 | * [obj.foo = function foo() { ... }] = a; | |
996 | * [Foo = function() { ... }] = a; | |
997 | */ | |
998 | case "AssignmentExpression": | |
999 | case "AssignmentPattern": | |
1000 | if (parent.left.type === "MemberExpression") { | |
1001 | return false; | |
1002 | } | |
1003 | if ( | |
1004 | capIsConstructor && | |
1005 | isAnonymous && | |
1006 | parent.left.type === "Identifier" && | |
1007 | startsWithUpperCase(parent.left.name) | |
1008 | ) { | |
1009 | return false; | |
1010 | } | |
1011 | return true; | |
1012 | ||
1013 | /* | |
1014 | * e.g. | |
1015 | * var Foo = function() { ... }; | |
1016 | */ | |
1017 | case "VariableDeclarator": | |
1018 | return !( | |
1019 | capIsConstructor && | |
1020 | isAnonymous && | |
1021 | parent.init === currentNode && | |
1022 | parent.id.type === "Identifier" && | |
1023 | startsWithUpperCase(parent.id.name) | |
1024 | ); | |
1025 | ||
1026 | /* | |
1027 | * e.g. | |
1028 | * var foo = function foo() { ... }.bind(obj); | |
1029 | * (function foo() { ... }).call(obj); | |
1030 | * (function foo() { ... }).apply(obj, []); | |
1031 | */ | |
1032 | case "MemberExpression": | |
6f036462 TL |
1033 | if ( |
1034 | parent.object === currentNode && | |
1035 | isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern) | |
1036 | ) { | |
1037 | const maybeCalleeNode = parent.parent.type === "ChainExpression" | |
1038 | ? parent.parent | |
1039 | : parent; | |
1040 | ||
1041 | return !( | |
1042 | isCallee(maybeCalleeNode) && | |
1043 | maybeCalleeNode.parent.arguments.length >= 1 && | |
1044 | !isNullOrUndefined(maybeCalleeNode.parent.arguments[0]) | |
1045 | ); | |
1046 | } | |
1047 | return true; | |
eb39fafa DC |
1048 | |
1049 | /* | |
1050 | * e.g. | |
1051 | * Reflect.apply(function() {}, obj, []); | |
1052 | * Array.from([], function() {}, obj); | |
1053 | * list.forEach(function() {}, obj); | |
1054 | */ | |
1055 | case "CallExpression": | |
1056 | if (isReflectApply(parent.callee)) { | |
1057 | return ( | |
1058 | parent.arguments.length !== 3 || | |
1059 | parent.arguments[0] !== currentNode || | |
1060 | isNullOrUndefined(parent.arguments[1]) | |
1061 | ); | |
1062 | } | |
1063 | if (isArrayFromMethod(parent.callee)) { | |
1064 | return ( | |
1065 | parent.arguments.length !== 3 || | |
1066 | parent.arguments[1] !== currentNode || | |
1067 | isNullOrUndefined(parent.arguments[2]) | |
1068 | ); | |
1069 | } | |
1070 | if (isMethodWhichHasThisArg(parent.callee)) { | |
1071 | return ( | |
1072 | parent.arguments.length !== 2 || | |
1073 | parent.arguments[0] !== currentNode || | |
1074 | isNullOrUndefined(parent.arguments[1]) | |
1075 | ); | |
1076 | } | |
1077 | return true; | |
1078 | ||
1079 | // Otherwise `this` is default. | |
1080 | default: | |
1081 | return true; | |
1082 | } | |
1083 | } | |
1084 | ||
1085 | /* istanbul ignore next */ | |
1086 | return true; | |
1087 | }, | |
1088 | ||
1089 | /** | |
1090 | * Get the precedence level based on the node type | |
1091 | * @param {ASTNode} node node to evaluate | |
1092 | * @returns {int} precedence level | |
1093 | * @private | |
1094 | */ | |
1095 | getPrecedence(node) { | |
1096 | switch (node.type) { | |
1097 | case "SequenceExpression": | |
1098 | return 0; | |
1099 | ||
1100 | case "AssignmentExpression": | |
1101 | case "ArrowFunctionExpression": | |
1102 | case "YieldExpression": | |
1103 | return 1; | |
1104 | ||
1105 | case "ConditionalExpression": | |
1106 | return 3; | |
1107 | ||
1108 | case "LogicalExpression": | |
1109 | switch (node.operator) { | |
1110 | case "||": | |
d3726936 | 1111 | case "??": |
eb39fafa DC |
1112 | return 4; |
1113 | case "&&": | |
1114 | return 5; | |
1115 | ||
1116 | // no default | |
1117 | } | |
1118 | ||
1119 | /* falls through */ | |
1120 | ||
1121 | case "BinaryExpression": | |
1122 | ||
1123 | switch (node.operator) { | |
1124 | case "|": | |
1125 | return 6; | |
1126 | case "^": | |
1127 | return 7; | |
1128 | case "&": | |
1129 | return 8; | |
1130 | case "==": | |
1131 | case "!=": | |
1132 | case "===": | |
1133 | case "!==": | |
1134 | return 9; | |
1135 | case "<": | |
1136 | case "<=": | |
1137 | case ">": | |
1138 | case ">=": | |
1139 | case "in": | |
1140 | case "instanceof": | |
1141 | return 10; | |
1142 | case "<<": | |
1143 | case ">>": | |
1144 | case ">>>": | |
1145 | return 11; | |
1146 | case "+": | |
1147 | case "-": | |
1148 | return 12; | |
1149 | case "*": | |
1150 | case "/": | |
1151 | case "%": | |
1152 | return 13; | |
1153 | case "**": | |
1154 | return 15; | |
1155 | ||
1156 | // no default | |
1157 | } | |
1158 | ||
1159 | /* falls through */ | |
1160 | ||
1161 | case "UnaryExpression": | |
1162 | case "AwaitExpression": | |
1163 | return 16; | |
1164 | ||
1165 | case "UpdateExpression": | |
1166 | return 17; | |
1167 | ||
1168 | case "CallExpression": | |
6f036462 | 1169 | case "ChainExpression": |
eb39fafa DC |
1170 | case "ImportExpression": |
1171 | return 18; | |
1172 | ||
1173 | case "NewExpression": | |
1174 | return 19; | |
1175 | ||
1176 | default: | |
1177 | return 20; | |
1178 | } | |
1179 | }, | |
1180 | ||
1181 | /** | |
1182 | * Checks whether the given node is an empty block node or not. | |
1183 | * @param {ASTNode|null} node The node to check. | |
1184 | * @returns {boolean} `true` if the node is an empty block. | |
1185 | */ | |
1186 | isEmptyBlock(node) { | |
1187 | return Boolean(node && node.type === "BlockStatement" && node.body.length === 0); | |
1188 | }, | |
1189 | ||
1190 | /** | |
1191 | * Checks whether the given node is an empty function node or not. | |
1192 | * @param {ASTNode|null} node The node to check. | |
1193 | * @returns {boolean} `true` if the node is an empty function. | |
1194 | */ | |
1195 | isEmptyFunction(node) { | |
1196 | return isFunction(node) && module.exports.isEmptyBlock(node.body); | |
1197 | }, | |
1198 | ||
eb39fafa DC |
1199 | /** |
1200 | * Get directives from directive prologue of a Program or Function node. | |
1201 | * @param {ASTNode} node The node to check. | |
1202 | * @returns {ASTNode[]} The directives found in the directive prologue. | |
1203 | */ | |
1204 | getDirectivePrologue(node) { | |
1205 | const directives = []; | |
1206 | ||
1207 | // Directive prologues only occur at the top of files or functions. | |
1208 | if ( | |
1209 | node.type === "Program" || | |
1210 | node.type === "FunctionDeclaration" || | |
1211 | node.type === "FunctionExpression" || | |
1212 | ||
1213 | /* | |
1214 | * Do not check arrow functions with implicit return. | |
1215 | * `() => "use strict";` returns the string `"use strict"`. | |
1216 | */ | |
1217 | (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement") | |
1218 | ) { | |
1219 | const statements = node.type === "Program" ? node.body : node.body.body; | |
1220 | ||
1221 | for (const statement of statements) { | |
1222 | if ( | |
1223 | statement.type === "ExpressionStatement" && | |
1224 | statement.expression.type === "Literal" | |
1225 | ) { | |
1226 | directives.push(statement); | |
1227 | } else { | |
1228 | break; | |
1229 | } | |
1230 | } | |
1231 | } | |
1232 | ||
1233 | return directives; | |
1234 | }, | |
1235 | ||
1236 | ||
1237 | /** | |
1238 | * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added | |
1239 | * after the node will be parsed as a decimal point, rather than a property-access dot. | |
1240 | * @param {ASTNode} node The node to check. | |
1241 | * @returns {boolean} `true` if this node is a decimal integer. | |
1242 | * @example | |
1243 | * | |
6f036462 TL |
1244 | * 0 // true |
1245 | * 5 // true | |
1246 | * 50 // true | |
1247 | * 5_000 // true | |
1248 | * 1_234_56 // true | |
1249 | * 08 // true | |
1250 | * 0192 // true | |
1251 | * 5. // false | |
1252 | * .5 // false | |
1253 | * 5.0 // false | |
1254 | * 5.00_00 // false | |
1255 | * 05 // false | |
1256 | * 0x5 // false | |
1257 | * 0b101 // false | |
1258 | * 0b11_01 // false | |
1259 | * 0o5 // false | |
1260 | * 5e0 // false | |
1261 | * 5e1_000 // false | |
1262 | * 5n // false | |
1263 | * 1_000n // false | |
1264 | * '5' // false | |
eb39fafa DC |
1265 | */ |
1266 | isDecimalInteger(node) { | |
1267 | return node.type === "Literal" && typeof node.value === "number" && | |
1268 | DECIMAL_INTEGER_PATTERN.test(node.raw); | |
1269 | }, | |
1270 | ||
1271 | /** | |
1272 | * Determines whether this token is a decimal integer numeric token. | |
1273 | * This is similar to isDecimalInteger(), but for tokens. | |
1274 | * @param {Token} token The token to check. | |
1275 | * @returns {boolean} `true` if this token is a decimal integer. | |
1276 | */ | |
1277 | isDecimalIntegerNumericToken(token) { | |
1278 | return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value); | |
1279 | }, | |
1280 | ||
1281 | /** | |
1282 | * Gets the name and kind of the given function node. | |
1283 | * | |
1284 | * - `function foo() {}` .................... `function 'foo'` | |
1285 | * - `(function foo() {})` .................. `function 'foo'` | |
1286 | * - `(function() {})` ...................... `function` | |
1287 | * - `function* foo() {}` ................... `generator function 'foo'` | |
1288 | * - `(function* foo() {})` ................. `generator function 'foo'` | |
1289 | * - `(function*() {})` ..................... `generator function` | |
1290 | * - `() => {}` ............................. `arrow function` | |
1291 | * - `async () => {}` ....................... `async arrow function` | |
1292 | * - `({ foo: function foo() {} })` ......... `method 'foo'` | |
1293 | * - `({ foo: function() {} })` ............. `method 'foo'` | |
1294 | * - `({ ['foo']: function() {} })` ......... `method 'foo'` | |
1295 | * - `({ [foo]: function() {} })` ........... `method` | |
1296 | * - `({ foo() {} })` ....................... `method 'foo'` | |
1297 | * - `({ foo: function* foo() {} })` ........ `generator method 'foo'` | |
1298 | * - `({ foo: function*() {} })` ............ `generator method 'foo'` | |
1299 | * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'` | |
1300 | * - `({ [foo]: function*() {} })` .......... `generator method` | |
1301 | * - `({ *foo() {} })` ...................... `generator method 'foo'` | |
1302 | * - `({ foo: async function foo() {} })` ... `async method 'foo'` | |
1303 | * - `({ foo: async function() {} })` ....... `async method 'foo'` | |
1304 | * - `({ ['foo']: async function() {} })` ... `async method 'foo'` | |
1305 | * - `({ [foo]: async function() {} })` ..... `async method` | |
1306 | * - `({ async foo() {} })` ................. `async method 'foo'` | |
1307 | * - `({ get foo() {} })` ................... `getter 'foo'` | |
1308 | * - `({ set foo(a) {} })` .................. `setter 'foo'` | |
1309 | * - `class A { constructor() {} }` ......... `constructor` | |
1310 | * - `class A { foo() {} }` ................. `method 'foo'` | |
1311 | * - `class A { *foo() {} }` ................ `generator method 'foo'` | |
1312 | * - `class A { async foo() {} }` ........... `async method 'foo'` | |
1313 | * - `class A { ['foo']() {} }` ............. `method 'foo'` | |
1314 | * - `class A { *['foo']() {} }` ............ `generator method 'foo'` | |
1315 | * - `class A { async ['foo']() {} }` ....... `async method 'foo'` | |
1316 | * - `class A { [foo]() {} }` ............... `method` | |
1317 | * - `class A { *[foo]() {} }` .............. `generator method` | |
1318 | * - `class A { async [foo]() {} }` ......... `async method` | |
1319 | * - `class A { get foo() {} }` ............. `getter 'foo'` | |
1320 | * - `class A { set foo(a) {} }` ............ `setter 'foo'` | |
1321 | * - `class A { static foo() {} }` .......... `static method 'foo'` | |
1322 | * - `class A { static *foo() {} }` ......... `static generator method 'foo'` | |
1323 | * - `class A { static async foo() {} }` .... `static async method 'foo'` | |
1324 | * - `class A { static get foo() {} }` ...... `static getter 'foo'` | |
1325 | * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` | |
1326 | * @param {ASTNode} node The function node to get. | |
1327 | * @returns {string} The name and kind of the function node. | |
1328 | */ | |
1329 | getFunctionNameWithKind(node) { | |
1330 | const parent = node.parent; | |
1331 | const tokens = []; | |
1332 | ||
1333 | if (parent.type === "MethodDefinition" && parent.static) { | |
1334 | tokens.push("static"); | |
1335 | } | |
1336 | if (node.async) { | |
1337 | tokens.push("async"); | |
1338 | } | |
1339 | if (node.generator) { | |
1340 | tokens.push("generator"); | |
1341 | } | |
1342 | ||
1343 | if (node.type === "ArrowFunctionExpression") { | |
1344 | tokens.push("arrow", "function"); | |
1345 | } else if (parent.type === "Property" || parent.type === "MethodDefinition") { | |
1346 | if (parent.kind === "constructor") { | |
1347 | return "constructor"; | |
1348 | } | |
1349 | if (parent.kind === "get") { | |
1350 | tokens.push("getter"); | |
1351 | } else if (parent.kind === "set") { | |
1352 | tokens.push("setter"); | |
1353 | } else { | |
1354 | tokens.push("method"); | |
1355 | } | |
1356 | } else { | |
1357 | tokens.push("function"); | |
1358 | } | |
1359 | ||
1360 | if (node.id) { | |
1361 | tokens.push(`'${node.id.name}'`); | |
1362 | } else { | |
6f036462 | 1363 | const name = getStaticPropertyName(parent); |
eb39fafa DC |
1364 | |
1365 | if (name !== null) { | |
1366 | tokens.push(`'${name}'`); | |
1367 | } | |
1368 | } | |
1369 | ||
1370 | return tokens.join(" "); | |
1371 | }, | |
1372 | ||
1373 | /** | |
1374 | * Gets the location of the given function node for reporting. | |
1375 | * | |
1376 | * - `function foo() {}` | |
1377 | * ^^^^^^^^^^^^ | |
1378 | * - `(function foo() {})` | |
1379 | * ^^^^^^^^^^^^ | |
1380 | * - `(function() {})` | |
1381 | * ^^^^^^^^ | |
1382 | * - `function* foo() {}` | |
1383 | * ^^^^^^^^^^^^^ | |
1384 | * - `(function* foo() {})` | |
1385 | * ^^^^^^^^^^^^^ | |
1386 | * - `(function*() {})` | |
1387 | * ^^^^^^^^^ | |
1388 | * - `() => {}` | |
1389 | * ^^ | |
1390 | * - `async () => {}` | |
1391 | * ^^ | |
1392 | * - `({ foo: function foo() {} })` | |
1393 | * ^^^^^^^^^^^^^^^^^ | |
1394 | * - `({ foo: function() {} })` | |
1395 | * ^^^^^^^^^^^^^ | |
1396 | * - `({ ['foo']: function() {} })` | |
1397 | * ^^^^^^^^^^^^^^^^^ | |
1398 | * - `({ [foo]: function() {} })` | |
1399 | * ^^^^^^^^^^^^^^^ | |
1400 | * - `({ foo() {} })` | |
1401 | * ^^^ | |
1402 | * - `({ foo: function* foo() {} })` | |
1403 | * ^^^^^^^^^^^^^^^^^^ | |
1404 | * - `({ foo: function*() {} })` | |
1405 | * ^^^^^^^^^^^^^^ | |
1406 | * - `({ ['foo']: function*() {} })` | |
1407 | * ^^^^^^^^^^^^^^^^^^ | |
1408 | * - `({ [foo]: function*() {} })` | |
1409 | * ^^^^^^^^^^^^^^^^ | |
1410 | * - `({ *foo() {} })` | |
1411 | * ^^^^ | |
1412 | * - `({ foo: async function foo() {} })` | |
1413 | * ^^^^^^^^^^^^^^^^^^^^^^^ | |
1414 | * - `({ foo: async function() {} })` | |
1415 | * ^^^^^^^^^^^^^^^^^^^ | |
1416 | * - `({ ['foo']: async function() {} })` | |
1417 | * ^^^^^^^^^^^^^^^^^^^^^^^ | |
1418 | * - `({ [foo]: async function() {} })` | |
1419 | * ^^^^^^^^^^^^^^^^^^^^^ | |
1420 | * - `({ async foo() {} })` | |
1421 | * ^^^^^^^^^ | |
1422 | * - `({ get foo() {} })` | |
1423 | * ^^^^^^^ | |
1424 | * - `({ set foo(a) {} })` | |
1425 | * ^^^^^^^ | |
1426 | * - `class A { constructor() {} }` | |
1427 | * ^^^^^^^^^^^ | |
1428 | * - `class A { foo() {} }` | |
1429 | * ^^^ | |
1430 | * - `class A { *foo() {} }` | |
1431 | * ^^^^ | |
1432 | * - `class A { async foo() {} }` | |
1433 | * ^^^^^^^^^ | |
1434 | * - `class A { ['foo']() {} }` | |
1435 | * ^^^^^^^ | |
1436 | * - `class A { *['foo']() {} }` | |
1437 | * ^^^^^^^^ | |
1438 | * - `class A { async ['foo']() {} }` | |
1439 | * ^^^^^^^^^^^^^ | |
1440 | * - `class A { [foo]() {} }` | |
1441 | * ^^^^^ | |
1442 | * - `class A { *[foo]() {} }` | |
1443 | * ^^^^^^ | |
1444 | * - `class A { async [foo]() {} }` | |
1445 | * ^^^^^^^^^^^ | |
1446 | * - `class A { get foo() {} }` | |
1447 | * ^^^^^^^ | |
1448 | * - `class A { set foo(a) {} }` | |
1449 | * ^^^^^^^ | |
1450 | * - `class A { static foo() {} }` | |
1451 | * ^^^^^^^^^^ | |
1452 | * - `class A { static *foo() {} }` | |
1453 | * ^^^^^^^^^^^ | |
1454 | * - `class A { static async foo() {} }` | |
1455 | * ^^^^^^^^^^^^^^^^ | |
1456 | * - `class A { static get foo() {} }` | |
1457 | * ^^^^^^^^^^^^^^ | |
1458 | * - `class A { static set foo(a) {} }` | |
1459 | * ^^^^^^^^^^^^^^ | |
1460 | * @param {ASTNode} node The function node to get. | |
1461 | * @param {SourceCode} sourceCode The source code object to get tokens. | |
1462 | * @returns {string} The location of the function node for reporting. | |
1463 | */ | |
1464 | getFunctionHeadLoc(node, sourceCode) { | |
1465 | const parent = node.parent; | |
1466 | let start = null; | |
1467 | let end = null; | |
1468 | ||
1469 | if (node.type === "ArrowFunctionExpression") { | |
1470 | const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken); | |
1471 | ||
1472 | start = arrowToken.loc.start; | |
1473 | end = arrowToken.loc.end; | |
1474 | } else if (parent.type === "Property" || parent.type === "MethodDefinition") { | |
1475 | start = parent.loc.start; | |
1476 | end = getOpeningParenOfParams(node, sourceCode).loc.start; | |
1477 | } else { | |
1478 | start = node.loc.start; | |
1479 | end = getOpeningParenOfParams(node, sourceCode).loc.start; | |
1480 | } | |
1481 | ||
1482 | return { | |
1483 | start: Object.assign({}, start), | |
1484 | end: Object.assign({}, end) | |
1485 | }; | |
1486 | }, | |
1487 | ||
1488 | /** | |
1489 | * Gets next location when the result is not out of bound, otherwise returns null. | |
ebb53d86 TL |
1490 | * |
1491 | * Assumptions: | |
1492 | * | |
1493 | * - The given location represents a valid location in the given source code. | |
1494 | * - Columns are 0-based. | |
1495 | * - Lines are 1-based. | |
1496 | * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location. | |
1497 | * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end. | |
1498 | * The start (column 0) of that extra line is considered to be a valid location. | |
1499 | * | |
1500 | * Examples of successive locations (line, column): | |
1501 | * | |
1502 | * code: foo | |
1503 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null | |
1504 | * | |
1505 | * code: foo<LF> | |
1506 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null | |
1507 | * | |
1508 | * code: foo<CR><LF> | |
1509 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null | |
1510 | * | |
1511 | * code: a<LF>b | |
1512 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null | |
1513 | * | |
1514 | * code: a<LF>b<LF> | |
1515 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null | |
1516 | * | |
1517 | * code: a<CR><LF>b<CR><LF> | |
1518 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null | |
1519 | * | |
1520 | * code: a<LF><LF> | |
1521 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null | |
1522 | * | |
1523 | * code: <LF> | |
1524 | * locations: (1, 0) -> (2, 0) -> null | |
1525 | * | |
1526 | * code: | |
1527 | * locations: (1, 0) -> null | |
eb39fafa DC |
1528 | * @param {SourceCode} sourceCode The sourceCode |
1529 | * @param {{line: number, column: number}} location The location | |
1530 | * @returns {{line: number, column: number} | null} Next location | |
1531 | */ | |
ebb53d86 TL |
1532 | getNextLocation(sourceCode, { line, column }) { |
1533 | if (column < sourceCode.lines[line - 1].length) { | |
1534 | return { | |
1535 | line, | |
1536 | column: column + 1 | |
1537 | }; | |
1538 | } | |
eb39fafa | 1539 | |
ebb53d86 TL |
1540 | if (line < sourceCode.lines.length) { |
1541 | return { | |
1542 | line: line + 1, | |
1543 | column: 0 | |
1544 | }; | |
eb39fafa DC |
1545 | } |
1546 | ||
ebb53d86 | 1547 | return null; |
eb39fafa DC |
1548 | }, |
1549 | ||
1550 | /** | |
1551 | * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses | |
1552 | * surrounding the node. | |
1553 | * @param {SourceCode} sourceCode The source code object | |
1554 | * @param {ASTNode} node An expression node | |
1555 | * @returns {string} The text representing the node, with all surrounding parentheses included | |
1556 | */ | |
1557 | getParenthesisedText(sourceCode, node) { | |
1558 | let leftToken = sourceCode.getFirstToken(node); | |
1559 | let rightToken = sourceCode.getLastToken(node); | |
1560 | ||
1561 | while ( | |
1562 | sourceCode.getTokenBefore(leftToken) && | |
1563 | sourceCode.getTokenBefore(leftToken).type === "Punctuator" && | |
1564 | sourceCode.getTokenBefore(leftToken).value === "(" && | |
1565 | sourceCode.getTokenAfter(rightToken) && | |
1566 | sourceCode.getTokenAfter(rightToken).type === "Punctuator" && | |
1567 | sourceCode.getTokenAfter(rightToken).value === ")" | |
1568 | ) { | |
1569 | leftToken = sourceCode.getTokenBefore(leftToken); | |
1570 | rightToken = sourceCode.getTokenAfter(rightToken); | |
1571 | } | |
1572 | ||
1573 | return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]); | |
1574 | }, | |
1575 | ||
1576 | /* | |
1577 | * Determine if a node has a possiblity to be an Error object | |
1578 | * @param {ASTNode} node ASTNode to check | |
1579 | * @returns {boolean} True if there is a chance it contains an Error obj | |
1580 | */ | |
1581 | couldBeError(node) { | |
1582 | switch (node.type) { | |
1583 | case "Identifier": | |
1584 | case "CallExpression": | |
1585 | case "NewExpression": | |
1586 | case "MemberExpression": | |
1587 | case "TaggedTemplateExpression": | |
1588 | case "YieldExpression": | |
1589 | case "AwaitExpression": | |
6f036462 | 1590 | case "ChainExpression": |
eb39fafa DC |
1591 | return true; // possibly an error object. |
1592 | ||
1593 | case "AssignmentExpression": | |
6f036462 TL |
1594 | if (["=", "&&="].includes(node.operator)) { |
1595 | return module.exports.couldBeError(node.right); | |
1596 | } | |
1597 | ||
1598 | if (["||=", "??="].includes(node.operator)) { | |
1599 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right); | |
1600 | } | |
1601 | ||
1602 | /** | |
1603 | * All other assignment operators are mathematical assignment operators (arithmetic or bitwise). | |
1604 | * An assignment expression with a mathematical operator can either evaluate to a primitive value, | |
1605 | * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object. | |
1606 | */ | |
1607 | return false; | |
eb39fafa DC |
1608 | |
1609 | case "SequenceExpression": { | |
1610 | const exprs = node.expressions; | |
1611 | ||
1612 | return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]); | |
1613 | } | |
1614 | ||
1615 | case "LogicalExpression": | |
6f036462 TL |
1616 | |
1617 | /* | |
1618 | * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it | |
1619 | * doesn't short-circuit, it takes the value from the right side, so the right side must always be | |
1620 | * a plausible error. A future improvement could verify that the left side could be truthy by | |
1621 | * excluding falsy literals. | |
1622 | */ | |
1623 | if (node.operator === "&&") { | |
1624 | return module.exports.couldBeError(node.right); | |
1625 | } | |
1626 | ||
eb39fafa DC |
1627 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right); |
1628 | ||
1629 | case "ConditionalExpression": | |
1630 | return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate); | |
1631 | ||
1632 | default: | |
1633 | return false; | |
1634 | } | |
1635 | }, | |
1636 | ||
eb39fafa DC |
1637 | /** |
1638 | * Check if a given node is a numeric literal or not. | |
1639 | * @param {ASTNode} node The node to check. | |
1640 | * @returns {boolean} `true` if the node is a number or bigint literal. | |
1641 | */ | |
1642 | isNumericLiteral(node) { | |
1643 | return ( | |
1644 | node.type === "Literal" && | |
1645 | (typeof node.value === "number" || Boolean(node.bigint)) | |
1646 | ); | |
1647 | }, | |
1648 | ||
1649 | /** | |
1650 | * Determines whether two tokens can safely be placed next to each other without merging into a single token | |
1651 | * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. | |
1652 | * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used. | |
1653 | * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed | |
1654 | * next to each other, behavior is undefined (although it should return `true` in most cases). | |
1655 | */ | |
1656 | canTokensBeAdjacent(leftValue, rightValue) { | |
1657 | const espreeOptions = { | |
1658 | ecmaVersion: espree.latestEcmaVersion, | |
1659 | comment: true, | |
1660 | range: true | |
1661 | }; | |
1662 | ||
1663 | let leftToken; | |
1664 | ||
1665 | if (typeof leftValue === "string") { | |
1666 | let tokens; | |
1667 | ||
1668 | try { | |
1669 | tokens = espree.tokenize(leftValue, espreeOptions); | |
d3726936 | 1670 | } catch { |
eb39fafa DC |
1671 | return false; |
1672 | } | |
1673 | ||
1674 | const comments = tokens.comments; | |
1675 | ||
1676 | leftToken = tokens[tokens.length - 1]; | |
1677 | if (comments.length) { | |
1678 | const lastComment = comments[comments.length - 1]; | |
1679 | ||
1680 | if (lastComment.range[0] > leftToken.range[0]) { | |
1681 | leftToken = lastComment; | |
1682 | } | |
1683 | } | |
1684 | } else { | |
1685 | leftToken = leftValue; | |
1686 | } | |
1687 | ||
1688 | if (leftToken.type === "Shebang") { | |
1689 | return false; | |
1690 | } | |
1691 | ||
1692 | let rightToken; | |
1693 | ||
1694 | if (typeof rightValue === "string") { | |
1695 | let tokens; | |
1696 | ||
1697 | try { | |
1698 | tokens = espree.tokenize(rightValue, espreeOptions); | |
d3726936 | 1699 | } catch { |
eb39fafa DC |
1700 | return false; |
1701 | } | |
1702 | ||
1703 | const comments = tokens.comments; | |
1704 | ||
1705 | rightToken = tokens[0]; | |
1706 | if (comments.length) { | |
1707 | const firstComment = comments[0]; | |
1708 | ||
1709 | if (firstComment.range[0] < rightToken.range[0]) { | |
1710 | rightToken = firstComment; | |
1711 | } | |
1712 | } | |
1713 | } else { | |
1714 | rightToken = rightValue; | |
1715 | } | |
1716 | ||
1717 | if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") { | |
1718 | if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") { | |
1719 | const PLUS_TOKENS = new Set(["+", "++"]); | |
1720 | const MINUS_TOKENS = new Set(["-", "--"]); | |
1721 | ||
1722 | return !( | |
1723 | PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) || | |
1724 | MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value) | |
1725 | ); | |
1726 | } | |
1727 | if (leftToken.type === "Punctuator" && leftToken.value === "/") { | |
1728 | return !["Block", "Line", "RegularExpression"].includes(rightToken.type); | |
1729 | } | |
1730 | return true; | |
1731 | } | |
1732 | ||
1733 | if ( | |
1734 | leftToken.type === "String" || rightToken.type === "String" || | |
1735 | leftToken.type === "Template" || rightToken.type === "Template" | |
1736 | ) { | |
1737 | return true; | |
1738 | } | |
1739 | ||
1740 | if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) { | |
1741 | return true; | |
1742 | } | |
1743 | ||
1744 | if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") { | |
1745 | return true; | |
1746 | } | |
1747 | ||
1748 | return false; | |
1749 | }, | |
1750 | ||
1751 | /** | |
1752 | * Get the `loc` object of a given name in a `/*globals` directive comment. | |
1753 | * @param {SourceCode} sourceCode The source code to convert index to loc. | |
1754 | * @param {Comment} comment The `/*globals` directive comment which include the name. | |
1755 | * @param {string} name The name to find. | |
1756 | * @returns {SourceLocation} The `loc` object. | |
1757 | */ | |
1758 | getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) { | |
1759 | const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu"); | |
1760 | ||
1761 | // To ignore the first text "global". | |
1762 | namePattern.lastIndex = comment.value.indexOf("global") + 6; | |
1763 | ||
1764 | // Search a given variable name. | |
1765 | const match = namePattern.exec(comment.value); | |
1766 | ||
1767 | // Convert the index to loc. | |
1768 | const start = sourceCode.getLocFromIndex( | |
1769 | comment.range[0] + | |
1770 | "/*".length + | |
1771 | (match ? match.index + 1 : 0) | |
1772 | ); | |
1773 | const end = { | |
1774 | line: start.line, | |
1775 | column: start.column + (match ? name.length : 1) | |
1776 | }; | |
1777 | ||
1778 | return { start, end }; | |
1779 | }, | |
1780 | ||
1781 | /** | |
6f036462 TL |
1782 | * Determines whether the given raw string contains an octal escape sequence |
1783 | * or a non-octal decimal escape sequence ("\8", "\9"). | |
eb39fafa | 1784 | * |
6f036462 TL |
1785 | * "\1", "\2" ... "\7", "\8", "\9" |
1786 | * "\00", "\01" ... "\07", "\08", "\09" | |
eb39fafa DC |
1787 | * |
1788 | * "\0", when not followed by a digit, is not an octal escape sequence. | |
1789 | * @param {string} rawString A string in its raw representation. | |
6f036462 TL |
1790 | * @returns {boolean} `true` if the string contains at least one octal escape sequence |
1791 | * or at least one non-octal decimal escape sequence. | |
eb39fafa | 1792 | */ |
6f036462 TL |
1793 | hasOctalOrNonOctalDecimalEscapeSequence(rawString) { |
1794 | return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString); | |
d3726936 TL |
1795 | }, |
1796 | ||
1797 | isLogicalExpression, | |
1798 | isCoalesceExpression, | |
6f036462 TL |
1799 | isMixedLogicalAndCoalesceExpressions, |
1800 | isNullLiteral, | |
1801 | getStaticStringValue, | |
1802 | getStaticPropertyName, | |
1803 | skipChainExpression, | |
1804 | isSpecificId, | |
1805 | isSpecificMemberAccess, | |
1806 | equalLiteralValue, | |
1807 | isSameReference, | |
1808 | isLogicalAssignmentOperator | |
eb39fafa | 1809 | }; |