1206 lines
38 KiB
JavaScript
1206 lines
38 KiB
JavaScript
import { extname } from "pathe";
|
|
import { parse, parseExpression } from "@babel/parser";
|
|
|
|
//#region src/check.ts
|
|
/**
|
|
* Checks if the given node matches the specified type(s).
|
|
*
|
|
* @param node - The node to check.
|
|
* @param types - The type(s) to match against. It can be a single type or an array of types.
|
|
* @returns True if the node matches the specified type(s), false otherwise.
|
|
*/
|
|
function isTypeOf(node, types) {
|
|
if (!node) return false;
|
|
return [].concat(types).some((type) => {
|
|
if (type === "Function") return isFunctionType(node);
|
|
else if (type === "Literal") return isLiteralType(node);
|
|
else if (type === "Expression") return isExpressionType(node);
|
|
else return node.type === type;
|
|
});
|
|
}
|
|
/**
|
|
* Checks if the given node is a CallExpression with the specified callee.
|
|
*
|
|
* @param node - The node to check.
|
|
* @param test - The callee to compare against. It can be a string, an array of strings, or a function that takes a string and returns a boolean.
|
|
* @returns True if the node is a CallExpression with the specified callee, false otherwise.
|
|
*/
|
|
function isCallOf(node, test) {
|
|
return !!node && node.type === "CallExpression" && isIdentifierOf(node.callee, test);
|
|
}
|
|
/**
|
|
* Checks if the given node is a TaggedTemplateExpression with the specified callee.
|
|
*
|
|
* @param node - The node to check.
|
|
* @param test - The callee to compare against. It can be a string, an array of strings, or a function that takes a string and returns a boolean.
|
|
* @returns True if the node is a TaggedTemplateExpression with the specified callee, false otherwise.
|
|
*/
|
|
function isTaggedFunctionCallOf(node, test) {
|
|
return !!node && node.type === "TaggedTemplateExpression" && isIdentifierOf(node.tag, test);
|
|
}
|
|
/**
|
|
* Checks if the given node is an Identifier with the specified name.
|
|
*
|
|
* @param node - The node to check.
|
|
* @param test - The name to compare against. It can be a string or an array of strings.
|
|
* @returns True if the node is an Identifier with the specified name, false otherwise.
|
|
*/
|
|
function isIdentifierOf(node, test) {
|
|
return isIdentifier(node) && match(node.name, test);
|
|
}
|
|
/**
|
|
* Checks if the given node is a literal type.
|
|
*
|
|
* @param node - The node to check.
|
|
* @returns True if the node is a literal type, false otherwise.
|
|
*/
|
|
function isLiteralType(node) {
|
|
return !!node && node.type.endsWith("Literal");
|
|
}
|
|
/**
|
|
* Checks if the given node is a function type.
|
|
*
|
|
* @param node - The node to check.
|
|
* @returns True if the node is a function type, false otherwise.
|
|
*/
|
|
function isFunctionType(node) {
|
|
return !!node && !node.type.startsWith("TS") && /Function(?:Expression|Declaration)$|Method$/.test(node.type);
|
|
}
|
|
/**
|
|
* Checks if the given node is a declaration type.
|
|
*
|
|
* @param node - The node to check.
|
|
* @returns True if the node is a declaration type, false otherwise.
|
|
*/
|
|
function isDeclarationType(node) {
|
|
if (!node) return false;
|
|
switch (node.type) {
|
|
case "FunctionDeclaration":
|
|
case "VariableDeclaration":
|
|
case "ClassDeclaration":
|
|
case "ExportAllDeclaration":
|
|
case "ExportDefaultDeclaration":
|
|
case "ExportNamedDeclaration":
|
|
case "ImportDeclaration":
|
|
case "DeclareClass":
|
|
case "DeclareFunction":
|
|
case "DeclareInterface":
|
|
case "DeclareModule":
|
|
case "DeclareModuleExports":
|
|
case "DeclareTypeAlias":
|
|
case "DeclareOpaqueType":
|
|
case "DeclareVariable":
|
|
case "DeclareExportDeclaration":
|
|
case "DeclareExportAllDeclaration":
|
|
case "InterfaceDeclaration":
|
|
case "OpaqueType":
|
|
case "TypeAlias":
|
|
case "EnumDeclaration":
|
|
case "TSDeclareFunction":
|
|
case "TSInterfaceDeclaration":
|
|
case "TSTypeAliasDeclaration":
|
|
case "TSEnumDeclaration":
|
|
case "TSModuleDeclaration": return true;
|
|
case "Placeholder": if (node.expectedNode === "Declaration") return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Checks if the given node is an expression type.
|
|
*
|
|
* @param node - The node to check.
|
|
* @returns True if the node is an expression type, false otherwise.
|
|
*/
|
|
function isExpressionType(node) {
|
|
return !!node && (node.type.endsWith("Expression") || isLiteralType(node) || [
|
|
"Identifier",
|
|
"MetaProperty",
|
|
"Super",
|
|
"Import",
|
|
"JSXElement",
|
|
"JSXFragment",
|
|
"TopicReference",
|
|
"PipelineBareFunction",
|
|
"PipelinePrimaryTopicReference",
|
|
"TSTypeAssertion"
|
|
].includes(node.type));
|
|
}
|
|
function match(value, test) {
|
|
if (Array.isArray(test)) return test.includes(value);
|
|
if (typeof test === "function") return test(value);
|
|
return value === test;
|
|
}
|
|
/* v8 ignore start */
|
|
/**
|
|
* Checks if the input `node` is a reference to a bound variable.
|
|
*
|
|
* Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts
|
|
*
|
|
* To avoid runtime dependency on `@babel/types` (which includes process references)
|
|
* This file should not change very often in babel but we may need to keep it
|
|
* up-to-date from time to time.
|
|
*
|
|
* @param node - The node to check.
|
|
* @param parent - The parent node of the input `node`.
|
|
* @param grandparent - The grandparent node of the input `node`.
|
|
* @returns True if the input `node` is a reference to a bound variable, false otherwise.
|
|
*/
|
|
function isReferenced(node, parent, grandparent) {
|
|
switch (parent.type) {
|
|
case "MemberExpression":
|
|
case "OptionalMemberExpression":
|
|
if (parent.property === node) return !!parent.computed;
|
|
return parent.object === node;
|
|
case "JSXMemberExpression": return parent.object === node;
|
|
case "VariableDeclarator": return parent.init === node;
|
|
case "ArrowFunctionExpression": return parent.body === node;
|
|
case "PrivateName": return false;
|
|
case "ClassMethod":
|
|
case "ClassPrivateMethod":
|
|
case "ObjectMethod":
|
|
if (parent.key === node) return !!parent.computed;
|
|
return false;
|
|
case "ObjectProperty":
|
|
if (parent.key === node) return !!parent.computed;
|
|
return !grandparent || grandparent.type !== "ObjectPattern";
|
|
case "ClassProperty":
|
|
case "ClassAccessorProperty":
|
|
if (parent.key === node) return !!parent.computed;
|
|
return true;
|
|
case "ClassPrivateProperty": return parent.key !== node;
|
|
case "ClassDeclaration":
|
|
case "ClassExpression": return parent.superClass === node;
|
|
case "AssignmentExpression": return parent.right === node;
|
|
case "AssignmentPattern": return parent.right === node;
|
|
case "LabeledStatement": return false;
|
|
case "CatchClause": return false;
|
|
case "RestElement": return false;
|
|
case "BreakStatement":
|
|
case "ContinueStatement": return false;
|
|
case "FunctionDeclaration":
|
|
case "FunctionExpression": return false;
|
|
case "ExportNamespaceSpecifier":
|
|
case "ExportDefaultSpecifier": return false;
|
|
case "ExportSpecifier":
|
|
if (grandparent?.source) return false;
|
|
return parent.local === node;
|
|
case "ImportDefaultSpecifier":
|
|
case "ImportNamespaceSpecifier":
|
|
case "ImportSpecifier": return false;
|
|
case "ImportAttribute": return false;
|
|
case "JSXAttribute":
|
|
case "JSXNamespacedName": return false;
|
|
case "ObjectPattern":
|
|
case "ArrayPattern": return false;
|
|
case "MetaProperty": return false;
|
|
case "ObjectTypeProperty": return parent.key !== node;
|
|
case "TSEnumMember": return parent.id !== node;
|
|
case "TSPropertySignature":
|
|
if (parent.key === node) return !!parent.computed;
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
function isIdentifier(node) {
|
|
return !!node && (node.type === "Identifier" || node.type === "JSXIdentifier");
|
|
}
|
|
function isStaticProperty(node) {
|
|
return !!node && (node.type === "ObjectProperty" || node.type === "ObjectMethod") && !node.computed;
|
|
}
|
|
function isStaticPropertyKey(node, parent) {
|
|
return isStaticProperty(parent) && parent.key === node;
|
|
}
|
|
function isForStatement(stmt) {
|
|
return stmt.type === "ForOfStatement" || stmt.type === "ForInStatement" || stmt.type === "ForStatement";
|
|
}
|
|
function isReferencedIdentifier(id, parent, parentStack) {
|
|
if (!parent) return true;
|
|
if (id.name === "arguments") return false;
|
|
if (isReferenced(id, parent, parentStack.at(-2))) return true;
|
|
switch (parent.type) {
|
|
case "AssignmentExpression":
|
|
case "AssignmentPattern": return true;
|
|
case "ObjectProperty": return parent.key !== id && isInDestructureAssignment(parent, parentStack);
|
|
case "ArrayPattern": return isInDestructureAssignment(parent, parentStack);
|
|
}
|
|
return false;
|
|
}
|
|
function isInDestructureAssignment(parent, parentStack) {
|
|
if (parent && (parent.type === "ObjectProperty" || parent.type === "ArrayPattern")) {
|
|
let i = parentStack.length;
|
|
while (i--) {
|
|
const p = parentStack[i];
|
|
if (p.type === "AssignmentExpression") return true;
|
|
else if (p.type !== "ObjectProperty" && !p.type.endsWith("Pattern")) break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function isInNewExpression(parentStack) {
|
|
let i = parentStack.length;
|
|
while (i--) {
|
|
const p = parentStack[i];
|
|
if (p.type === "NewExpression") return true;
|
|
else if (p.type !== "MemberExpression") break;
|
|
}
|
|
return false;
|
|
}
|
|
/* v8 ignore end */
|
|
|
|
//#endregion
|
|
//#region src/create.ts
|
|
/**
|
|
* Creates a string literal AST node.
|
|
*
|
|
* @param value - The value of the string literal.
|
|
* @returns The string literal AST node.
|
|
*/
|
|
function createStringLiteral(value) {
|
|
return {
|
|
type: "StringLiteral",
|
|
value,
|
|
extra: {
|
|
rawValue: value,
|
|
raw: JSON.stringify(value)
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Creates a TypeScript union type AST node.
|
|
*
|
|
* @param types - An array of TypeScript types.
|
|
* @returns The TypeScript union type AST node.
|
|
*/
|
|
function createTSUnionType(types) {
|
|
return {
|
|
type: "TSUnionType",
|
|
types
|
|
};
|
|
}
|
|
/**
|
|
* Creates a TypeScript literal type AST node.
|
|
*
|
|
* @param literal - The literal value.
|
|
* @returns The TypeScript literal type AST node.
|
|
*/
|
|
function createTSLiteralType(literal) {
|
|
return {
|
|
type: "TSLiteralType",
|
|
literal
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/extract.ts
|
|
/**
|
|
* Extract identifiers of the given node.
|
|
* @param node The node to extract.
|
|
* @param identifiers The array to store the extracted identifiers.
|
|
* @see https://github.com/vuejs/core/blob/1f6a1102aa09960f76a9af2872ef01e7da8538e3/packages/compiler-core/src/babelUtils.ts#L208
|
|
*/
|
|
function extractIdentifiers(node, identifiers = []) {
|
|
switch (node.type) {
|
|
case "Identifier":
|
|
case "JSXIdentifier":
|
|
identifiers.push(node);
|
|
break;
|
|
case "MemberExpression":
|
|
case "JSXMemberExpression": {
|
|
let object = node;
|
|
while (object.type === "MemberExpression") object = object.object;
|
|
identifiers.push(object);
|
|
break;
|
|
}
|
|
case "ObjectPattern":
|
|
for (const prop of node.properties) if (prop.type === "RestElement") extractIdentifiers(prop.argument, identifiers);
|
|
else extractIdentifiers(prop.value, identifiers);
|
|
break;
|
|
case "ArrayPattern":
|
|
node.elements.forEach((element) => {
|
|
element && extractIdentifiers(element, identifiers);
|
|
});
|
|
break;
|
|
case "RestElement":
|
|
extractIdentifiers(node.argument, identifiers);
|
|
break;
|
|
case "AssignmentPattern":
|
|
extractIdentifiers(node.left, identifiers);
|
|
break;
|
|
}
|
|
return identifiers;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/lang.ts
|
|
const REGEX_DTS = /\.d\.[cm]?ts(\?.*)?$/;
|
|
const REGEX_LANG_TS = /^[cm]?tsx?$/;
|
|
const REGEX_LANG_JSX = /^[cm]?[jt]sx$/;
|
|
/**
|
|
* Returns the language (extension name) of a given filename.
|
|
* @param filename - The name of the file.
|
|
* @returns The language of the file.
|
|
*/
|
|
function getLang(filename) {
|
|
if (isDts(filename)) return "dts";
|
|
return extname(filename).replace(/^\./, "").replace(/\?.*$/, "");
|
|
}
|
|
/**
|
|
* Checks if a filename represents a TypeScript declaration file (.d.ts).
|
|
* @param filename - The name of the file to check.
|
|
* @returns A boolean value indicating whether the filename is a TypeScript declaration file.
|
|
*/
|
|
function isDts(filename) {
|
|
return REGEX_DTS.test(filename);
|
|
}
|
|
/**
|
|
* Checks if the given language (ts, mts, cjs, dts, tsx...) is TypeScript.
|
|
* @param lang - The language to check.
|
|
* @returns A boolean indicating whether the language is TypeScript.
|
|
*/
|
|
function isTs(lang) {
|
|
return !!lang && (lang === "dts" || REGEX_LANG_TS.test(lang));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/loc.ts
|
|
/**
|
|
* Locates the trailing comma in the given code within the specified range.
|
|
*
|
|
* @param code - The code to search for the trailing comma.
|
|
* @param start - The start index of the range to search within.
|
|
* @param end - The end index of the range to search within.
|
|
* @param comments - Optional array of comments to exclude from the search range.
|
|
* @returns The index of the trailing comma, or -1 if not found.
|
|
*/
|
|
function locateTrailingComma(code, start, end, comments = []) {
|
|
let i = start;
|
|
while (i < end) {
|
|
if (comments.some((c) => i >= c.start && i < c.end)) {
|
|
i++;
|
|
continue;
|
|
}
|
|
const char = code[i];
|
|
if (["}", ")"].includes(char)) return -1;
|
|
if (char === ",") return i;
|
|
i++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parse.ts
|
|
function hasPlugin(plugins, plugin) {
|
|
return plugins.some((p) => (Array.isArray(p) ? p[0] : p) === plugin);
|
|
}
|
|
/**
|
|
* Gets the Babel parser options for the given language.
|
|
* @param lang - The language of the code (optional).
|
|
* @param options - The parser options (optional).
|
|
* @returns The Babel parser options for the given language.
|
|
*/
|
|
function getBabelParserOptions(lang, options = {}) {
|
|
const plugins = [...options.plugins || []];
|
|
if (isTs(lang)) {
|
|
if (!hasPlugin(plugins, "typescript")) plugins.push(lang === "dts" ? ["typescript", { dts: true }] : "typescript");
|
|
if (!hasPlugin(plugins, "decorators") && !hasPlugin(plugins, "decorators-legacy")) plugins.push("decorators-legacy");
|
|
if (!hasPlugin(plugins, "importAttributes") && !hasPlugin(plugins, "importAssertions") && !hasPlugin(plugins, "deprecatedImportAssert")) plugins.push("importAttributes", "deprecatedImportAssert");
|
|
if (!hasPlugin(plugins, "explicitResourceManagement")) plugins.push("explicitResourceManagement");
|
|
if (REGEX_LANG_JSX.test(lang) && !hasPlugin(plugins, "jsx")) plugins.push("jsx");
|
|
} else if (!hasPlugin(plugins, "jsx")) plugins.push("jsx");
|
|
return {
|
|
sourceType: "module",
|
|
...options,
|
|
plugins
|
|
};
|
|
}
|
|
/**
|
|
* Parses the given code using Babel parser.
|
|
*
|
|
* @param code - The code to parse.
|
|
* @param lang - The language of the code (optional).
|
|
* @param options - The parser options (optional).
|
|
* @param options.cache - Whether to cache the result (optional).
|
|
* @returns The parse result, including the program, errors, and comments.
|
|
*/
|
|
function babelParse(code, lang, { cache,...options } = {}) {
|
|
let result;
|
|
if (cache) result = parseCache.get(code);
|
|
if (!result) {
|
|
result = parse(code, getBabelParserOptions(lang, options));
|
|
if (cache) parseCache.set(code, result);
|
|
}
|
|
const { program, type,...rest } = result;
|
|
return {
|
|
...program,
|
|
...rest
|
|
};
|
|
}
|
|
const parseCache = /* @__PURE__ */ new Map();
|
|
/**
|
|
* Parses the given code using the Babel parser as an expression.
|
|
*
|
|
* @template T - The type of the parsed AST node.
|
|
* @param {string} code - The code to parse.
|
|
* @param {string} [lang] - The language to parse. Defaults to undefined.
|
|
* @param {ParserOptions} [options] - The options to configure the parser. Defaults to an empty object.
|
|
* @returns {ParseResult<T>} - The result of the parsing operation.
|
|
*/
|
|
function babelParseExpression(code, lang, options = {}) {
|
|
return parseExpression(code, getBabelParserOptions(lang, options));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/resolve.ts
|
|
/**
|
|
* Resolves a string representation of the given node.
|
|
* @param node The node to resolve.
|
|
* @param computed Whether the node is computed or not.
|
|
* @returns The resolved string representation of the node.
|
|
*/
|
|
function resolveString(node, computed = false) {
|
|
if (typeof node === "string") return node;
|
|
else if (node.type === "Identifier") {
|
|
if (computed) throw new TypeError("Invalid Identifier");
|
|
return node.name;
|
|
} else if (node.type === "PrivateName") return `#${node.id.name}`;
|
|
else if (node.type === "ThisExpression") return "this";
|
|
else if (node.type === "Super") return "super";
|
|
return String(resolveLiteral(node));
|
|
}
|
|
/**
|
|
* Resolves the value of a literal node.
|
|
* @param node The literal node to resolve.
|
|
* @returns The resolved value of the literal node.
|
|
*/
|
|
function resolveLiteral(node) {
|
|
switch (node.type) {
|
|
case "TemplateLiteral": return resolveTemplateLiteral(node);
|
|
case "NullLiteral": return null;
|
|
case "BigIntLiteral": return BigInt(node.value);
|
|
case "RegExpLiteral": return new RegExp(node.pattern, node.flags);
|
|
case "BooleanLiteral":
|
|
case "NumericLiteral":
|
|
case "StringLiteral": return node.value;
|
|
case "DecimalLiteral": return Number(node.value);
|
|
}
|
|
}
|
|
/**
|
|
* Resolves a template literal node into a string.
|
|
* @param node The template literal node to resolve.
|
|
* @returns The resolved string representation of the template literal.
|
|
*/
|
|
function resolveTemplateLiteral(node) {
|
|
return node.quasis.reduce((prev, curr, idx) => {
|
|
const expr = node.expressions[idx];
|
|
if (expr) {
|
|
if (!isLiteralType(expr)) throw new TypeError("TemplateLiteral expression must be a literal");
|
|
return prev + curr.value.cooked + resolveLiteral(expr);
|
|
}
|
|
return prev + curr.value.cooked;
|
|
}, "");
|
|
}
|
|
/**
|
|
* Resolves the identifier node into an array of strings.
|
|
* @param node The identifier node to resolve.
|
|
* @returns An array of resolved strings representing the identifier.
|
|
* @throws TypeError If the identifier is invalid.
|
|
*/
|
|
function resolveIdentifier(node) {
|
|
if (isTypeOf(node, [
|
|
"Identifier",
|
|
"PrivateName",
|
|
"ThisExpression",
|
|
"Super"
|
|
])) return [resolveString(node)];
|
|
const left = node.type === "TSQualifiedName" ? node.left : node.object;
|
|
const right = node.type === "TSQualifiedName" ? node.right : node.property;
|
|
const computed = node.type === "TSQualifiedName" ? false : node.computed;
|
|
if (isTypeOf(left, [
|
|
"Identifier",
|
|
"MemberExpression",
|
|
"ThisExpression",
|
|
"Super",
|
|
"TSQualifiedName"
|
|
])) {
|
|
const keys = resolveIdentifier(left);
|
|
if (isTypeOf(right, [
|
|
"Identifier",
|
|
"PrivateName",
|
|
"Literal"
|
|
])) keys.push(resolveString(right, computed));
|
|
else throw new TypeError("Invalid Identifier");
|
|
return keys;
|
|
}
|
|
throw new TypeError("Invalid Identifier");
|
|
}
|
|
function tryResolveIdentifier(...args) {
|
|
try {
|
|
return resolveIdentifier(...args);
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
function resolveObjectKey(node, raw = false) {
|
|
const { key, computed } = node;
|
|
switch (key.type) {
|
|
case "StringLiteral":
|
|
case "NumericLiteral": return raw ? key.extra.raw : key.value;
|
|
case "Identifier":
|
|
if (!computed) return raw ? `"${key.name}"` : key.name;
|
|
throw "Cannot resolve computed Identifier";
|
|
default: throw new SyntaxError(`Unexpected node type: ${key.type}`);
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/walker.js
|
|
/**
|
|
* @typedef { import('estree').Node} Node
|
|
* @typedef {{
|
|
* skip: () => void;
|
|
* remove: () => void;
|
|
* replace: (node: Node) => void;
|
|
* }} WalkerContext
|
|
*/
|
|
var WalkerBase = class {
|
|
constructor() {
|
|
/** @type {boolean} */
|
|
this.should_skip = false;
|
|
/** @type {boolean} */
|
|
this.should_remove = false;
|
|
/** @type {Node | null} */
|
|
this.replacement = null;
|
|
/** @type {WalkerContext} */
|
|
this.context = {
|
|
skip: () => this.should_skip = true,
|
|
remove: () => this.should_remove = true,
|
|
replace: (node) => this.replacement = node
|
|
};
|
|
}
|
|
/**
|
|
* @template {Node} Parent
|
|
* @param {Parent | null | undefined} parent
|
|
* @param {keyof Parent | null | undefined} prop
|
|
* @param {number | null | undefined} index
|
|
* @param {Node} node
|
|
*/
|
|
replace(parent, prop, index, node) {
|
|
if (parent && prop) if (index != null)
|
|
/** @type {Array<Node>} */ parent[prop][index] = node;
|
|
else
|
|
/** @type {Node} */ parent[prop] = node;
|
|
}
|
|
/**
|
|
* @template {Node} Parent
|
|
* @param {Parent | null | undefined} parent
|
|
* @param {keyof Parent | null | undefined} prop
|
|
* @param {number | null | undefined} index
|
|
*/
|
|
remove(parent, prop, index) {
|
|
if (parent && prop) if (index !== null && index !== void 0)
|
|
/** @type {Array<Node>} */ parent[prop].splice(index, 1);
|
|
else delete parent[prop];
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/sync.js
|
|
/**
|
|
* @typedef { import('estree').Node} Node
|
|
* @typedef { import('./walker.js').WalkerContext} WalkerContext
|
|
* @typedef {(
|
|
* this: WalkerContext,
|
|
* node: Node,
|
|
* parent: Node | null,
|
|
* key: string | number | symbol | null | undefined,
|
|
* index: number | null | undefined
|
|
* ) => void} SyncHandler
|
|
*/
|
|
var SyncWalker = class extends WalkerBase {
|
|
/**
|
|
*
|
|
* @param {SyncHandler} [enter]
|
|
* @param {SyncHandler} [leave]
|
|
*/
|
|
constructor(enter, leave) {
|
|
super();
|
|
/** @type {boolean} */
|
|
this.should_skip = false;
|
|
/** @type {boolean} */
|
|
this.should_remove = false;
|
|
/** @type {Node | null} */
|
|
this.replacement = null;
|
|
/** @type {WalkerContext} */
|
|
this.context = {
|
|
skip: () => this.should_skip = true,
|
|
remove: () => this.should_remove = true,
|
|
replace: (node) => this.replacement = node
|
|
};
|
|
/** @type {SyncHandler | undefined} */
|
|
this.enter = enter;
|
|
/** @type {SyncHandler | undefined} */
|
|
this.leave = leave;
|
|
}
|
|
/**
|
|
* @template {Node} Parent
|
|
* @param {Node} node
|
|
* @param {Parent | null} parent
|
|
* @param {keyof Parent} [prop]
|
|
* @param {number | null} [index]
|
|
* @returns {Node | null}
|
|
*/
|
|
visit(node, parent, prop, index) {
|
|
if (node) {
|
|
if (this.enter) {
|
|
const _should_skip = this.should_skip;
|
|
const _should_remove = this.should_remove;
|
|
const _replacement = this.replacement;
|
|
this.should_skip = false;
|
|
this.should_remove = false;
|
|
this.replacement = null;
|
|
this.enter.call(this.context, node, parent, prop, index);
|
|
if (this.replacement) {
|
|
node = this.replacement;
|
|
this.replace(parent, prop, index, node);
|
|
}
|
|
if (this.should_remove) this.remove(parent, prop, index);
|
|
const skipped = this.should_skip;
|
|
const removed = this.should_remove;
|
|
this.should_skip = _should_skip;
|
|
this.should_remove = _should_remove;
|
|
this.replacement = _replacement;
|
|
if (skipped) return node;
|
|
if (removed) return null;
|
|
}
|
|
/** @type {keyof Node} */
|
|
let key;
|
|
for (key in node) {
|
|
/** @type {unknown} */
|
|
const value = node[key];
|
|
if (value && typeof value === "object") {
|
|
if (Array.isArray(value)) {
|
|
const nodes = value;
|
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
const item = nodes[i];
|
|
if (isNode$1(item)) {
|
|
if (!this.visit(item, node, key, i)) i--;
|
|
}
|
|
}
|
|
} else if (isNode$1(value)) this.visit(value, node, key, null);
|
|
}
|
|
}
|
|
if (this.leave) {
|
|
const _replacement = this.replacement;
|
|
const _should_remove = this.should_remove;
|
|
this.replacement = null;
|
|
this.should_remove = false;
|
|
this.leave.call(this.context, node, parent, prop, index);
|
|
if (this.replacement) {
|
|
node = this.replacement;
|
|
this.replace(parent, prop, index, node);
|
|
}
|
|
if (this.should_remove) this.remove(parent, prop, index);
|
|
const removed = this.should_remove;
|
|
this.replacement = _replacement;
|
|
this.should_remove = _should_remove;
|
|
if (removed) return null;
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
};
|
|
/**
|
|
* Ducktype a node.
|
|
*
|
|
* @param {unknown} value
|
|
* @returns {value is Node}
|
|
*/
|
|
function isNode$1(value) {
|
|
return value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
|
|
}
|
|
|
|
//#endregion
|
|
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/async.js
|
|
/**
|
|
* @typedef { import('estree').Node} Node
|
|
* @typedef { import('./walker.js').WalkerContext} WalkerContext
|
|
* @typedef {(
|
|
* this: WalkerContext,
|
|
* node: Node,
|
|
* parent: Node | null,
|
|
* key: string | number | symbol | null | undefined,
|
|
* index: number | null | undefined
|
|
* ) => Promise<void>} AsyncHandler
|
|
*/
|
|
var AsyncWalker = class extends WalkerBase {
|
|
/**
|
|
*
|
|
* @param {AsyncHandler} [enter]
|
|
* @param {AsyncHandler} [leave]
|
|
*/
|
|
constructor(enter, leave) {
|
|
super();
|
|
/** @type {boolean} */
|
|
this.should_skip = false;
|
|
/** @type {boolean} */
|
|
this.should_remove = false;
|
|
/** @type {Node | null} */
|
|
this.replacement = null;
|
|
/** @type {WalkerContext} */
|
|
this.context = {
|
|
skip: () => this.should_skip = true,
|
|
remove: () => this.should_remove = true,
|
|
replace: (node) => this.replacement = node
|
|
};
|
|
/** @type {AsyncHandler | undefined} */
|
|
this.enter = enter;
|
|
/** @type {AsyncHandler | undefined} */
|
|
this.leave = leave;
|
|
}
|
|
/**
|
|
* @template {Node} Parent
|
|
* @param {Node} node
|
|
* @param {Parent | null} parent
|
|
* @param {keyof Parent} [prop]
|
|
* @param {number | null} [index]
|
|
* @returns {Promise<Node | null>}
|
|
*/
|
|
async visit(node, parent, prop, index) {
|
|
if (node) {
|
|
if (this.enter) {
|
|
const _should_skip = this.should_skip;
|
|
const _should_remove = this.should_remove;
|
|
const _replacement = this.replacement;
|
|
this.should_skip = false;
|
|
this.should_remove = false;
|
|
this.replacement = null;
|
|
await this.enter.call(this.context, node, parent, prop, index);
|
|
if (this.replacement) {
|
|
node = this.replacement;
|
|
this.replace(parent, prop, index, node);
|
|
}
|
|
if (this.should_remove) this.remove(parent, prop, index);
|
|
const skipped = this.should_skip;
|
|
const removed = this.should_remove;
|
|
this.should_skip = _should_skip;
|
|
this.should_remove = _should_remove;
|
|
this.replacement = _replacement;
|
|
if (skipped) return node;
|
|
if (removed) return null;
|
|
}
|
|
/** @type {keyof Node} */
|
|
let key;
|
|
for (key in node) {
|
|
/** @type {unknown} */
|
|
const value = node[key];
|
|
if (value && typeof value === "object") {
|
|
if (Array.isArray(value)) {
|
|
const nodes = value;
|
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
const item = nodes[i];
|
|
if (isNode(item)) {
|
|
if (!await this.visit(item, node, key, i)) i--;
|
|
}
|
|
}
|
|
} else if (isNode(value)) await this.visit(value, node, key, null);
|
|
}
|
|
}
|
|
if (this.leave) {
|
|
const _replacement = this.replacement;
|
|
const _should_remove = this.should_remove;
|
|
this.replacement = null;
|
|
this.should_remove = false;
|
|
await this.leave.call(this.context, node, parent, prop, index);
|
|
if (this.replacement) {
|
|
node = this.replacement;
|
|
this.replace(parent, prop, index, node);
|
|
}
|
|
if (this.should_remove) this.remove(parent, prop, index);
|
|
const removed = this.should_remove;
|
|
this.replacement = _replacement;
|
|
this.should_remove = _should_remove;
|
|
if (removed) return null;
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
};
|
|
/**
|
|
* Ducktype a node.
|
|
*
|
|
* @param {unknown} value
|
|
* @returns {value is Node}
|
|
*/
|
|
function isNode(value) {
|
|
return value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
|
|
}
|
|
|
|
//#endregion
|
|
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/index.js
|
|
/**
|
|
* @typedef {import('estree').Node} Node
|
|
* @typedef {import('./sync.js').SyncHandler} SyncHandler
|
|
* @typedef {import('./async.js').AsyncHandler} AsyncHandler
|
|
*/
|
|
/**
|
|
* @param {Node} ast
|
|
* @param {{
|
|
* enter?: SyncHandler
|
|
* leave?: SyncHandler
|
|
* }} walker
|
|
* @returns {Node | null}
|
|
*/
|
|
function walk(ast, { enter, leave }) {
|
|
const instance = new SyncWalker(enter, leave);
|
|
return instance.visit(ast, null);
|
|
}
|
|
/**
|
|
* @param {Node} ast
|
|
* @param {{
|
|
* enter?: AsyncHandler
|
|
* leave?: AsyncHandler
|
|
* }} walker
|
|
* @returns {Promise<Node | null>}
|
|
*/
|
|
async function asyncWalk(ast, { enter, leave }) {
|
|
const instance = new AsyncWalker(enter, leave);
|
|
return await instance.visit(ast, null);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/utils.ts
|
|
const TS_NODE_TYPES = [
|
|
"TSAsExpression",
|
|
"TSTypeAssertion",
|
|
"TSNonNullExpression",
|
|
"TSInstantiationExpression",
|
|
"TSSatisfiesExpression"
|
|
];
|
|
/**
|
|
* Unwraps a TypeScript node by recursively traversing the AST until a non-TypeScript node is found.
|
|
* @param node - The TypeScript node to unwrap.
|
|
* @returns The unwrapped node.
|
|
*/
|
|
function unwrapTSNode(node) {
|
|
if (isTypeOf(node, TS_NODE_TYPES)) return unwrapTSNode(node.expression);
|
|
else return node;
|
|
}
|
|
/**
|
|
* Escapes a raw key by checking if it needs to be wrapped with quotes or not.
|
|
*
|
|
* @param rawKey - The raw key to escape.
|
|
* @returns The escaped key.
|
|
*/
|
|
function escapeKey(rawKey) {
|
|
if (String(+rawKey) === rawKey) return rawKey;
|
|
try {
|
|
const node = parseExpression(`({${rawKey}: 1})`);
|
|
if (node.properties[0].key.type === "Identifier") return rawKey;
|
|
} catch {}
|
|
return JSON.stringify(rawKey);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/walk.ts
|
|
/**
|
|
* Walks the AST and applies the provided handlers.
|
|
*
|
|
* @template T - The type of the AST node.
|
|
* @param {T} node - The root node of the AST.
|
|
* @param {WalkHandlers<T, void>} hooks - The handlers to be applied during the walk.
|
|
* @returns {T | null} - The modified AST node or null if the node is removed.
|
|
*/
|
|
const walkAST = walk;
|
|
/**
|
|
* Asynchronously walks the AST starting from the given node,
|
|
* applying the provided handlers to each node encountered.
|
|
*
|
|
* @template T - The type of the AST node.
|
|
* @param {T} node - The root node of the AST.
|
|
* @param {WalkHandlers<T, Promise<void>>} handlers - The handlers to be applied to each node.
|
|
* @returns {Promise<T | null>} - A promise that resolves to the modified AST or null if the AST is empty.
|
|
*/
|
|
const walkASTAsync = asyncWalk;
|
|
/**
|
|
* Walks through an ImportDeclaration node and populates the provided imports object.
|
|
*
|
|
* @param imports - The object to store the import bindings.
|
|
* @param node - The ImportDeclaration node to walk through.
|
|
*/
|
|
function walkImportDeclaration(imports, node) {
|
|
if (node.importKind === "type") return;
|
|
const source = node.source.value;
|
|
for (const specifier of node.specifiers) {
|
|
const isType = specifier.type === "ImportSpecifier" && specifier.importKind === "type";
|
|
const local = specifier.local.name;
|
|
const imported = specifier.type === "ImportSpecifier" ? resolveString(specifier.imported) : specifier.type === "ImportNamespaceSpecifier" ? "*" : "default";
|
|
imports[local] = {
|
|
source,
|
|
local,
|
|
imported,
|
|
specifier,
|
|
isType
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Walks through an ExportDeclaration node and populates the exports object with the relevant information.
|
|
* @param exports - The object to store the export information.
|
|
* @param node - The ExportDeclaration node to process.
|
|
*/
|
|
function walkExportDeclaration(exports, node) {
|
|
let local;
|
|
let exported;
|
|
let isType;
|
|
let source;
|
|
let specifier;
|
|
let declaration;
|
|
function setExport() {
|
|
exports[exported] = {
|
|
source,
|
|
local,
|
|
exported,
|
|
specifier,
|
|
isType,
|
|
declaration
|
|
};
|
|
}
|
|
if (node.type === "ExportNamedDeclaration") {
|
|
if (node.specifiers.length > 0) for (const s of node.specifiers) {
|
|
const isExportSpecifier = s.type === "ExportSpecifier";
|
|
isType = node.exportKind === "type" || isExportSpecifier && s.exportKind === "type";
|
|
local = isExportSpecifier ? s.local.name : s.type === "ExportNamespaceSpecifier" ? "*" : "default";
|
|
source = node.source ? node.source.value : null;
|
|
exported = isExportSpecifier ? resolveString(s.exported) : s.exported.name;
|
|
declaration = null;
|
|
specifier = s;
|
|
setExport();
|
|
}
|
|
else if (node.specifiers.length === 0 && !!node.declaration) {
|
|
if (node.declaration.type === "VariableDeclaration") for (const decl of node.declaration.declarations) {
|
|
/* c8 ignore next 4 */
|
|
if (decl.id.type !== "Identifier") continue;
|
|
local = resolveString(decl.id);
|
|
source = null;
|
|
exported = local;
|
|
isType = node.exportKind === "type";
|
|
declaration = node.declaration;
|
|
specifier = null;
|
|
setExport();
|
|
}
|
|
else if ("id" in node.declaration && node.declaration.id && node.declaration.id.type === "Identifier") {
|
|
local = resolveString(node.declaration.id);
|
|
source = null;
|
|
exported = local;
|
|
isType = node.exportKind === "type";
|
|
declaration = node.declaration;
|
|
specifier = null;
|
|
setExport();
|
|
}
|
|
}
|
|
return;
|
|
} else if (node.type === "ExportDefaultDeclaration") {
|
|
if (isExpressionType(node.declaration)) local = "name" in node.declaration ? node.declaration.name : "default";
|
|
else local = resolveString(node.declaration.id || "default");
|
|
source = null;
|
|
exported = "default";
|
|
isType = false;
|
|
declaration = node.declaration;
|
|
specifier = null;
|
|
} else {
|
|
local = "*";
|
|
source = resolveString(node.source);
|
|
exported = "*";
|
|
isType = node.exportKind === "type";
|
|
specifier = null;
|
|
declaration = null;
|
|
}
|
|
setExport();
|
|
}
|
|
/**
|
|
* Modified from https://github.com/vuejs/core/blob/main/packages/compiler-core/src/babelUtils.ts
|
|
* To support browser environments and JSX.
|
|
*
|
|
* https://github.com/vuejs/core/blob/main/LICENSE
|
|
*/
|
|
/**
|
|
* Return value indicates whether the AST walked can be a constant
|
|
*/
|
|
function walkIdentifiers(root, onIdentifier, includeAll = false, parentStack = [], knownIds = Object.create(null)) {
|
|
const rootExp = root.type === "Program" ? root.body[0].type === "ExpressionStatement" && root.body[0].expression : root;
|
|
walkAST(root, {
|
|
enter(node, parent) {
|
|
parent && parentStack.push(parent);
|
|
if (parent && parent.type.startsWith("TS") && !TS_NODE_TYPES.includes(parent.type)) return this.skip();
|
|
if (isIdentifier(node)) {
|
|
const isLocal = !!knownIds[node.name];
|
|
const isRefed = isReferencedIdentifier(node, parent, parentStack);
|
|
if (includeAll || isRefed && !isLocal) onIdentifier(node, parent, parentStack, isRefed, isLocal);
|
|
} else if (node.type === "ObjectProperty" && parent?.type === "ObjectPattern") node.inPattern = true;
|
|
else if (isFunctionType(node))
|
|
/* v8 ignore start */
|
|
if (node.scopeIds) node.scopeIds.forEach((id) => markKnownIds(id, knownIds));
|
|
else walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));
|
|
else if (node.type === "BlockStatement")
|
|
/* v8 ignore start */
|
|
if (node.scopeIds) node.scopeIds.forEach((id) => markKnownIds(id, knownIds));
|
|
else walkBlockDeclarations(node, (id) => markScopeIdentifier(node, id, knownIds));
|
|
else if (node.type === "CatchClause" && node.param) for (const id of extractIdentifiers(node.param)) markScopeIdentifier(node, id, knownIds);
|
|
else if (isForStatement(node)) walkForStatement(node, false, (id) => markScopeIdentifier(node, id, knownIds));
|
|
},
|
|
leave(node, parent) {
|
|
parent && parentStack.pop();
|
|
if (node !== rootExp && node.scopeIds) for (const id of node.scopeIds) {
|
|
knownIds[id]--;
|
|
if (knownIds[id] === 0) delete knownIds[id];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function walkFunctionParams(node, onIdent) {
|
|
for (const p of node.params) for (const id of extractIdentifiers(p)) onIdent(id);
|
|
}
|
|
function walkBlockDeclarations(block, onIdent) {
|
|
for (const stmt of block.body) if (stmt.type === "VariableDeclaration") {
|
|
if (stmt.declare) continue;
|
|
for (const decl of stmt.declarations) for (const id of extractIdentifiers(decl.id)) onIdent(id);
|
|
} else if (stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration") {
|
|
/* v8 ignore next */
|
|
if (stmt.declare || !stmt.id) continue;
|
|
onIdent(stmt.id);
|
|
} else if (isForStatement(stmt)) walkForStatement(stmt, true, onIdent);
|
|
}
|
|
function walkForStatement(stmt, isVar, onIdent) {
|
|
const variable = stmt.type === "ForStatement" ? stmt.init : stmt.left;
|
|
if (variable && variable.type === "VariableDeclaration" && (variable.kind === "var" ? isVar : !isVar)) for (const decl of variable.declarations) for (const id of extractIdentifiers(decl.id)) onIdent(id);
|
|
}
|
|
function markKnownIds(name, knownIds) {
|
|
if (name in knownIds) knownIds[name]++;
|
|
else knownIds[name] = 1;
|
|
}
|
|
function markScopeIdentifier(node, child, knownIds) {
|
|
const { name } = child;
|
|
/* v8 ignore start */
|
|
if (node.scopeIds && node.scopeIds.has(name)) return;
|
|
/* v8 ignore end */
|
|
markKnownIds(name, knownIds);
|
|
(node.scopeIds || (node.scopeIds = /* @__PURE__ */ new Set())).add(name);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/scope.ts
|
|
const extractors = {
|
|
ArrayPattern(names, param) {
|
|
for (const element of param.elements) if (element) extractors[element.type](names, element);
|
|
},
|
|
AssignmentPattern(names, param) {
|
|
extractors[param.left.type](names, param.left);
|
|
},
|
|
Identifier(names, param) {
|
|
names.push(param.name);
|
|
},
|
|
MemberExpression() {},
|
|
ObjectPattern(names, param) {
|
|
for (const prop of param.properties) if (prop.type === "RestElement") extractors.RestElement(names, prop);
|
|
else extractors[prop.value.type](names, prop.value);
|
|
},
|
|
RestElement(names, param) {
|
|
extractors[param.argument.type](names, param.argument);
|
|
}
|
|
};
|
|
function extractAssignedNames(param) {
|
|
const names = [];
|
|
extractors[param.type](names, param);
|
|
return names;
|
|
}
|
|
const blockDeclarations = {
|
|
const: true,
|
|
let: true
|
|
};
|
|
/**
|
|
* Represents a scope.
|
|
*/
|
|
var Scope = class {
|
|
parent;
|
|
isBlockScope;
|
|
declarations;
|
|
constructor(options = {}) {
|
|
this.parent = options.parent;
|
|
this.isBlockScope = !!options.block;
|
|
this.declarations = Object.create(null);
|
|
if (options.params) options.params.forEach((param) => {
|
|
extractAssignedNames(param).forEach((name) => {
|
|
this.declarations[name] = true;
|
|
});
|
|
});
|
|
}
|
|
addDeclaration(node, isBlockDeclaration, isVar) {
|
|
if (!isBlockDeclaration && this.isBlockScope) this.parent.addDeclaration(node, isBlockDeclaration, isVar);
|
|
else if (node.id) extractAssignedNames(node.id).forEach((name) => {
|
|
this.declarations[name] = true;
|
|
});
|
|
}
|
|
contains(name) {
|
|
return this.declarations[name] || (this.parent ? this.parent.contains(name) : false);
|
|
}
|
|
};
|
|
/**
|
|
* Attaches scopes to the given AST
|
|
*
|
|
* @param ast - The AST to attach scopes to.
|
|
* @param propertyName - The name of the property to attach the scopes to. Default is 'scope'.
|
|
* @returns The root scope of the AST.
|
|
*/
|
|
function attachScopes(ast, propertyName = "scope") {
|
|
let scope = new Scope();
|
|
walkAST(ast, {
|
|
enter(node, parent) {
|
|
if (/(?:Function|Class)Declaration/.test(node.type)) scope.addDeclaration(node, false, false);
|
|
if (node.type === "VariableDeclaration") {
|
|
const { kind } = node;
|
|
const isBlockDeclaration = blockDeclarations[kind];
|
|
node.declarations.forEach((declaration) => {
|
|
scope.addDeclaration(declaration, isBlockDeclaration, true);
|
|
});
|
|
}
|
|
let newScope;
|
|
if (/Function/.test(node.type)) {
|
|
const func = node;
|
|
newScope = new Scope({
|
|
parent: scope,
|
|
block: false,
|
|
params: func.params
|
|
});
|
|
if (func.type === "FunctionExpression" && func.id) newScope.addDeclaration(func, false, false);
|
|
}
|
|
if (/For(?:In|Of)?Statement/.test(node.type)) newScope = new Scope({
|
|
parent: scope,
|
|
block: true
|
|
});
|
|
if (node.type === "BlockStatement" && !/Function/.test(parent.type)) newScope = new Scope({
|
|
parent: scope,
|
|
block: true
|
|
});
|
|
if (node.type === "CatchClause") newScope = new Scope({
|
|
parent: scope,
|
|
params: node.param ? [node.param] : [],
|
|
block: true
|
|
});
|
|
if (newScope) {
|
|
Object.defineProperty(node, propertyName, {
|
|
value: newScope,
|
|
configurable: true
|
|
});
|
|
scope = newScope;
|
|
}
|
|
},
|
|
leave(node) {
|
|
if (node[propertyName]) scope = scope.parent;
|
|
}
|
|
});
|
|
return scope;
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
//#endregion
|
|
export { REGEX_DTS, REGEX_LANG_JSX, REGEX_LANG_TS, Scope, TS_NODE_TYPES, attachScopes, babelParse, babelParseExpression, createStringLiteral, createTSLiteralType, createTSUnionType, escapeKey, extractIdentifiers, getBabelParserOptions, getLang, isCallOf, isDeclarationType, isDts, isExpressionType, isForStatement, isFunctionType, isIdentifier, isIdentifierOf, isInDestructureAssignment, isInNewExpression, isLiteralType, isReferenced, isReferencedIdentifier, isStaticProperty, isStaticPropertyKey, isTaggedFunctionCallOf, isTs, isTypeOf, locateTrailingComma, parseCache, resolveIdentifier, resolveLiteral, resolveObjectKey, resolveString, resolveTemplateLiteral, tryResolveIdentifier, unwrapTSNode, walkAST, walkASTAsync, walkBlockDeclarations, walkExportDeclaration, walkFunctionParams, walkIdentifiers, walkImportDeclaration }; |