UNPKG

45.7 kBJavaScriptView Raw
1/**
2 * @fileoverview Common utils for AST.
3 * @author Gyandeep Singh
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const esutils = require("esutils");
13const espree = require("espree");
14
15//------------------------------------------------------------------------------
16// Helpers
17//------------------------------------------------------------------------------
18
19const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
20const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u;
21const arrayOrTypedArrayPattern = /Array$/u;
22const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u;
23const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u;
24const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u;
25const thisTagPattern = /^[\s*]*@this/mu;
26
27
28const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;
29const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
30const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/u;
31const SHEBANG_MATCHER = /^#!([^\r\n]+)/u;
32
33// A set of node types that can contain a list of statements
34const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
35
36/**
37 * Checks reference if is non initializer and writable.
38 * @param {Reference} reference - A reference to check.
39 * @param {int} index - The index of the reference in the references.
40 * @param {Reference[]} references - The array that the reference belongs to.
41 * @returns {boolean} Success/Failure
42 * @private
43 */
44function isModifyingReference(reference, index, references) {
45 const identifier = reference.identifier;
46
47 /*
48 * Destructuring assignments can have multiple default value, so
49 * possibly there are multiple writeable references for the same
50 * identifier.
51 */
52 const modifyingDifferentIdentifier = index === 0 ||
53 references[index - 1].identifier !== identifier;
54
55 return (identifier &&
56 reference.init === false &&
57 reference.isWrite() &&
58 modifyingDifferentIdentifier
59 );
60}
61
62/**
63 * Checks whether the given string starts with uppercase or not.
64 *
65 * @param {string} s - The string to check.
66 * @returns {boolean} `true` if the string starts with uppercase.
67 */
68function startsWithUpperCase(s) {
69 return s[0] !== s[0].toLocaleLowerCase();
70}
71
72/**
73 * Checks whether or not a node is a constructor.
74 * @param {ASTNode} node - A function node to check.
75 * @returns {boolean} Wehether or not a node is a constructor.
76 */
77function isES5Constructor(node) {
78 return (node.id && startsWithUpperCase(node.id.name));
79}
80
81/**
82 * Finds a function node from ancestors of a node.
83 * @param {ASTNode} node - A start node to find.
84 * @returns {Node|null} A found function node.
85 */
86function getUpperFunction(node) {
87 for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
88 if (anyFunctionPattern.test(currentNode.type)) {
89 return currentNode;
90 }
91 }
92 return null;
93}
94
95/**
96 * Checks whether a given node is a function node or not.
97 * The following types are function nodes:
98 *
99 * - ArrowFunctionExpression
100 * - FunctionDeclaration
101 * - FunctionExpression
102 *
103 * @param {ASTNode|null} node - A node to check.
104 * @returns {boolean} `true` if the node is a function node.
105 */
106function isFunction(node) {
107 return Boolean(node && anyFunctionPattern.test(node.type));
108}
109
110/**
111 * Checks whether a given node is a loop node or not.
112 * The following types are loop nodes:
113 *
114 * - DoWhileStatement
115 * - ForInStatement
116 * - ForOfStatement
117 * - ForStatement
118 * - WhileStatement
119 *
120 * @param {ASTNode|null} node - A node to check.
121 * @returns {boolean} `true` if the node is a loop node.
122 */
123function isLoop(node) {
124 return Boolean(node && anyLoopPattern.test(node.type));
125}
126
127/**
128 * Checks whether the given node is in a loop or not.
129 *
130 * @param {ASTNode} node - The node to check.
131 * @returns {boolean} `true` if the node is in a loop.
132 */
133function isInLoop(node) {
134 for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) {
135 if (isLoop(currentNode)) {
136 return true;
137 }
138 }
139
140 return false;
141}
142
143/**
144 * Checks whether or not a node is `null` or `undefined`.
145 * @param {ASTNode} node - A node to check.
146 * @returns {boolean} Whether or not the node is a `null` or `undefined`.
147 * @public
148 */
149function isNullOrUndefined(node) {
150 return (
151 module.exports.isNullLiteral(node) ||
152 (node.type === "Identifier" && node.name === "undefined") ||
153 (node.type === "UnaryExpression" && node.operator === "void")
154 );
155}
156
157/**
158 * Checks whether or not a node is callee.
159 * @param {ASTNode} node - A node to check.
160 * @returns {boolean} Whether or not the node is callee.
161 */
162function isCallee(node) {
163 return node.parent.type === "CallExpression" && node.parent.callee === node;
164}
165
166/**
167 * Checks whether or not a node is `Reflect.apply`.
168 * @param {ASTNode} node - A node to check.
169 * @returns {boolean} Whether or not the node is a `Reflect.apply`.
170 */
171function isReflectApply(node) {
172 return (
173 node.type === "MemberExpression" &&
174 node.object.type === "Identifier" &&
175 node.object.name === "Reflect" &&
176 node.property.type === "Identifier" &&
177 node.property.name === "apply" &&
178 node.computed === false
179 );
180}
181
182/**
183 * Checks whether or not a node is `Array.from`.
184 * @param {ASTNode} node - A node to check.
185 * @returns {boolean} Whether or not the node is a `Array.from`.
186 */
187function isArrayFromMethod(node) {
188 return (
189 node.type === "MemberExpression" &&
190 node.object.type === "Identifier" &&
191 arrayOrTypedArrayPattern.test(node.object.name) &&
192 node.property.type === "Identifier" &&
193 node.property.name === "from" &&
194 node.computed === false
195 );
196}
197
198/**
199 * Checks whether or not a node is a method which has `thisArg`.
200 * @param {ASTNode} node - A node to check.
201 * @returns {boolean} Whether or not the node is a method which has `thisArg`.
202 */
203function isMethodWhichHasThisArg(node) {
204 for (
205 let currentNode = node;
206 currentNode.type === "MemberExpression" && !currentNode.computed;
207 currentNode = currentNode.property
208 ) {
209 if (currentNode.property.type === "Identifier") {
210 return arrayMethodPattern.test(currentNode.property.name);
211 }
212 }
213
214 return false;
215}
216
217/**
218 * Creates the negate function of the given function.
219 * @param {Function} f - The function to negate.
220 * @returns {Function} Negated function.
221 */
222function negate(f) {
223 return token => !f(token);
224}
225
226/**
227 * Checks whether or not a node has a `@this` tag in its comments.
228 * @param {ASTNode} node - A node to check.
229 * @param {SourceCode} sourceCode - A SourceCode instance to get comments.
230 * @returns {boolean} Whether or not the node has a `@this` tag in its comments.
231 */
232function hasJSDocThisTag(node, sourceCode) {
233 const jsdocComment = sourceCode.getJSDocComment(node);
234
235 if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
236 return true;
237 }
238
239 // Checks `@this` in its leading comments for callbacks,
240 // because callbacks don't have its JSDoc comment.
241 // e.g.
242 // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
243 return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value));
244}
245
246/**
247 * Determines if a node is surrounded by parentheses.
248 * @param {SourceCode} sourceCode The ESLint source code object
249 * @param {ASTNode} node The node to be checked.
250 * @returns {boolean} True if the node is parenthesised.
251 * @private
252 */
253function isParenthesised(sourceCode, node) {
254 const previousToken = sourceCode.getTokenBefore(node),
255 nextToken = sourceCode.getTokenAfter(node);
256
257 return Boolean(previousToken && nextToken) &&
258 previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
259 nextToken.value === ")" && nextToken.range[0] >= node.range[1];
260}
261
262/**
263 * Checks if the given token is an arrow token or not.
264 *
265 * @param {Token} token - The token to check.
266 * @returns {boolean} `true` if the token is an arrow token.
267 */
268function isArrowToken(token) {
269 return token.value === "=>" && token.type === "Punctuator";
270}
271
272/**
273 * Checks if the given token is a comma token or not.
274 *
275 * @param {Token} token - The token to check.
276 * @returns {boolean} `true` if the token is a comma token.
277 */
278function isCommaToken(token) {
279 return token.value === "," && token.type === "Punctuator";
280}
281
282/**
283 * Checks if the given token is a semicolon token or not.
284 *
285 * @param {Token} token - The token to check.
286 * @returns {boolean} `true` if the token is a semicolon token.
287 */
288function isSemicolonToken(token) {
289 return token.value === ";" && token.type === "Punctuator";
290}
291
292/**
293 * Checks if the given token is a colon token or not.
294 *
295 * @param {Token} token - The token to check.
296 * @returns {boolean} `true` if the token is a colon token.
297 */
298function isColonToken(token) {
299 return token.value === ":" && token.type === "Punctuator";
300}
301
302/**
303 * Checks if the given token is an opening parenthesis token or not.
304 *
305 * @param {Token} token - The token to check.
306 * @returns {boolean} `true` if the token is an opening parenthesis token.
307 */
308function isOpeningParenToken(token) {
309 return token.value === "(" && token.type === "Punctuator";
310}
311
312/**
313 * Checks if the given token is a closing parenthesis token or not.
314 *
315 * @param {Token} token - The token to check.
316 * @returns {boolean} `true` if the token is a closing parenthesis token.
317 */
318function isClosingParenToken(token) {
319 return token.value === ")" && token.type === "Punctuator";
320}
321
322/**
323 * Checks if the given token is an opening square bracket token or not.
324 *
325 * @param {Token} token - The token to check.
326 * @returns {boolean} `true` if the token is an opening square bracket token.
327 */
328function isOpeningBracketToken(token) {
329 return token.value === "[" && token.type === "Punctuator";
330}
331
332/**
333 * Checks if the given token is a closing square bracket token or not.
334 *
335 * @param {Token} token - The token to check.
336 * @returns {boolean} `true` if the token is a closing square bracket token.
337 */
338function isClosingBracketToken(token) {
339 return token.value === "]" && token.type === "Punctuator";
340}
341
342/**
343 * Checks if the given token is an opening brace token or not.
344 *
345 * @param {Token} token - The token to check.
346 * @returns {boolean} `true` if the token is an opening brace token.
347 */
348function isOpeningBraceToken(token) {
349 return token.value === "{" && token.type === "Punctuator";
350}
351
352/**
353 * Checks if the given token is a closing brace token or not.
354 *
355 * @param {Token} token - The token to check.
356 * @returns {boolean} `true` if the token is a closing brace token.
357 */
358function isClosingBraceToken(token) {
359 return token.value === "}" && token.type === "Punctuator";
360}
361
362/**
363 * Checks if the given token is a comment token or not.
364 *
365 * @param {Token} token - The token to check.
366 * @returns {boolean} `true` if the token is a comment token.
367 */
368function isCommentToken(token) {
369 return token.type === "Line" || token.type === "Block" || token.type === "Shebang";
370}
371
372/**
373 * Checks if the given token is a keyword token or not.
374 *
375 * @param {Token} token - The token to check.
376 * @returns {boolean} `true` if the token is a keyword token.
377 */
378function isKeywordToken(token) {
379 return token.type === "Keyword";
380}
381
382/**
383 * Gets the `(` token of the given function node.
384 *
385 * @param {ASTNode} node - The function node to get.
386 * @param {SourceCode} sourceCode - The source code object to get tokens.
387 * @returns {Token} `(` token.
388 */
389function getOpeningParenOfParams(node, sourceCode) {
390 return node.id
391 ? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
392 : sourceCode.getFirstToken(node, isOpeningParenToken);
393}
394
395/**
396 * Creates a version of the LINEBREAK_MATCHER regex with the global flag.
397 * Global regexes are mutable, so this needs to be a function instead of a constant.
398 * @returns {RegExp} A global regular expression that matches line terminators
399 */
400function createGlobalLinebreakMatcher() {
401 return new RegExp(LINEBREAK_MATCHER.source, "gu");
402}
403
404/**
405 * Checks whether or not the tokens of two given nodes are same.
406 * @param {ASTNode} left - A node 1 to compare.
407 * @param {ASTNode} right - A node 2 to compare.
408 * @param {SourceCode} sourceCode - The ESLint source code object.
409 * @returns {boolean} the source code for the given node.
410 */
411function equalTokens(left, right, sourceCode) {
412 const tokensL = sourceCode.getTokens(left);
413 const tokensR = sourceCode.getTokens(right);
414
415 if (tokensL.length !== tokensR.length) {
416 return false;
417 }
418 for (let i = 0; i < tokensL.length; ++i) {
419 if (tokensL[i].type !== tokensR[i].type ||
420 tokensL[i].value !== tokensR[i].value
421 ) {
422 return false;
423 }
424 }
425
426 return true;
427}
428
429//------------------------------------------------------------------------------
430// Public Interface
431//------------------------------------------------------------------------------
432
433module.exports = {
434 COMMENTS_IGNORE_PATTERN,
435 LINEBREAKS,
436 LINEBREAK_MATCHER,
437 SHEBANG_MATCHER,
438 STATEMENT_LIST_PARENTS,
439
440 /**
441 * Determines whether two adjacent tokens are on the same line.
442 * @param {Object} left - The left token object.
443 * @param {Object} right - The right token object.
444 * @returns {boolean} Whether or not the tokens are on the same line.
445 * @public
446 */
447 isTokenOnSameLine(left, right) {
448 return left.loc.end.line === right.loc.start.line;
449 },
450
451 isNullOrUndefined,
452 isCallee,
453 isES5Constructor,
454 getUpperFunction,
455 isFunction,
456 isLoop,
457 isInLoop,
458 isArrayFromMethod,
459 isParenthesised,
460 createGlobalLinebreakMatcher,
461 equalTokens,
462
463 isArrowToken,
464 isClosingBraceToken,
465 isClosingBracketToken,
466 isClosingParenToken,
467 isColonToken,
468 isCommaToken,
469 isCommentToken,
470 isKeywordToken,
471 isNotClosingBraceToken: negate(isClosingBraceToken),
472 isNotClosingBracketToken: negate(isClosingBracketToken),
473 isNotClosingParenToken: negate(isClosingParenToken),
474 isNotColonToken: negate(isColonToken),
475 isNotCommaToken: negate(isCommaToken),
476 isNotOpeningBraceToken: negate(isOpeningBraceToken),
477 isNotOpeningBracketToken: negate(isOpeningBracketToken),
478 isNotOpeningParenToken: negate(isOpeningParenToken),
479 isNotSemicolonToken: negate(isSemicolonToken),
480 isOpeningBraceToken,
481 isOpeningBracketToken,
482 isOpeningParenToken,
483 isSemicolonToken,
484
485 /**
486 * Checks whether or not a given node is a string literal.
487 * @param {ASTNode} node - A node to check.
488 * @returns {boolean} `true` if the node is a string literal.
489 */
490 isStringLiteral(node) {
491 return (
492 (node.type === "Literal" && typeof node.value === "string") ||
493 node.type === "TemplateLiteral"
494 );
495 },
496
497 /**
498 * Checks whether a given node is a breakable statement or not.
499 * The node is breakable if the node is one of the following type:
500 *
501 * - DoWhileStatement
502 * - ForInStatement
503 * - ForOfStatement
504 * - ForStatement
505 * - SwitchStatement
506 * - WhileStatement
507 *
508 * @param {ASTNode} node - A node to check.
509 * @returns {boolean} `true` if the node is breakable.
510 */
511 isBreakableStatement(node) {
512 return breakableTypePattern.test(node.type);
513 },
514
515 /**
516 * Gets the label if the parent node of a given node is a LabeledStatement.
517 *
518 * @param {ASTNode} node - A node to get.
519 * @returns {string|null} The label or `null`.
520 */
521 getLabel(node) {
522 if (node.parent.type === "LabeledStatement") {
523 return node.parent.label.name;
524 }
525 return null;
526 },
527
528 /**
529 * Gets references which are non initializer and writable.
530 * @param {Reference[]} references - An array of references.
531 * @returns {Reference[]} An array of only references which are non initializer and writable.
532 * @public
533 */
534 getModifyingReferences(references) {
535 return references.filter(isModifyingReference);
536 },
537
538 /**
539 * Validate that a string passed in is surrounded by the specified character
540 * @param {string} val The text to check.
541 * @param {string} character The character to see if it's surrounded by.
542 * @returns {boolean} True if the text is surrounded by the character, false if not.
543 * @private
544 */
545 isSurroundedBy(val, character) {
546 return val[0] === character && val[val.length - 1] === character;
547 },
548
549 /**
550 * Returns whether the provided node is an ESLint directive comment or not
551 * @param {Line|Block} node The comment token to be checked
552 * @returns {boolean} `true` if the node is an ESLint directive comment
553 */
554 isDirectiveComment(node) {
555 const comment = node.value.trim();
556
557 return (
558 node.type === "Line" && comment.indexOf("eslint-") === 0 ||
559 node.type === "Block" && (
560 comment.indexOf("global ") === 0 ||
561 comment.indexOf("eslint ") === 0 ||
562 comment.indexOf("eslint-") === 0
563 )
564 );
565 },
566
567 /**
568 * Gets the trailing statement of a given node.
569 *
570 * if (code)
571 * consequent;
572 *
573 * When taking this `IfStatement`, returns `consequent;` statement.
574 *
575 * @param {ASTNode} A node to get.
576 * @returns {ASTNode|null} The trailing statement's node.
577 */
578 getTrailingStatement: esutils.ast.trailingStatement,
579
580 /**
581 * Finds the variable by a given name in a given scope and its upper scopes.
582 *
583 * @param {eslint-scope.Scope} initScope - A scope to start find.
584 * @param {string} name - A variable name to find.
585 * @returns {eslint-scope.Variable|null} A found variable or `null`.
586 */
587 getVariableByName(initScope, name) {
588 let scope = initScope;
589
590 while (scope) {
591 const variable = scope.set.get(name);
592
593 if (variable) {
594 return variable;
595 }
596
597 scope = scope.upper;
598 }
599
600 return null;
601 },
602
603 /**
604 * Checks whether or not a given function node is the default `this` binding.
605 *
606 * First, this checks the node:
607 *
608 * - The function name does not start with uppercase (it's a constructor).
609 * - The function does not have a JSDoc comment that has a @this tag.
610 *
611 * Next, this checks the location of the node.
612 * If the location is below, this judges `this` is valid.
613 *
614 * - The location is not on an object literal.
615 * - The location is not assigned to a variable which starts with an uppercase letter.
616 * - The location is not on an ES2015 class.
617 * - Its `bind`/`call`/`apply` method is not called directly.
618 * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given.
619 *
620 * @param {ASTNode} node - A function node to check.
621 * @param {SourceCode} sourceCode - A SourceCode instance to get comments.
622 * @returns {boolean} The function node is the default `this` binding.
623 */
624 isDefaultThisBinding(node, sourceCode) {
625 if (isES5Constructor(node) || hasJSDocThisTag(node, sourceCode)) {
626 return false;
627 }
628 const isAnonymous = node.id === null;
629 let currentNode = node;
630
631 while (currentNode) {
632 const parent = currentNode.parent;
633
634 switch (parent.type) {
635
636 /*
637 * Looks up the destination.
638 * e.g., obj.foo = nativeFoo || function foo() { ... };
639 */
640 case "LogicalExpression":
641 case "ConditionalExpression":
642 currentNode = parent;
643 break;
644
645 /*
646 * If the upper function is IIFE, checks the destination of the return value.
647 * e.g.
648 * obj.foo = (function() {
649 * // setup...
650 * return function foo() { ... };
651 * })();
652 * obj.foo = (() =>
653 * function foo() { ... }
654 * )();
655 */
656 case "ReturnStatement": {
657 const func = getUpperFunction(parent);
658
659 if (func === null || !isCallee(func)) {
660 return true;
661 }
662 currentNode = func.parent;
663 break;
664 }
665 case "ArrowFunctionExpression":
666 if (currentNode !== parent.body || !isCallee(parent)) {
667 return true;
668 }
669 currentNode = parent.parent;
670 break;
671
672 /*
673 * e.g.
674 * var obj = { foo() { ... } };
675 * var obj = { foo: function() { ... } };
676 * class A { constructor() { ... } }
677 * class A { foo() { ... } }
678 * class A { get foo() { ... } }
679 * class A { set foo() { ... } }
680 * class A { static foo() { ... } }
681 */
682 case "Property":
683 case "MethodDefinition":
684 return parent.value !== currentNode;
685
686 /*
687 * e.g.
688 * obj.foo = function foo() { ... };
689 * Foo = function() { ... };
690 * [obj.foo = function foo() { ... }] = a;
691 * [Foo = function() { ... }] = a;
692 */
693 case "AssignmentExpression":
694 case "AssignmentPattern":
695 if (parent.left.type === "MemberExpression") {
696 return false;
697 }
698 if (
699 isAnonymous &&
700 parent.left.type === "Identifier" &&
701 startsWithUpperCase(parent.left.name)
702 ) {
703 return false;
704 }
705 return true;
706
707 /*
708 * e.g.
709 * var Foo = function() { ... };
710 */
711 case "VariableDeclarator":
712 return !(
713 isAnonymous &&
714 parent.init === currentNode &&
715 parent.id.type === "Identifier" &&
716 startsWithUpperCase(parent.id.name)
717 );
718
719 /*
720 * e.g.
721 * var foo = function foo() { ... }.bind(obj);
722 * (function foo() { ... }).call(obj);
723 * (function foo() { ... }).apply(obj, []);
724 */
725 case "MemberExpression":
726 return (
727 parent.object !== currentNode ||
728 parent.property.type !== "Identifier" ||
729 !bindOrCallOrApplyPattern.test(parent.property.name) ||
730 !isCallee(parent) ||
731 parent.parent.arguments.length === 0 ||
732 isNullOrUndefined(parent.parent.arguments[0])
733 );
734
735 /*
736 * e.g.
737 * Reflect.apply(function() {}, obj, []);
738 * Array.from([], function() {}, obj);
739 * list.forEach(function() {}, obj);
740 */
741 case "CallExpression":
742 if (isReflectApply(parent.callee)) {
743 return (
744 parent.arguments.length !== 3 ||
745 parent.arguments[0] !== currentNode ||
746 isNullOrUndefined(parent.arguments[1])
747 );
748 }
749 if (isArrayFromMethod(parent.callee)) {
750 return (
751 parent.arguments.length !== 3 ||
752 parent.arguments[1] !== currentNode ||
753 isNullOrUndefined(parent.arguments[2])
754 );
755 }
756 if (isMethodWhichHasThisArg(parent.callee)) {
757 return (
758 parent.arguments.length !== 2 ||
759 parent.arguments[0] !== currentNode ||
760 isNullOrUndefined(parent.arguments[1])
761 );
762 }
763 return true;
764
765 // Otherwise `this` is default.
766 default:
767 return true;
768 }
769 }
770
771 /* istanbul ignore next */
772 return true;
773 },
774
775 /**
776 * Get the precedence level based on the node type
777 * @param {ASTNode} node node to evaluate
778 * @returns {int} precedence level
779 * @private
780 */
781 getPrecedence(node) {
782 switch (node.type) {
783 case "SequenceExpression":
784 return 0;
785
786 case "AssignmentExpression":
787 case "ArrowFunctionExpression":
788 case "YieldExpression":
789 return 1;
790
791 case "ConditionalExpression":
792 return 3;
793
794 case "LogicalExpression":
795 switch (node.operator) {
796 case "||":
797 return 4;
798 case "&&":
799 return 5;
800
801 // no default
802 }
803
804 /* falls through */
805
806 case "BinaryExpression":
807
808 switch (node.operator) {
809 case "|":
810 return 6;
811 case "^":
812 return 7;
813 case "&":
814 return 8;
815 case "==":
816 case "!=":
817 case "===":
818 case "!==":
819 return 9;
820 case "<":
821 case "<=":
822 case ">":
823 case ">=":
824 case "in":
825 case "instanceof":
826 return 10;
827 case "<<":
828 case ">>":
829 case ">>>":
830 return 11;
831 case "+":
832 case "-":
833 return 12;
834 case "*":
835 case "/":
836 case "%":
837 return 13;
838 case "**":
839 return 15;
840
841 // no default
842 }
843
844 /* falls through */
845
846 case "UnaryExpression":
847 case "AwaitExpression":
848 return 16;
849
850 case "UpdateExpression":
851 return 17;
852
853 case "CallExpression":
854 return 18;
855
856 case "NewExpression":
857 return 19;
858
859 default:
860 return 20;
861 }
862 },
863
864 /**
865 * Checks whether the given node is an empty block node or not.
866 *
867 * @param {ASTNode|null} node - The node to check.
868 * @returns {boolean} `true` if the node is an empty block.
869 */
870 isEmptyBlock(node) {
871 return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
872 },
873
874 /**
875 * Checks whether the given node is an empty function node or not.
876 *
877 * @param {ASTNode|null} node - The node to check.
878 * @returns {boolean} `true` if the node is an empty function.
879 */
880 isEmptyFunction(node) {
881 return isFunction(node) && module.exports.isEmptyBlock(node.body);
882 },
883
884 /**
885 * Gets the property name of a given node.
886 * The node can be a MemberExpression, a Property, or a MethodDefinition.
887 *
888 * If the name is dynamic, this returns `null`.
889 *
890 * For examples:
891 *
892 * a.b // => "b"
893 * a["b"] // => "b"
894 * a['b'] // => "b"
895 * a[`b`] // => "b"
896 * a[100] // => "100"
897 * a[b] // => null
898 * a["a" + "b"] // => null
899 * a[tag`b`] // => null
900 * a[`${b}`] // => null
901 *
902 * let a = {b: 1} // => "b"
903 * let a = {["b"]: 1} // => "b"
904 * let a = {['b']: 1} // => "b"
905 * let a = {[`b`]: 1} // => "b"
906 * let a = {[100]: 1} // => "100"
907 * let a = {[b]: 1} // => null
908 * let a = {["a" + "b"]: 1} // => null
909 * let a = {[tag`b`]: 1} // => null
910 * let a = {[`${b}`]: 1} // => null
911 *
912 * @param {ASTNode} node - The node to get.
913 * @returns {string|null} The property name if static. Otherwise, null.
914 */
915 getStaticPropertyName(node) {
916 let prop;
917
918 switch (node && node.type) {
919 case "Property":
920 case "MethodDefinition":
921 prop = node.key;
922 break;
923
924 case "MemberExpression":
925 prop = node.property;
926 break;
927
928 // no default
929 }
930
931 switch (prop && prop.type) {
932 case "Literal":
933 return String(prop.value);
934
935 case "TemplateLiteral":
936 if (prop.expressions.length === 0 && prop.quasis.length === 1) {
937 return prop.quasis[0].value.cooked;
938 }
939 break;
940
941 case "Identifier":
942 if (!node.computed) {
943 return prop.name;
944 }
945 break;
946
947 // no default
948 }
949
950 return null;
951 },
952
953 /**
954 * Get directives from directive prologue of a Program or Function node.
955 * @param {ASTNode} node - The node to check.
956 * @returns {ASTNode[]} The directives found in the directive prologue.
957 */
958 getDirectivePrologue(node) {
959 const directives = [];
960
961 // Directive prologues only occur at the top of files or functions.
962 if (
963 node.type === "Program" ||
964 node.type === "FunctionDeclaration" ||
965 node.type === "FunctionExpression" ||
966
967 /*
968 * Do not check arrow functions with implicit return.
969 * `() => "use strict";` returns the string `"use strict"`.
970 */
971 (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement")
972 ) {
973 const statements = node.type === "Program" ? node.body : node.body.body;
974
975 for (const statement of statements) {
976 if (
977 statement.type === "ExpressionStatement" &&
978 statement.expression.type === "Literal"
979 ) {
980 directives.push(statement);
981 } else {
982 break;
983 }
984 }
985 }
986
987 return directives;
988 },
989
990
991 /**
992 * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added
993 * after the node will be parsed as a decimal point, rather than a property-access dot.
994 * @param {ASTNode} node - The node to check.
995 * @returns {boolean} `true` if this node is a decimal integer.
996 * @example
997 *
998 * 5 // true
999 * 5. // false
1000 * 5.0 // false
1001 * 05 // false
1002 * 0x5 // false
1003 * 0b101 // false
1004 * 0o5 // false
1005 * 5e0 // false
1006 * '5' // false
1007 */
1008 isDecimalInteger(node) {
1009 return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/u.test(node.raw);
1010 },
1011
1012 /**
1013 * Gets the name and kind of the given function node.
1014 *
1015 * - `function foo() {}` .................... `function 'foo'`
1016 * - `(function foo() {})` .................. `function 'foo'`
1017 * - `(function() {})` ...................... `function`
1018 * - `function* foo() {}` ................... `generator function 'foo'`
1019 * - `(function* foo() {})` ................. `generator function 'foo'`
1020 * - `(function*() {})` ..................... `generator function`
1021 * - `() => {}` ............................. `arrow function`
1022 * - `async () => {}` ....................... `async arrow function`
1023 * - `({ foo: function foo() {} })` ......... `method 'foo'`
1024 * - `({ foo: function() {} })` ............. `method 'foo'`
1025 * - `({ ['foo']: function() {} })` ......... `method 'foo'`
1026 * - `({ [foo]: function() {} })` ........... `method`
1027 * - `({ foo() {} })` ....................... `method 'foo'`
1028 * - `({ foo: function* foo() {} })` ........ `generator method 'foo'`
1029 * - `({ foo: function*() {} })` ............ `generator method 'foo'`
1030 * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'`
1031 * - `({ [foo]: function*() {} })` .......... `generator method`
1032 * - `({ *foo() {} })` ...................... `generator method 'foo'`
1033 * - `({ foo: async function foo() {} })` ... `async method 'foo'`
1034 * - `({ foo: async function() {} })` ....... `async method 'foo'`
1035 * - `({ ['foo']: async function() {} })` ... `async method 'foo'`
1036 * - `({ [foo]: async function() {} })` ..... `async method`
1037 * - `({ async foo() {} })` ................. `async method 'foo'`
1038 * - `({ get foo() {} })` ................... `getter 'foo'`
1039 * - `({ set foo(a) {} })` .................. `setter 'foo'`
1040 * - `class A { constructor() {} }` ......... `constructor`
1041 * - `class A { foo() {} }` ................. `method 'foo'`
1042 * - `class A { *foo() {} }` ................ `generator method 'foo'`
1043 * - `class A { async foo() {} }` ........... `async method 'foo'`
1044 * - `class A { ['foo']() {} }` ............. `method 'foo'`
1045 * - `class A { *['foo']() {} }` ............ `generator method 'foo'`
1046 * - `class A { async ['foo']() {} }` ....... `async method 'foo'`
1047 * - `class A { [foo]() {} }` ............... `method`
1048 * - `class A { *[foo]() {} }` .............. `generator method`
1049 * - `class A { async [foo]() {} }` ......... `async method`
1050 * - `class A { get foo() {} }` ............. `getter 'foo'`
1051 * - `class A { set foo(a) {} }` ............ `setter 'foo'`
1052 * - `class A { static foo() {} }` .......... `static method 'foo'`
1053 * - `class A { static *foo() {} }` ......... `static generator method 'foo'`
1054 * - `class A { static async foo() {} }` .... `static async method 'foo'`
1055 * - `class A { static get foo() {} }` ...... `static getter 'foo'`
1056 * - `class A { static set foo(a) {} }` ..... `static setter 'foo'`
1057 *
1058 * @param {ASTNode} node - The function node to get.
1059 * @returns {string} The name and kind of the function node.
1060 */
1061 getFunctionNameWithKind(node) {
1062 const parent = node.parent;
1063 const tokens = [];
1064
1065 if (parent.type === "MethodDefinition" && parent.static) {
1066 tokens.push("static");
1067 }
1068 if (node.async) {
1069 tokens.push("async");
1070 }
1071 if (node.generator) {
1072 tokens.push("generator");
1073 }
1074
1075 if (node.type === "ArrowFunctionExpression") {
1076 tokens.push("arrow", "function");
1077 } else if (parent.type === "Property" || parent.type === "MethodDefinition") {
1078 if (parent.kind === "constructor") {
1079 return "constructor";
1080 }
1081 if (parent.kind === "get") {
1082 tokens.push("getter");
1083 } else if (parent.kind === "set") {
1084 tokens.push("setter");
1085 } else {
1086 tokens.push("method");
1087 }
1088 } else {
1089 tokens.push("function");
1090 }
1091
1092 if (node.id) {
1093 tokens.push(`'${node.id.name}'`);
1094 } else {
1095 const name = module.exports.getStaticPropertyName(parent);
1096
1097 if (name) {
1098 tokens.push(`'${name}'`);
1099 }
1100 }
1101
1102 return tokens.join(" ");
1103 },
1104
1105 /**
1106 * Gets the location of the given function node for reporting.
1107 *
1108 * - `function foo() {}`
1109 * ^^^^^^^^^^^^
1110 * - `(function foo() {})`
1111 * ^^^^^^^^^^^^
1112 * - `(function() {})`
1113 * ^^^^^^^^
1114 * - `function* foo() {}`
1115 * ^^^^^^^^^^^^^
1116 * - `(function* foo() {})`
1117 * ^^^^^^^^^^^^^
1118 * - `(function*() {})`
1119 * ^^^^^^^^^
1120 * - `() => {}`
1121 * ^^
1122 * - `async () => {}`
1123 * ^^
1124 * - `({ foo: function foo() {} })`
1125 * ^^^^^^^^^^^^^^^^^
1126 * - `({ foo: function() {} })`
1127 * ^^^^^^^^^^^^^
1128 * - `({ ['foo']: function() {} })`
1129 * ^^^^^^^^^^^^^^^^^
1130 * - `({ [foo]: function() {} })`
1131 * ^^^^^^^^^^^^^^^
1132 * - `({ foo() {} })`
1133 * ^^^
1134 * - `({ foo: function* foo() {} })`
1135 * ^^^^^^^^^^^^^^^^^^
1136 * - `({ foo: function*() {} })`
1137 * ^^^^^^^^^^^^^^
1138 * - `({ ['foo']: function*() {} })`
1139 * ^^^^^^^^^^^^^^^^^^
1140 * - `({ [foo]: function*() {} })`
1141 * ^^^^^^^^^^^^^^^^
1142 * - `({ *foo() {} })`
1143 * ^^^^
1144 * - `({ foo: async function foo() {} })`
1145 * ^^^^^^^^^^^^^^^^^^^^^^^
1146 * - `({ foo: async function() {} })`
1147 * ^^^^^^^^^^^^^^^^^^^
1148 * - `({ ['foo']: async function() {} })`
1149 * ^^^^^^^^^^^^^^^^^^^^^^^
1150 * - `({ [foo]: async function() {} })`
1151 * ^^^^^^^^^^^^^^^^^^^^^
1152 * - `({ async foo() {} })`
1153 * ^^^^^^^^^
1154 * - `({ get foo() {} })`
1155 * ^^^^^^^
1156 * - `({ set foo(a) {} })`
1157 * ^^^^^^^
1158 * - `class A { constructor() {} }`
1159 * ^^^^^^^^^^^
1160 * - `class A { foo() {} }`
1161 * ^^^
1162 * - `class A { *foo() {} }`
1163 * ^^^^
1164 * - `class A { async foo() {} }`
1165 * ^^^^^^^^^
1166 * - `class A { ['foo']() {} }`
1167 * ^^^^^^^
1168 * - `class A { *['foo']() {} }`
1169 * ^^^^^^^^
1170 * - `class A { async ['foo']() {} }`
1171 * ^^^^^^^^^^^^^
1172 * - `class A { [foo]() {} }`
1173 * ^^^^^
1174 * - `class A { *[foo]() {} }`
1175 * ^^^^^^
1176 * - `class A { async [foo]() {} }`
1177 * ^^^^^^^^^^^
1178 * - `class A { get foo() {} }`
1179 * ^^^^^^^
1180 * - `class A { set foo(a) {} }`
1181 * ^^^^^^^
1182 * - `class A { static foo() {} }`
1183 * ^^^^^^^^^^
1184 * - `class A { static *foo() {} }`
1185 * ^^^^^^^^^^^
1186 * - `class A { static async foo() {} }`
1187 * ^^^^^^^^^^^^^^^^
1188 * - `class A { static get foo() {} }`
1189 * ^^^^^^^^^^^^^^
1190 * - `class A { static set foo(a) {} }`
1191 * ^^^^^^^^^^^^^^
1192 *
1193 * @param {ASTNode} node - The function node to get.
1194 * @param {SourceCode} sourceCode - The source code object to get tokens.
1195 * @returns {string} The location of the function node for reporting.
1196 */
1197 getFunctionHeadLoc(node, sourceCode) {
1198 const parent = node.parent;
1199 let start = null;
1200 let end = null;
1201
1202 if (node.type === "ArrowFunctionExpression") {
1203 const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
1204
1205 start = arrowToken.loc.start;
1206 end = arrowToken.loc.end;
1207 } else if (parent.type === "Property" || parent.type === "MethodDefinition") {
1208 start = parent.loc.start;
1209 end = getOpeningParenOfParams(node, sourceCode).loc.start;
1210 } else {
1211 start = node.loc.start;
1212 end = getOpeningParenOfParams(node, sourceCode).loc.start;
1213 }
1214
1215 return {
1216 start: Object.assign({}, start),
1217 end: Object.assign({}, end)
1218 };
1219 },
1220
1221 /**
1222 * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
1223 * surrounding the node.
1224 * @param {SourceCode} sourceCode The source code object
1225 * @param {ASTNode} node An expression node
1226 * @returns {string} The text representing the node, with all surrounding parentheses included
1227 */
1228 getParenthesisedText(sourceCode, node) {
1229 let leftToken = sourceCode.getFirstToken(node);
1230 let rightToken = sourceCode.getLastToken(node);
1231
1232 while (
1233 sourceCode.getTokenBefore(leftToken) &&
1234 sourceCode.getTokenBefore(leftToken).type === "Punctuator" &&
1235 sourceCode.getTokenBefore(leftToken).value === "(" &&
1236 sourceCode.getTokenAfter(rightToken) &&
1237 sourceCode.getTokenAfter(rightToken).type === "Punctuator" &&
1238 sourceCode.getTokenAfter(rightToken).value === ")"
1239 ) {
1240 leftToken = sourceCode.getTokenBefore(leftToken);
1241 rightToken = sourceCode.getTokenAfter(rightToken);
1242 }
1243
1244 return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
1245 },
1246
1247 /*
1248 * Determine if a node has a possiblity to be an Error object
1249 * @param {ASTNode} node ASTNode to check
1250 * @returns {boolean} True if there is a chance it contains an Error obj
1251 */
1252 couldBeError(node) {
1253 switch (node.type) {
1254 case "Identifier":
1255 case "CallExpression":
1256 case "NewExpression":
1257 case "MemberExpression":
1258 case "TaggedTemplateExpression":
1259 case "YieldExpression":
1260 case "AwaitExpression":
1261 return true; // possibly an error object.
1262
1263 case "AssignmentExpression":
1264 return module.exports.couldBeError(node.right);
1265
1266 case "SequenceExpression": {
1267 const exprs = node.expressions;
1268
1269 return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]);
1270 }
1271
1272 case "LogicalExpression":
1273 return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
1274
1275 case "ConditionalExpression":
1276 return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate);
1277
1278 default:
1279 return false;
1280 }
1281 },
1282
1283 /**
1284 * Determines whether the given node is a `null` literal.
1285 * @param {ASTNode} node The node to check
1286 * @returns {boolean} `true` if the node is a `null` literal
1287 */
1288 isNullLiteral(node) {
1289
1290 /*
1291 * Checking `node.value === null` does not guarantee that a literal is a null literal.
1292 * When parsing values that cannot be represented in the current environment (e.g. unicode
1293 * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
1294 * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
1295 * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
1296 */
1297 return node.type === "Literal" && node.value === null && !node.regex;
1298 },
1299
1300 /**
1301 * Determines whether two tokens can safely be placed next to each other without merging into a single token
1302 * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used.
1303 * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used.
1304 * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed
1305 * next to each other, behavior is undefined (although it should return `true` in most cases).
1306 */
1307 canTokensBeAdjacent(leftValue, rightValue) {
1308 let leftToken;
1309
1310 if (typeof leftValue === "string") {
1311 const leftTokens = espree.tokenize(leftValue, { ecmaVersion: 2015 });
1312
1313 leftToken = leftTokens[leftTokens.length - 1];
1314 } else {
1315 leftToken = leftValue;
1316 }
1317
1318 const rightToken = typeof rightValue === "string" ? espree.tokenize(rightValue, { ecmaVersion: 2015 })[0] : rightValue;
1319
1320 if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") {
1321 if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") {
1322 const PLUS_TOKENS = new Set(["+", "++"]);
1323 const MINUS_TOKENS = new Set(["-", "--"]);
1324
1325 return !(
1326 PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) ||
1327 MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)
1328 );
1329 }
1330 return true;
1331 }
1332
1333 if (
1334 leftToken.type === "String" || rightToken.type === "String" ||
1335 leftToken.type === "Template" || rightToken.type === "Template"
1336 ) {
1337 return true;
1338 }
1339
1340 if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) {
1341 return true;
1342 }
1343
1344 return false;
1345 }
1346};