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 const property = calleeNode.computed
495 ? getStaticValueR(calleeNode.property, initialScope)
496 : { value: calleeNode.property.name };
498 if (object != null && property != null) {
499 const receiver = object.value;
500 const methodName = property.value;
501 if (callAllowed.has(receiver[methodName])) {
502 return { value: receiver[methodName](...args) }
504 if (callPassThrough.has(receiver[methodName])) {
505 return { value: args[0] }
509 const callee = getStaticValueR(calleeNode, initialScope);
510 if (callee != null) {
511 const func = callee.value;
512 if (callAllowed.has(func)) {
513 return { value: func(...args) }
515 if (callPassThrough.has(func)) {
516 return { value: args[0] }
525 ConditionalExpression(node, initialScope) {
526 const test = getStaticValueR(node.test, initialScope);
529 ? getStaticValueR(node.consequent, initialScope)
530 : getStaticValueR(node.alternate, initialScope)
535 ExpressionStatement(node, initialScope) {
536 return getStaticValueR(node.expression, initialScope)
539 Identifier(node, initialScope) {
540 if (initialScope != null) {
541 const variable = findVariable(initialScope, node);
546 variable.defs.length === 0 &&
547 builtinNames.has(variable.name) &&
548 variable.name in globalObject
550 return { value: globalObject[variable.name] }
554 if (variable != null && variable.defs.length === 1) {
555 const def = variable.defs[0];
558 def.parent.kind === "const" &&
559 // TODO(mysticatea): don't support destructuring here.
560 def.node.id.type === "Identifier"
562 return getStaticValueR(def.node.init, initialScope)
570 //istanbul ignore if : this is implementation-specific behavior.
571 if ((node.regex != null || node.bigint != null) && node.value == null) {
572 // It was a RegExp/BigInt literal, but Node.js didn't support it.
575 return { value: node.value }
578 LogicalExpression(node, initialScope) {
579 const left = getStaticValueR(node.left, initialScope);
582 (node.operator === "||" && Boolean(left.value) === true) ||
583 (node.operator === "&&" && Boolean(left.value) === false)
588 const right = getStaticValueR(node.right, initialScope);
597 MemberExpression(node, initialScope) {
598 const object = getStaticValueR(node.object, initialScope);
599 const property = node.computed
600 ? getStaticValueR(node.property, initialScope)
601 : { value: node.property.name };
606 !isGetter(object.value, property.value)
608 return { value: object.value[property.value] }
613 NewExpression(node, initialScope) {
614 const callee = getStaticValueR(node.callee, initialScope);
615 const args = getElementValues(node.arguments, initialScope);
617 if (callee != null && args != null) {
618 const Func = callee.value;
619 if (callAllowed.has(Func)) {
620 return { value: new Func(...args) }
627 ObjectExpression(node, initialScope) {
630 for (const propertyNode of node.properties) {
631 if (propertyNode.type === "Property") {
632 if (propertyNode.kind !== "init") {
635 const key = propertyNode.computed
636 ? getStaticValueR(propertyNode.key, initialScope)
637 : { value: propertyNode.key.name };
638 const value = getStaticValueR(propertyNode.value, initialScope);
639 if (key == null || value == null) {
642 object[key.value] = value.value;
644 propertyNode.type === "SpreadElement" ||
645 propertyNode.type === "ExperimentalSpreadProperty"
647 const argument = getStaticValueR(
648 propertyNode.argument,
651 if (argument == null) {
654 Object.assign(object, argument.value);
660 return { value: object }
663 SequenceExpression(node, initialScope) {
664 const last = node.expressions[node.expressions.length - 1];
665 return getStaticValueR(last, initialScope)
668 TaggedTemplateExpression(node, initialScope) {
669 const tag = getStaticValueR(node.tag, initialScope);
670 const expressions = getElementValues(
671 node.quasi.expressions,
675 if (tag != null && expressions != null) {
676 const func = tag.value;
677 const strings = node.quasi.quasis.map(q => q.value.cooked);
678 strings.raw = node.quasi.quasis.map(q => q.value.raw);
680 if (func === String.raw) {
681 return { value: func(strings, ...expressions) }
688 TemplateLiteral(node, initialScope) {
689 const expressions = getElementValues(node.expressions, initialScope);
690 if (expressions != null) {
691 let value = node.quasis[0].value.cooked;
692 for (let i = 0; i < expressions.length; ++i) {
693 value += expressions[i];
694 value += node.quasis[i + 1].value.cooked;
701 UnaryExpression(node, initialScope) {
702 if (node.operator === "delete") {
706 if (node.operator === "void") {
707 return { value: undefined }
710 const arg = getStaticValueR(node.argument, initialScope);
712 switch (node.operator) {
714 return { value: -arg.value }
716 return { value: +arg.value } //eslint-disable-line no-implicit-coercion
718 return { value: !arg.value }
720 return { value: ~arg.value }
722 return { value: typeof arg.value }
733 * Get the value of a given node if it's a static value.
734 * @param {Node} node The node to get.
735 * @param {Scope|undefined} initialScope The scope to start finding variable.
736 * @returns {{value:any}|null} The static value of the node, or `null`.
738 function getStaticValueR(node, initialScope) {
739 if (node != null && Object.hasOwnProperty.call(operations, node.type)) {
740 return operations[node.type](node, initialScope)
746 * Get the value of a given node if it's a static value.
747 * @param {Node} node The node to get.
748 * @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.
749 * @returns {{value:any}|null} The static value of the node, or `null`.
751 function getStaticValue(node, initialScope = null) {
753 return getStaticValueR(node, initialScope)
760 * Get the value of a given node if it's a literal or a template literal.
761 * @param {Node} node The node to get.
762 * @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.
763 * @returns {string|null} The value of the node, or `null`.
765 function getStringIfConstant(node, initialScope = null) {
766 // Handle the literals that the platform doesn't support natively.
767 if (node && node.type === "Literal" && node.value === null) {
769 return `/${node.regex.pattern}/${node.regex.flags}`
776 const evaluated = getStaticValue(node, initialScope);
777 return evaluated && String(evaluated.value)
781 * Get the property name from a MemberExpression node or a Property node.
782 * @param {Node} node The node to get.
783 * @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.
784 * @returns {string|null} The property name of the node.
786 function getPropertyName(node, initialScope) {
788 case "MemberExpression":
790 return getStringIfConstant(node.property, initialScope)
792 return node.property.name
795 case "MethodDefinition":
797 return getStringIfConstant(node.key, initialScope)
799 if (node.key.type === "Literal") {
800 return String(node.key.value)
811 * Get the name and kind of the given function node.
812 * @param {ASTNode} node - The function node to get.
813 * @returns {string} The name and kind of the function node.
815 function getFunctionNameWithKind(node) {
816 const parent = node.parent;
819 if (parent.type === "MethodDefinition" && parent.static) {
820 tokens.push("static");
823 tokens.push("async");
825 if (node.generator) {
826 tokens.push("generator");
829 if (node.type === "ArrowFunctionExpression") {
830 tokens.push("arrow", "function");
832 parent.type === "Property" ||
833 parent.type === "MethodDefinition"
835 if (parent.kind === "constructor") {
838 if (parent.kind === "get") {
839 tokens.push("getter");
840 } else if (parent.kind === "set") {
841 tokens.push("setter");
843 tokens.push("method");
846 tokens.push("function");
850 tokens.push(`'${node.id.name}'`);
852 const name = getPropertyName(parent);
855 tokens.push(`'${name}'`);
859 return tokens.join(" ")
862 const typeConversionBinaryOps = Object.freeze(
884 const typeConversionUnaryOps = Object.freeze(new Set(["-", "+", "!", "~"]));
885 const visitor = Object.freeze(
886 Object.assign(Object.create(null), {
887 $visit(node, options, visitorKeys) {
888 const { type } = node;
890 if (typeof this[type] === "function") {
891 return this[type](node, options, visitorKeys)
894 return this.$visitChildren(node, options, visitorKeys)
897 $visitChildren(node, options, visitorKeys) {
898 const { type } = node;
900 for (const key of visitorKeys[type] || evk.getKeys(node)) {
901 const value = node[key];
903 if (Array.isArray(value)) {
904 for (const element of value) {
907 this.$visit(element, options, visitorKeys)
912 } else if (value && this.$visit(value, options, visitorKeys)) {
920 ArrowFunctionExpression() {
923 AssignmentExpression() {
929 BinaryExpression(node, options, visitorKeys) {
931 options.considerImplicitTypeConversion &&
932 typeConversionBinaryOps.has(node.operator) &&
933 (node.left.type !== "Literal" || node.right.type !== "Literal")
937 return this.$visitChildren(node, options, visitorKeys)
942 FunctionExpression() {
948 MemberExpression(node, options, visitorKeys) {
949 if (options.considerGetters) {
953 options.considerImplicitTypeConversion &&
955 node.property.type !== "Literal"
959 return this.$visitChildren(node, options, visitorKeys)
961 MethodDefinition(node, options, visitorKeys) {
963 options.considerImplicitTypeConversion &&
965 node.key.type !== "Literal"
969 return this.$visitChildren(node, options, visitorKeys)
974 Property(node, options, visitorKeys) {
976 options.considerImplicitTypeConversion &&
978 node.key.type !== "Literal"
982 return this.$visitChildren(node, options, visitorKeys)
984 UnaryExpression(node, options, visitorKeys) {
985 if (node.operator === "delete") {
989 options.considerImplicitTypeConversion &&
990 typeConversionUnaryOps.has(node.operator) &&
991 node.argument.type !== "Literal"
995 return this.$visitChildren(node, options, visitorKeys)
1007 * Check whether a given node has any side effect or not.
1008 * @param {Node} node The node to get.
1009 * @param {SourceCode} sourceCode The source code object.
1010 * @param {object} [options] The option object.
1011 * @param {boolean} [options.considerGetters=false] If `true` then it considers member accesses as the node which has side effects.
1012 * @param {boolean} [options.considerImplicitTypeConversion=false] If `true` then it considers implicit type conversion as the node which has side effects.
1013 * @param {object} [options.visitorKeys=evk.KEYS] The keys to traverse nodes. Use `context.getSourceCode().visitorKeys`.
1014 * @returns {boolean} `true` if the node has a certain side effect.
1016 function hasSideEffect(
1019 { considerGetters = false, considerImplicitTypeConversion = false } = {}
1021 return visitor.$visit(
1023 { considerGetters, considerImplicitTypeConversion },
1024 sourceCode.visitorKeys || evk.KEYS
1029 * Get the left parenthesis of the parent node syntax if it exists.
1030 * E.g., `if (a) {}` then the `(`.
1031 * @param {Node} node The AST node to check.
1032 * @param {SourceCode} sourceCode The source code object to get tokens.
1033 * @returns {Token|null} The left parenthesis of the parent node syntax
1035 function getParentSyntaxParen(node, sourceCode) {
1036 const parent = node.parent;
1038 switch (parent.type) {
1039 case "CallExpression":
1040 case "NewExpression":
1041 if (parent.arguments.length === 1 && parent.arguments[0] === node) {
1042 return sourceCode.getTokenAfter(
1049 case "DoWhileStatement":
1050 if (parent.test === node) {
1051 return sourceCode.getTokenAfter(
1059 case "WhileStatement":
1060 if (parent.test === node) {
1061 return sourceCode.getFirstToken(parent, 1)
1065 case "ImportExpression":
1066 if (parent.source === node) {
1067 return sourceCode.getFirstToken(parent, 1)
1071 case "SwitchStatement":
1072 if (parent.discriminant === node) {
1073 return sourceCode.getFirstToken(parent, 1)
1077 case "WithStatement":
1078 if (parent.object === node) {
1079 return sourceCode.getFirstToken(parent, 1)
1089 * Check whether a given node is parenthesized or not.
1090 * @param {number} times The number of parantheses.
1091 * @param {Node} node The AST node to check.
1092 * @param {SourceCode} sourceCode The source code object to get tokens.
1093 * @returns {boolean} `true` if the node is parenthesized the given times.
1096 * Check whether a given node is parenthesized or not.
1097 * @param {Node} node The AST node to check.
1098 * @param {SourceCode} sourceCode The source code object to get tokens.
1099 * @returns {boolean} `true` if the node is parenthesized.
1101 function isParenthesized(
1106 let times, node, sourceCode, maybeLeftParen, maybeRightParen;
1107 if (typeof timesOrNode === "number") {
1108 times = timesOrNode | 0;
1109 node = nodeOrSourceCode;
1110 sourceCode = optionalSourceCode;
1111 if (!(times >= 1)) {
1112 throw new TypeError("'times' should be a positive integer.")
1117 sourceCode = nodeOrSourceCode;
1124 maybeLeftParen = maybeRightParen = node;
1126 maybeLeftParen = sourceCode.getTokenBefore(maybeLeftParen);
1127 maybeRightParen = sourceCode.getTokenAfter(maybeRightParen);
1129 maybeLeftParen != null &&
1130 maybeRightParen != null &&
1131 isOpeningParenToken(maybeLeftParen) &&
1132 isClosingParenToken(maybeRightParen) &&
1133 // Avoid false positive such as `if (a) {}`
1134 maybeLeftParen !== getParentSyntaxParen(node, sourceCode) &&
1142 * @author Toru Nagashima <https://github.com/mysticatea>
1143 * See LICENSE file in root directory for full license.
1146 const placeholder = /\$(?:[$&`']|[1-9][0-9]?)/gu;
1148 /** @type {WeakMap<PatternMatcher, {pattern:RegExp,escaped:boolean}>} */
1149 const internal = new WeakMap();
1152 * Check whether a given character is escaped or not.
1153 * @param {string} str The string to check.
1154 * @param {number} index The location of the character to check.
1155 * @returns {boolean} `true` if the character is escaped.
1157 function isEscaped(str, index) {
1158 let escaped = false;
1159 for (let i = index - 1; i >= 0 && str.charCodeAt(i) === 0x5c; --i) {
1166 * Replace a given string by a given matcher.
1167 * @param {PatternMatcher} matcher The pattern matcher.
1168 * @param {string} str The string to be replaced.
1169 * @param {string} replacement The new substring to replace each matched part.
1170 * @returns {string} The replaced string.
1172 function replaceS(matcher, str, replacement) {
1176 /** @type {RegExpExecArray} */
1180 * @param {string} key The placeholder.
1181 * @returns {string} The replaced string.
1183 function replacer(key) {
1190 return str.slice(0, match.index)
1192 return str.slice(match.index + match[0].length)
1194 const i = key.slice(1);
1203 for (match of matcher.execAll(str)) {
1204 chunks.push(str.slice(index, match.index));
1205 chunks.push(replacement.replace(placeholder, replacer));
1206 index = match.index + match[0].length;
1208 chunks.push(str.slice(index));
1210 return chunks.join("")
1214 * Replace a given string by a given matcher.
1215 * @param {PatternMatcher} matcher The pattern matcher.
1216 * @param {string} str The string to be replaced.
1217 * @param {(...strs[])=>string} replace The function to replace each matched part.
1218 * @returns {string} The replaced string.
1220 function replaceF(matcher, str, replace) {
1224 for (const match of matcher.execAll(str)) {
1225 chunks.push(str.slice(index, match.index));
1226 chunks.push(String(replace(...match, match.index, match.input)));
1227 index = match.index + match[0].length;
1229 chunks.push(str.slice(index));
1231 return chunks.join("")
1235 * The class to find patterns as considering escape sequences.
1237 class PatternMatcher {
1239 * Initialize this matcher.
1240 * @param {RegExp} pattern The pattern to match.
1241 * @param {{escaped:boolean}} options The options.
1243 constructor(pattern, { escaped = false } = {}) {
1244 if (!(pattern instanceof RegExp)) {
1245 throw new TypeError("'pattern' should be a RegExp instance.")
1247 if (!pattern.flags.includes("g")) {
1248 throw new Error("'pattern' should contains 'g' flag.")
1251 internal.set(this, {
1252 pattern: new RegExp(pattern.source, pattern.flags),
1253 escaped: Boolean(escaped),
1258 * Find the pattern in a given string.
1259 * @param {string} str The string to find.
1260 * @returns {IterableIterator<RegExpExecArray>} The iterator which iterate the matched information.
1263 const { pattern, escaped } = internal.get(this);
1267 pattern.lastIndex = 0;
1268 while ((match = pattern.exec(str)) != null) {
1269 if (escaped || !isEscaped(str, match.index)) {
1270 lastIndex = pattern.lastIndex;
1272 pattern.lastIndex = lastIndex;
1278 * Check whether the pattern is found in a given string.
1279 * @param {string} str The string to check.
1280 * @returns {boolean} `true` if the pattern was found in the string.
1283 const it = this.execAll(str);
1284 const ret = it.next();
1289 * Replace a given string.
1290 * @param {string} str The string to be replaced.
1291 * @param {(string|((...strs:string[])=>string))} replacer The string or function to replace. This is the same as the 2nd argument of `String.prototype.replace`.
1292 * @returns {string} The replaced string.
1294 [Symbol.replace](str, replacer) {
1295 return typeof replacer === "function"
1296 ? replaceF(this, String(str), replacer)
1297 : replaceS(this, String(str), String(replacer))
1301 const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
1302 const has = Function.call.bind(Object.hasOwnProperty);
1304 const READ = Symbol("read");
1305 const CALL = Symbol("call");
1306 const CONSTRUCT = Symbol("construct");
1307 const ESM = Symbol("esm");
1309 const requireCall = { require: { [CALL]: true } };
1312 * Check whether a given variable is modified or not.
1313 * @param {Variable} variable The variable to check.
1314 * @returns {boolean} `true` if the variable is modified.
1316 function isModifiedGlobal(variable) {
1319 variable.defs.length !== 0 ||
1320 variable.references.some(r => r.isWrite())
1325 * Check if the value of a given node is passed through to the parent syntax as-is.
1326 * For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through.
1327 * @param {Node} node A node to check.
1328 * @returns {boolean} `true` if the node is passed through.
1330 function isPassThrough(node) {
1331 const parent = node.parent;
1333 switch (parent && parent.type) {
1334 case "ConditionalExpression":
1335 return parent.consequent === node || parent.alternate === node
1336 case "LogicalExpression":
1338 case "SequenceExpression":
1339 return parent.expressions[parent.expressions.length - 1] === node
1347 * The reference tracker.
1349 class ReferenceTracker {
1351 * Initialize this tracker.
1352 * @param {Scope} globalScope The global scope.
1353 * @param {object} [options] The options.
1354 * @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules.
1355 * @param {string[]} [options.globalObjectNames=["global","self","window"]] The variable names for Global Object.
1361 globalObjectNames = ["global", "self", "window"],
1364 this.variableStack = [];
1365 this.globalScope = globalScope;
1367 this.globalObjectNames = globalObjectNames.slice(0);
1371 * Iterate the references of global variables.
1372 * @param {object} traceMap The trace map.
1373 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1375 *iterateGlobalReferences(traceMap) {
1376 for (const key of Object.keys(traceMap)) {
1377 const nextTraceMap = traceMap[key];
1379 const variable = this.globalScope.set.get(key);
1381 if (isModifiedGlobal(variable)) {
1385 yield* this._iterateVariableReferences(
1393 for (const key of this.globalObjectNames) {
1395 const variable = this.globalScope.set.get(key);
1397 if (isModifiedGlobal(variable)) {
1401 yield* this._iterateVariableReferences(
1411 * Iterate the references of CommonJS modules.
1412 * @param {object} traceMap The trace map.
1413 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1415 *iterateCjsReferences(traceMap) {
1416 for (const { node } of this.iterateGlobalReferences(requireCall)) {
1417 const key = getStringIfConstant(node.arguments[0]);
1418 if (key == null || !has(traceMap, key)) {
1422 const nextTraceMap = traceMap[key];
1425 if (nextTraceMap[READ]) {
1430 info: nextTraceMap[READ],
1433 yield* this._iteratePropertyReferences(node, path, nextTraceMap);
1438 * Iterate the references of ES modules.
1439 * @param {object} traceMap The trace map.
1440 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1442 *iterateEsmReferences(traceMap) {
1443 const programNode = this.globalScope.block;
1445 for (const node of programNode.body) {
1446 if (!IMPORT_TYPE.test(node.type) || node.source == null) {
1449 const moduleId = node.source.value;
1451 if (!has(traceMap, moduleId)) {
1454 const nextTraceMap = traceMap[moduleId];
1455 const path = [moduleId];
1457 if (nextTraceMap[READ]) {
1458 yield { node, path, type: READ, info: nextTraceMap[READ] };
1461 if (node.type === "ExportAllDeclaration") {
1462 for (const key of Object.keys(nextTraceMap)) {
1463 const exportTraceMap = nextTraceMap[key];
1464 if (exportTraceMap[READ]) {
1467 path: path.concat(key),
1469 info: exportTraceMap[READ],
1474 for (const specifier of node.specifiers) {
1475 const esm = has(nextTraceMap, ESM);
1476 const it = this._iterateImportReferences(
1481 : this.mode === "legacy"
1483 { default: nextTraceMap },
1486 : { default: nextTraceMap }
1492 for (const report of it) {
1493 report.path = report.path.filter(exceptDefault);
1495 report.path.length >= 2 ||
1496 report.type !== READ
1508 * Iterate the references for a given variable.
1509 * @param {Variable} variable The variable to iterate that references.
1510 * @param {string[]} path The current path.
1511 * @param {object} traceMap The trace map.
1512 * @param {boolean} shouldReport = The flag to report those references.
1513 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1515 *_iterateVariableReferences(variable, path, traceMap, shouldReport) {
1516 if (this.variableStack.includes(variable)) {
1519 this.variableStack.push(variable);
1521 for (const reference of variable.references) {
1522 if (!reference.isRead()) {
1525 const node = reference.identifier;
1527 if (shouldReport && traceMap[READ]) {
1528 yield { node, path, type: READ, info: traceMap[READ] };
1530 yield* this._iteratePropertyReferences(node, path, traceMap);
1533 this.variableStack.pop();
1538 * Iterate the references for a given AST node.
1539 * @param rootNode The AST node to iterate references.
1540 * @param {string[]} path The current path.
1541 * @param {object} traceMap The trace map.
1542 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1544 //eslint-disable-next-line complexity
1545 *_iteratePropertyReferences(rootNode, path, traceMap) {
1546 let node = rootNode;
1547 while (isPassThrough(node)) {
1551 const parent = node.parent;
1552 if (parent.type === "MemberExpression") {
1553 if (parent.object === node) {
1554 const key = getPropertyName(parent);
1555 if (key == null || !has(traceMap, key)) {
1559 path = path.concat(key); //eslint-disable-line no-param-reassign
1560 const nextTraceMap = traceMap[key];
1561 if (nextTraceMap[READ]) {
1566 info: nextTraceMap[READ],
1569 yield* this._iteratePropertyReferences(
1577 if (parent.type === "CallExpression") {
1578 if (parent.callee === node && traceMap[CALL]) {
1579 yield { node: parent, path, type: CALL, info: traceMap[CALL] };
1583 if (parent.type === "NewExpression") {
1584 if (parent.callee === node && traceMap[CONSTRUCT]) {
1589 info: traceMap[CONSTRUCT],
1594 if (parent.type === "AssignmentExpression") {
1595 if (parent.right === node) {
1596 yield* this._iterateLhsReferences(parent.left, path, traceMap);
1597 yield* this._iteratePropertyReferences(parent, path, traceMap);
1601 if (parent.type === "AssignmentPattern") {
1602 if (parent.right === node) {
1603 yield* this._iterateLhsReferences(parent.left, path, traceMap);
1607 if (parent.type === "VariableDeclarator") {
1608 if (parent.init === node) {
1609 yield* this._iterateLhsReferences(parent.id, path, traceMap);
1615 * Iterate the references for a given Pattern node.
1616 * @param {Node} patternNode The Pattern node to iterate references.
1617 * @param {string[]} path The current path.
1618 * @param {object} traceMap The trace map.
1619 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1621 *_iterateLhsReferences(patternNode, path, traceMap) {
1622 if (patternNode.type === "Identifier") {
1623 const variable = findVariable(this.globalScope, patternNode);
1624 if (variable != null) {
1625 yield* this._iterateVariableReferences(
1634 if (patternNode.type === "ObjectPattern") {
1635 for (const property of patternNode.properties) {
1636 const key = getPropertyName(property);
1638 if (key == null || !has(traceMap, key)) {
1642 const nextPath = path.concat(key);
1643 const nextTraceMap = traceMap[key];
1644 if (nextTraceMap[READ]) {
1649 info: nextTraceMap[READ],
1652 yield* this._iterateLhsReferences(
1660 if (patternNode.type === "AssignmentPattern") {
1661 yield* this._iterateLhsReferences(patternNode.left, path, traceMap);
1666 * Iterate the references for a given ModuleSpecifier node.
1667 * @param {Node} specifierNode The ModuleSpecifier node to iterate references.
1668 * @param {string[]} path The current path.
1669 * @param {object} traceMap The trace map.
1670 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1672 *_iterateImportReferences(specifierNode, path, traceMap) {
1673 const type = specifierNode.type;
1675 if (type === "ImportSpecifier" || type === "ImportDefaultSpecifier") {
1677 type === "ImportDefaultSpecifier"
1679 : specifierNode.imported.name;
1680 if (!has(traceMap, key)) {
1684 path = path.concat(key); //eslint-disable-line no-param-reassign
1685 const nextTraceMap = traceMap[key];
1686 if (nextTraceMap[READ]) {
1688 node: specifierNode,
1691 info: nextTraceMap[READ],
1694 yield* this._iterateVariableReferences(
1695 findVariable(this.globalScope, specifierNode.local),
1704 if (type === "ImportNamespaceSpecifier") {
1705 yield* this._iterateVariableReferences(
1706 findVariable(this.globalScope, specifierNode.local),
1714 if (type === "ExportSpecifier") {
1715 const key = specifierNode.local.name;
1716 if (!has(traceMap, key)) {
1720 path = path.concat(key); //eslint-disable-line no-param-reassign
1721 const nextTraceMap = traceMap[key];
1722 if (nextTraceMap[READ]) {
1724 node: specifierNode,
1727 info: nextTraceMap[READ],
1734 ReferenceTracker.READ = READ;
1735 ReferenceTracker.CALL = CALL;
1736 ReferenceTracker.CONSTRUCT = CONSTRUCT;
1737 ReferenceTracker.ESM = ESM;
1740 * This is a predicate function for Array#filter.
1741 * @param {string} name A name part.
1742 * @param {number} index The index of the name.
1743 * @returns {boolean} `false` if it's default.
1745 function exceptDefault(name, index) {
1746 return !(index === 1 && name === "default")
1754 getFunctionHeadLocation,
1755 getFunctionNameWithKind,
1759 getStringIfConstant,
1762 isClosingBraceToken,
1763 isClosingBracketToken,
1764 isClosingParenToken,
1769 isNotClosingBraceToken,
1770 isNotClosingBracketToken,
1771 isNotClosingParenToken,
1775 isNotOpeningBraceToken,
1776 isNotOpeningBracketToken,
1777 isNotOpeningParenToken,
1778 isNotSemicolonToken,
1779 isOpeningBraceToken,
1780 isOpeningBracketToken,
1781 isOpeningParenToken,
1789 exports.CALL = CALL;
1790 exports.CONSTRUCT = CONSTRUCT;
1792 exports.PatternMatcher = PatternMatcher;
1793 exports.READ = READ;
1794 exports.ReferenceTracker = ReferenceTracker;
1795 exports.default = index;
1796 exports.findVariable = findVariable;
1797 exports.getFunctionHeadLocation = getFunctionHeadLocation;
1798 exports.getFunctionNameWithKind = getFunctionNameWithKind;
1799 exports.getInnermostScope = getInnermostScope;
1800 exports.getPropertyName = getPropertyName;
1801 exports.getStaticValue = getStaticValue;
1802 exports.getStringIfConstant = getStringIfConstant;
1803 exports.hasSideEffect = hasSideEffect;
1804 exports.isArrowToken = isArrowToken;
1805 exports.isClosingBraceToken = isClosingBraceToken;
1806 exports.isClosingBracketToken = isClosingBracketToken;
1807 exports.isClosingParenToken = isClosingParenToken;
1808 exports.isColonToken = isColonToken;
1809 exports.isCommaToken = isCommaToken;
1810 exports.isCommentToken = isCommentToken;
1811 exports.isNotArrowToken = isNotArrowToken;
1812 exports.isNotClosingBraceToken = isNotClosingBraceToken;
1813 exports.isNotClosingBracketToken = isNotClosingBracketToken;
1814 exports.isNotClosingParenToken = isNotClosingParenToken;
1815 exports.isNotColonToken = isNotColonToken;
1816 exports.isNotCommaToken = isNotCommaToken;
1817 exports.isNotCommentToken = isNotCommentToken;
1818 exports.isNotOpeningBraceToken = isNotOpeningBraceToken;
1819 exports.isNotOpeningBracketToken = isNotOpeningBracketToken;
1820 exports.isNotOpeningParenToken = isNotOpeningParenToken;
1821 exports.isNotSemicolonToken = isNotSemicolonToken;
1822 exports.isOpeningBraceToken = isOpeningBraceToken;
1823 exports.isOpeningBracketToken = isOpeningBracketToken;
1824 exports.isOpeningParenToken = isOpeningParenToken;
1825 exports.isParenthesized = isParenthesized;
1826 exports.isSemicolonToken = isSemicolonToken;
1827 //# sourceMappingURL=index.js.map