1 /*! @author Toru Nagashima <https://github.com/mysticatea> */
4 Object.defineProperty(exports, '__esModule', { value: true });
6 function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
8 var evk = _interopDefault(require('eslint-visitor-keys'));
11 * Get the innermost scope which contains a given location.
12 * @param {Scope} initialScope The initial scope to search.
13 * @param {Node} node The location to search.
14 * @returns {Scope} The innermost scope.
16 function getInnermostScope(initialScope, node) {
17 const location = node.range[0];
19 let scope = initialScope;
23 for (const childScope of scope.childScopes) {
24 const range = childScope.block.range;
26 if (range[0] <= location && location < range[1]) {
38 * Find the variable of a given name.
39 * @param {Scope} initialScope The scope to start finding.
40 * @param {string|Node} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node.
41 * @returns {Variable|null} The found variable or null.
43 function findVariable(initialScope, nameOrNode) {
45 let scope = initialScope;
47 if (typeof nameOrNode === "string") {
50 name = nameOrNode.name;
51 scope = getInnermostScope(scope, nameOrNode);
54 while (scope != null) {
55 const variable = scope.set.get(name);
56 if (variable != null) {
66 * Negate the result of `this` calling.
67 * @param {Token} token The token to check.
68 * @returns {boolean} `true` if the result of `this(token)` is `false`.
70 function negate0(token) {
71 return !this(token) //eslint-disable-line no-invalid-this
75 * Creates the negate function of the given function.
76 * @param {function(Token):boolean} f - The function to negate.
77 * @returns {function(Token):boolean} Negated function.
80 return negate0.bind(f)
84 * Checks if the given token is an arrow token or not.
85 * @param {Token} token - The token to check.
86 * @returns {boolean} `true` if the token is an arrow token.
88 function isArrowToken(token) {
89 return token.value === "=>" && token.type === "Punctuator"
93 * Checks if the given token is a comma token or not.
94 * @param {Token} token - The token to check.
95 * @returns {boolean} `true` if the token is a comma token.
97 function isCommaToken(token) {
98 return token.value === "," && token.type === "Punctuator"
102 * Checks if the given token is a semicolon token or not.
103 * @param {Token} token - The token to check.
104 * @returns {boolean} `true` if the token is a semicolon token.
106 function isSemicolonToken(token) {
107 return token.value === ";" && token.type === "Punctuator"
111 * Checks if the given token is a colon token or not.
112 * @param {Token} token - The token to check.
113 * @returns {boolean} `true` if the token is a colon token.
115 function isColonToken(token) {
116 return token.value === ":" && token.type === "Punctuator"
120 * Checks if the given token is an opening parenthesis token or not.
121 * @param {Token} token - The token to check.
122 * @returns {boolean} `true` if the token is an opening parenthesis token.
124 function isOpeningParenToken(token) {
125 return token.value === "(" && token.type === "Punctuator"
129 * Checks if the given token is a closing parenthesis token or not.
130 * @param {Token} token - The token to check.
131 * @returns {boolean} `true` if the token is a closing parenthesis token.
133 function isClosingParenToken(token) {
134 return token.value === ")" && token.type === "Punctuator"
138 * Checks if the given token is an opening square bracket token or not.
139 * @param {Token} token - The token to check.
140 * @returns {boolean} `true` if the token is an opening square bracket token.
142 function isOpeningBracketToken(token) {
143 return token.value === "[" && token.type === "Punctuator"
147 * Checks if the given token is a closing square bracket token or not.
148 * @param {Token} token - The token to check.
149 * @returns {boolean} `true` if the token is a closing square bracket token.
151 function isClosingBracketToken(token) {
152 return token.value === "]" && token.type === "Punctuator"
156 * Checks if the given token is an opening brace token or not.
157 * @param {Token} token - The token to check.
158 * @returns {boolean} `true` if the token is an opening brace token.
160 function isOpeningBraceToken(token) {
161 return token.value === "{" && token.type === "Punctuator"
165 * Checks if the given token is a closing brace token or not.
166 * @param {Token} token - The token to check.
167 * @returns {boolean} `true` if the token is a closing brace token.
169 function isClosingBraceToken(token) {
170 return token.value === "}" && token.type === "Punctuator"
174 * Checks if the given token is a comment token or not.
175 * @param {Token} token - The token to check.
176 * @returns {boolean} `true` if the token is a comment token.
178 function isCommentToken(token) {
180 token.type === "Line" ||
181 token.type === "Block" ||
182 token.type === "Shebang"
186 const isNotArrowToken = negate(isArrowToken);
187 const isNotCommaToken = negate(isCommaToken);
188 const isNotSemicolonToken = negate(isSemicolonToken);
189 const isNotColonToken = negate(isColonToken);
190 const isNotOpeningParenToken = negate(isOpeningParenToken);
191 const isNotClosingParenToken = negate(isClosingParenToken);
192 const isNotOpeningBracketToken = negate(isOpeningBracketToken);
193 const isNotClosingBracketToken = negate(isClosingBracketToken);
194 const isNotOpeningBraceToken = negate(isOpeningBraceToken);
195 const isNotClosingBraceToken = negate(isClosingBraceToken);
196 const isNotCommentToken = negate(isCommentToken);
199 * Get the `(` token of the given function node.
200 * @param {Node} node - The function node to get.
201 * @param {SourceCode} sourceCode - The source code object to get tokens.
202 * @returns {Token} `(` token.
204 function getOpeningParenOfParams(node, sourceCode) {
206 ? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
207 : sourceCode.getFirstToken(node, isOpeningParenToken)
211 * Get the location of the given function node for reporting.
212 * @param {Node} node - The function node to get.
213 * @param {SourceCode} sourceCode - The source code object to get tokens.
214 * @returns {string} The location of the function node for reporting.
216 function getFunctionHeadLocation(node, sourceCode) {
217 const parent = node.parent;
221 if (node.type === "ArrowFunctionExpression") {
222 const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
224 start = arrowToken.loc.start;
225 end = arrowToken.loc.end;
227 parent.type === "Property" ||
228 parent.type === "MethodDefinition"
230 start = parent.loc.start;
231 end = getOpeningParenOfParams(node, sourceCode).loc.start;
233 start = node.loc.start;
234 end = getOpeningParenOfParams(node, sourceCode).loc.start;
238 start: Object.assign({}, start),
239 end: Object.assign({}, end),
243 /* globals BigInt, globalThis, global, self, window */
246 typeof globalThis !== "undefined"
248 : typeof self !== "undefined"
250 : typeof window !== "undefined"
252 : typeof global !== "undefined"
256 const builtinNames = Object.freeze(
267 "decodeURIComponent",
269 "encodeURIComponent",
306 const callAllowed = new Set(
309 typeof BigInt === "function" ? BigInt : undefined,
321 ...Object.getOwnPropertyNames(Math)
323 .filter(f => typeof f === "function"),
342 String.fromCodePoint,
348 ].filter(f => typeof f === "function")
350 const callPassThrough = new Set([
352 Object.preventExtensions,
357 * Get the property descriptor.
358 * @param {object} object The object to get.
359 * @param {string|number|symbol} name The property name to get.
361 function getPropertyDescriptor(object, name) {
363 while ((typeof x === "object" || typeof x === "function") && x !== null) {
364 const d = Object.getOwnPropertyDescriptor(x, name);
368 x = Object.getPrototypeOf(x);
374 * Check if a property is getter or not.
375 * @param {object} object The object to check.
376 * @param {string|number|symbol} name The property name to check.
378 function isGetter(object, name) {
379 const d = getPropertyDescriptor(object, name);
380 return d != null && d.get != null
384 * Get the element values of a given node list.
385 * @param {Node[]} nodeList The node list to get values.
386 * @param {Scope|undefined} initialScope The initial scope to find variables.
387 * @returns {any[]|null} The value list if all nodes are constant. Otherwise, null.
389 function getElementValues(nodeList, initialScope) {
390 const valueList = [];
392 for (let i = 0; i < nodeList.length; ++i) {
393 const elementNode = nodeList[i];
395 if (elementNode == null) {
396 valueList.length = i + 1;
397 } else if (elementNode.type === "SpreadElement") {
398 const argument = getStaticValueR(elementNode.argument, initialScope);
399 if (argument == null) {
402 valueList.push(...argument.value);
404 const element = getStaticValueR(elementNode, initialScope);
405 if (element == null) {
408 valueList.push(element.value);
415 const operations = Object.freeze({
416 ArrayExpression(node, initialScope) {
417 const elements = getElementValues(node.elements, initialScope);
418 return elements != null ? { value: elements } : null
421 AssignmentExpression(node, initialScope) {
422 if (node.operator === "=") {
423 return getStaticValueR(node.right, initialScope)
428 //eslint-disable-next-line complexity
429 BinaryExpression(node, initialScope) {
430 if (node.operator === "in" || node.operator === "instanceof") {
435 const left = getStaticValueR(node.left, initialScope);
436 const right = getStaticValueR(node.right, initialScope);
437 if (left != null && right != null) {
438 switch (node.operator) {
440 return { value: left.value == right.value } //eslint-disable-line eqeqeq
442 return { value: left.value != right.value } //eslint-disable-line eqeqeq
444 return { value: left.value === right.value }
446 return { value: left.value !== right.value }
448 return { value: left.value < right.value }
450 return { value: left.value <= right.value }
452 return { value: left.value > right.value }
454 return { value: left.value >= right.value }
456 return { value: left.value << right.value }
458 return { value: left.value >> right.value }
460 return { value: left.value >>> right.value }
462 return { value: left.value + right.value }
464 return { value: left.value - right.value }
466 return { value: left.value * right.value }
468 return { value: left.value / right.value }
470 return { value: left.value % right.value }
472 return { value: Math.pow(left.value, right.value) }
474 return { value: left.value | right.value }
476 return { value: left.value ^ right.value }
478 return { value: left.value & right.value }
487 CallExpression(node, initialScope) {
488 const calleeNode = node.callee;
489 const args = getElementValues(node.arguments, initialScope);
492 if (calleeNode.type === "MemberExpression") {
493 const object = getStaticValueR(calleeNode.object, initialScope);
494 if (object != null) {
496 object.value == null &&
497 (object.optional || node.optional)
499 return { value: undefined, optional: true }
501 const property = calleeNode.computed
502 ? getStaticValueR(calleeNode.property, initialScope)
503 : { value: calleeNode.property.name };
505 if (property != null) {
506 const receiver = object.value;
507 const methodName = property.value;
508 if (callAllowed.has(receiver[methodName])) {
509 return { value: receiver[methodName](...args) }
511 if (callPassThrough.has(receiver[methodName])) {
512 return { value: args[0] }
517 const callee = getStaticValueR(calleeNode, initialScope);
518 if (callee != null) {
519 if (callee.value == null && node.optional) {
520 return { value: undefined, optional: true }
522 const func = callee.value;
523 if (callAllowed.has(func)) {
524 return { value: func(...args) }
526 if (callPassThrough.has(func)) {
527 return { value: args[0] }
536 ConditionalExpression(node, initialScope) {
537 const test = getStaticValueR(node.test, initialScope);
540 ? getStaticValueR(node.consequent, initialScope)
541 : getStaticValueR(node.alternate, initialScope)
546 ExpressionStatement(node, initialScope) {
547 return getStaticValueR(node.expression, initialScope)
550 Identifier(node, initialScope) {
551 if (initialScope != null) {
552 const variable = findVariable(initialScope, node);
557 variable.defs.length === 0 &&
558 builtinNames.has(variable.name) &&
559 variable.name in globalObject
561 return { value: globalObject[variable.name] }
565 if (variable != null && variable.defs.length === 1) {
566 const def = variable.defs[0];
569 def.parent.kind === "const" &&
570 // TODO(mysticatea): don't support destructuring here.
571 def.node.id.type === "Identifier"
573 return getStaticValueR(def.node.init, initialScope)
581 //istanbul ignore if : this is implementation-specific behavior.
582 if ((node.regex != null || node.bigint != null) && node.value == null) {
583 // It was a RegExp/BigInt literal, but Node.js didn't support it.
586 return { value: node.value }
589 LogicalExpression(node, initialScope) {
590 const left = getStaticValueR(node.left, initialScope);
593 (node.operator === "||" && Boolean(left.value) === true) ||
594 (node.operator === "&&" && Boolean(left.value) === false) ||
595 (node.operator === "??" && left.value != null)
600 const right = getStaticValueR(node.right, initialScope);
609 MemberExpression(node, initialScope) {
610 const object = getStaticValueR(node.object, initialScope);
611 if (object != null) {
612 if (object.value == null && (object.optional || node.optional)) {
613 return { value: undefined, optional: true }
615 const property = node.computed
616 ? getStaticValueR(node.property, initialScope)
617 : { value: node.property.name };
619 if (property != null && !isGetter(object.value, property.value)) {
620 return { value: object.value[property.value] }
626 ChainExpression(node, initialScope) {
627 const expression = getStaticValueR(node.expression, initialScope);
628 if (expression != null) {
629 return { value: expression.value }
634 NewExpression(node, initialScope) {
635 const callee = getStaticValueR(node.callee, initialScope);
636 const args = getElementValues(node.arguments, initialScope);
638 if (callee != null && args != null) {
639 const Func = callee.value;
640 if (callAllowed.has(Func)) {
641 return { value: new Func(...args) }
648 ObjectExpression(node, initialScope) {
651 for (const propertyNode of node.properties) {
652 if (propertyNode.type === "Property") {
653 if (propertyNode.kind !== "init") {
656 const key = propertyNode.computed
657 ? getStaticValueR(propertyNode.key, initialScope)
658 : { value: propertyNode.key.name };
659 const value = getStaticValueR(propertyNode.value, initialScope);
660 if (key == null || value == null) {
663 object[key.value] = value.value;
665 propertyNode.type === "SpreadElement" ||
666 propertyNode.type === "ExperimentalSpreadProperty"
668 const argument = getStaticValueR(
669 propertyNode.argument,
672 if (argument == null) {
675 Object.assign(object, argument.value);
681 return { value: object }
684 SequenceExpression(node, initialScope) {
685 const last = node.expressions[node.expressions.length - 1];
686 return getStaticValueR(last, initialScope)
689 TaggedTemplateExpression(node, initialScope) {
690 const tag = getStaticValueR(node.tag, initialScope);
691 const expressions = getElementValues(
692 node.quasi.expressions,
696 if (tag != null && expressions != null) {
697 const func = tag.value;
698 const strings = node.quasi.quasis.map(q => q.value.cooked);
699 strings.raw = node.quasi.quasis.map(q => q.value.raw);
701 if (func === String.raw) {
702 return { value: func(strings, ...expressions) }
709 TemplateLiteral(node, initialScope) {
710 const expressions = getElementValues(node.expressions, initialScope);
711 if (expressions != null) {
712 let value = node.quasis[0].value.cooked;
713 for (let i = 0; i < expressions.length; ++i) {
714 value += expressions[i];
715 value += node.quasis[i + 1].value.cooked;
722 UnaryExpression(node, initialScope) {
723 if (node.operator === "delete") {
727 if (node.operator === "void") {
728 return { value: undefined }
731 const arg = getStaticValueR(node.argument, initialScope);
733 switch (node.operator) {
735 return { value: -arg.value }
737 return { value: +arg.value } //eslint-disable-line no-implicit-coercion
739 return { value: !arg.value }
741 return { value: ~arg.value }
743 return { value: typeof arg.value }
754 * Get the value of a given node if it's a static value.
755 * @param {Node} node The node to get.
756 * @param {Scope|undefined} initialScope The scope to start finding variable.
757 * @returns {{value:any}|{value:undefined,optional?:true}|null} The static value of the node, or `null`.
759 function getStaticValueR(node, initialScope) {
760 if (node != null && Object.hasOwnProperty.call(operations, node.type)) {
761 return operations[node.type](node, initialScope)
767 * Get the value of a given node if it's a static value.
768 * @param {Node} node The node to get.
769 * @param {Scope} [initialScope] The scope to start finding variable. Optional. If this scope was given, this tries to resolve identifier references which are in the given node as much as possible.
770 * @returns {{value:any}|{value:undefined,optional?:true}|null} The static value of the node, or `null`.
772 function getStaticValue(node, initialScope = null) {
774 return getStaticValueR(node, initialScope)
781 * Get the value of a given node if it's a literal or a template literal.
782 * @param {Node} node The node to get.
783 * @param {Scope} [initialScope] The scope to start finding variable. Optional. If the node is an Identifier node and this scope was given, this checks the variable of the identifier, and returns the value of it if the variable is a constant.
784 * @returns {string|null} The value of the node, or `null`.
786 function getStringIfConstant(node, initialScope = null) {
787 // Handle the literals that the platform doesn't support natively.
788 if (node && node.type === "Literal" && node.value === null) {
790 return `/${node.regex.pattern}/${node.regex.flags}`
797 const evaluated = getStaticValue(node, initialScope);
798 return evaluated && String(evaluated.value)
802 * Get the property name from a MemberExpression node or a Property node.
803 * @param {Node} node The node to get.
804 * @param {Scope} [initialScope] The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it.
805 * @returns {string|null} The property name of the node.
807 function getPropertyName(node, initialScope) {
809 case "MemberExpression":
811 return getStringIfConstant(node.property, initialScope)
813 return node.property.name
816 case "MethodDefinition":
818 return getStringIfConstant(node.key, initialScope)
820 if (node.key.type === "Literal") {
821 return String(node.key.value)
832 * Get the name and kind of the given function node.
833 * @param {ASTNode} node - The function node to get.
834 * @returns {string} The name and kind of the function node.
836 function getFunctionNameWithKind(node) {
837 const parent = node.parent;
840 if (parent.type === "MethodDefinition" && parent.static) {
841 tokens.push("static");
844 tokens.push("async");
846 if (node.generator) {
847 tokens.push("generator");
850 if (node.type === "ArrowFunctionExpression") {
851 tokens.push("arrow", "function");
853 parent.type === "Property" ||
854 parent.type === "MethodDefinition"
856 if (parent.kind === "constructor") {
859 if (parent.kind === "get") {
860 tokens.push("getter");
861 } else if (parent.kind === "set") {
862 tokens.push("setter");
864 tokens.push("method");
867 tokens.push("function");
871 tokens.push(`'${node.id.name}'`);
873 const name = getPropertyName(parent);
876 tokens.push(`'${name}'`);
880 if (node.type === "ArrowFunctionExpression") {
882 parent.type === "VariableDeclarator" &&
884 parent.id.type === "Identifier"
886 tokens.push(`'${parent.id.name}'`);
889 parent.type === "AssignmentExpression" &&
891 parent.left.type === "Identifier"
893 tokens.push(`'${parent.left.name}'`);
897 return tokens.join(" ")
900 const typeConversionBinaryOps = Object.freeze(
922 const typeConversionUnaryOps = Object.freeze(new Set(["-", "+", "!", "~"]));
925 * Check whether the given value is an ASTNode or not.
926 * @param {any} x The value to check.
927 * @returns {boolean} `true` if the value is an ASTNode.
930 return x !== null && typeof x === "object" && typeof x.type === "string"
933 const visitor = Object.freeze(
934 Object.assign(Object.create(null), {
935 $visit(node, options, visitorKeys) {
936 const { type } = node;
938 if (typeof this[type] === "function") {
939 return this[type](node, options, visitorKeys)
942 return this.$visitChildren(node, options, visitorKeys)
945 $visitChildren(node, options, visitorKeys) {
946 const { type } = node;
948 for (const key of visitorKeys[type] || evk.getKeys(node)) {
949 const value = node[key];
951 if (Array.isArray(value)) {
952 for (const element of value) {
955 this.$visit(element, options, visitorKeys)
962 this.$visit(value, options, visitorKeys)
971 ArrowFunctionExpression() {
974 AssignmentExpression() {
980 BinaryExpression(node, options, visitorKeys) {
982 options.considerImplicitTypeConversion &&
983 typeConversionBinaryOps.has(node.operator) &&
984 (node.left.type !== "Literal" || node.right.type !== "Literal")
988 return this.$visitChildren(node, options, visitorKeys)
993 FunctionExpression() {
999 MemberExpression(node, options, visitorKeys) {
1000 if (options.considerGetters) {
1004 options.considerImplicitTypeConversion &&
1006 node.property.type !== "Literal"
1010 return this.$visitChildren(node, options, visitorKeys)
1012 MethodDefinition(node, options, visitorKeys) {
1014 options.considerImplicitTypeConversion &&
1016 node.key.type !== "Literal"
1020 return this.$visitChildren(node, options, visitorKeys)
1025 Property(node, options, visitorKeys) {
1027 options.considerImplicitTypeConversion &&
1029 node.key.type !== "Literal"
1033 return this.$visitChildren(node, options, visitorKeys)
1035 UnaryExpression(node, options, visitorKeys) {
1036 if (node.operator === "delete") {
1040 options.considerImplicitTypeConversion &&
1041 typeConversionUnaryOps.has(node.operator) &&
1042 node.argument.type !== "Literal"
1046 return this.$visitChildren(node, options, visitorKeys)
1048 UpdateExpression() {
1058 * Check whether a given node has any side effect or not.
1059 * @param {Node} node The node to get.
1060 * @param {SourceCode} sourceCode The source code object.
1061 * @param {object} [options] The option object.
1062 * @param {boolean} [options.considerGetters=false] If `true` then it considers member accesses as the node which has side effects.
1063 * @param {boolean} [options.considerImplicitTypeConversion=false] If `true` then it considers implicit type conversion as the node which has side effects.
1064 * @param {object} [options.visitorKeys=evk.KEYS] The keys to traverse nodes. Use `context.getSourceCode().visitorKeys`.
1065 * @returns {boolean} `true` if the node has a certain side effect.
1067 function hasSideEffect(
1070 { considerGetters = false, considerImplicitTypeConversion = false } = {}
1072 return visitor.$visit(
1074 { considerGetters, considerImplicitTypeConversion },
1075 sourceCode.visitorKeys || evk.KEYS
1080 * Get the left parenthesis of the parent node syntax if it exists.
1081 * E.g., `if (a) {}` then the `(`.
1082 * @param {Node} node The AST node to check.
1083 * @param {SourceCode} sourceCode The source code object to get tokens.
1084 * @returns {Token|null} The left parenthesis of the parent node syntax
1086 function getParentSyntaxParen(node, sourceCode) {
1087 const parent = node.parent;
1089 switch (parent.type) {
1090 case "CallExpression":
1091 case "NewExpression":
1092 if (parent.arguments.length === 1 && parent.arguments[0] === node) {
1093 return sourceCode.getTokenAfter(
1100 case "DoWhileStatement":
1101 if (parent.test === node) {
1102 return sourceCode.getTokenAfter(
1110 case "WhileStatement":
1111 if (parent.test === node) {
1112 return sourceCode.getFirstToken(parent, 1)
1116 case "ImportExpression":
1117 if (parent.source === node) {
1118 return sourceCode.getFirstToken(parent, 1)
1122 case "SwitchStatement":
1123 if (parent.discriminant === node) {
1124 return sourceCode.getFirstToken(parent, 1)
1128 case "WithStatement":
1129 if (parent.object === node) {
1130 return sourceCode.getFirstToken(parent, 1)
1140 * Check whether a given node is parenthesized or not.
1141 * @param {number} times The number of parantheses.
1142 * @param {Node} node The AST node to check.
1143 * @param {SourceCode} sourceCode The source code object to get tokens.
1144 * @returns {boolean} `true` if the node is parenthesized the given times.
1147 * Check whether a given node is parenthesized or not.
1148 * @param {Node} node The AST node to check.
1149 * @param {SourceCode} sourceCode The source code object to get tokens.
1150 * @returns {boolean} `true` if the node is parenthesized.
1152 function isParenthesized(
1157 let times, node, sourceCode, maybeLeftParen, maybeRightParen;
1158 if (typeof timesOrNode === "number") {
1159 times = timesOrNode | 0;
1160 node = nodeOrSourceCode;
1161 sourceCode = optionalSourceCode;
1162 if (!(times >= 1)) {
1163 throw new TypeError("'times' should be a positive integer.")
1168 sourceCode = nodeOrSourceCode;
1175 maybeLeftParen = maybeRightParen = node;
1177 maybeLeftParen = sourceCode.getTokenBefore(maybeLeftParen);
1178 maybeRightParen = sourceCode.getTokenAfter(maybeRightParen);
1180 maybeLeftParen != null &&
1181 maybeRightParen != null &&
1182 isOpeningParenToken(maybeLeftParen) &&
1183 isClosingParenToken(maybeRightParen) &&
1184 // Avoid false positive such as `if (a) {}`
1185 maybeLeftParen !== getParentSyntaxParen(node, sourceCode) &&
1193 * @author Toru Nagashima <https://github.com/mysticatea>
1194 * See LICENSE file in root directory for full license.
1197 const placeholder = /\$(?:[$&`']|[1-9][0-9]?)/gu;
1199 /** @type {WeakMap<PatternMatcher, {pattern:RegExp,escaped:boolean}>} */
1200 const internal = new WeakMap();
1203 * Check whether a given character is escaped or not.
1204 * @param {string} str The string to check.
1205 * @param {number} index The location of the character to check.
1206 * @returns {boolean} `true` if the character is escaped.
1208 function isEscaped(str, index) {
1209 let escaped = false;
1210 for (let i = index - 1; i >= 0 && str.charCodeAt(i) === 0x5c; --i) {
1217 * Replace a given string by a given matcher.
1218 * @param {PatternMatcher} matcher The pattern matcher.
1219 * @param {string} str The string to be replaced.
1220 * @param {string} replacement The new substring to replace each matched part.
1221 * @returns {string} The replaced string.
1223 function replaceS(matcher, str, replacement) {
1227 /** @type {RegExpExecArray} */
1231 * @param {string} key The placeholder.
1232 * @returns {string} The replaced string.
1234 function replacer(key) {
1241 return str.slice(0, match.index)
1243 return str.slice(match.index + match[0].length)
1245 const i = key.slice(1);
1254 for (match of matcher.execAll(str)) {
1255 chunks.push(str.slice(index, match.index));
1256 chunks.push(replacement.replace(placeholder, replacer));
1257 index = match.index + match[0].length;
1259 chunks.push(str.slice(index));
1261 return chunks.join("")
1265 * Replace a given string by a given matcher.
1266 * @param {PatternMatcher} matcher The pattern matcher.
1267 * @param {string} str The string to be replaced.
1268 * @param {(...strs[])=>string} replace The function to replace each matched part.
1269 * @returns {string} The replaced string.
1271 function replaceF(matcher, str, replace) {
1275 for (const match of matcher.execAll(str)) {
1276 chunks.push(str.slice(index, match.index));
1277 chunks.push(String(replace(...match, match.index, match.input)));
1278 index = match.index + match[0].length;
1280 chunks.push(str.slice(index));
1282 return chunks.join("")
1286 * The class to find patterns as considering escape sequences.
1288 class PatternMatcher {
1290 * Initialize this matcher.
1291 * @param {RegExp} pattern The pattern to match.
1292 * @param {{escaped:boolean}} options The options.
1294 constructor(pattern, { escaped = false } = {}) {
1295 if (!(pattern instanceof RegExp)) {
1296 throw new TypeError("'pattern' should be a RegExp instance.")
1298 if (!pattern.flags.includes("g")) {
1299 throw new Error("'pattern' should contains 'g' flag.")
1302 internal.set(this, {
1303 pattern: new RegExp(pattern.source, pattern.flags),
1304 escaped: Boolean(escaped),
1309 * Find the pattern in a given string.
1310 * @param {string} str The string to find.
1311 * @returns {IterableIterator<RegExpExecArray>} The iterator which iterate the matched information.
1314 const { pattern, escaped } = internal.get(this);
1318 pattern.lastIndex = 0;
1319 while ((match = pattern.exec(str)) != null) {
1320 if (escaped || !isEscaped(str, match.index)) {
1321 lastIndex = pattern.lastIndex;
1323 pattern.lastIndex = lastIndex;
1329 * Check whether the pattern is found in a given string.
1330 * @param {string} str The string to check.
1331 * @returns {boolean} `true` if the pattern was found in the string.
1334 const it = this.execAll(str);
1335 const ret = it.next();
1340 * Replace a given string.
1341 * @param {string} str The string to be replaced.
1342 * @param {(string|((...strs:string[])=>string))} replacer The string or function to replace. This is the same as the 2nd argument of `String.prototype.replace`.
1343 * @returns {string} The replaced string.
1345 [Symbol.replace](str, replacer) {
1346 return typeof replacer === "function"
1347 ? replaceF(this, String(str), replacer)
1348 : replaceS(this, String(str), String(replacer))
1352 const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
1353 const has = Function.call.bind(Object.hasOwnProperty);
1355 const READ = Symbol("read");
1356 const CALL = Symbol("call");
1357 const CONSTRUCT = Symbol("construct");
1358 const ESM = Symbol("esm");
1360 const requireCall = { require: { [CALL]: true } };
1363 * Check whether a given variable is modified or not.
1364 * @param {Variable} variable The variable to check.
1365 * @returns {boolean} `true` if the variable is modified.
1367 function isModifiedGlobal(variable) {
1370 variable.defs.length !== 0 ||
1371 variable.references.some(r => r.isWrite())
1376 * Check if the value of a given node is passed through to the parent syntax as-is.
1377 * For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through.
1378 * @param {Node} node A node to check.
1379 * @returns {boolean} `true` if the node is passed through.
1381 function isPassThrough(node) {
1382 const parent = node.parent;
1384 switch (parent && parent.type) {
1385 case "ConditionalExpression":
1386 return parent.consequent === node || parent.alternate === node
1387 case "LogicalExpression":
1389 case "SequenceExpression":
1390 return parent.expressions[parent.expressions.length - 1] === node
1391 case "ChainExpression":
1400 * The reference tracker.
1402 class ReferenceTracker {
1404 * Initialize this tracker.
1405 * @param {Scope} globalScope The global scope.
1406 * @param {object} [options] The options.
1407 * @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules.
1408 * @param {string[]} [options.globalObjectNames=["global","globalThis","self","window"]] The variable names for Global Object.
1414 globalObjectNames = ["global", "globalThis", "self", "window"],
1417 this.variableStack = [];
1418 this.globalScope = globalScope;
1420 this.globalObjectNames = globalObjectNames.slice(0);
1424 * Iterate the references of global variables.
1425 * @param {object} traceMap The trace map.
1426 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1428 *iterateGlobalReferences(traceMap) {
1429 for (const key of Object.keys(traceMap)) {
1430 const nextTraceMap = traceMap[key];
1432 const variable = this.globalScope.set.get(key);
1434 if (isModifiedGlobal(variable)) {
1438 yield* this._iterateVariableReferences(
1446 for (const key of this.globalObjectNames) {
1448 const variable = this.globalScope.set.get(key);
1450 if (isModifiedGlobal(variable)) {
1454 yield* this._iterateVariableReferences(
1464 * Iterate the references of CommonJS modules.
1465 * @param {object} traceMap The trace map.
1466 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1468 *iterateCjsReferences(traceMap) {
1469 for (const { node } of this.iterateGlobalReferences(requireCall)) {
1470 const key = getStringIfConstant(node.arguments[0]);
1471 if (key == null || !has(traceMap, key)) {
1475 const nextTraceMap = traceMap[key];
1478 if (nextTraceMap[READ]) {
1483 info: nextTraceMap[READ],
1486 yield* this._iteratePropertyReferences(node, path, nextTraceMap);
1491 * Iterate the references of ES modules.
1492 * @param {object} traceMap The trace map.
1493 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1495 *iterateEsmReferences(traceMap) {
1496 const programNode = this.globalScope.block;
1498 for (const node of programNode.body) {
1499 if (!IMPORT_TYPE.test(node.type) || node.source == null) {
1502 const moduleId = node.source.value;
1504 if (!has(traceMap, moduleId)) {
1507 const nextTraceMap = traceMap[moduleId];
1508 const path = [moduleId];
1510 if (nextTraceMap[READ]) {
1511 yield { node, path, type: READ, info: nextTraceMap[READ] };
1514 if (node.type === "ExportAllDeclaration") {
1515 for (const key of Object.keys(nextTraceMap)) {
1516 const exportTraceMap = nextTraceMap[key];
1517 if (exportTraceMap[READ]) {
1520 path: path.concat(key),
1522 info: exportTraceMap[READ],
1527 for (const specifier of node.specifiers) {
1528 const esm = has(nextTraceMap, ESM);
1529 const it = this._iterateImportReferences(
1534 : this.mode === "legacy"
1536 { default: nextTraceMap },
1539 : { default: nextTraceMap }
1545 for (const report of it) {
1546 report.path = report.path.filter(exceptDefault);
1548 report.path.length >= 2 ||
1549 report.type !== READ
1561 * Iterate the references for a given variable.
1562 * @param {Variable} variable The variable to iterate that references.
1563 * @param {string[]} path The current path.
1564 * @param {object} traceMap The trace map.
1565 * @param {boolean} shouldReport = The flag to report those references.
1566 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1568 *_iterateVariableReferences(variable, path, traceMap, shouldReport) {
1569 if (this.variableStack.includes(variable)) {
1572 this.variableStack.push(variable);
1574 for (const reference of variable.references) {
1575 if (!reference.isRead()) {
1578 const node = reference.identifier;
1580 if (shouldReport && traceMap[READ]) {
1581 yield { node, path, type: READ, info: traceMap[READ] };
1583 yield* this._iteratePropertyReferences(node, path, traceMap);
1586 this.variableStack.pop();
1591 * Iterate the references for a given AST node.
1592 * @param rootNode The AST node to iterate references.
1593 * @param {string[]} path The current path.
1594 * @param {object} traceMap The trace map.
1595 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1597 //eslint-disable-next-line complexity
1598 *_iteratePropertyReferences(rootNode, path, traceMap) {
1599 let node = rootNode;
1600 while (isPassThrough(node)) {
1604 const parent = node.parent;
1605 if (parent.type === "MemberExpression") {
1606 if (parent.object === node) {
1607 const key = getPropertyName(parent);
1608 if (key == null || !has(traceMap, key)) {
1612 path = path.concat(key); //eslint-disable-line no-param-reassign
1613 const nextTraceMap = traceMap[key];
1614 if (nextTraceMap[READ]) {
1619 info: nextTraceMap[READ],
1622 yield* this._iteratePropertyReferences(
1630 if (parent.type === "CallExpression") {
1631 if (parent.callee === node && traceMap[CALL]) {
1632 yield { node: parent, path, type: CALL, info: traceMap[CALL] };
1636 if (parent.type === "NewExpression") {
1637 if (parent.callee === node && traceMap[CONSTRUCT]) {
1642 info: traceMap[CONSTRUCT],
1647 if (parent.type === "AssignmentExpression") {
1648 if (parent.right === node) {
1649 yield* this._iterateLhsReferences(parent.left, path, traceMap);
1650 yield* this._iteratePropertyReferences(parent, path, traceMap);
1654 if (parent.type === "AssignmentPattern") {
1655 if (parent.right === node) {
1656 yield* this._iterateLhsReferences(parent.left, path, traceMap);
1660 if (parent.type === "VariableDeclarator") {
1661 if (parent.init === node) {
1662 yield* this._iterateLhsReferences(parent.id, path, traceMap);
1668 * Iterate the references for a given Pattern node.
1669 * @param {Node} patternNode The Pattern node to iterate references.
1670 * @param {string[]} path The current path.
1671 * @param {object} traceMap The trace map.
1672 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1674 *_iterateLhsReferences(patternNode, path, traceMap) {
1675 if (patternNode.type === "Identifier") {
1676 const variable = findVariable(this.globalScope, patternNode);
1677 if (variable != null) {
1678 yield* this._iterateVariableReferences(
1687 if (patternNode.type === "ObjectPattern") {
1688 for (const property of patternNode.properties) {
1689 const key = getPropertyName(property);
1691 if (key == null || !has(traceMap, key)) {
1695 const nextPath = path.concat(key);
1696 const nextTraceMap = traceMap[key];
1697 if (nextTraceMap[READ]) {
1702 info: nextTraceMap[READ],
1705 yield* this._iterateLhsReferences(
1713 if (patternNode.type === "AssignmentPattern") {
1714 yield* this._iterateLhsReferences(patternNode.left, path, traceMap);
1719 * Iterate the references for a given ModuleSpecifier node.
1720 * @param {Node} specifierNode The ModuleSpecifier node to iterate references.
1721 * @param {string[]} path The current path.
1722 * @param {object} traceMap The trace map.
1723 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1725 *_iterateImportReferences(specifierNode, path, traceMap) {
1726 const type = specifierNode.type;
1728 if (type === "ImportSpecifier" || type === "ImportDefaultSpecifier") {
1730 type === "ImportDefaultSpecifier"
1732 : specifierNode.imported.name;
1733 if (!has(traceMap, key)) {
1737 path = path.concat(key); //eslint-disable-line no-param-reassign
1738 const nextTraceMap = traceMap[key];
1739 if (nextTraceMap[READ]) {
1741 node: specifierNode,
1744 info: nextTraceMap[READ],
1747 yield* this._iterateVariableReferences(
1748 findVariable(this.globalScope, specifierNode.local),
1757 if (type === "ImportNamespaceSpecifier") {
1758 yield* this._iterateVariableReferences(
1759 findVariable(this.globalScope, specifierNode.local),
1767 if (type === "ExportSpecifier") {
1768 const key = specifierNode.local.name;
1769 if (!has(traceMap, key)) {
1773 path = path.concat(key); //eslint-disable-line no-param-reassign
1774 const nextTraceMap = traceMap[key];
1775 if (nextTraceMap[READ]) {
1777 node: specifierNode,
1780 info: nextTraceMap[READ],
1787 ReferenceTracker.READ = READ;
1788 ReferenceTracker.CALL = CALL;
1789 ReferenceTracker.CONSTRUCT = CONSTRUCT;
1790 ReferenceTracker.ESM = ESM;
1793 * This is a predicate function for Array#filter.
1794 * @param {string} name A name part.
1795 * @param {number} index The index of the name.
1796 * @returns {boolean} `false` if it's default.
1798 function exceptDefault(name, index) {
1799 return !(index === 1 && name === "default")
1807 getFunctionHeadLocation,
1808 getFunctionNameWithKind,
1812 getStringIfConstant,
1815 isClosingBraceToken,
1816 isClosingBracketToken,
1817 isClosingParenToken,
1822 isNotClosingBraceToken,
1823 isNotClosingBracketToken,
1824 isNotClosingParenToken,
1828 isNotOpeningBraceToken,
1829 isNotOpeningBracketToken,
1830 isNotOpeningParenToken,
1831 isNotSemicolonToken,
1832 isOpeningBraceToken,
1833 isOpeningBracketToken,
1834 isOpeningParenToken,
1842 exports.CALL = CALL;
1843 exports.CONSTRUCT = CONSTRUCT;
1845 exports.PatternMatcher = PatternMatcher;
1846 exports.READ = READ;
1847 exports.ReferenceTracker = ReferenceTracker;
1848 exports.default = index;
1849 exports.findVariable = findVariable;
1850 exports.getFunctionHeadLocation = getFunctionHeadLocation;
1851 exports.getFunctionNameWithKind = getFunctionNameWithKind;
1852 exports.getInnermostScope = getInnermostScope;
1853 exports.getPropertyName = getPropertyName;
1854 exports.getStaticValue = getStaticValue;
1855 exports.getStringIfConstant = getStringIfConstant;
1856 exports.hasSideEffect = hasSideEffect;
1857 exports.isArrowToken = isArrowToken;
1858 exports.isClosingBraceToken = isClosingBraceToken;
1859 exports.isClosingBracketToken = isClosingBracketToken;
1860 exports.isClosingParenToken = isClosingParenToken;
1861 exports.isColonToken = isColonToken;
1862 exports.isCommaToken = isCommaToken;
1863 exports.isCommentToken = isCommentToken;
1864 exports.isNotArrowToken = isNotArrowToken;
1865 exports.isNotClosingBraceToken = isNotClosingBraceToken;
1866 exports.isNotClosingBracketToken = isNotClosingBracketToken;
1867 exports.isNotClosingParenToken = isNotClosingParenToken;
1868 exports.isNotColonToken = isNotColonToken;
1869 exports.isNotCommaToken = isNotCommaToken;
1870 exports.isNotCommentToken = isNotCommentToken;
1871 exports.isNotOpeningBraceToken = isNotOpeningBraceToken;
1872 exports.isNotOpeningBracketToken = isNotOpeningBracketToken;
1873 exports.isNotOpeningParenToken = isNotOpeningParenToken;
1874 exports.isNotSemicolonToken = isNotSemicolonToken;
1875 exports.isOpeningBraceToken = isOpeningBraceToken;
1876 exports.isOpeningBracketToken = isOpeningBracketToken;
1877 exports.isOpeningParenToken = isOpeningParenToken;
1878 exports.isParenthesized = isParenthesized;
1879 exports.isSemicolonToken = isSemicolonToken;
1880 //# sourceMappingURL=index.js.map