import { walk } from 'estree-walker'; import { i as getMagicString } from './unimport.B4o9Mg_h.mjs'; function createEstreeDetector(parse) { return async (code, ctx, options, id) => { const s = getMagicString(code); const map = await ctx.getImportMap(); let matchedImports = []; const enableAutoImport = options?.autoImport !== false; const enableTransformVirtualImports = options?.transformVirtualImports !== false && ctx.options.virtualImports?.length; if (enableAutoImport || enableTransformVirtualImports) { const ast = parse(s.original, id); const virtualImports = createVirtualImports(map, ctx.options.virtualImports); const scopes = traveseScopes( ast, enableTransformVirtualImports ? virtualImports.walk : {} ); if (enableAutoImport) { const identifiers = scopes.unmatched; matchedImports.push( ...Array.from(identifiers).map((name) => { const item = map.get(name); if (item && !item.disabled) return item; return null; }).filter(Boolean) ); for (const addon of ctx.addons) matchedImports = await addon.matchImports?.call(ctx, identifiers, matchedImports) || matchedImports; } virtualImports.ranges.forEach(([start, end]) => { s.remove(start, end); }); matchedImports.push(...virtualImports.imports); } return { s, strippedCode: code.toString(), matchedImports, isCJSContext: false, firstOccurrence: 0 // TODO: }; }; } function traveseScopes(ast, additionalWalk) { const scopes = []; let scopeCurrent = void 0; const scopesStack = []; function pushScope(node) { scopeCurrent = { node, parent: scopeCurrent, declarations: /* @__PURE__ */ new Set(), references: /* @__PURE__ */ new Set() }; scopes.push(scopeCurrent); scopesStack.push(scopeCurrent); } function popScope(node) { const scope = scopesStack.pop(); if (scope?.node !== node) throw new Error("Scope mismatch"); scopeCurrent = scopesStack.at(-1); } function declarePattern(node) { switch (node.type) { case "Identifier": scopeCurrent.declarations.add(node.name); return; case "ObjectPattern": for (const property of node.properties) declarePattern(property.type === "Property" ? property.value : property.argument); return; case "ArrayPattern": for (const element of node.elements) { if (element) declarePattern(element); } return; case "AssignmentPattern": declarePattern(node.left); return; case "RestElement": declarePattern(node.argument); } } pushScope(void 0); walk(ast, { enter(node, parent, prop, index) { additionalWalk?.enter?.call(this, node, parent, prop, index); switch (node.type) { // ====== Declaration ====== case "ImportSpecifier": case "ImportDefaultSpecifier": case "ImportNamespaceSpecifier": scopeCurrent.declarations.add(node.local.name); return; case "ClassDeclaration": if (node.id) scopeCurrent.declarations.add(node.id.name); return; case "VariableDeclarator": declarePattern(node.id); return; // ====== Scope ====== // parameters and catch bindings live in a scope of their own, so that // they are visible to the body (block or expression) but not to the // code surrounding the function case "FunctionDeclaration": if (node.id) scopeCurrent.declarations.add(node.id.name); pushScope(node); for (const param of node.params) declarePattern(param); return; case "FunctionExpression": case "ArrowFunctionExpression": pushScope(node); for (const param of node.params) declarePattern(param); return; case "CatchClause": pushScope(node); if (node.param) declarePattern(node.param); return; case "BlockStatement": pushScope(node); return; // ====== Reference ====== case "Identifier": switch (parent?.type) { case "CallExpression": if (parent.callee === node || parent.arguments.includes(node)) scopeCurrent.references.add(node.name); return; case "MemberExpression": if (parent.object === node || parent.computed && parent.property === node) scopeCurrent.references.add(node.name); return; case "VariableDeclarator": if (parent.init === node) scopeCurrent.references.add(node.name); return; case "SpreadElement": if (parent.argument === node) scopeCurrent.references.add(node.name); return; case "ClassDeclaration": if (parent.superClass === node) scopeCurrent.references.add(node.name); return; case "Property": case "PropertyDefinition": if (parent.value === node || parent.computed && parent.key === node) scopeCurrent.references.add(node.name); return; case "MethodDefinition": if (parent.computed && parent.key === node) scopeCurrent.references.add(node.name); return; case "TemplateLiteral": if (parent.expressions.includes(node)) scopeCurrent.references.add(node.name); return; case "AssignmentExpression": case "AssignmentPattern": // e.g. function foo(p = bar) { ... } case "ForOfStatement": case "ForInStatement": if (parent.right === node) scopeCurrent.references.add(node.name); return; case "ReturnStatement": case "ThrowStatement": if (parent.argument === node) scopeCurrent.references.add(node.name); return; case "ExportDefaultDeclaration": if (parent.declaration === node) scopeCurrent.references.add(node.name); return; case "IfStatement": case "WhileStatement": case "DoWhileStatement": if (parent.test === node) scopeCurrent.references.add(node.name); return; case "SwitchStatement": if (parent.discriminant === node) scopeCurrent.references.add(node.name); return; case "SwitchCase": if (parent.test === node) scopeCurrent.references.add(node.name); return; } if (parent?.type.includes("Expression")) scopeCurrent.references.add(node.name); } }, leave(node, parent, prop, index) { additionalWalk?.leave?.call(this, node, parent, prop, index); switch (node.type) { case "BlockStatement": case "FunctionDeclaration": case "FunctionExpression": case "ArrowFunctionExpression": case "CatchClause": popScope(node); } } }); const unmatched = /* @__PURE__ */ new Set(); for (const scope of scopes) { for (const name of scope.references) { let defined = false; let parent = scope; while (parent) { if (parent.declarations.has(name)) { defined = true; break; } parent = parent?.parent; } if (!defined) unmatched.add(name); } } return { unmatched, scopes }; } function createVirtualImports(importMap, virtualImports = []) { const imports = []; const ranges = []; return { imports, ranges, walk: { enter(node) { if (node.type === "ImportDeclaration") { if (virtualImports.includes(node.source.value)) { ranges.push([node.start, node.end]); node.specifiers.forEach((i) => { if (i.type === "ImportSpecifier" && i.imported.type === "Identifier") { const original = importMap.get(i.imported.name); if (!original) throw new Error(`[unimport] failed to find "${i.imported.name}" imported from "${node.source.value}"`); imports.push({ from: original.from, name: original.name, as: i.local.name }); } }); } } } } }; } export { createEstreeDetector as c };