UNPKG

vue-hook-optimizer

Version:

a tool that helps refactor and optimize hook abstractions in Vue components

1,124 lines (1,118 loc) 79.8 kB
import _traverse from "@babel/traverse"; import { babelParse, compileTemplate, parse } from "@vue/compiler-sfc"; //#region src/analyze/utils.ts let NodeType = /* @__PURE__ */ function(NodeType$1) { NodeType$1["var"] = "var"; NodeType$1["fun"] = "fun"; return NodeType$1; }({}); var NodeCollection = class { constructor(_lineOffset = 0, _addInfo = true) { this.lineOffset = 0; this.addInfo = true; this.nodes = /* @__PURE__ */ new Map(); this.lineOffset = _lineOffset; this.addInfo = _addInfo; } addNode(label, node, options = { isComputed: false, isMethod: false, comment: "" }) { if (this.nodes.has(label)) return; if (!options.isComputed && (node.type === "VariableDeclarator" && [ "ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression" ].includes(node.init?.type || "") || node.type === "ObjectProperty" && [ "ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression" ].includes(node.value?.type || "") || node.type === "FunctionDeclaration" || node.type === "ObjectMethod" || node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") || options.isMethod) this.nodes.set(label, { label, type: NodeType.fun, ...this.addInfo ? { info: { line: (node.loc?.start.line || 1) - 1 + this.lineOffset, column: node.loc?.start.column || 0, ...options.comment ? { comment: options.comment } : {} } } : {} }); else this.nodes.set(label, { label, type: NodeType.var, ...this.addInfo ? { info: { line: (node.loc?.start.line || 1) - 1 + this.lineOffset, column: node.loc?.start.column || 0, ...options.comment ? { comment: options.comment } : {} } } : {} }); } addTypedNode(label, node) { this.nodes.set(label, { label, type: node.type, ...this.addInfo ? { info: { ...node.info || {} } } : {} }); } getNode(label) { return this.nodes.get(label); } map(graph) { const nodes = new Set(Array.from(graph.nodes).map((node) => { return this.nodes.get(node); }).filter((node) => !!node)); const edges = new Map(Array.from(graph.edges).map(([from, to]) => { const labelMap = /* @__PURE__ */ new Map(); for (const item of to) { const node = this.nodes.get(item.label); if (!node) continue; const existing = labelMap.get(item.label); if (!existing || existing.type === "get" && item.type === "set") labelMap.set(item.label, { node, type: item.type }); } const items = Array.from(labelMap.values()); return [this.nodes.get(from), new Set(items)]; })); return { nodes, edges }; } }; function getComment(node) { let comment = ""; node.leadingComments?.forEach((_comment) => { if (_comment.loc.end.line > node.loc.start.line) return; if (_comment.value.trim().startsWith("*")) comment += `${_comment.value.trim().replace(/^\s*\*+\s*\**/gm, "").trim()}\n`; }); node.trailingComments?.forEach((_comment) => { if (_comment.loc.end.line > node.loc.start.line) return; if (_comment.value.trim().startsWith("*")) comment += `${_comment.value.trim().replace(/^\s*\*+\s*\**/gm, "").trim()}\n`; else comment += `${_comment.value.trim()}\n`; }); return comment.trim(); } function isWritingNode(path) { const assignParent = path.findParent((p) => p.isAssignmentExpression()); if (assignParent) { const leftNode = assignParent.node.left; if (leftNode.start != null && path.node.start >= leftNode.start && path.node.end <= leftNode.end) return true; } const updateParent = path.findParent((p) => p.isUpdateExpression()); if (updateParent) { const argNode = updateParent.node.argument; if (argNode.start != null && path.node.start >= argNode.start && path.node.end <= argNode.end) return true; } return false; } function isCallingNode(path) { const parent = path.parentPath; if (parent && parent.isCallExpression()) return parent.node.callee === path.node; return false; } function getRelationType(path) { if (path.node.type === "Identifier" && isCallingNode(path)) return "call"; if (isWritingNode(path)) return "set"; return "get"; } //#endregion //#region src/analyze/setupScript.ts const traverse$3 = _traverse.default?.default || _traverse.default || _traverse; const ignoreFunctionsName = [ "defineProps", "defineEmits", "withDefaults" ]; const watchHooks = [ "watch", "watchArray", "watchAtMost", "watchDebounced", "watchDeep", "watchIgnorable", "watchImmediate", "watchOnce", "watchPausable", "watchThrottled", "watchTriggerable", "watchWithFilter" ]; function processSetup(ast, parentScope, parentPath, _spread, _lineOffset = 0) { const spread = _spread || []; const nodeCollection = new NodeCollection(_lineOffset); const graph = { nodes: /* @__PURE__ */ new Set(), edges: /* @__PURE__ */ new Map(), spread: /* @__PURE__ */ new Map() }; traverse$3(ast, { VariableDeclaration(path) { path.node.declarations.forEach((declaration) => { if (declaration.id.type === "ArrayPattern") declaration.id.elements.forEach((element) => { if (element?.type === "Identifier") { const name = element.name; const binding = path.scope.getBinding(name); if (binding && (path.parent.type === "Program" || parentPath?.type === "ObjectMethod" && parentPath.body === path.parent) && !(declaration.init?.type === "CallExpression" && declaration.init?.callee.type === "Identifier" && ignoreFunctionsName.includes(declaration.init?.callee.name))) { graph.nodes.add(name); nodeCollection.addNode(name, element, { comment: getComment(path.node) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } if (element?.type === "RestElement" && element.argument.type === "Identifier") { const name = element.argument.name; const binding = path.scope.getBinding(name); if (binding && (path.parent.type === "Program" || parentPath?.type === "ObjectMethod" && parentPath.body === path.parent) && !(declaration.init?.type === "CallExpression" && declaration.init?.callee.type === "Identifier" && ignoreFunctionsName.includes(declaration.init?.callee.name))) { graph.nodes.add(name); nodeCollection.addNode(name, element.argument, { comment: getComment(path.node) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } }); if (declaration.id.type === "ObjectPattern") declaration.id.properties.forEach((property) => { if (property.type === "ObjectProperty" && property.value.type === "Identifier") { const name = property.value.name; const binding = path.scope.getBinding(name); if (binding && (path.parent.type === "Program" || parentPath?.type === "ObjectMethod" && parentPath.body === path.parent) && !(declaration.init?.type === "CallExpression" && declaration.init?.callee.type === "Identifier" && ignoreFunctionsName.includes(declaration.init?.callee.name))) { graph.nodes.add(name); nodeCollection.addNode(name, property.value, { comment: getComment(property) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } if (property.type === "RestElement" && property.argument.type === "Identifier") { const name = property.argument.name; const binding = path.scope.getBinding(name); if (binding && (path.parent.type === "Program" || parentPath?.type === "ObjectMethod" && parentPath.body === path.parent) && !(declaration.init?.type === "CallExpression" && declaration.init?.callee.type === "Identifier" && ignoreFunctionsName.includes(declaration.init?.callee.name))) { graph.nodes.add(name); nodeCollection.addNode(name, property.argument, { comment: getComment(property) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } }); if (declaration.id?.type === "Identifier") { const name = declaration.id.name; const binding = path.scope.getBinding(name); if (binding && (path.parent.type === "Program" || parentPath?.type === "ObjectMethod" && parentPath.body === path.parent) && !(declaration.init?.type === "CallExpression" && declaration.init?.callee.type === "Identifier" && ignoreFunctionsName.includes(declaration.init?.callee.name))) { graph.nodes.add(name); nodeCollection.addNode(name, declaration, { comment: getComment(path.node) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); if (spread.includes(name)) { if (declaration.init?.type === "ObjectExpression") declaration.init?.properties.forEach((prop) => { if ((prop.type === "ObjectProperty" || prop.type === "ObjectMethod") && prop.key.type === "Identifier") { const keyName = prop.key.name; graph.nodes.add(keyName); nodeCollection.addNode(keyName, prop, { comment: getComment(prop) }); if (!graph.edges.get(keyName)) graph.edges.set(keyName, /* @__PURE__ */ new Set()); if (graph.spread.has(name)) graph.spread.get(name)?.add(keyName); else graph.spread.set(name, new Set([keyName])); } else if (prop.type === "SpreadElement") console.warn("not support spread in spread"); }); if (declaration.init?.type === "CallExpression" && declaration.init?.callee.type === "Identifier" && declaration.init?.callee.name === "reactive") { const arg = declaration.init?.arguments[0]; if (arg.type === "ObjectExpression") arg.properties.forEach((prop) => { if ((prop.type === "ObjectProperty" || prop.type === "ObjectMethod") && prop.key.type === "Identifier") { const keyName = prop.key.name; graph.nodes.add(keyName); nodeCollection.addNode(keyName, prop, { comment: getComment(prop) }); if (!graph.edges.get(keyName)) graph.edges.set(keyName, /* @__PURE__ */ new Set()); if (graph.spread.has(name)) graph.spread.get(name)?.add(keyName); else graph.spread.set(name, new Set([keyName])); } else if (prop.type === "SpreadElement") console.warn("not support spread in spread"); }); } } } } }); }, FunctionDeclaration(path) { const name = path.node.id?.name; if (name) { const binding = path.scope.getBinding(name); if (binding && (path.parent.type === "Program" || parentPath?.type === "ObjectMethod" && parentPath.body === path.parent)) { graph.nodes.add(name); nodeCollection.addNode(name, path.node.id, { isMethod: true, comment: getComment(path.node) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } } }, parentScope, parentPath); function traverseHooks(node, patentScope) { if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.type === "Identifier" || node.type === "CallExpression" && node.callee.type === "Identifier") { const hookName = (() => { if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.type === "Identifier") return node.expression.callee.name; if (node.type === "CallExpression" && node.callee.type === "Identifier") return node.callee.name; })() || ""; if (!hookName) return; const hookBinding = patentScope.getBinding(hookName); if (!(hookBinding === void 0 || hookBinding?.scope.block.type === "Program" || parentScope === hookBinding?.scope)) return; const expression = node.type === "ExpressionStatement" ? node.expression : node; const watchArgs = /* @__PURE__ */ new Set(); if (hookName === "provide") traverse$3(expression, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) { const _node = nodeCollection.getNode(path1.node.name); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; } } }, patentScope, node); else if (watchHooks.includes(hookName)) if (expression.arguments[0].type === "Identifier") { const binding = patentScope.getBinding(expression.arguments[0].name); if (graph.nodes.has(expression.arguments[0].name) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) watchArgs.add(expression.arguments[0]); } else traverse$3(expression.arguments[0], { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) watchArgs.add(path1.node); } }, patentScope, node); else if (hookName === "useEffect" && expression.arguments[1].type === "ArrayExpression") traverse$3(expression.arguments[1], { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) watchArgs.add(path1.node); } }, patentScope, node); expression.arguments.forEach((argNode, index) => { if (watchHooks.includes(hookName) && index === 0 && argNode.type === "Identifier") { const _node = nodeCollection.getNode(argNode.name); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; return; } if (argNode.type === "Identifier") { const binding = patentScope.getBinding(argNode.name); if (graph.nodes.has(argNode.name) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) { const _node = nodeCollection.getNode(argNode.name); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; } } else traverse$3(argNode, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) { if ([...watchHooks, "useEffect"].includes(hookName) && watchArgs.size > 0) { const watchArgsNames = Array.from(watchArgs).map((arg) => arg.name); watchArgs.forEach((watchArg) => { if (!watchArgsNames.includes(path1.node.name)) graph.edges.get(watchArg.name)?.add({ label: path1.node.name, type: getRelationType(path1) }); }); } const _node = nodeCollection.getNode(path1.node.name); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; } } }, patentScope, node); }); } } traverse$3(ast, { FunctionDeclaration(path) { const name = path.node.id?.name; if (name && graph.nodes.has(name)) traverse$3(path.node.body, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.name, type: getRelationType(path1) }); }, MemberExpression(path1) { if (path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name)) { const binding = path1.scope.getBinding(path1.node.object.name); if (spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier" && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.property.name, type: getRelationType(path1) }); } } }, path.scope, path); }, VariableDeclarator(path) { if (path.node.init) { if (path.node.id.type === "ArrayPattern") path.node.id.elements.forEach((element) => { if (element?.type === "Identifier") { const name = element.name; if (name && graph.nodes.has(name) && path.node.init?.type === "CallExpression") traverse$3(path.node.init, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.name, type: getRelationType(path1) }); }, MemberExpression(path1) { if (path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name)) { const binding = path1.scope.getBinding(path1.node.object.name); if (spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier" && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.property.name, type: getRelationType(path1) }); } } }, path.scope, path); } }); else if (path.node.id.type === "ObjectPattern") path.node.id.properties.forEach((property) => { if (property.type === "ObjectProperty" && property.value.type === "Identifier") { const name = property.value.name; if (name && graph.nodes.has(name) && path.node.init) traverse$3(path.node.init, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.name, type: getRelationType(path1) }); }, MemberExpression(path1) { if (path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name)) { const binding = path1.scope.getBinding(path1.node.object.name); if (spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier" && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.property.name, type: getRelationType(path1) }); } } }, path.scope, path); } }); else if ([ "CallExpression", "ArrowFunctionExpression", "FunctionDeclaration" ].includes(path.node.init.type) && path.node.id.type === "Identifier") { if (path.node.init.type === "CallExpression" && path.node.init.callee.type === "Identifier" && [...watchHooks, "watchEffect"].includes(path.node.init.callee.name)) traverseHooks(path.node.init, path.scope); const name = path.node.id?.name; if (name && graph.nodes.has(name)) traverse$3(path.node.init, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.name, type: getRelationType(path1) }); }, MemberExpression(path1) { if (path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name)) { const binding = path1.scope.getBinding(path1.node.object.name); if (spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier" && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.property.name, type: getRelationType(path1) }); } } }, path.scope, path); } else if (path.node.id.type === "Identifier") { const name = path.node.id.name; if (path.node.init.type === "Identifier") { const binding = path.scope.getBinding(path.node.init.name); if (graph.nodes.has(path.node.init.name) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path.node.init.name, type: getRelationType(path) }); } else traverse$3(path.node.init, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.name, type: getRelationType(path1) }); } }, path.scope, path); } } }, ObjectMethod(path) { if (path.node.key.type === "Identifier" && graph.nodes.has(path.node.key.name)) { const name = path.node.key.name; traverse$3(path.node.body, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (graph.nodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node) && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.name, type: getRelationType(path1) }); }, MemberExpression(path1) { if (path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name)) { const binding = path1.scope.getBinding(path1.node.object.name); if (spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier" && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.property.name, type: getRelationType(path1) }); } } }, path.scope, path); } }, ObjectProperty(path) { if (path.node.key.type === "Identifier" && graph.nodes.has(path.node.key.name)) { const name = path.node.key.name; traverse$3(path.node.value, { MemberExpression(path1) { if (path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name)) { const binding = path1.scope.getBinding(path1.node.object.name); if (spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier" && (binding?.scope.block.type === "Program" || parentScope === binding?.scope)) graph.edges.get(name)?.add({ label: path1.node.property.name, type: getRelationType(path1) }); } } }, path.scope, path); } }, ExpressionStatement(path) { if (path.type === "ExpressionStatement" && path.node.expression.type === "CallExpression" && path.node.expression.callee.type === "Identifier") { const name = path.node.expression.callee.name; if (graph.nodes.has(name) && path.scope.block.type === "Program") { const _node = nodeCollection.getNode(name); if (_node?.info?.used) _node?.info?.used?.add("Call Expression"); else if (_node) _node.info = { ..._node?.info, used: new Set(["Call Expression"]) }; } else traverseHooks(path.node.expression, path.scope); } if (path.type === "ExpressionStatement" && path.node.expression.type === "AssignmentExpression" && path.node.expression.right.type === "CallExpression" && path.node.expression.right.callee.type === "Identifier") traverseHooks(path.node.expression.right, path.scope); } }, parentScope, parentPath); return { graph, nodeCollection }; } function analyze$1(content, lineOffset = 0, jsx = false) { const ast = babelParse(content, { sourceType: "module", plugins: ["typescript", ...jsx ? ["jsx"] : []] }); const { graph, nodeCollection } = processSetup(ast, void 0, void 0, void 0, lineOffset); return nodeCollection.map(graph); } //#endregion //#region src/analyze/options.ts const traverse$2 = _traverse.default?.default || _traverse.default || _traverse; const vueLifeCycleHooks = [ "beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "beforeDestroy", "destroyed", "activated", "deactivated", "errorCaptured", "renderTracked", "renderTriggered", "provide" ]; function analyze(content, lineOffset = 0, jsx = false) { const ast = babelParse(content, { sourceType: "module", plugins: ["typescript", ...jsx ? ["jsx"] : []] }); let nodeCollection = new NodeCollection(lineOffset); const tNodes = /* @__PURE__ */ new Map(); const graph = { nodes: /* @__PURE__ */ new Set(), edges: /* @__PURE__ */ new Map() }; /** used in render block or setup return */ const nodesUsedInTemplate = /* @__PURE__ */ new Set(); function process(node, path) { traverse$2(node, { ObjectProperty(path1) { if (path.node.declaration.type === "ObjectExpression" && path1.parent === path.node.declaration || path.node.declaration.type === "CallExpression" && path1.parent === path.node.declaration.arguments[0]) { if (path1.node.key.type === "Identifier" && path1.node.key.name === "data" && (path1.node.value.type === "ArrowFunctionExpression" || path1.node.value.type === "FunctionExpression")) { const dataNode = path1.node.value; traverse$2(dataNode, { ReturnStatement(path2) { if (path2.parent === dataNode.body) { if (path2.node.argument?.type === "ObjectExpression") path2.node.argument.properties.forEach((prop) => { if (prop.type === "ObjectProperty") { if (prop.key.type === "Identifier") { const name = prop.key.name; graph.nodes.add(name); tNodes.set(name, prop.key); nodeCollection.addNode(name, prop, { comment: getComment(prop) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } }); } } }, path1.scope, path1); } if (path1.node.key.type === "Identifier" && path1.node.key.name === "computed") { const computedNode = path1.node; if (computedNode.value.type === "ObjectExpression") computedNode.value.properties.forEach((prop) => { if (prop.type === "ObjectProperty" || prop.type === "ObjectMethod") { if (prop.key.type === "Identifier") { const name = prop.key.name; graph.nodes.add(name); tNodes.set(name, prop.key); nodeCollection.addNode(name, prop, { isComputed: true, comment: getComment(prop) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } }); } if (path1.node.key.type === "Identifier" && path1.node.key.name === "methods") { const methodsNode = path1.node; if (methodsNode.value.type === "ObjectExpression") methodsNode.value.properties.forEach((prop) => { if (prop.type === "ObjectProperty" || prop.type === "ObjectMethod") { if (prop.key.type === "Identifier") { const name = prop.key.name; graph.nodes.add(name); tNodes.set(name, prop.key); nodeCollection.addNode(name, prop, { isMethod: true, comment: getComment(prop) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } }); } if (path1.node.key.type === "Identifier" && path1.node.key.name === "render" && (path1.node.value.type === "ArrowFunctionExpression" || path1.node.value.type === "FunctionExpression")) traverse$2(path1.node.value, { ReturnStatement(path2) { const templateNode = path2.node; traverse$2(templateNode, { MemberExpression(path3) { if (path3.node.object && path3.node.object.type === "ThisExpression") { if (path3.node.property && path3.node.property.type === "Identifier") nodesUsedInTemplate.add(path3.node.property.name); } } }, path2.scope, path2); } }, path1.scope, path1); } }, ObjectMethod(path1) { if (path.node.declaration.type === "ObjectExpression" && path1.parent === path.node.declaration || path.node.declaration.type === "CallExpression" && path1.parent === path.node.declaration.arguments[0]) { if (path1.node.key.type === "Identifier" && path1.node.key.name === "setup") { const setupNode = path1.node; const spread = []; traverse$2(setupNode, { ReturnStatement(path2) { if (path2.node.argument?.type === "ObjectExpression") { const returnNode = path2.node.argument; traverse$2(returnNode, { SpreadElement(path3) { if (path3.node.argument.type === "CallExpression" && path3.node.argument.callee.type === "Identifier" && path3.node.argument.callee.name === "toRefs" && path3.node.argument.arguments[0].type === "Identifier") spread.push(path3.node.argument.arguments[0].name); else if (path3.node.argument.type === "Identifier") spread.push(path3.node.argument.name); } }, path2.scope, path2); } if (path2.node.argument?.type === "FunctionExpression" || path2.node.argument?.type === "ArrowFunctionExpression") { const templateNode = path2.node.argument.body; traverse$2(templateNode, { Identifier(path3) { const binding = path3.scope.getBinding(path3.node.name); if (binding?.scope === path1.scope) nodesUsedInTemplate.add(path3.node.name); }, JSXIdentifier(path3) { const binding = path3.scope.getBinding(path3.node.name); if (binding?.scope === path1.scope) nodesUsedInTemplate.add(path3.node.name); } }, path2.scope, path2); } } }, path1.scope, path1); const { graph: { nodes: tempNodes, edges: tempEdges, spread: tempSpread }, nodeCollection: tempNodeCollection } = processSetup(setupNode, path1.scope, setupNode, spread, lineOffset); traverse$2(setupNode, { ReturnStatement(path2) { if (path2.scope !== path1.scope) return; if (path2.node.argument?.type === "ObjectExpression") { const returnNode = path2.node.argument; traverse$2(returnNode, { ObjectProperty(path3) { if (path3.parent === returnNode) { if (path3.node.key.type === "Identifier" && path3.node.value.type === "Identifier" && tempNodes.has(path3.node.value.name)) { const valName = path3.node.value.name; if (!graph.nodes.has(valName)) { graph.nodes.add(valName); tNodes.set(valName, path3.node.value); nodeCollection.addTypedNode(valName, tempNodeCollection.nodes.get(valName)); } if (!graph.edges.has(valName)) graph.edges.set(valName, new Set([...Array.from(tempEdges.get(valName) || /* @__PURE__ */ new Set())])); const name = path3.node.key.name; if (name !== valName) { graph.nodes.add(name); tNodes.set(name, path3.node.key); nodeCollection.addNode(name, path3.node.key, { comment: getComment(path3.node) }); graph.edges.set(name, new Set([{ label: valName, type: getRelationType(path3) }])); } } } }, SpreadElement(path3) { if (path3.node.argument.type === "CallExpression" && path3.node.argument.callee.type === "Identifier" && path3.node.argument.callee.name === "toRefs" && path3.node.argument.arguments[0].type === "Identifier" && tempSpread.get(path3.node.argument.arguments[0].name)) tempSpread.get(path3.node.argument.arguments[0].name)?.forEach((name) => { graph.nodes.add(name); tNodes.set(name, path3.node.argument.arguments[0]); nodeCollection.addTypedNode(name, tempNodeCollection.nodes.get(name)); if (!graph.edges.get(name)) { graph.edges.set(name, /* @__PURE__ */ new Set()); tempEdges.get(name)?.forEach((edge) => { graph.edges.get(name)?.add(edge); }); } }); else if (path3.node.argument.type === "Identifier" && tempSpread.get(path3.node.argument.name)) tempSpread.get(path3.node.argument.name)?.forEach((name) => { graph.nodes.add(name); tNodes.set(name, path3.node.argument); nodeCollection.addTypedNode(name, tempNodeCollection.nodes.get(name)); if (!graph.edges.get(name)) { graph.edges.set(name, /* @__PURE__ */ new Set()); tempEdges.get(name)?.forEach((edge) => { graph.edges.get(name)?.add(edge); }); } }); } }, path2.scope, path2); } else { graph.edges = tempEdges; graph.nodes = tempNodes; nodeCollection = tempNodeCollection; } } }, path1.scope, path1); } if (path1.node.key.type === "Identifier" && path1.node.key.name === "data") { const dataNode = path1.node; traverse$2(dataNode, { ReturnStatement(path2) { if (path2.parent === dataNode.body) { if (path2.node.argument?.type === "ObjectExpression") path2.node.argument.properties.forEach((prop) => { if (prop.type === "ObjectProperty") { if (prop.key.type === "Identifier") { const name = prop.key.name; graph.nodes.add(name); tNodes.set(name, prop.key); nodeCollection.addNode(name, prop, { comment: getComment(prop) }); if (!graph.edges.get(name)) graph.edges.set(name, /* @__PURE__ */ new Set()); } } }); } } }, path1.scope, path1); } if (path1.node.key.type === "Identifier" && path1.node.key.name === "render") traverse$2(path1.node, { ReturnStatement(path2) { const templateNode = path2.node; traverse$2(templateNode, { MemberExpression(path3) { if (path3.node.object && path3.node.object.type === "ThisExpression") { if (path3.node.property && path3.node.property.type === "Identifier") nodesUsedInTemplate.add(path3.node.property.name); } } }, path2.scope, path2); } }, path1.scope, path1); } } }, path.scope, path); traverse$2(node, { ObjectMethod(path1) { if (path.node.declaration.type === "ObjectExpression" && path1.parent === path.node.declaration || path.node.declaration.type === "CallExpression" && path1.parent === path.node.declaration.arguments[0]) { if (path1.node.key.type === "Identifier" && vueLifeCycleHooks.includes(path1.node.key.name)) { const hookName = path1.node.key.name; traverse$2(path1.node.body, { MemberExpression(path2) { if (path2.node.object.type === "ThisExpression" && path2.node.property.type === "Identifier") { const _node = nodeCollection.getNode(path2.node.property.name); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; } } }, path1.scope, path1); } } }, ObjectProperty(path1) { if (path.node.declaration.type === "ObjectExpression" && path1.parent === path.node.declaration || path.node.declaration.type === "CallExpression" && path1.parent === path.node.declaration.arguments[0]) { if (path1.node.key.type === "Identifier" && path1.node.key.name === "computed") { const computedNode = path1.node; if (computedNode.value.type === "ObjectExpression") computedNode.value.properties.forEach((prop) => { if (prop.type === "ObjectMethod" && prop.key.type === "Identifier") { const name = prop.key.name; traverse$2(prop, { MemberExpression(path2) { if (path2.node.object.type === "ThisExpression" && path2.node.property.type === "Identifier") graph.edges.get(name)?.add({ label: path2.node.property.name, type: getRelationType(path2) }); } }, path1.scope, path1); } if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.value.type === "ObjectExpression") { const name = prop.key.name; prop.value.properties.forEach((prop1) => { if (prop1.type === "ObjectProperty" && prop1.key.type === "Identifier" && prop1.key.name === "get") traverse$2(prop1, { MemberExpression(path2) { if (path2.node.object.type === "ThisExpression" && path2.node.property.type === "Identifier") graph.edges.get(name)?.add({ label: path2.node.property.name, type: getRelationType(path2) }); } }, path1.scope, path1); }); } }); } if (path1.node.key.type === "Identifier" && path1.node.key.name === "methods") { const methodsNode = path1.node; if (methodsNode.value.type === "ObjectExpression") methodsNode.value.properties.forEach((prop) => { if ((prop.type === "ObjectMethod" || prop.type === "ObjectProperty") && prop.key.type === "Identifier") { const name = prop.key.name; traverse$2(prop, { MemberExpression(path2) { if (path2.node.object.type === "ThisExpression" && path2.node.property.type === "Identifier") graph.edges.get(name)?.add({ label: path2.node.property.name, type: getRelationType(path2) }); } }, path1.scope, path1); } }); } if (path1.node.key.type === "Identifier" && [...watchHooks, ...vueLifeCycleHooks].includes(path1.node.key.name)) { const hookName = path1.node.key.name; if (watchHooks.includes(hookName) && path1.node.value.type === "ObjectExpression") path1.node.value.properties.forEach((prop) => { if ((prop.type === "ObjectProperty" || prop.type === "ObjectMethod") && (prop.key.type === "Identifier" || prop.key.type === "StringLiteral")) { const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value.split(".")[0] : ""; const watchArg = tNodes.get(keyName); const _node = nodeCollection.getNode(keyName); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; traverse$2(path1.node.value, { MemberExpression(path2) { if (path2.node.object.type === "ThisExpression" && path2.node.property.type === "Identifier") { if (watchArg && watchArg.name !== path2.node.property.name) graph.edges.get(watchArg.name)?.add({ label: path2.node.property.name, type: getRelationType(path2) }); } } }, path1.scope, path1); } }); else traverse$2(path1.node.value, { MemberExpression(path2) { if (path2.node.object.type === "ThisExpression" && path2.node.property.type === "Identifier") { const _node = nodeCollection.getNode(path2.node.property.name); if (_node?.info?.used) _node?.info?.used?.add(hookName); else if (_node) _node.info = { ..._node?.info, used: new Set([hookName]) }; } } }, path1.scope, path1); } } } }, path.scope, path); } traverse$2(ast, { ExportDefaultDeclaration(path) { if (path.node.declaration.type === "ObjectExpression") process(path.node.declaration, path); else if (path.node.declaration.type === "CallExpression" && path.node.declaration.callee.type === "Identifier" && path.node.declaration.callee.name === "defineComponent" && path.node.declaration.arguments[0].type === "ObjectExpression") process(path.node.declaration.arguments[0], path); } }); return { graph: nodeCollection.map(graph), nodesUsedInTemplate }; } //#endregion //#region src/analyze/style.ts var LexerState = /* @__PURE__ */ function(LexerState$1) { LexerState$1[LexerState$1["inParens"] = 0] = "inParens"; LexerState$1[LexerState$1["inSingleQuoteString"] = 1] = "inSingleQuoteString"; LexerState$1[LexerState$1["inDoubleQuoteString"] = 2] = "inDoubleQuoteString"; return LexerState$1; }(LexerState || {}); function lexBinding(content, start) { let state = LexerState.inParens; let parenDepth = 0; for (let i = start; i < content.length; i++) { const char = content.charAt(i); switch (state) { case LexerState.inParens: if (char === "'") state = LexerState.inSingleQuoteString; else if (char === "\"") state = LexerState.inDoubleQuoteString; else if (char === "(") parenDepth++; else if (char === ")") if (parenDepth > 0) parenDepth--; else return i; break; case LexerState.inSingleQuoteString: if (char === "'") state = LexerState.inParens; break; case LexerState.inDoubleQuoteString: if (char === "\"") state = LexerState.inParens; break; } } return null; } function normalizeExpression(exp) { exp = exp.trim(); if (exp[0] === "'" && exp[exp.length - 1] === "'" || exp[0] === "\"" && exp[exp.length - 1] === "\"") return exp.slice(1, -1); return exp; } const vBindRE = /v-bind\s*\(/g; function analyze$2(styles) { const nodes = /* @__PURE__ */ new Set(); styles.forEach((style) => { let match; const content = style.content.replace(/\/\*([\s\S]*?)\*\/|\/\/.*/g, ""); while (match = vBindRE.exec(content)) { const start = match.index + match[0].length; const end = lexBinding(content, start); if (end !== null) { const variable = normalizeExpression(content.slice(start, end)); nodes.add(variable); } } }); return nodes; } //#endregion //#region src/analyze/template.ts const traverse$1 = _traverse.default?.default || _traverse.default || _traverse; function analyze$3(content) { const id = "template"; const { code } = compileTemplate({ id, source: content, filename: `${id}.js` }); const ast = babelParse(code, { sourceType: "module", plugins: ["typescript"] }); const nodes = /* @__PURE__ */ new Set(); traverse$1(ast, { MemberExpression(path) { if (path.type === "MemberExpression") { if (path.node.object && path.node.object.type === "Identifier" && path.node.object.name === "_ctx") { if (path.node.property && path.node.property.type === "Identifier") nodes.add(path.node.property.name); } } }, ObjectProperty(path) { if (path.node.key.type === "Identifier" && path.node.key.name === "ref") { if (path.node.value.type === "StringLiteral") { const name = path.node.value.value; if (name) nodes.add(name); } } }, CallExpression(path) { if (path.node.callee.type === "Identifier" && path.node.callee.name === "_resolveComponent") { if (path.node.arguments[0].type === "StringLiteral") { const name = path.node.arguments[0].value; if (name) nodes.add(name); } } } }); return nodes; } //#endregion //#region src/utils/traverse.ts const traverse = _traverse.default?.default || _traverse.default || _traverse; /** * 递归遍历如下结构: * let { loc, loc: locd, loc: { start, end }, loc: { start: { line: { deep } }} } = node; * 解出 loc, locd, start, end, deep */ function rescureObjectPattern({ node, rootScope, res, parentScope, parentPath }) { traverse(node, { ObjectProperty(path1) { if (path1.node.type === "ObjectProperty" && path1.node.key.type === "Identifier" && path1.node.value.type === "Identifier") { const name = path1.node.value.name; const _scope = path1.scope.getBinding(name)?.scope; if (_scope && _scope === rootScope) res.push(path1.node.value); } else if (path1.node.type === "ObjectProperty" && path1.node.key.type === "Identifier" && path1.node.value.type === "ObjectPattern") rescureObjectPattern({ node: path1.node.value, rootScope, res, parentScope: path1.scope, parentPath: path1 }); }, RestElement(path1) { if (path1.node.argument.type === "Identifier") { const name = path1.node.argument.name; const _scope = path1.scope.getBinding(name)?.scope; if (_scope && _scope === rootScope) res.push(path1.node.argument); } } }, parentScope, parentPath); } /** * 递归遍历如下结构: * let [foo, [bar, baz]] = [1, [[2], 3]]; * 解出 foo, bar, baz */ function rescureArrayPattern({ node, rootScope, res, parentScope, parentPath }) { traverse(node, { Identifier(path1) { if (path1.node.type === "Identifier") { const name = path1.node.name; const _scope = path1.scope.getBinding(name)?.scope; if (_scope && _scope === rootScope) res.push(path1.node); } }, ArrayPattern(path1) { if (path1.node.type === "ArrayPattern") rescureArrayPattern({ node: path1.node, rootScope, res, parentScope: path1.scope, parentPath: path1 }); } }, parentScope, parentPath); } function parseNodeIdentifierPattern({ path, rootScope, cb }) { if (path.node.id.type !== "Identifier") return; if (path.node.init?.type === "ArrowFunctionExpression" || path.node.init?.type === "FunctionExpression") cb?.({ name: path.node.id.name, node: path.node, path, scope: rootScope }); else cb?.({ name: path.node.id.name, node: path.node, path, scope: rootScope }); } function parseNodeObjectPattern({ path, rootScope, cb }) { if (path.node.id.type !== "ObjectPattern") return; path.node.id.properties.forEach((property) => { if (property.type === "ObjectProperty" && property.key.type === "Identifier" && property.value.type === "Identifier") cb?.({ name: property.value.name, node: property, path, scope: rootScope }); else if (property.type === "ObjectProperty" && property.key.type === "Identifier" && property.value.type === "AssignmentPattern") cb?.({ name: property.key.name, node: property, path, scope: rootScope }); else if (property.type === "RestElement" && property.argument.type === "Identifier") cb?.({ name: property.argument.name, node: property, path, scope: rootScope }); else if (property.type === "ObjectProperty" && property.key.type === "Identifier" && property.value.type === "ObjectPattern") { const res = []; rescureObjectPattern({ node: property.value, rootScope, res, parentScope: path.scope, parentPath: path }); res.forEach((r) => cb?.({ name: r.name, node: r, path, scope: rootScope })); } }); } function parseNodeArrayPattern({ path, rootScope, cb }) { if (path.node.id.type !== "ArrayPattern") return; path.node.id.elements.forEach((ele) => { if (ele?.type === "Identifier") cb?.({ name: ele.name, node: ele, path, scope: rootScope }); else if (ele?.type === "ArrayPattern") { const res = []; rescureArrayPattern({ node: ele, rootScope, res, parentScope: path.scope, parentPath: path }); res.forEach((r) => cb?.({ name: r.name, node: r, path, scope: rootScope })); } else if (ele?.type === "AssignmentPattern") { if (ele.left.type === "Identifier") cb?.({ name: ele.left.name, node: ele, path, scope: rootScope }); } else if (ele?.type === "RestElement") { if (ele.argument.type === "Identifier") cb?.({ name: ele.argument.name, node: ele, path, scope: rootScope }); } }); } function parseNodeFunctionPattern({ path, rootScope, cb }) { if (path.node.type !== "FunctionDeclaration") return; if (path.node.id?.type === "Identifier") cb?.({ name: path.node.id.name, node: path.node, path, scope: rootScope }); } function parseEdgeLeftIdentifierPattern({ path, rootScope, cb, collectionNodes, spread }) { if (!path.node.id || path.node.id.type !== "Identifier") return; if (path.node.init?.type && [ "ArrowFunctionExpression", "FunctionExpression", "CallExpression", "ObjectExpression", "ArrayExpression" ].includes(path.node.init.type)) { if (collectionNodes.has(path.node.id.name) && path.scope.getBinding(path.node.id.name)?.scope === rootScope) { const name = path.node.id.name; traverse(path.node.init, { Identifier(path1) { const binding = path1.scope.getBinding(path1.node.name); if (binding?.scope === rootScope && collectionNodes.has(path1.node.name) && (path1.parent.type !== "MemberExpression" && path1.parent.type !== "OptionalMemberExpression" || path1.parent.object === path1.node)) cb?.({ fromName: name, toName: path1.node.name, path: path1, scope: rootScope, collectionNodes }); }, MemberExpression(path1) { if (spread?.length && path1.node.object.type === "Identifier" && spread.includes(path1.node.object.name) && path1.node.property.type === "Identifier") cb?.({ fromName: name, toName: path1.node.property.name, toScope: path1.scope.getBinding(path1.node.object.name)?.scope, path: path1, scope: rootScope, collectionNodes }); } }, path.scope, path); } } } function parseEdgeLeftObjectPattern({ path, rootScope, cb, collectionNodes }) { if (!path.node.id || path.node.id.type !== "ObjectPattern") return; if (path.node.init?.type && [ "ArrowFunctionExpression", "FunctionExpression", "CallExpression", "ObjectExpression", "ArrayExpression" ].includes(path.node.