UNPKG

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