[backend] fix: 修复异常处理和类型转换问题

This commit is contained in:
xsl
2026-01-26 11:53:40 +08:00
parent 7ccc2a6ac6
commit 83e05bf85f
28639 changed files with 2506458 additions and 93 deletions
+737
View File
@@ -1881,3 +1881,740 @@ function replaceS(matcher, str, replacement) {
* @returns {string} The replaced string.
*/
function replaceF(matcher, str, replace) {
const chunks = [];
let index = 0;
for (const match of matcher.execAll(str)) {
chunks.push(str.slice(index, match.index));
chunks.push(
String(
replace(
.../** @type {[string, ...string[]]} */ (
/** @type {string[]} */ (match)
),
match.index,
match.input,
),
),
);
index = match.index + match[0].length;
}
chunks.push(str.slice(index));
return chunks.join("")
}
/**
* The class to find patterns as considering escape sequences.
*/
class PatternMatcher {
/**
* Initialize this matcher.
* @param {RegExp} pattern The pattern to match.
* @param {{escaped?:boolean}} [options] The options.
*/
constructor(pattern, options = {}) {
const { escaped = false } = options;
if (!(pattern instanceof RegExp)) {
throw new TypeError("'pattern' should be a RegExp instance.")
}
if (!pattern.flags.includes("g")) {
throw new Error("'pattern' should contains 'g' flag.")
}
internal.set(this, {
pattern: new RegExp(pattern.source, pattern.flags),
escaped: Boolean(escaped),
});
}
/**
* Find the pattern in a given string.
* @param {string} str The string to find.
* @returns {IterableIterator<RegExpExecArray>} The iterator which iterate the matched information.
*/
*execAll(str) {
const { pattern, escaped } =
/** @type {{pattern:RegExp,escaped:boolean}} */ (internal.get(this));
let match = null;
let lastIndex = 0;
pattern.lastIndex = 0;
while ((match = pattern.exec(str)) != null) {
if (escaped || !isEscaped(str, match.index)) {
lastIndex = pattern.lastIndex;
yield match;
pattern.lastIndex = lastIndex;
}
}
}
/**
* Check whether the pattern is found in a given string.
* @param {string} str The string to check.
* @returns {boolean} `true` if the pattern was found in the string.
*/
test(str) {
const it = this.execAll(str);
const ret = it.next();
return !ret.done
}
/**
* Replace a given string.
* @param {string} str The string to be replaced.
* @param {(string|((...strs:string[])=>string))} replacer The string or function to replace. This is the same as the 2nd argument of `String.prototype.replace`.
* @returns {string} The replaced string.
*/
[Symbol.replace](str, replacer) {
return typeof replacer === "function"
? replaceF(this, String(str), replacer)
: replaceS(this, String(str), String(replacer))
}
}
/** @typedef {import("eslint").Scope.Scope} Scope */
/** @typedef {import("eslint").Scope.Variable} Variable */
/** @typedef {import("eslint").Rule.Node} RuleNode */
/** @typedef {import("estree").Node} Node */
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").Pattern} Pattern */
/** @typedef {import("estree").Identifier} Identifier */
/** @typedef {import("estree").SimpleCallExpression} CallExpression */
/** @typedef {import("estree").Program} Program */
/** @typedef {import("estree").ImportDeclaration} ImportDeclaration */
/** @typedef {import("estree").ExportAllDeclaration} ExportAllDeclaration */
/** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */
/** @typedef {import("estree").ExportNamedDeclaration} ExportNamedDeclaration */
/** @typedef {import("estree").ImportSpecifier} ImportSpecifier */
/** @typedef {import("estree").ImportDefaultSpecifier} ImportDefaultSpecifier */
/** @typedef {import("estree").ImportNamespaceSpecifier} ImportNamespaceSpecifier */
/** @typedef {import("estree").ExportSpecifier} ExportSpecifier */
/** @typedef {import("estree").Property} Property */
/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */
/** @typedef {import("estree").Literal} Literal */
/** @typedef {import("@typescript-eslint/types").TSESTree.Node} TSESTreeNode */
/** @typedef {import("./types.mjs").ReferenceTrackerOptions} ReferenceTrackerOptions */
/**
* @template T
* @typedef {import("./types.mjs").TraceMap<T>} TraceMap
*/
/**
* @template T
* @typedef {import("./types.mjs").TraceMapObject<T>} TraceMapObject
*/
/**
* @template T
* @typedef {import("./types.mjs").TrackedReferences<T>} TrackedReferences
*/
const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
/**
* Check whether a given node is an import node or not.
* @param {Node} node
* @returns {node is ImportDeclaration|ExportAllDeclaration|ExportNamedDeclaration&{source: Literal}} `true` if the node is an import node.
*/
function isHasSource(node) {
return (
IMPORT_TYPE.test(node.type) &&
/** @type {ImportDeclaration|ExportAllDeclaration|ExportNamedDeclaration} */ (
node
).source != null
)
}
const has =
/** @type {<T>(traceMap: TraceMap<unknown>, v: T) => v is (string extends T ? string : T)} */ (
Function.call.bind(Object.hasOwnProperty)
);
const READ = Symbol("read");
const CALL = Symbol("call");
const CONSTRUCT = Symbol("construct");
const ESM = Symbol("esm");
const requireCall = { require: { [CALL]: true } };
/**
* Check whether a given variable is modified or not.
* @param {Variable|undefined} variable The variable to check.
* @returns {boolean} `true` if the variable is modified.
*/
function isModifiedGlobal(variable) {
return (
variable == null ||
variable.defs.length !== 0 ||
variable.references.some((r) => r.isWrite())
)
}
/**
* Check if the value of a given node is passed through to the parent syntax as-is.
* For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through.
* @param {Node} node A node to check.
* @returns {node is RuleNode & {parent: Expression}} `true` if the node is passed through.
*/
function isPassThrough(node) {
const parent = /** @type {TSESTreeNode} */ (node).parent;
if (parent) {
switch (parent.type) {
case "ConditionalExpression":
return parent.consequent === node || parent.alternate === node
case "LogicalExpression":
return true
case "SequenceExpression":
return (
parent.expressions[parent.expressions.length - 1] === node
)
case "ChainExpression":
return true
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSTypeAssertion":
case "TSNonNullExpression":
case "TSInstantiationExpression":
return true
default:
return false
}
}
return false
}
/**
* The reference tracker.
*/
class ReferenceTracker {
/**
* Initialize this tracker.
* @param {Scope} globalScope The global scope.
* @param {object} [options] The options.
* @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules.
* @param {string[]} [options.globalObjectNames=["global","globalThis","self","window"]] The variable names for Global Object.
*/
constructor(globalScope, options = {}) {
const {
mode = "strict",
globalObjectNames = ["global", "globalThis", "self", "window"],
} = options;
/** @private @type {Variable[]} */
this.variableStack = [];
/** @private */
this.globalScope = globalScope;
/** @private */
this.mode = mode;
/** @private */
this.globalObjectNames = globalObjectNames.slice(0);
}
/**
* Iterate the references of global variables.
* @template T
* @param {TraceMap<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
*iterateGlobalReferences(traceMap) {
for (const key of Object.keys(traceMap)) {
const nextTraceMap = traceMap[key];
const path = [key];
const variable = this.globalScope.set.get(key);
if (isModifiedGlobal(variable)) {
continue
}
yield* this._iterateVariableReferences(
/** @type {Variable} */ (variable),
path,
nextTraceMap,
true,
);
}
for (const key of this.globalObjectNames) {
/** @type {string[]} */
const path = [];
const variable = this.globalScope.set.get(key);
if (isModifiedGlobal(variable)) {
continue
}
yield* this._iterateVariableReferences(
/** @type {Variable} */ (variable),
path,
traceMap,
false,
);
}
}
/**
* Iterate the references of CommonJS modules.
* @template T
* @param {TraceMap<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
*iterateCjsReferences(traceMap) {
for (const { node } of this.iterateGlobalReferences(requireCall)) {
const key = getStringIfConstant(
/** @type {CallExpression} */ (node).arguments[0],
);
if (key == null || !has(traceMap, key)) {
continue
}
const nextTraceMap = traceMap[key];
const path = [key];
if (nextTraceMap[READ]) {
yield {
node,
path,
type: READ,
info: nextTraceMap[READ],
};
}
yield* this._iteratePropertyReferences(
/** @type {CallExpression} */ (node),
path,
nextTraceMap,
);
}
}
/**
* Iterate the references of ES modules.
* @template T
* @param {TraceMap<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
*iterateEsmReferences(traceMap) {
const programNode = /** @type {Program} */ (this.globalScope.block);
for (const node of programNode.body) {
if (!isHasSource(node)) {
continue
}
const moduleId = /** @type {string} */ (node.source.value);
if (!has(traceMap, moduleId)) {
continue
}
const nextTraceMap = traceMap[moduleId];
const path = [moduleId];
if (nextTraceMap[READ]) {
yield {
// eslint-disable-next-line object-shorthand -- apply type
node: /** @type {RuleNode} */ (node),
path,
type: READ,
info: nextTraceMap[READ],
};
}
if (node.type === "ExportAllDeclaration") {
for (const key of Object.keys(nextTraceMap)) {
const exportTraceMap = nextTraceMap[key];
if (exportTraceMap[READ]) {
yield {
// eslint-disable-next-line object-shorthand -- apply type
node: /** @type {RuleNode} */ (node),
path: path.concat(key),
type: READ,
info: exportTraceMap[READ],
};
}
}
} else {
for (const specifier of node.specifiers) {
const esm = has(nextTraceMap, ESM);
const it = this._iterateImportReferences(
specifier,
path,
esm
? nextTraceMap
: this.mode === "legacy"
? { default: nextTraceMap, ...nextTraceMap }
: { default: nextTraceMap },
);
if (esm) {
yield* it;
} else {
for (const report of it) {
report.path = report.path.filter(exceptDefault);
if (
report.path.length >= 2 ||
report.type !== READ
) {
yield report;
}
}
}
}
}
}
}
/**
* Iterate the property references for a given expression AST node.
* @template T
* @param {Expression} node The expression AST node to iterate property references.
* @param {TraceMap<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate property references.
*/
*iteratePropertyReferences(node, traceMap) {
yield* this._iteratePropertyReferences(node, [], traceMap);
}
/**
* Iterate the references for a given variable.
* @private
* @template T
* @param {Variable} variable The variable to iterate that references.
* @param {string[]} path The current path.
* @param {TraceMapObject<T>} traceMap The trace map.
* @param {boolean} shouldReport = The flag to report those references.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
*_iterateVariableReferences(variable, path, traceMap, shouldReport) {
if (this.variableStack.includes(variable)) {
return
}
this.variableStack.push(variable);
try {
for (const reference of variable.references) {
if (!reference.isRead()) {
continue
}
const node = /** @type {RuleNode & Identifier} */ (
reference.identifier
);
if (shouldReport && traceMap[READ]) {
yield { node, path, type: READ, info: traceMap[READ] };
}
yield* this._iteratePropertyReferences(node, path, traceMap);
}
} finally {
this.variableStack.pop();
}
}
/**
* Iterate the references for a given AST node.
* @private
* @template T
* @param {Expression} rootNode The AST node to iterate references.
* @param {string[]} path The current path.
* @param {TraceMapObject<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
//eslint-disable-next-line complexity
*_iteratePropertyReferences(rootNode, path, traceMap) {
let node = rootNode;
while (isPassThrough(node)) {
node = node.parent;
}
const parent = /** @type {RuleNode} */ (node).parent;
if (!parent) {
return
}
if (parent.type === "MemberExpression") {
if (parent.object === node) {
const key = getPropertyName(parent);
if (key == null || !has(traceMap, key)) {
return
}
path = path.concat(key); //eslint-disable-line no-param-reassign
const nextTraceMap = traceMap[key];
if (nextTraceMap[READ]) {
yield {
node: parent,
path,
type: READ,
info: nextTraceMap[READ],
};
}
yield* this._iteratePropertyReferences(
parent,
path,
nextTraceMap,
);
}
return
}
if (parent.type === "CallExpression") {
if (parent.callee === node && traceMap[CALL]) {
yield { node: parent, path, type: CALL, info: traceMap[CALL] };
}
return
}
if (parent.type === "NewExpression") {
if (parent.callee === node && traceMap[CONSTRUCT]) {
yield {
node: parent,
path,
type: CONSTRUCT,
info: traceMap[CONSTRUCT],
};
}
return
}
if (parent.type === "AssignmentExpression") {
if (parent.right === node) {
yield* this._iterateLhsReferences(parent.left, path, traceMap);
yield* this._iteratePropertyReferences(parent, path, traceMap);
}
return
}
if (parent.type === "AssignmentPattern") {
if (parent.right === node) {
yield* this._iterateLhsReferences(parent.left, path, traceMap);
}
return
}
if (parent.type === "VariableDeclarator") {
if (parent.init === node) {
yield* this._iterateLhsReferences(parent.id, path, traceMap);
}
}
}
/**
* Iterate the references for a given Pattern node.
* @private
* @template T
* @param {Pattern} patternNode The Pattern node to iterate references.
* @param {string[]} path The current path.
* @param {TraceMapObject<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
*_iterateLhsReferences(patternNode, path, traceMap) {
if (patternNode.type === "Identifier") {
const variable = findVariable(this.globalScope, patternNode);
if (variable != null) {
yield* this._iterateVariableReferences(
variable,
path,
traceMap,
false,
);
}
return
}
if (patternNode.type === "ObjectPattern") {
for (const property of patternNode.properties) {
const key = getPropertyName(
/** @type {AssignmentProperty} */ (property),
);
if (key == null || !has(traceMap, key)) {
continue
}
const nextPath = path.concat(key);
const nextTraceMap = traceMap[key];
if (nextTraceMap[READ]) {
yield {
node: /** @type {RuleNode} */ (property),
path: nextPath,
type: READ,
info: nextTraceMap[READ],
};
}
yield* this._iterateLhsReferences(
/** @type {AssignmentProperty} */ (property).value,
nextPath,
nextTraceMap,
);
}
return
}
if (patternNode.type === "AssignmentPattern") {
yield* this._iterateLhsReferences(patternNode.left, path, traceMap);
}
}
/**
* Iterate the references for a given ModuleSpecifier node.
* @private
* @template T
* @param {ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier | ExportSpecifier} specifierNode The ModuleSpecifier node to iterate references.
* @param {string[]} path The current path.
* @param {TraceMapObject<T>} traceMap The trace map.
* @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
*/
*_iterateImportReferences(specifierNode, path, traceMap) {
const type = specifierNode.type;
if (type === "ImportSpecifier" || type === "ImportDefaultSpecifier") {
const key =
type === "ImportDefaultSpecifier"
? "default"
: specifierNode.imported.type === "Identifier"
? specifierNode.imported.name
: specifierNode.imported.value;
if (!has(traceMap, key)) {
return
}
path = path.concat(key); //eslint-disable-line no-param-reassign
const nextTraceMap = traceMap[key];
if (nextTraceMap[READ]) {
yield {
node: /** @type {RuleNode} */ (specifierNode),
path,
type: READ,
info: nextTraceMap[READ],
};
}
yield* this._iterateVariableReferences(
/** @type {Variable} */ (
findVariable(this.globalScope, specifierNode.local)
),
path,
nextTraceMap,
false,
);
return
}
if (type === "ImportNamespaceSpecifier") {
yield* this._iterateVariableReferences(
/** @type {Variable} */ (
findVariable(this.globalScope, specifierNode.local)
),
path,
traceMap,
false,
);
return
}
if (type === "ExportSpecifier") {
const key =
specifierNode.local.type === "Identifier"
? specifierNode.local.name
: specifierNode.local.value;
if (!has(traceMap, key)) {
return
}
path = path.concat(key); //eslint-disable-line no-param-reassign
const nextTraceMap = traceMap[key];
if (nextTraceMap[READ]) {
yield {
node: /** @type {RuleNode} */ (specifierNode),
path,
type: READ,
info: nextTraceMap[READ],
};
}
}
}
}
ReferenceTracker.READ = READ;
ReferenceTracker.CALL = CALL;
ReferenceTracker.CONSTRUCT = CONSTRUCT;
ReferenceTracker.ESM = ESM;
/**
* This is a predicate function for Array#filter.
* @param {string} name A name part.
* @param {number} index The index of the name.
* @returns {boolean} `false` if it's default.
*/
function exceptDefault(name, index) {
return !(index === 1 && name === "default")
}
/** @typedef {import("./types.mjs").StaticValue} StaticValue */
var index = {
CALL,
CONSTRUCT,
ESM,
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,
PatternMatcher,
READ,
ReferenceTracker,
};
exports.CALL = CALL;
exports.CONSTRUCT = CONSTRUCT;
exports.ESM = ESM;
exports.PatternMatcher = PatternMatcher;
exports.READ = READ;
exports.ReferenceTracker = ReferenceTracker;
exports["default"] = index;
exports.findVariable = findVariable;
exports.getFunctionHeadLocation = getFunctionHeadLocation;
exports.getFunctionNameWithKind = getFunctionNameWithKind;
exports.getInnermostScope = getInnermostScope;
exports.getPropertyName = getPropertyName;
exports.getStaticValue = getStaticValue;
exports.getStringIfConstant = getStringIfConstant;
exports.hasSideEffect = hasSideEffect;
exports.isArrowToken = isArrowToken;
exports.isClosingBraceToken = isClosingBraceToken;
exports.isClosingBracketToken = isClosingBracketToken;
exports.isClosingParenToken = isClosingParenToken;
exports.isColonToken = isColonToken;
exports.isCommaToken = isCommaToken;
exports.isCommentToken = isCommentToken;
exports.isNotArrowToken = isNotArrowToken;
exports.isNotClosingBraceToken = isNotClosingBraceToken;
exports.isNotClosingBracketToken = isNotClosingBracketToken;
exports.isNotClosingParenToken = isNotClosingParenToken;
exports.isNotColonToken = isNotColonToken;
exports.isNotCommaToken = isNotCommaToken;
exports.isNotCommentToken = isNotCommentToken;
exports.isNotOpeningBraceToken = isNotOpeningBraceToken;
exports.isNotOpeningBracketToken = isNotOpeningBracketToken;
exports.isNotOpeningParenToken = isNotOpeningParenToken;
exports.isNotSemicolonToken = isNotSemicolonToken;
exports.isOpeningBraceToken = isOpeningBraceToken;
exports.isOpeningBracketToken = isOpeningBracketToken;
exports.isOpeningParenToken = isOpeningParenToken;
exports.isParenthesized = isParenthesized;
exports.isSemicolonToken = isSemicolonToken;
//# sourceMappingURL=index.js.map