vue-hook-optimizer
Version:
a tool that helps refactor and optimize hook abstractions in Vue components
1,112 lines (1,105 loc) • 81.3 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const __babel_traverse = __toESM(require("@babel/traverse"));
const __vue_compiler_sfc = __toESM(require("@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 = __babel_traverse.default.default?.default || __babel_traverse.default.default || __babel_traverse.default;
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 = (0, __vue_compiler_sfc.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 = __babel_traverse.default.default?.default || __babel_traverse.default.default || __babel_traverse.default;
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 = (0, __vue_compiler_sfc.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 = __babel_traverse.default.default?.default || __babel_traverse.default.default || __babel_traverse.default;
function analyze$3(content) {
const id = "template";
const { code } = (0, __vue_compiler_sfc.compileTemplate)({
id,
source: content,
filename: `${id}.js`
});
const ast = (0, __vue_compiler_sfc.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 = __babel_traverse.default.default?.default || __babel_traverse.default.default || __babel_traverse.default;
/**
* 递归遍历如下结构:
* 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 === rootScop