1 /*! @author Toru Nagashima <https://github.com/mysticatea> */
2 import evk from 'eslint-visitor-keys';
5 * Get the innermost scope which contains a given location.
6 * @param {Scope} initialScope The initial scope to search.
7 * @param {Node} node The location to search.
8 * @returns {Scope} The innermost scope.
10 function getInnermostScope(initialScope, node) {
11 const location = node.range[0];
13 let scope = initialScope;
17 for (const childScope of scope.childScopes) {
18 const range = childScope.block.range;
20 if (range[0] <= location && location < range[1]) {
32 * Find the variable of a given name.
33 * @param {Scope} initialScope The scope to start finding.
34 * @param {string|Node} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node.
35 * @returns {Variable|null} The found variable or null.
37 function findVariable(initialScope, nameOrNode) {
39 let scope = initialScope;
41 if (typeof nameOrNode === "string") {
44 name = nameOrNode.name;
45 scope = getInnermostScope(scope, nameOrNode);
48 while (scope != null) {
49 const variable = scope.set.get(name);
50 if (variable != null) {
60 * Negate the result of `this` calling.
61 * @param {Token} token The token to check.
62 * @returns {boolean} `true` if the result of `this(token)` is `false`.
64 function negate0(token) {
65 return !this(token) //eslint-disable-line no-invalid-this
69 * Creates the negate function of the given function.
70 * @param {function(Token):boolean} f - The function to negate.
71 * @returns {function(Token):boolean} Negated function.
74 return negate0.bind(f)
78 * Checks if the given token is an arrow token or not.
79 * @param {Token} token - The token to check.
80 * @returns {boolean} `true` if the token is an arrow token.
82 function isArrowToken(token) {
83 return token.value === "=>" && token.type === "Punctuator"
87 * Checks if the given token is a comma token or not.
88 * @param {Token} token - The token to check.
89 * @returns {boolean} `true` if the token is a comma token.
91 function isCommaToken(token) {
92 return token.value === "," && token.type === "Punctuator"
96 * Checks if the given token is a semicolon token or not.
97 * @param {Token} token - The token to check.
98 * @returns {boolean} `true` if the token is a semicolon token.
100 function isSemicolonToken(token) {
101 return token.value === ";" && token.type === "Punctuator"
105 * Checks if the given token is a colon token or not.
106 * @param {Token} token - The token to check.
107 * @returns {boolean} `true` if the token is a colon token.
109 function isColonToken(token) {
110 return token.value === ":" && token.type === "Punctuator"
114 * Checks if the given token is an opening parenthesis token or not.
115 * @param {Token} token - The token to check.
116 * @returns {boolean} `true` if the token is an opening parenthesis token.
118 function isOpeningParenToken(token) {
119 return token.value === "(" && token.type === "Punctuator"
123 * Checks if the given token is a closing parenthesis token or not.
124 * @param {Token} token - The token to check.
125 * @returns {boolean} `true` if the token is a closing parenthesis token.
127 function isClosingParenToken(token) {
128 return token.value === ")" && token.type === "Punctuator"
132 * Checks if the given token is an opening square bracket token or not.
133 * @param {Token} token - The token to check.
134 * @returns {boolean} `true` if the token is an opening square bracket token.
136 function isOpeningBracketToken(token) {
137 return token.value === "[" && token.type === "Punctuator"
141 * Checks if the given token is a closing square bracket token or not.
142 * @param {Token} token - The token to check.
143 * @returns {boolean} `true` if the token is a closing square bracket token.
145 function isClosingBracketToken(token) {
146 return token.value === "]" && token.type === "Punctuator"
150 * Checks if the given token is an opening brace token or not.
151 * @param {Token} token - The token to check.
152 * @returns {boolean} `true` if the token is an opening brace token.
154 function isOpeningBraceToken(token) {
155 return token.value === "{" && token.type === "Punctuator"
159 * Checks if the given token is a closing brace token or not.
160 * @param {Token} token - The token to check.
161 * @returns {boolean} `true` if the token is a closing brace token.
163 function isClosingBraceToken(token) {
164 return token.value === "}" && token.type === "Punctuator"
168 * Checks if the given token is a comment token or not.
169 * @param {Token} token - The token to check.
170 * @returns {boolean} `true` if the token is a comment token.
172 function isCommentToken(token) {
174 token.type === "Line" ||
175 token.type === "Block" ||
176 token.type === "Shebang"
180 const isNotArrowToken = negate(isArrowToken);
181 const isNotCommaToken = negate(isCommaToken);
182 const isNotSemicolonToken = negate(isSemicolonToken);
183 const isNotColonToken = negate(isColonToken);
184 const isNotOpeningParenToken = negate(isOpeningParenToken);
185 const isNotClosingParenToken = negate(isClosingParenToken);
186 const isNotOpeningBracketToken = negate(isOpeningBracketToken);
187 const isNotClosingBracketToken = negate(isClosingBracketToken);
188 const isNotOpeningBraceToken = negate(isOpeningBraceToken);
189 const isNotClosingBraceToken = negate(isClosingBraceToken);
190 const isNotCommentToken = negate(isCommentToken);
193 * Get the `(` token of the given function node.
194 * @param {Node} node - The function node to get.
195 * @param {SourceCode} sourceCode - The source code object to get tokens.
196 * @returns {Token} `(` token.
198 function getOpeningParenOfParams(node, sourceCode) {
200 ? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
201 : sourceCode.getFirstToken(node, isOpeningParenToken)
205 * Get the location of the given function node for reporting.
206 * @param {Node} node - The function node to get.
207 * @param {SourceCode} sourceCode - The source code object to get tokens.
208 * @returns {string} The location of the function node for reporting.
210 function getFunctionHeadLocation(node, sourceCode) {
211 const parent = node.parent;
215 if (node.type === "ArrowFunctionExpression") {
216 const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
218 start = arrowToken.loc.start;
219 end = arrowToken.loc.end;
221 parent.type === "Property" ||
222 parent.type === "MethodDefinition"
224 start = parent.loc.start;
225 end = getOpeningParenOfParams(node, sourceCode).loc.start;
227 start = node.loc.start;
228 end = getOpeningParenOfParams(node, sourceCode).loc.start;
232 start: Object.assign({}, start),
233 end: Object.assign({}, end),
237 /* globals BigInt, globalThis, global, self, window */
240 typeof globalThis !== "undefined"
242 : typeof self !== "undefined"
244 : typeof window !== "undefined"
246 : typeof global !== "undefined"
250 const builtinNames = Object.freeze(
261 "decodeURIComponent",
263 "encodeURIComponent",
300 const callAllowed = new Set(
303 typeof BigInt === "function" ? BigInt : undefined,
315 ...Object.getOwnPropertyNames(Math)
317 .filter(f => typeof f === "function"),
336 String.fromCodePoint,
342 ].filter(f => typeof f === "function")
344 const callPassThrough = new Set([
346 Object.preventExtensions,
351 * Get the property descriptor.
352 * @param {object} object The object to get.
353 * @param {string|number|symbol} name The property name to get.
355 function getPropertyDescriptor(object, name) {
357 while ((typeof x === "object" || typeof x === "function") && x !== null) {
358 const d = Object.getOwnPropertyDescriptor(x, name);
362 x = Object.getPrototypeOf(x);
368 * Check if a property is getter or not.
369 * @param {object} object The object to check.
370 * @param {string|number|symbol} name The property name to check.
372 function isGetter(object, name) {
373 const d = getPropertyDescriptor(object, name);
374 return d != null && d.get != null
378 * Get the element values of a given node list.
379 * @param {Node[]} nodeList The node list to get values.
380 * @param {Scope|undefined} initialScope The initial scope to find variables.
381 * @returns {any[]|null} The value list if all nodes are constant. Otherwise, null.
383 function getElementValues(nodeList, initialScope) {
384 const valueList = [];
386 for (let i = 0; i < nodeList.length; ++i) {
387 const elementNode = nodeList[i];
389 if (elementNode == null) {
390 valueList.length = i + 1;
391 } else if (elementNode.type === "SpreadElement") {
392 const argument = getStaticValueR(elementNode.argument, initialScope);
393 if (argument == null) {
396 valueList.push(...argument.value);
398 const element = getStaticValueR(elementNode, initialScope);
399 if (element == null) {
402 valueList.push(element.value);
409 const operations = Object.freeze({
410 ArrayExpression(node, initialScope) {
411 const elements = getElementValues(node.elements, initialScope);
412 return elements != null ? { value: elements } : null
415 AssignmentExpression(node, initialScope) {
416 if (node.operator === "=") {
417 return getStaticValueR(node.right, initialScope)
422 //eslint-disable-next-line complexity
423 BinaryExpression(node, initialScope) {
424 if (node.operator === "in" || node.operator === "instanceof") {
429 const left = getStaticValueR(node.left, initialScope);
430 const right = getStaticValueR(node.right, initialScope);
431 if (left != null && right != null) {
432 switch (node.operator) {
434 return { value: left.value == right.value } //eslint-disable-line eqeqeq
436 return { value: left.value != right.value } //eslint-disable-line eqeqeq
438 return { value: left.value === right.value }
440 return { value: left.value !== right.value }
442 return { value: left.value < right.value }
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: Math.pow(left.value, right.value) }
468 return { value: left.value | right.value }
470 return { value: left.value ^ right.value }
472 return { value: left.value & right.value }
481 CallExpression(node, initialScope) {
482 const calleeNode = node.callee;
483 const args = getElementValues(node.arguments, initialScope);
486 if (calleeNode.type === "MemberExpression") {
487 const object = getStaticValueR(calleeNode.object, initialScope);
488 const property = calleeNode.computed
489 ? getStaticValueR(calleeNode.property, initialScope)
490 : { value: calleeNode.property.name };
492 if (object != null && property != null) {
493 const receiver = object.value;
494 const methodName = property.value;
495 if (callAllowed.has(receiver[methodName])) {
496 return { value: receiver[methodName](...args) }
498 if (callPassThrough.has(receiver[methodName])) {
499 return { value: args[0] }
503 const callee = getStaticValueR(calleeNode, initialScope);
504 if (callee != null) {
505 const func = callee.value;
506 if (callAllowed.has(func)) {
507 return { value: func(...args) }
509 if (callPassThrough.has(func)) {
510 return { value: args[0] }
519 ConditionalExpression(node, initialScope) {
520 const test = getStaticValueR(node.test, initialScope);
523 ? getStaticValueR(node.consequent, initialScope)
524 : getStaticValueR(node.alternate, initialScope)
529 ExpressionStatement(node, initialScope) {
530 return getStaticValueR(node.expression, initialScope)
533 Identifier(node, initialScope) {
534 if (initialScope != null) {
535 const variable = findVariable(initialScope, node);
540 variable.defs.length === 0 &&
541 builtinNames.has(variable.name) &&
542 variable.name in globalObject
544 return { value: globalObject[variable.name] }
548 if (variable != null && variable.defs.length === 1) {
549 const def = variable.defs[0];
552 def.parent.kind === "const" &&
553 // TODO(mysticatea): don't support destructuring here.
554 def.node.id.type === "Identifier"
556 return getStaticValueR(def.node.init, initialScope)
564 //istanbul ignore if : this is implementation-specific behavior.
565 if ((node.regex != null || node.bigint != null) && node.value == null) {
566 // It was a RegExp/BigInt literal, but Node.js didn't support it.
569 return { value: node.value }
572 LogicalExpression(node, initialScope) {
573 const left = getStaticValueR(node.left, initialScope);
576 (node.operator === "||" && Boolean(left.value) === true) ||
577 (node.operator === "&&" && Boolean(left.value) === false)
582 const right = getStaticValueR(node.right, initialScope);
591 MemberExpression(node, initialScope) {
592 const object = getStaticValueR(node.object, initialScope);
593 const property = node.computed
594 ? getStaticValueR(node.property, initialScope)
595 : { value: node.property.name };
600 !isGetter(object.value, property.value)
602 return { value: object.value[property.value] }
607 NewExpression(node, initialScope) {
608 const callee = getStaticValueR(node.callee, initialScope);
609 const args = getElementValues(node.arguments, initialScope);
611 if (callee != null && args != null) {
612 const Func = callee.value;
613 if (callAllowed.has(Func)) {
614 return { value: new Func(...args) }
621 ObjectExpression(node, initialScope) {
624 for (const propertyNode of node.properties) {
625 if (propertyNode.type === "Property") {
626 if (propertyNode.kind !== "init") {
629 const key = propertyNode.computed
630 ? getStaticValueR(propertyNode.key, initialScope)
631 : { value: propertyNode.key.name };
632 const value = getStaticValueR(propertyNode.value, initialScope);
633 if (key == null || value == null) {
636 object[key.value] = value.value;
638 propertyNode.type === "SpreadElement" ||
639 propertyNode.type === "ExperimentalSpreadProperty"
641 const argument = getStaticValueR(
642 propertyNode.argument,
645 if (argument == null) {
648 Object.assign(object, argument.value);
654 return { value: object }
657 SequenceExpression(node, initialScope) {
658 const last = node.expressions[node.expressions.length - 1];
659 return getStaticValueR(last, initialScope)
662 TaggedTemplateExpression(node, initialScope) {
663 const tag = getStaticValueR(node.tag, initialScope);
664 const expressions = getElementValues(
665 node.quasi.expressions,
669 if (tag != null && expressions != null) {
670 const func = tag.value;
671 const strings = node.quasi.quasis.map(q => q.value.cooked);
672 strings.raw = node.quasi.quasis.map(q => q.value.raw);
674 if (func === String.raw) {
675 return { value: func(strings, ...expressions) }
682 TemplateLiteral(node, initialScope) {
683 const expressions = getElementValues(node.expressions, initialScope);
684 if (expressions != null) {
685 let value = node.quasis[0].value.cooked;
686 for (let i = 0; i < expressions.length; ++i) {
687 value += expressions[i];
688 value += node.quasis[i + 1].value.cooked;
695 UnaryExpression(node, initialScope) {
696 if (node.operator === "delete") {
700 if (node.operator === "void") {
701 return { value: undefined }
704 const arg = getStaticValueR(node.argument, initialScope);
706 switch (node.operator) {
708 return { value: -arg.value }
710 return { value: +arg.value } //eslint-disable-line no-implicit-coercion
712 return { value: !arg.value }
714 return { value: ~arg.value }
716 return { value: typeof arg.value }
727 * Get the value of a given node if it's a static value.
728 * @param {Node} node The node to get.
729 * @param {Scope|undefined} initialScope The scope to start finding variable.
730 * @returns {{value:any}|null} The static value of the node, or `null`.
732 function getStaticValueR(node, initialScope) {
733 if (node != null && Object.hasOwnProperty.call(operations, node.type)) {
734 return operations[node.type](node, initialScope)
740 * Get the value of a given node if it's a static value.
741 * @param {Node} node The node to get.
742 * @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.
743 * @returns {{value:any}|null} The static value of the node, or `null`.
745 function getStaticValue(node, initialScope = null) {
747 return getStaticValueR(node, initialScope)
754 * Get the value of a given node if it's a literal or a template literal.
755 * @param {Node} node The node to get.
756 * @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.
757 * @returns {string|null} The value of the node, or `null`.
759 function getStringIfConstant(node, initialScope = null) {
760 // Handle the literals that the platform doesn't support natively.
761 if (node && node.type === "Literal" && node.value === null) {
763 return `/${node.regex.pattern}/${node.regex.flags}`
770 const evaluated = getStaticValue(node, initialScope);
771 return evaluated && String(evaluated.value)
775 * Get the property name from a MemberExpression node or a Property node.
776 * @param {Node} node The node to get.
777 * @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.
778 * @returns {string|null} The property name of the node.
780 function getPropertyName(node, initialScope) {
782 case "MemberExpression":
784 return getStringIfConstant(node.property, initialScope)
786 return node.property.name
789 case "MethodDefinition":
791 return getStringIfConstant(node.key, initialScope)
793 if (node.key.type === "Literal") {
794 return String(node.key.value)
805 * Get the name and kind of the given function node.
806 * @param {ASTNode} node - The function node to get.
807 * @returns {string} The name and kind of the function node.
809 function getFunctionNameWithKind(node) {
810 const parent = node.parent;
813 if (parent.type === "MethodDefinition" && parent.static) {
814 tokens.push("static");
817 tokens.push("async");
819 if (node.generator) {
820 tokens.push("generator");
823 if (node.type === "ArrowFunctionExpression") {
824 tokens.push("arrow", "function");
826 parent.type === "Property" ||
827 parent.type === "MethodDefinition"
829 if (parent.kind === "constructor") {
832 if (parent.kind === "get") {
833 tokens.push("getter");
834 } else if (parent.kind === "set") {
835 tokens.push("setter");
837 tokens.push("method");
840 tokens.push("function");
844 tokens.push(`'${node.id.name}'`);
846 const name = getPropertyName(parent);
849 tokens.push(`'${name}'`);
853 return tokens.join(" ")
856 const typeConversionBinaryOps = Object.freeze(
878 const typeConversionUnaryOps = Object.freeze(new Set(["-", "+", "!", "~"]));
879 const visitor = Object.freeze(
880 Object.assign(Object.create(null), {
881 $visit(node, options, visitorKeys) {
882 const { type } = node;
884 if (typeof this[type] === "function") {
885 return this[type](node, options, visitorKeys)
888 return this.$visitChildren(node, options, visitorKeys)
891 $visitChildren(node, options, visitorKeys) {
892 const { type } = node;
894 for (const key of visitorKeys[type] || evk.getKeys(node)) {
895 const value = node[key];
897 if (Array.isArray(value)) {
898 for (const element of value) {
901 this.$visit(element, options, visitorKeys)
906 } else if (value && this.$visit(value, options, visitorKeys)) {
914 ArrowFunctionExpression() {
917 AssignmentExpression() {
923 BinaryExpression(node, options, visitorKeys) {
925 options.considerImplicitTypeConversion &&
926 typeConversionBinaryOps.has(node.operator) &&
927 (node.left.type !== "Literal" || node.right.type !== "Literal")
931 return this.$visitChildren(node, options, visitorKeys)
936 FunctionExpression() {
942 MemberExpression(node, options, visitorKeys) {
943 if (options.considerGetters) {
947 options.considerImplicitTypeConversion &&
949 node.property.type !== "Literal"
953 return this.$visitChildren(node, options, visitorKeys)
955 MethodDefinition(node, options, visitorKeys) {
957 options.considerImplicitTypeConversion &&
959 node.key.type !== "Literal"
963 return this.$visitChildren(node, options, visitorKeys)
968 Property(node, options, visitorKeys) {
970 options.considerImplicitTypeConversion &&
972 node.key.type !== "Literal"
976 return this.$visitChildren(node, options, visitorKeys)
978 UnaryExpression(node, options, visitorKeys) {
979 if (node.operator === "delete") {
983 options.considerImplicitTypeConversion &&
984 typeConversionUnaryOps.has(node.operator) &&
985 node.argument.type !== "Literal"
989 return this.$visitChildren(node, options, visitorKeys)
1001 * Check whether a given node has any side effect or not.
1002 * @param {Node} node The node to get.
1003 * @param {SourceCode} sourceCode The source code object.
1004 * @param {object} [options] The option object.
1005 * @param {boolean} [options.considerGetters=false] If `true` then it considers member accesses as the node which has side effects.
1006 * @param {boolean} [options.considerImplicitTypeConversion=false] If `true` then it considers implicit type conversion as the node which has side effects.
1007 * @param {object} [options.visitorKeys=evk.KEYS] The keys to traverse nodes. Use `context.getSourceCode().visitorKeys`.
1008 * @returns {boolean} `true` if the node has a certain side effect.
1010 function hasSideEffect(
1013 { considerGetters = false, considerImplicitTypeConversion = false } = {}
1015 return visitor.$visit(
1017 { considerGetters, considerImplicitTypeConversion },
1018 sourceCode.visitorKeys || evk.KEYS
1023 * Get the left parenthesis of the parent node syntax if it exists.
1024 * E.g., `if (a) {}` then the `(`.
1025 * @param {Node} node The AST node to check.
1026 * @param {SourceCode} sourceCode The source code object to get tokens.
1027 * @returns {Token|null} The left parenthesis of the parent node syntax
1029 function getParentSyntaxParen(node, sourceCode) {
1030 const parent = node.parent;
1032 switch (parent.type) {
1033 case "CallExpression":
1034 case "NewExpression":
1035 if (parent.arguments.length === 1 && parent.arguments[0] === node) {
1036 return sourceCode.getTokenAfter(
1043 case "DoWhileStatement":
1044 if (parent.test === node) {
1045 return sourceCode.getTokenAfter(
1053 case "WhileStatement":
1054 if (parent.test === node) {
1055 return sourceCode.getFirstToken(parent, 1)
1059 case "ImportExpression":
1060 if (parent.source === node) {
1061 return sourceCode.getFirstToken(parent, 1)
1065 case "SwitchStatement":
1066 if (parent.discriminant === node) {
1067 return sourceCode.getFirstToken(parent, 1)
1071 case "WithStatement":
1072 if (parent.object === node) {
1073 return sourceCode.getFirstToken(parent, 1)
1083 * Check whether a given node is parenthesized or not.
1084 * @param {number} times The number of parantheses.
1085 * @param {Node} node The AST node to check.
1086 * @param {SourceCode} sourceCode The source code object to get tokens.
1087 * @returns {boolean} `true` if the node is parenthesized the given times.
1090 * Check whether a given node is parenthesized or not.
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.
1095 function isParenthesized(
1100 let times, node, sourceCode, maybeLeftParen, maybeRightParen;
1101 if (typeof timesOrNode === "number") {
1102 times = timesOrNode | 0;
1103 node = nodeOrSourceCode;
1104 sourceCode = optionalSourceCode;
1105 if (!(times >= 1)) {
1106 throw new TypeError("'times' should be a positive integer.")
1111 sourceCode = nodeOrSourceCode;
1118 maybeLeftParen = maybeRightParen = node;
1120 maybeLeftParen = sourceCode.getTokenBefore(maybeLeftParen);
1121 maybeRightParen = sourceCode.getTokenAfter(maybeRightParen);
1123 maybeLeftParen != null &&
1124 maybeRightParen != null &&
1125 isOpeningParenToken(maybeLeftParen) &&
1126 isClosingParenToken(maybeRightParen) &&
1127 // Avoid false positive such as `if (a) {}`
1128 maybeLeftParen !== getParentSyntaxParen(node, sourceCode) &&
1136 * @author Toru Nagashima <https://github.com/mysticatea>
1137 * See LICENSE file in root directory for full license.
1140 const placeholder = /\$(?:[$&`']|[1-9][0-9]?)/gu;
1142 /** @type {WeakMap<PatternMatcher, {pattern:RegExp,escaped:boolean}>} */
1143 const internal = new WeakMap();
1146 * Check whether a given character is escaped or not.
1147 * @param {string} str The string to check.
1148 * @param {number} index The location of the character to check.
1149 * @returns {boolean} `true` if the character is escaped.
1151 function isEscaped(str, index) {
1152 let escaped = false;
1153 for (let i = index - 1; i >= 0 && str.charCodeAt(i) === 0x5c; --i) {
1160 * Replace a given string by a given matcher.
1161 * @param {PatternMatcher} matcher The pattern matcher.
1162 * @param {string} str The string to be replaced.
1163 * @param {string} replacement The new substring to replace each matched part.
1164 * @returns {string} The replaced string.
1166 function replaceS(matcher, str, replacement) {
1170 /** @type {RegExpExecArray} */
1174 * @param {string} key The placeholder.
1175 * @returns {string} The replaced string.
1177 function replacer(key) {
1184 return str.slice(0, match.index)
1186 return str.slice(match.index + match[0].length)
1188 const i = key.slice(1);
1197 for (match of matcher.execAll(str)) {
1198 chunks.push(str.slice(index, match.index));
1199 chunks.push(replacement.replace(placeholder, replacer));
1200 index = match.index + match[0].length;
1202 chunks.push(str.slice(index));
1204 return chunks.join("")
1208 * Replace a given string by a given matcher.
1209 * @param {PatternMatcher} matcher The pattern matcher.
1210 * @param {string} str The string to be replaced.
1211 * @param {(...strs[])=>string} replace The function to replace each matched part.
1212 * @returns {string} The replaced string.
1214 function replaceF(matcher, str, replace) {
1218 for (const match of matcher.execAll(str)) {
1219 chunks.push(str.slice(index, match.index));
1220 chunks.push(String(replace(...match, match.index, match.input)));
1221 index = match.index + match[0].length;
1223 chunks.push(str.slice(index));
1225 return chunks.join("")
1229 * The class to find patterns as considering escape sequences.
1231 class PatternMatcher {
1233 * Initialize this matcher.
1234 * @param {RegExp} pattern The pattern to match.
1235 * @param {{escaped:boolean}} options The options.
1237 constructor(pattern, { escaped = false } = {}) {
1238 if (!(pattern instanceof RegExp)) {
1239 throw new TypeError("'pattern' should be a RegExp instance.")
1241 if (!pattern.flags.includes("g")) {
1242 throw new Error("'pattern' should contains 'g' flag.")
1245 internal.set(this, {
1246 pattern: new RegExp(pattern.source, pattern.flags),
1247 escaped: Boolean(escaped),
1252 * Find the pattern in a given string.
1253 * @param {string} str The string to find.
1254 * @returns {IterableIterator<RegExpExecArray>} The iterator which iterate the matched information.
1257 const { pattern, escaped } = internal.get(this);
1261 pattern.lastIndex = 0;
1262 while ((match = pattern.exec(str)) != null) {
1263 if (escaped || !isEscaped(str, match.index)) {
1264 lastIndex = pattern.lastIndex;
1266 pattern.lastIndex = lastIndex;
1272 * Check whether the pattern is found in a given string.
1273 * @param {string} str The string to check.
1274 * @returns {boolean} `true` if the pattern was found in the string.
1277 const it = this.execAll(str);
1278 const ret = it.next();
1283 * Replace a given string.
1284 * @param {string} str The string to be replaced.
1285 * @param {(string|((...strs:string[])=>string))} replacer The string or function to replace. This is the same as the 2nd argument of `String.prototype.replace`.
1286 * @returns {string} The replaced string.
1288 [Symbol.replace](str, replacer) {
1289 return typeof replacer === "function"
1290 ? replaceF(this, String(str), replacer)
1291 : replaceS(this, String(str), String(replacer))
1295 const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
1296 const has = Function.call.bind(Object.hasOwnProperty);
1298 const READ = Symbol("read");
1299 const CALL = Symbol("call");
1300 const CONSTRUCT = Symbol("construct");
1301 const ESM = Symbol("esm");
1303 const requireCall = { require: { [CALL]: true } };
1306 * Check whether a given variable is modified or not.
1307 * @param {Variable} variable The variable to check.
1308 * @returns {boolean} `true` if the variable is modified.
1310 function isModifiedGlobal(variable) {
1313 variable.defs.length !== 0 ||
1314 variable.references.some(r => r.isWrite())
1319 * Check if the value of a given node is passed through to the parent syntax as-is.
1320 * For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through.
1321 * @param {Node} node A node to check.
1322 * @returns {boolean} `true` if the node is passed through.
1324 function isPassThrough(node) {
1325 const parent = node.parent;
1327 switch (parent && parent.type) {
1328 case "ConditionalExpression":
1329 return parent.consequent === node || parent.alternate === node
1330 case "LogicalExpression":
1332 case "SequenceExpression":
1333 return parent.expressions[parent.expressions.length - 1] === node
1341 * The reference tracker.
1343 class ReferenceTracker {
1345 * Initialize this tracker.
1346 * @param {Scope} globalScope The global scope.
1347 * @param {object} [options] The options.
1348 * @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules.
1349 * @param {string[]} [options.globalObjectNames=["global","self","window"]] The variable names for Global Object.
1355 globalObjectNames = ["global", "self", "window"],
1358 this.variableStack = [];
1359 this.globalScope = globalScope;
1361 this.globalObjectNames = globalObjectNames.slice(0);
1365 * Iterate the references of global variables.
1366 * @param {object} traceMap The trace map.
1367 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1369 *iterateGlobalReferences(traceMap) {
1370 for (const key of Object.keys(traceMap)) {
1371 const nextTraceMap = traceMap[key];
1373 const variable = this.globalScope.set.get(key);
1375 if (isModifiedGlobal(variable)) {
1379 yield* this._iterateVariableReferences(
1387 for (const key of this.globalObjectNames) {
1389 const variable = this.globalScope.set.get(key);
1391 if (isModifiedGlobal(variable)) {
1395 yield* this._iterateVariableReferences(
1405 * Iterate the references of CommonJS modules.
1406 * @param {object} traceMap The trace map.
1407 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1409 *iterateCjsReferences(traceMap) {
1410 for (const { node } of this.iterateGlobalReferences(requireCall)) {
1411 const key = getStringIfConstant(node.arguments[0]);
1412 if (key == null || !has(traceMap, key)) {
1416 const nextTraceMap = traceMap[key];
1419 if (nextTraceMap[READ]) {
1424 info: nextTraceMap[READ],
1427 yield* this._iteratePropertyReferences(node, path, nextTraceMap);
1432 * Iterate the references of ES modules.
1433 * @param {object} traceMap The trace map.
1434 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1436 *iterateEsmReferences(traceMap) {
1437 const programNode = this.globalScope.block;
1439 for (const node of programNode.body) {
1440 if (!IMPORT_TYPE.test(node.type) || node.source == null) {
1443 const moduleId = node.source.value;
1445 if (!has(traceMap, moduleId)) {
1448 const nextTraceMap = traceMap[moduleId];
1449 const path = [moduleId];
1451 if (nextTraceMap[READ]) {
1452 yield { node, path, type: READ, info: nextTraceMap[READ] };
1455 if (node.type === "ExportAllDeclaration") {
1456 for (const key of Object.keys(nextTraceMap)) {
1457 const exportTraceMap = nextTraceMap[key];
1458 if (exportTraceMap[READ]) {
1461 path: path.concat(key),
1463 info: exportTraceMap[READ],
1468 for (const specifier of node.specifiers) {
1469 const esm = has(nextTraceMap, ESM);
1470 const it = this._iterateImportReferences(
1475 : this.mode === "legacy"
1477 { default: nextTraceMap },
1480 : { default: nextTraceMap }
1486 for (const report of it) {
1487 report.path = report.path.filter(exceptDefault);
1489 report.path.length >= 2 ||
1490 report.type !== READ
1502 * Iterate the references for a given variable.
1503 * @param {Variable} variable The variable to iterate that references.
1504 * @param {string[]} path The current path.
1505 * @param {object} traceMap The trace map.
1506 * @param {boolean} shouldReport = The flag to report those references.
1507 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1509 *_iterateVariableReferences(variable, path, traceMap, shouldReport) {
1510 if (this.variableStack.includes(variable)) {
1513 this.variableStack.push(variable);
1515 for (const reference of variable.references) {
1516 if (!reference.isRead()) {
1519 const node = reference.identifier;
1521 if (shouldReport && traceMap[READ]) {
1522 yield { node, path, type: READ, info: traceMap[READ] };
1524 yield* this._iteratePropertyReferences(node, path, traceMap);
1527 this.variableStack.pop();
1532 * Iterate the references for a given AST node.
1533 * @param rootNode The AST node to iterate references.
1534 * @param {string[]} path The current path.
1535 * @param {object} traceMap The trace map.
1536 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1538 //eslint-disable-next-line complexity
1539 *_iteratePropertyReferences(rootNode, path, traceMap) {
1540 let node = rootNode;
1541 while (isPassThrough(node)) {
1545 const parent = node.parent;
1546 if (parent.type === "MemberExpression") {
1547 if (parent.object === node) {
1548 const key = getPropertyName(parent);
1549 if (key == null || !has(traceMap, key)) {
1553 path = path.concat(key); //eslint-disable-line no-param-reassign
1554 const nextTraceMap = traceMap[key];
1555 if (nextTraceMap[READ]) {
1560 info: nextTraceMap[READ],
1563 yield* this._iteratePropertyReferences(
1571 if (parent.type === "CallExpression") {
1572 if (parent.callee === node && traceMap[CALL]) {
1573 yield { node: parent, path, type: CALL, info: traceMap[CALL] };
1577 if (parent.type === "NewExpression") {
1578 if (parent.callee === node && traceMap[CONSTRUCT]) {
1583 info: traceMap[CONSTRUCT],
1588 if (parent.type === "AssignmentExpression") {
1589 if (parent.right === node) {
1590 yield* this._iterateLhsReferences(parent.left, path, traceMap);
1591 yield* this._iteratePropertyReferences(parent, path, traceMap);
1595 if (parent.type === "AssignmentPattern") {
1596 if (parent.right === node) {
1597 yield* this._iterateLhsReferences(parent.left, path, traceMap);
1601 if (parent.type === "VariableDeclarator") {
1602 if (parent.init === node) {
1603 yield* this._iterateLhsReferences(parent.id, path, traceMap);
1609 * Iterate the references for a given Pattern node.
1610 * @param {Node} patternNode The Pattern node to iterate references.
1611 * @param {string[]} path The current path.
1612 * @param {object} traceMap The trace map.
1613 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1615 *_iterateLhsReferences(patternNode, path, traceMap) {
1616 if (patternNode.type === "Identifier") {
1617 const variable = findVariable(this.globalScope, patternNode);
1618 if (variable != null) {
1619 yield* this._iterateVariableReferences(
1628 if (patternNode.type === "ObjectPattern") {
1629 for (const property of patternNode.properties) {
1630 const key = getPropertyName(property);
1632 if (key == null || !has(traceMap, key)) {
1636 const nextPath = path.concat(key);
1637 const nextTraceMap = traceMap[key];
1638 if (nextTraceMap[READ]) {
1643 info: nextTraceMap[READ],
1646 yield* this._iterateLhsReferences(
1654 if (patternNode.type === "AssignmentPattern") {
1655 yield* this._iterateLhsReferences(patternNode.left, path, traceMap);
1660 * Iterate the references for a given ModuleSpecifier node.
1661 * @param {Node} specifierNode The ModuleSpecifier node to iterate references.
1662 * @param {string[]} path The current path.
1663 * @param {object} traceMap The trace map.
1664 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
1666 *_iterateImportReferences(specifierNode, path, traceMap) {
1667 const type = specifierNode.type;
1669 if (type === "ImportSpecifier" || type === "ImportDefaultSpecifier") {
1671 type === "ImportDefaultSpecifier"
1673 : specifierNode.imported.name;
1674 if (!has(traceMap, key)) {
1678 path = path.concat(key); //eslint-disable-line no-param-reassign
1679 const nextTraceMap = traceMap[key];
1680 if (nextTraceMap[READ]) {
1682 node: specifierNode,
1685 info: nextTraceMap[READ],
1688 yield* this._iterateVariableReferences(
1689 findVariable(this.globalScope, specifierNode.local),
1698 if (type === "ImportNamespaceSpecifier") {
1699 yield* this._iterateVariableReferences(
1700 findVariable(this.globalScope, specifierNode.local),
1708 if (type === "ExportSpecifier") {
1709 const key = specifierNode.local.name;
1710 if (!has(traceMap, key)) {
1714 path = path.concat(key); //eslint-disable-line no-param-reassign
1715 const nextTraceMap = traceMap[key];
1716 if (nextTraceMap[READ]) {
1718 node: specifierNode,
1721 info: nextTraceMap[READ],
1728 ReferenceTracker.READ = READ;
1729 ReferenceTracker.CALL = CALL;
1730 ReferenceTracker.CONSTRUCT = CONSTRUCT;
1731 ReferenceTracker.ESM = ESM;
1734 * This is a predicate function for Array#filter.
1735 * @param {string} name A name part.
1736 * @param {number} index The index of the name.
1737 * @returns {boolean} `false` if it's default.
1739 function exceptDefault(name, index) {
1740 return !(index === 1 && name === "default")
1748 getFunctionHeadLocation,
1749 getFunctionNameWithKind,
1753 getStringIfConstant,
1756 isClosingBraceToken,
1757 isClosingBracketToken,
1758 isClosingParenToken,
1763 isNotClosingBraceToken,
1764 isNotClosingBracketToken,
1765 isNotClosingParenToken,
1769 isNotOpeningBraceToken,
1770 isNotOpeningBracketToken,
1771 isNotOpeningParenToken,
1772 isNotSemicolonToken,
1773 isOpeningBraceToken,
1774 isOpeningBracketToken,
1775 isOpeningParenToken,
1783 export default index;
1784 export { CALL, CONSTRUCT, ESM, PatternMatcher, READ, ReferenceTracker, findVariable, getFunctionHeadLocation, getFunctionNameWithKind, getInnermostScope, getPropertyName, getStaticValue, getStringIfConstant, hasSideEffect, isArrowToken, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, isColonToken, isCommaToken, isCommentToken, isNotArrowToken, isNotClosingBraceToken, isNotClosingBracketToken, isNotClosingParenToken, isNotColonToken, isNotCommaToken, isNotCommentToken, isNotOpeningBraceToken, isNotOpeningBracketToken, isNotOpeningParenToken, isNotSemicolonToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isParenthesized, isSemicolonToken };
1785 //# sourceMappingURL=index.mjs.map