build: update npm install command to use --legacy-peer-deps
This change modifies the build scripts to use --legacy-peer-deps flag when installing npm dependencies to resolve potential peer dependency conflicts during the build process. The change is applied to both bash (build.sh) and batch (build.bat) build scripts.
This commit is contained in:
+218
-513
@@ -3,26 +3,11 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
CALL,
|
||||
CONSTRUCT,
|
||||
ReferenceTracker,
|
||||
getStaticValue,
|
||||
getStringIfConstant,
|
||||
} = require("@eslint-community/eslint-utils");
|
||||
const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils");
|
||||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
|
||||
const {
|
||||
isCombiningCharacter,
|
||||
isEmojiModifier,
|
||||
isRegionalIndicatorSymbol,
|
||||
isSurrogatePair,
|
||||
} = require("./utils/unicode");
|
||||
const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode");
|
||||
const astUtils = require("./utils/ast-utils.js");
|
||||
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
|
||||
const {
|
||||
parseStringLiteral,
|
||||
parseTemplateToken,
|
||||
} = require("./utils/char-source");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -41,555 +26,275 @@ const {
|
||||
* @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
|
||||
* @returns {IterableIterator<Character[]>} The list of character sequences.
|
||||
*/
|
||||
function* iterateCharacterSequence(nodes) {
|
||||
/** @type {Character[]} */
|
||||
let seq = [];
|
||||
function *iterateCharacterSequence(nodes) {
|
||||
|
||||
for (const node of nodes) {
|
||||
switch (node.type) {
|
||||
case "Character":
|
||||
seq.push(node);
|
||||
break;
|
||||
/** @type {Character[]} */
|
||||
let seq = [];
|
||||
|
||||
case "CharacterClassRange":
|
||||
seq.push(node.min);
|
||||
yield seq;
|
||||
seq = [node.max];
|
||||
break;
|
||||
for (const node of nodes) {
|
||||
switch (node.type) {
|
||||
case "Character":
|
||||
seq.push(node);
|
||||
break;
|
||||
|
||||
case "CharacterSet":
|
||||
case "CharacterClass": // [[]] nesting character class
|
||||
case "ClassStringDisjunction": // \q{...}
|
||||
case "ExpressionCharacterClass": // [A--B]
|
||||
if (seq.length > 0) {
|
||||
yield seq;
|
||||
seq = [];
|
||||
}
|
||||
break;
|
||||
case "CharacterClassRange":
|
||||
seq.push(node.min);
|
||||
yield seq;
|
||||
seq = [node.max];
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
}
|
||||
case "CharacterSet":
|
||||
case "CharacterClass": // [[]] nesting character class
|
||||
case "ClassStringDisjunction": // \q{...}
|
||||
case "ExpressionCharacterClass": // [A--B]
|
||||
if (seq.length > 0) {
|
||||
yield seq;
|
||||
seq = [];
|
||||
}
|
||||
break;
|
||||
|
||||
if (seq.length > 0) {
|
||||
yield seq;
|
||||
}
|
||||
// no default
|
||||
}
|
||||
}
|
||||
|
||||
if (seq.length > 0) {
|
||||
yield seq;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the given character node is a Unicode code point escape or not.
|
||||
* @param {Character} char the character node to check.
|
||||
* @returns {boolean} `true` if the character node is a Unicode code point escape.
|
||||
*/
|
||||
function isUnicodeCodePointEscape(char) {
|
||||
return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
|
||||
return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Each function returns matched characters if it detects that kind of problem.
|
||||
* @type {Record<string, (chars: Character[]) => IterableIterator<Character[]>>}
|
||||
* Each function returns `true` if it detects that kind of problem.
|
||||
* @type {Record<string, (chars: Character[]) => boolean>}
|
||||
*/
|
||||
const findCharacterSequences = {
|
||||
*surrogatePairWithoutUFlag(chars) {
|
||||
for (const [index, char] of chars.entries()) {
|
||||
const previous = chars[index - 1];
|
||||
const hasCharacterSequence = {
|
||||
surrogatePairWithoutUFlag(chars) {
|
||||
return chars.some((c, i) => {
|
||||
if (i === 0) {
|
||||
return false;
|
||||
}
|
||||
const c1 = chars[i - 1];
|
||||
|
||||
if (
|
||||
previous &&
|
||||
char &&
|
||||
isSurrogatePair(previous.value, char.value) &&
|
||||
!isUnicodeCodePointEscape(previous) &&
|
||||
!isUnicodeCodePointEscape(char)
|
||||
) {
|
||||
yield [previous, char];
|
||||
}
|
||||
}
|
||||
},
|
||||
return (
|
||||
isSurrogatePair(c1.value, c.value) &&
|
||||
!isUnicodeCodePointEscape(c1) &&
|
||||
!isUnicodeCodePointEscape(c)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
*surrogatePair(chars) {
|
||||
for (const [index, char] of chars.entries()) {
|
||||
const previous = chars[index - 1];
|
||||
surrogatePair(chars) {
|
||||
return chars.some((c, i) => {
|
||||
if (i === 0) {
|
||||
return false;
|
||||
}
|
||||
const c1 = chars[i - 1];
|
||||
|
||||
if (
|
||||
previous &&
|
||||
char &&
|
||||
isSurrogatePair(previous.value, char.value) &&
|
||||
(isUnicodeCodePointEscape(previous) ||
|
||||
isUnicodeCodePointEscape(char))
|
||||
) {
|
||||
yield [previous, char];
|
||||
}
|
||||
}
|
||||
},
|
||||
return (
|
||||
isSurrogatePair(c1.value, c.value) &&
|
||||
(
|
||||
isUnicodeCodePointEscape(c1) ||
|
||||
isUnicodeCodePointEscape(c)
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
*combiningClass(chars, unfilteredChars) {
|
||||
/*
|
||||
* When `allowEscape` is `true`, a combined character should only be allowed if the combining mark appears as an escape sequence.
|
||||
* This means that the base character should be considered even if it's escaped.
|
||||
*/
|
||||
for (const [index, char] of chars.entries()) {
|
||||
const previous = unfilteredChars[index - 1];
|
||||
combiningClass(chars) {
|
||||
return chars.some((c, i) => (
|
||||
i !== 0 &&
|
||||
isCombiningCharacter(c.value) &&
|
||||
!isCombiningCharacter(chars[i - 1].value)
|
||||
));
|
||||
},
|
||||
|
||||
if (
|
||||
previous &&
|
||||
char &&
|
||||
isCombiningCharacter(char.value) &&
|
||||
!isCombiningCharacter(previous.value)
|
||||
) {
|
||||
yield [previous, char];
|
||||
}
|
||||
}
|
||||
},
|
||||
emojiModifier(chars) {
|
||||
return chars.some((c, i) => (
|
||||
i !== 0 &&
|
||||
isEmojiModifier(c.value) &&
|
||||
!isEmojiModifier(chars[i - 1].value)
|
||||
));
|
||||
},
|
||||
|
||||
*emojiModifier(chars) {
|
||||
for (const [index, char] of chars.entries()) {
|
||||
const previous = chars[index - 1];
|
||||
regionalIndicatorSymbol(chars) {
|
||||
return chars.some((c, i) => (
|
||||
i !== 0 &&
|
||||
isRegionalIndicatorSymbol(c.value) &&
|
||||
isRegionalIndicatorSymbol(chars[i - 1].value)
|
||||
));
|
||||
},
|
||||
|
||||
if (
|
||||
previous &&
|
||||
char &&
|
||||
isEmojiModifier(char.value) &&
|
||||
!isEmojiModifier(previous.value)
|
||||
) {
|
||||
yield [previous, char];
|
||||
}
|
||||
}
|
||||
},
|
||||
zwj(chars) {
|
||||
const lastIndex = chars.length - 1;
|
||||
|
||||
*regionalIndicatorSymbol(chars) {
|
||||
for (const [index, char] of chars.entries()) {
|
||||
const previous = chars[index - 1];
|
||||
|
||||
if (
|
||||
previous &&
|
||||
char &&
|
||||
isRegionalIndicatorSymbol(char.value) &&
|
||||
isRegionalIndicatorSymbol(previous.value)
|
||||
) {
|
||||
yield [previous, char];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
*zwj(chars) {
|
||||
let sequence = null;
|
||||
|
||||
for (const [index, char] of chars.entries()) {
|
||||
const previous = chars[index - 1];
|
||||
const next = chars[index + 1];
|
||||
|
||||
if (
|
||||
previous &&
|
||||
char &&
|
||||
next &&
|
||||
char.value === 0x200d &&
|
||||
previous.value !== 0x200d &&
|
||||
next.value !== 0x200d
|
||||
) {
|
||||
if (sequence) {
|
||||
if (sequence.at(-1) === previous) {
|
||||
sequence.push(char, next); // append to the sequence
|
||||
} else {
|
||||
yield sequence;
|
||||
sequence = chars.slice(index - 1, index + 2);
|
||||
}
|
||||
} else {
|
||||
sequence = chars.slice(index - 1, index + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sequence) {
|
||||
yield sequence;
|
||||
}
|
||||
},
|
||||
return chars.some((c, i) => (
|
||||
i !== 0 &&
|
||||
i !== lastIndex &&
|
||||
c.value === 0x200d &&
|
||||
chars[i - 1].value !== 0x200d &&
|
||||
chars[i + 1].value !== 0x200d
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
const kinds = Object.keys(findCharacterSequences);
|
||||
|
||||
/**
|
||||
* Gets the value of the given node if it's a static value other than a regular expression object,
|
||||
* or the node's `regex` property.
|
||||
* The purpose of this method is to provide a replacement for `getStaticValue` in environments where certain regular expressions cannot be evaluated.
|
||||
* A known example is Node.js 18 which does not support the `v` flag.
|
||||
* Calling `getStaticValue` on a regular expression node with the `v` flag on Node.js 18 always returns `null`.
|
||||
* A limitation of this method is that it can only detect a regular expression if the specified node is itself a regular expression literal node.
|
||||
* @param {ASTNode | undefined} node The node to be inspected.
|
||||
* @param {Scope} initialScope Scope to start finding variables. This function tries to resolve identifier references which are in the given scope.
|
||||
* @returns {{ value: any } | { regex: { pattern: string, flags: string } } | null} The static value of the node, or `null`.
|
||||
*/
|
||||
function getStaticValueOrRegex(node, initialScope) {
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
if (node.type === "Literal" && node.regex) {
|
||||
return { regex: node.regex };
|
||||
}
|
||||
|
||||
const staticValue = getStaticValue(node, initialScope);
|
||||
|
||||
if (staticValue?.value instanceof RegExp) {
|
||||
return null;
|
||||
}
|
||||
return staticValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a specified regexpp character is represented as an acceptable escape sequence.
|
||||
* This function requires the source text of the character to be known.
|
||||
* @param {Character} char Character to check.
|
||||
* @param {string} charSource Source text of the character to check.
|
||||
* @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence.
|
||||
*/
|
||||
function checkForAcceptableEscape(char, charSource) {
|
||||
if (!charSource.startsWith("\\")) {
|
||||
return false;
|
||||
}
|
||||
const match = /(?<=^\\+).$/su.exec(charSource);
|
||||
|
||||
return match?.[0] !== String.fromCodePoint(char.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a specified regexpp character is represented as an acceptable escape sequence.
|
||||
* This function works with characters that are produced by a string or template literal.
|
||||
* It requires the source text and the CodeUnit list of the literal to be known.
|
||||
* @param {Character} char Character to check.
|
||||
* @param {string} nodeSource Source text of the string or template literal that produces the character.
|
||||
* @param {CodeUnit[]} codeUnits List of CodeUnit objects of the literal that produces the character.
|
||||
* @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence.
|
||||
*/
|
||||
function checkForAcceptableEscapeInString(char, nodeSource, codeUnits) {
|
||||
const firstIndex = char.start;
|
||||
const lastIndex = char.end - 1;
|
||||
const start = codeUnits[firstIndex].start;
|
||||
const end = codeUnits[lastIndex].end;
|
||||
const charSource = nodeSource.slice(start, end);
|
||||
|
||||
return checkForAcceptableEscape(char, charSource);
|
||||
}
|
||||
const kinds = Object.keys(hasCharacterSequence);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../types').Rule.RuleModule} */
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
meta: {
|
||||
type: "problem",
|
||||
|
||||
defaultOptions: [
|
||||
{
|
||||
allowEscape: false,
|
||||
},
|
||||
],
|
||||
docs: {
|
||||
description: "Disallow characters which are made with multiple code points in character class syntax",
|
||||
recommended: true,
|
||||
url: "https://eslint.org/docs/latest/rules/no-misleading-character-class"
|
||||
},
|
||||
|
||||
docs: {
|
||||
description:
|
||||
"Disallow characters which are made with multiple code points in character class syntax",
|
||||
recommended: true,
|
||||
url: "https://eslint.org/docs/latest/rules/no-misleading-character-class",
|
||||
},
|
||||
hasSuggestions: true,
|
||||
|
||||
hasSuggestions: true,
|
||||
schema: [],
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
allowEscape: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
messages: {
|
||||
surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
|
||||
surrogatePair: "Unexpected surrogate pair in character class.",
|
||||
combiningClass: "Unexpected combined character in character class.",
|
||||
emojiModifier: "Unexpected modified Emoji in character class.",
|
||||
regionalIndicatorSymbol: "Unexpected national flag in character class.",
|
||||
zwj: "Unexpected joined character sequence in character class.",
|
||||
suggestUnicodeFlag: "Add unicode 'u' flag to regex."
|
||||
}
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const parser = new RegExpParser();
|
||||
|
||||
messages: {
|
||||
surrogatePairWithoutUFlag:
|
||||
"Unexpected surrogate pair in character class. Use 'u' flag.",
|
||||
surrogatePair: "Unexpected surrogate pair in character class.",
|
||||
combiningClass: "Unexpected combined character in character class.",
|
||||
emojiModifier: "Unexpected modified Emoji in character class.",
|
||||
regionalIndicatorSymbol:
|
||||
"Unexpected national flag in character class.",
|
||||
zwj: "Unexpected joined character sequence in character class.",
|
||||
suggestUnicodeFlag: "Add unicode 'u' flag to regex.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const [{ allowEscape }] = context.options;
|
||||
const sourceCode = context.sourceCode;
|
||||
const parser = new RegExpParser();
|
||||
const checkedPatternNodes = new Set();
|
||||
/**
|
||||
* Verify a given regular expression.
|
||||
* @param {Node} node The node to report.
|
||||
* @param {string} pattern The regular expression pattern to verify.
|
||||
* @param {string} flags The flags of the regular expression.
|
||||
* @param {Function} unicodeFixer Fixer for missing "u" flag.
|
||||
* @returns {void}
|
||||
*/
|
||||
function verify(node, pattern, flags, unicodeFixer) {
|
||||
let patternNode;
|
||||
|
||||
/**
|
||||
* Verify a given regular expression.
|
||||
* @param {Node} node The node to report.
|
||||
* @param {string} pattern The regular expression pattern to verify.
|
||||
* @param {string} flags The flags of the regular expression.
|
||||
* @param {Function} unicodeFixer Fixer for missing "u" flag.
|
||||
* @returns {void}
|
||||
*/
|
||||
function verify(node, pattern, flags, unicodeFixer) {
|
||||
let patternNode;
|
||||
try {
|
||||
patternNode = parser.parsePattern(
|
||||
pattern,
|
||||
0,
|
||||
pattern.length,
|
||||
{
|
||||
unicode: flags.includes("u"),
|
||||
unicodeSets: flags.includes("v")
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
|
||||
try {
|
||||
patternNode = parser.parsePattern(pattern, 0, pattern.length, {
|
||||
unicode: flags.includes("u"),
|
||||
unicodeSets: flags.includes("v"),
|
||||
});
|
||||
} catch {
|
||||
// Ignore regular expressions with syntax errors
|
||||
return;
|
||||
}
|
||||
// Ignore regular expressions with syntax errors
|
||||
return;
|
||||
}
|
||||
|
||||
let codeUnits = null;
|
||||
const foundKinds = new Set();
|
||||
|
||||
/**
|
||||
* Checks whether a specified regexpp character is represented as an acceptable escape sequence.
|
||||
* For the purposes of this rule, an escape sequence is considered acceptable if it consists of one or more backslashes followed by the character being escaped.
|
||||
* @param {Character} char Character to check.
|
||||
* @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence.
|
||||
*/
|
||||
function isAcceptableEscapeSequence(char) {
|
||||
if (node.type === "Literal" && node.regex) {
|
||||
return checkForAcceptableEscape(char, char.raw);
|
||||
}
|
||||
if (node.type === "Literal" && typeof node.value === "string") {
|
||||
const nodeSource = node.raw;
|
||||
visitRegExpAST(patternNode, {
|
||||
onCharacterClassEnter(ccNode) {
|
||||
for (const chars of iterateCharacterSequence(ccNode.elements)) {
|
||||
for (const kind of kinds) {
|
||||
if (hasCharacterSequence[kind](chars)) {
|
||||
foundKinds.add(kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
codeUnits ??= parseStringLiteral(nodeSource);
|
||||
for (const kind of foundKinds) {
|
||||
let suggest;
|
||||
|
||||
return checkForAcceptableEscapeInString(
|
||||
char,
|
||||
nodeSource,
|
||||
codeUnits,
|
||||
);
|
||||
}
|
||||
if (astUtils.isStaticTemplateLiteral(node)) {
|
||||
const nodeSource = sourceCode.getText(node);
|
||||
if (kind === "surrogatePairWithoutUFlag") {
|
||||
suggest = [{
|
||||
messageId: "suggestUnicodeFlag",
|
||||
fix: unicodeFixer
|
||||
}];
|
||||
}
|
||||
|
||||
codeUnits ??= parseTemplateToken(nodeSource);
|
||||
context.report({
|
||||
node,
|
||||
messageId: kind,
|
||||
suggest
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return checkForAcceptableEscapeInString(
|
||||
char,
|
||||
nodeSource,
|
||||
codeUnits,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
"Literal[regex]"(node) {
|
||||
verify(node, node.regex.pattern, node.regex.flags, fixer => {
|
||||
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const foundKindMatches = new Map();
|
||||
return fixer.insertTextAfter(node, "u");
|
||||
});
|
||||
},
|
||||
"Program"(node) {
|
||||
const scope = sourceCode.getScope(node);
|
||||
const tracker = new ReferenceTracker(scope);
|
||||
|
||||
visitRegExpAST(patternNode, {
|
||||
onCharacterClassEnter(ccNode) {
|
||||
for (const unfilteredChars of iterateCharacterSequence(
|
||||
ccNode.elements,
|
||||
)) {
|
||||
let chars;
|
||||
/*
|
||||
* Iterate calls of RegExp.
|
||||
* E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
|
||||
* `const {RegExp: a} = window; new a()`, etc...
|
||||
*/
|
||||
for (const { node: refNode } of tracker.iterateGlobalReferences({
|
||||
RegExp: { [CALL]: true, [CONSTRUCT]: true }
|
||||
})) {
|
||||
const [patternNode, flagsNode] = refNode.arguments;
|
||||
const pattern = getStringIfConstant(patternNode, scope);
|
||||
const flags = getStringIfConstant(flagsNode, scope);
|
||||
|
||||
if (allowEscape) {
|
||||
// Replace escape sequences with null to avoid having them flagged.
|
||||
chars = unfilteredChars.map(char =>
|
||||
isAcceptableEscapeSequence(char) ? null : char,
|
||||
);
|
||||
} else {
|
||||
chars = unfilteredChars;
|
||||
}
|
||||
for (const kind of kinds) {
|
||||
const matches = findCharacterSequences[kind](
|
||||
chars,
|
||||
unfilteredChars,
|
||||
);
|
||||
if (typeof pattern === "string") {
|
||||
verify(refNode, pattern, flags || "", fixer => {
|
||||
|
||||
if (foundKindMatches.has(kind)) {
|
||||
foundKindMatches.get(kind).push(...matches);
|
||||
} else {
|
||||
foundKindMatches.set(kind, [...matches]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the report loc(s) for a range of matches.
|
||||
* Only literals and expression-less templates generate granular errors.
|
||||
* @param {Character[][]} matches Lists of individual characters being reported on.
|
||||
* @returns {Location[]} locs for context.report.
|
||||
* @see https://github.com/eslint/eslint/pull/17515
|
||||
*/
|
||||
function getNodeReportLocations(matches) {
|
||||
if (
|
||||
!astUtils.isStaticTemplateLiteral(node) &&
|
||||
node.type !== "Literal"
|
||||
) {
|
||||
return matches.length ? [node.loc] : [];
|
||||
}
|
||||
return matches.map(chars => {
|
||||
const firstIndex = chars[0].start;
|
||||
const lastIndex = chars.at(-1).end - 1;
|
||||
let start;
|
||||
let end;
|
||||
if (refNode.arguments.length === 1) {
|
||||
const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis
|
||||
|
||||
if (node.type === "TemplateLiteral") {
|
||||
const source = sourceCode.getText(node);
|
||||
const offset = node.range[0];
|
||||
return fixer.insertTextAfter(
|
||||
penultimateToken,
|
||||
astUtils.isCommaToken(penultimateToken)
|
||||
? ' "u",'
|
||||
: ', "u"'
|
||||
);
|
||||
}
|
||||
|
||||
codeUnits ??= parseTemplateToken(source);
|
||||
start = offset + codeUnits[firstIndex].start;
|
||||
end = offset + codeUnits[lastIndex].end;
|
||||
} else if (typeof node.value === "string") {
|
||||
// String Literal
|
||||
const source = node.raw;
|
||||
const offset = node.range[0];
|
||||
if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
|
||||
const range = [flagsNode.range[0], flagsNode.range[1] - 1];
|
||||
|
||||
codeUnits ??= parseStringLiteral(source);
|
||||
start = offset + codeUnits[firstIndex].start;
|
||||
end = offset + codeUnits[lastIndex].end;
|
||||
} else {
|
||||
// RegExp Literal
|
||||
const offset = node.range[0] + 1; // Add 1 to skip the leading slash.
|
||||
return fixer.insertTextAfterRange(range, "u");
|
||||
}
|
||||
|
||||
start = offset + firstIndex;
|
||||
end = offset + lastIndex + 1;
|
||||
}
|
||||
|
||||
return {
|
||||
start: sourceCode.getLocFromIndex(start),
|
||||
end: sourceCode.getLocFromIndex(end),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
for (const [kind, matches] of foundKindMatches) {
|
||||
let suggest;
|
||||
|
||||
if (kind === "surrogatePairWithoutUFlag") {
|
||||
suggest = [
|
||||
{
|
||||
messageId: "suggestUnicodeFlag",
|
||||
fix: unicodeFixer,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const locs = getNodeReportLocations(matches);
|
||||
|
||||
for (const loc of locs) {
|
||||
context.report({
|
||||
node,
|
||||
loc,
|
||||
messageId: kind,
|
||||
suggest,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"Literal[regex]"(node) {
|
||||
if (checkedPatternNodes.has(node)) {
|
||||
return;
|
||||
}
|
||||
verify(node, node.regex.pattern, node.regex.flags, fixer => {
|
||||
if (
|
||||
!isValidWithUnicodeFlag(
|
||||
context.languageOptions.ecmaVersion,
|
||||
node.regex.pattern,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.insertTextAfter(node, "u");
|
||||
});
|
||||
},
|
||||
Program(node) {
|
||||
const scope = sourceCode.getScope(node);
|
||||
const tracker = new ReferenceTracker(scope);
|
||||
|
||||
/*
|
||||
* Iterate calls of RegExp.
|
||||
* E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
|
||||
* `const {RegExp: a} = window; new a()`, etc...
|
||||
*/
|
||||
for (const { node: refNode } of tracker.iterateGlobalReferences(
|
||||
{
|
||||
RegExp: { [CALL]: true, [CONSTRUCT]: true },
|
||||
},
|
||||
)) {
|
||||
let pattern, flags;
|
||||
const [patternNode, flagsNode] = refNode.arguments;
|
||||
const evaluatedPattern = getStaticValueOrRegex(
|
||||
patternNode,
|
||||
scope,
|
||||
);
|
||||
|
||||
if (!evaluatedPattern) {
|
||||
continue;
|
||||
}
|
||||
if (flagsNode) {
|
||||
if (evaluatedPattern.regex) {
|
||||
pattern = evaluatedPattern.regex.pattern;
|
||||
checkedPatternNodes.add(patternNode);
|
||||
} else {
|
||||
pattern = String(evaluatedPattern.value);
|
||||
}
|
||||
flags = getStringIfConstant(flagsNode, scope);
|
||||
} else {
|
||||
if (evaluatedPattern.regex) {
|
||||
continue;
|
||||
}
|
||||
pattern = String(evaluatedPattern.value);
|
||||
flags = "";
|
||||
}
|
||||
|
||||
if (typeof flags === "string") {
|
||||
verify(patternNode, pattern, flags, fixer => {
|
||||
if (
|
||||
!isValidWithUnicodeFlag(
|
||||
context.languageOptions.ecmaVersion,
|
||||
pattern,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (refNode.arguments.length === 1) {
|
||||
const penultimateToken =
|
||||
sourceCode.getLastToken(refNode, {
|
||||
skip: 1,
|
||||
}); // skip closing parenthesis
|
||||
|
||||
return fixer.insertTextAfter(
|
||||
penultimateToken,
|
||||
astUtils.isCommaToken(penultimateToken)
|
||||
? ' "u",'
|
||||
: ', "u"',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(flagsNode.type === "Literal" &&
|
||||
typeof flagsNode.value === "string") ||
|
||||
flagsNode.type === "TemplateLiteral"
|
||||
) {
|
||||
const range = [
|
||||
flagsNode.range[0],
|
||||
flagsNode.range[1] - 1,
|
||||
];
|
||||
|
||||
return fixer.insertTextAfterRange(range, "u");
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user