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.
179 lines
5.8 KiB
JavaScript
179 lines
5.8 KiB
JavaScript
/**
|
|
* @fileoverview Rule to enforce requiring named capture groups in regular expression.
|
|
* @author Pig Fang <https://github.com/g-plane>
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Requirements
|
|
//------------------------------------------------------------------------------
|
|
|
|
const {
|
|
CALL,
|
|
CONSTRUCT,
|
|
ReferenceTracker,
|
|
getStringIfConstant
|
|
} = require("@eslint-community/eslint-utils");
|
|
const regexpp = require("@eslint-community/regexpp");
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helpers
|
|
//------------------------------------------------------------------------------
|
|
|
|
const parser = new regexpp.RegExpParser();
|
|
|
|
/**
|
|
* Creates fixer suggestions for the regex, if statically determinable.
|
|
* @param {number} groupStart Starting index of the regex group.
|
|
* @param {string} pattern The regular expression pattern to be checked.
|
|
* @param {string} rawText Source text of the regexNode.
|
|
* @param {ASTNode} regexNode AST node which contains the regular expression.
|
|
* @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
|
|
*/
|
|
function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
|
|
switch (regexNode.type) {
|
|
case "Literal":
|
|
if (typeof regexNode.value === "string" && rawText.includes("\\")) {
|
|
return null;
|
|
}
|
|
break;
|
|
case "TemplateLiteral":
|
|
if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) {
|
|
return null;
|
|
}
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
const start = regexNode.range[0] + groupStart + 2;
|
|
|
|
return [
|
|
{
|
|
fix(fixer) {
|
|
const existingTemps = pattern.match(/temp\d+/gu) || [];
|
|
const highestTempCount = existingTemps.reduce(
|
|
(previous, next) =>
|
|
Math.max(previous, Number(next.slice("temp".length))),
|
|
0
|
|
);
|
|
|
|
return fixer.insertTextBeforeRange(
|
|
[start, start],
|
|
`?<temp${highestTempCount + 1}>`
|
|
);
|
|
},
|
|
messageId: "addGroupName"
|
|
},
|
|
{
|
|
fix(fixer) {
|
|
return fixer.insertTextBeforeRange(
|
|
[start, start],
|
|
"?:"
|
|
);
|
|
},
|
|
messageId: "addNonCapture"
|
|
}
|
|
];
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//------------------------------------------------------------------------------
|
|
|
|
/** @type {import('../shared/types').Rule} */
|
|
module.exports = {
|
|
meta: {
|
|
type: "suggestion",
|
|
|
|
docs: {
|
|
description: "Enforce using named capture group in regular expression",
|
|
recommended: false,
|
|
url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group"
|
|
},
|
|
|
|
hasSuggestions: true,
|
|
|
|
schema: [],
|
|
|
|
messages: {
|
|
addGroupName: "Add name to capture group.",
|
|
addNonCapture: "Convert group to non-capturing.",
|
|
required: "Capture group '{{group}}' should be converted to a named or non-capturing group."
|
|
}
|
|
},
|
|
|
|
create(context) {
|
|
const sourceCode = context.sourceCode;
|
|
|
|
/**
|
|
* Function to check regular expression.
|
|
* @param {string} pattern The regular expression pattern to be checked.
|
|
* @param {ASTNode} node AST node which contains the regular expression or a call/new expression.
|
|
* @param {ASTNode} regexNode AST node which contains the regular expression.
|
|
* @param {string|null} flags The regular expression flags to be checked.
|
|
* @returns {void}
|
|
*/
|
|
function checkRegex(pattern, node, regexNode, flags) {
|
|
let ast;
|
|
|
|
try {
|
|
ast = parser.parsePattern(pattern, 0, pattern.length, {
|
|
unicode: Boolean(flags && flags.includes("u")),
|
|
unicodeSets: Boolean(flags && flags.includes("v"))
|
|
});
|
|
} catch {
|
|
|
|
// ignore regex syntax errors
|
|
return;
|
|
}
|
|
|
|
regexpp.visitRegExpAST(ast, {
|
|
onCapturingGroupEnter(group) {
|
|
if (!group.name) {
|
|
const rawText = sourceCode.getText(regexNode);
|
|
const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode);
|
|
|
|
context.report({
|
|
node,
|
|
messageId: "required",
|
|
data: {
|
|
group: group.raw
|
|
},
|
|
suggest
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
Literal(node) {
|
|
if (node.regex) {
|
|
checkRegex(node.regex.pattern, node, node, node.regex.flags);
|
|
}
|
|
},
|
|
Program(node) {
|
|
const scope = sourceCode.getScope(node);
|
|
const tracker = new ReferenceTracker(scope);
|
|
const traceMap = {
|
|
RegExp: {
|
|
[CALL]: true,
|
|
[CONSTRUCT]: true
|
|
}
|
|
};
|
|
|
|
for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) {
|
|
const regex = getStringIfConstant(refNode.arguments[0]);
|
|
const flags = getStringIfConstant(refNode.arguments[1]);
|
|
|
|
if (regex) {
|
|
checkRegex(regex, refNode, refNode.arguments[0], flags);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|