UNPKG

vue-hook-optimizer

Version:

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

1 lines 188 kB
{"version":3,"sources":["../src/analyze/options.ts","../src/analyze/setupScript.ts","../src/analyze/utils.ts","../src/analyze/style.ts","../src/analyze/template.ts","../src/analyze/tsx.ts","../src/utils/traverse.ts","../src/mermaid.ts","../src/suggest/filter.ts","../src/suggest/split.ts","../src/suggest/utils.ts","../src/suggest/index.ts","../src/vis.ts","../src/index.ts"],"sourcesContent":["import type { NodePath } from '@babel/traverse';\nimport type * as t from '@babel/types';\nimport type { RelationType } from './utils';\nimport _traverse from '@babel/traverse';\nimport { babelParse } from '@vue/compiler-sfc';\nimport { processSetup, watchHooks } from './setupScript';\nimport { getComment, getRelationType, NodeCollection } from './utils';\n\nconst traverse: typeof _traverse\n // @ts-expect-error unwarp default\n = _traverse.default?.default || _traverse.default || _traverse;\n\nconst vueLifeCycleHooks = [\n 'beforeCreate',\n 'created',\n 'beforeMount',\n 'mounted',\n 'beforeUpdate',\n 'updated',\n 'beforeDestroy',\n 'destroyed',\n 'activated',\n 'deactivated',\n 'errorCaptured',\n 'renderTracked',\n 'renderTriggered',\n 'provide',\n];\n\nexport function analyze(\n content: string,\n lineOffset = 0,\n jsx = false,\n) {\n // console.log({lineOffset});\n // console.log(content);\n const ast = babelParse(content, { sourceType: 'module', plugins: [\n 'typescript',\n ...jsx\n ? ['jsx' as const]\n : [],\n ] });\n\n // ---\n\n let nodeCollection = new NodeCollection(lineOffset);\n\n const tNodes = new Map<string, t.Identifier>();\n const graph = {\n nodes: new Set<string>(),\n edges: new Map<string, Set<{ label: string, type: RelationType }>>(),\n };\n\n /** used in render block or setup return */\n const nodesUsedInTemplate = new Set<string>();\n\n function process(node: t.ObjectExpression, path: NodePath<t.ExportDefaultDeclaration>) {\n traverse(node, {\n ObjectProperty(path1) {\n if (\n (\n path.node.declaration.type === 'ObjectExpression'\n && path1.parent === path.node.declaration\n ) || (\n path.node.declaration.type === 'CallExpression'\n && path1.parent === path.node.declaration.arguments[0]\n )\n ) {\n // data\n if (\n path1.node.key.type === 'Identifier'\n && path1.node.key.name === 'data'\n && (\n path1.node.value.type === 'ArrowFunctionExpression'\n || path1.node.value.type === 'FunctionExpression'\n )\n ) {\n const dataNode = path1.node.value;\n\n traverse(dataNode, {\n ReturnStatement(path2) {\n if (path2.parent === dataNode.body) {\n if (path2.node.argument?.type === 'ObjectExpression') {\n path2.node.argument.properties.forEach((prop) => {\n if (prop.type === 'ObjectProperty') {\n if (prop.key.type === 'Identifier') {\n const name = prop.key.name;\n graph.nodes.add(name);\n tNodes.set(name, prop.key);\n nodeCollection.addNode(name, prop, {\n comment: getComment(prop),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n });\n }\n }\n },\n }, path1.scope, path1);\n }\n\n // computed\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'computed') {\n const computedNode = path1.node;\n if (computedNode.value.type === 'ObjectExpression') {\n computedNode.value.properties.forEach((prop) => {\n if (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') {\n if (prop.key.type === 'Identifier') {\n const name = prop.key.name;\n graph.nodes.add(name);\n tNodes.set(name, prop.key);\n nodeCollection.addNode(name, prop, {\n isComputed: true,\n comment: getComment(prop),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n });\n }\n }\n\n // methods\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'methods') {\n const methodsNode = path1.node;\n if (methodsNode.value.type === 'ObjectExpression') {\n methodsNode.value.properties.forEach((prop) => {\n if (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') {\n if (prop.key.type === 'Identifier') {\n const name = prop.key.name;\n graph.nodes.add(name);\n tNodes.set(name, prop.key);\n nodeCollection.addNode(name, prop, {\n isMethod: true,\n comment: getComment(prop),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n });\n }\n }\n\n if (\n path1.node.key.type === 'Identifier'\n && path1.node.key.name === 'render'\n && (\n path1.node.value.type === 'ArrowFunctionExpression'\n || path1.node.value.type === 'FunctionExpression'\n )\n ) {\n traverse(path1.node.value, {\n ReturnStatement(path2) {\n const templateNode = path2.node;\n traverse(templateNode, {\n MemberExpression(path3) {\n if (path3.node.object && path3.node.object.type === 'ThisExpression') {\n if (path3.node.property && path3.node.property.type === 'Identifier') {\n nodesUsedInTemplate.add(path3.node.property.name);\n }\n }\n },\n }, path2.scope, path2);\n },\n }, path1.scope, path1);\n }\n }\n },\n ObjectMethod(path1) {\n if (\n (\n path.node.declaration.type === 'ObjectExpression'\n && path1.parent === path.node.declaration\n ) || (\n path.node.declaration.type === 'CallExpression'\n && path1.parent === path.node.declaration.arguments[0]\n )\n ) {\n // setup\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'setup') {\n const setupNode = path1.node;\n\n const spread: string[] = [];\n\n traverse(setupNode, {\n ReturnStatement(path2) {\n if (path2.node.argument?.type === 'ObjectExpression') {\n const returnNode = path2.node.argument;\n traverse(returnNode, {\n SpreadElement(path3) {\n // ...toRefs(xxx)\n if (\n path3.node.argument.type === 'CallExpression'\n && path3.node.argument.callee.type === 'Identifier'\n && path3.node.argument.callee.name === 'toRefs'\n && path3.node.argument.arguments[0].type === 'Identifier'\n ) {\n spread.push(path3.node.argument.arguments[0].name);\n }\n // ...xxx\n else if (\n path3.node.argument.type === 'Identifier'\n ) {\n spread.push(path3.node.argument.name);\n }\n },\n }, path2.scope, path2);\n }\n if (\n path2.node.argument?.type === 'FunctionExpression'\n || path2.node.argument?.type === 'ArrowFunctionExpression'\n ) {\n const templateNode = path2.node.argument.body;\n traverse(templateNode, {\n Identifier(path3) {\n const binding = path3.scope.getBinding(path3.node.name);\n if (binding?.scope === path1.scope) {\n nodesUsedInTemplate.add(path3.node.name);\n }\n },\n JSXIdentifier(path3) {\n const binding = path3.scope.getBinding(path3.node.name);\n if (binding?.scope === path1.scope) {\n nodesUsedInTemplate.add(path3.node.name);\n }\n },\n }, path2.scope, path2);\n }\n },\n }, path1.scope, path1);\n\n const {\n graph: {\n nodes: tempNodes,\n edges: tempEdges,\n spread: tempSpread,\n },\n nodeCollection: tempNodeCollection,\n } = processSetup(setupNode, path1.scope, setupNode, spread, lineOffset);\n\n // 3 filter data by return\n traverse(setupNode, {\n ReturnStatement(path2) {\n // only process return in setupNode scope\n if (path2.scope !== path1.scope) {\n return;\n }\n\n if (path2.node.argument?.type === 'ObjectExpression') {\n const returnNode = path2.node.argument;\n traverse(returnNode, {\n ObjectProperty(path3) {\n if (path3.parent === returnNode) {\n if (\n path3.node.key.type === 'Identifier'\n && path3.node.value.type === 'Identifier'\n && tempNodes.has(path3.node.value.name)\n ) {\n const valName = path3.node.value.name;\n if (!graph.nodes.has(valName)) {\n graph.nodes.add(valName);\n tNodes.set(valName, path3.node.value);\n nodeCollection.addTypedNode(\n valName,\n tempNodeCollection.nodes.get(valName)!,\n );\n }\n if (!graph.edges.has(valName)) {\n graph.edges.set(valName, new Set([...Array.from(\n tempEdges.get(valName) || new Set<{ label: string, type: RelationType }>(),\n )]));\n }\n\n const name = path3.node.key.name;\n if (name !== valName) {\n graph.nodes.add(name);\n tNodes.set(name, path3.node.key);\n nodeCollection.addNode(name, path3.node.key, {\n comment: getComment(path3.node),\n });\n graph.edges.set(name, new Set([{\n label: valName,\n type: getRelationType(path3),\n }]));\n }\n }\n }\n },\n SpreadElement(path3) {\n // ...toRefs(xxx)\n if (\n path3.node.argument.type === 'CallExpression'\n && path3.node.argument.callee.type === 'Identifier'\n && path3.node.argument.callee.name === 'toRefs'\n && path3.node.argument.arguments[0].type === 'Identifier'\n && tempSpread.get(path3.node.argument.arguments[0].name)\n ) {\n tempSpread.get(path3.node.argument.arguments[0].name)?.forEach((name) => {\n graph.nodes.add(name);\n // @ts-expect-error Identifier\n tNodes.set(name, path3.node.argument.arguments[0]);\n nodeCollection.addTypedNode(name, tempNodeCollection.nodes.get(name)!);\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n tempEdges.get(name)?.forEach((edge) => {\n graph.edges.get(name)?.add(edge);\n });\n }\n });\n }\n // ...xxx\n else if (\n path3.node.argument.type === 'Identifier'\n && tempSpread.get(path3.node.argument.name)\n ) {\n tempSpread.get(path3.node.argument.name)?.forEach((name) => {\n graph.nodes.add(name);\n // @ts-expect-error Identifier\n tNodes.set(name, path3.node.argument);\n nodeCollection.addTypedNode(name, tempNodeCollection.nodes.get(name)!);\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n tempEdges.get(name)?.forEach((edge) => {\n graph.edges.get(name)?.add(edge);\n });\n }\n });\n }\n },\n }, path2.scope, path2);\n }\n else {\n graph.edges = tempEdges;\n graph.nodes = tempNodes;\n nodeCollection = tempNodeCollection;\n }\n },\n }, path1.scope, path1);\n }\n\n // data\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'data') {\n const dataNode = path1.node;\n\n traverse(dataNode, {\n ReturnStatement(path2) {\n if (path2.parent === dataNode.body) {\n if (path2.node.argument?.type === 'ObjectExpression') {\n path2.node.argument.properties.forEach((prop) => {\n if (prop.type === 'ObjectProperty') {\n if (prop.key.type === 'Identifier') {\n const name = prop.key.name;\n graph.nodes.add(name);\n tNodes.set(name, prop.key);\n nodeCollection.addNode(name, prop, {\n comment: getComment(prop),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n });\n }\n }\n },\n }, path1.scope, path1);\n }\n\n // render\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'render') {\n traverse(path1.node, {\n ReturnStatement(path2) {\n const templateNode = path2.node;\n traverse(templateNode, {\n MemberExpression(path3) {\n if (path3.node.object && path3.node.object.type === 'ThisExpression') {\n if (path3.node.property && path3.node.property.type === 'Identifier') {\n nodesUsedInTemplate.add(path3.node.property.name);\n }\n }\n },\n }, path2.scope, path2);\n },\n }, path1.scope, path1);\n }\n }\n },\n }, path.scope, path);\n\n traverse(node, {\n ObjectMethod(path1) {\n if (\n (\n path.node.declaration.type === 'ObjectExpression'\n && path1.parent === path.node.declaration\n ) || (\n path.node.declaration.type === 'CallExpression'\n && path1.parent === path.node.declaration.arguments[0]\n )\n ) {\n if (path1.node.key.type === 'Identifier' && vueLifeCycleHooks.includes(path1.node.key.name)) {\n const hookName = path1.node.key.name;\n\n traverse(path1.node.body, {\n MemberExpression(path2) {\n if (path2.node.object.type === 'ThisExpression' && path2.node.property.type === 'Identifier') {\n const _node = nodeCollection.getNode(path2.node.property.name);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n }\n },\n }, path1.scope, path1);\n }\n }\n },\n ObjectProperty(path1) {\n if (\n (\n path.node.declaration.type === 'ObjectExpression'\n && path1.parent === path.node.declaration\n ) || (\n path.node.declaration.type === 'CallExpression'\n && path1.parent === path.node.declaration.arguments[0]\n )\n ) {\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'computed') {\n const computedNode = path1.node;\n if (computedNode.value.type === 'ObjectExpression') {\n computedNode.value.properties.forEach((prop) => {\n if (prop.type === 'ObjectMethod' && prop.key.type === 'Identifier') {\n const name = prop.key.name;\n traverse(prop, {\n MemberExpression(path2) {\n if (path2.node.object.type === 'ThisExpression' && path2.node.property.type === 'Identifier') {\n graph.edges.get(name)?.add({\n label: path2.node.property.name,\n type: getRelationType(path2),\n });\n }\n },\n }, path1.scope, path1);\n }\n\n if (\n prop.type === 'ObjectProperty'\n && prop.key.type === 'Identifier'\n && prop.value.type === 'ObjectExpression'\n ) {\n const name = prop.key.name;\n prop.value.properties.forEach((prop1) => {\n if (\n prop1.type === 'ObjectProperty'\n && prop1.key.type === 'Identifier'\n && prop1.key.name === 'get'\n ) {\n traverse(prop1, {\n MemberExpression(path2) {\n if (\n path2.node.object.type === 'ThisExpression'\n && path2.node.property.type === 'Identifier'\n ) {\n graph.edges.get(name)?.add({\n label: path2.node.property.name,\n type: getRelationType(path2),\n });\n }\n },\n }, path1.scope, path1);\n }\n });\n }\n });\n }\n }\n\n if (path1.node.key.type === 'Identifier' && path1.node.key.name === 'methods') {\n const methodsNode = path1.node;\n if (methodsNode.value.type === 'ObjectExpression') {\n methodsNode.value.properties.forEach((prop) => {\n if (\n (prop.type === 'ObjectMethod'\n || prop.type === 'ObjectProperty')\n && prop.key.type === 'Identifier'\n ) {\n const name = prop.key.name;\n traverse(prop, {\n MemberExpression(path2) {\n if (path2.node.object.type === 'ThisExpression' && path2.node.property.type === 'Identifier') {\n graph.edges.get(name)?.add({\n label: path2.node.property.name,\n type: getRelationType(path2),\n });\n }\n },\n }, path1.scope, path1);\n }\n });\n }\n }\n\n if (path1.node.key.type === 'Identifier' && [...watchHooks, ...vueLifeCycleHooks].includes(path1.node.key.name)) {\n const hookName = path1.node.key.name;\n\n if (watchHooks.includes(hookName) && path1.node.value.type === 'ObjectExpression') {\n path1.node.value.properties.forEach((prop) => {\n if ((prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') && (\n prop.key.type === 'Identifier' || prop.key.type === 'StringLiteral'\n )) {\n const keyName = prop.key.type === 'Identifier'\n ? prop.key.name\n : prop.key.type === 'StringLiteral'\n ? prop.key.value.split('.')[0]\n : '';\n const watchArg = tNodes.get(keyName);\n\n const _node = nodeCollection.getNode(keyName);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n\n traverse(path1.node.value, {\n MemberExpression(path2) {\n if (path2.node.object.type === 'ThisExpression' && path2.node.property.type === 'Identifier') {\n if (watchArg && watchArg.name !== path2.node.property.name) {\n graph.edges.get(watchArg.name)?.add({\n label: path2.node.property.name,\n type: getRelationType(path2),\n });\n }\n }\n },\n }, path1.scope, path1);\n }\n });\n }\n else {\n traverse(path1.node.value, {\n MemberExpression(path2) {\n if (path2.node.object.type === 'ThisExpression' && path2.node.property.type === 'Identifier') {\n const _node = nodeCollection.getNode(path2.node.property.name);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n }\n },\n }, path1.scope, path1);\n }\n }\n }\n },\n }, path.scope, path);\n }\n\n traverse(ast, {\n ExportDefaultDeclaration(path) {\n // export default {}\n if (path.node.declaration.type === 'ObjectExpression') {\n process(path.node.declaration, path);\n }\n // export default defineComponent({})\n else if (path.node.declaration.type === 'CallExpression'\n && path.node.declaration.callee.type === 'Identifier'\n && path.node.declaration.callee.name === 'defineComponent'\n && path.node.declaration.arguments[0].type === 'ObjectExpression'\n ) {\n process(path.node.declaration.arguments[0], path);\n }\n },\n });\n\n return {\n graph: nodeCollection.map(graph),\n nodesUsedInTemplate,\n };\n}\n","import type { NodePath, Scope, VisitNode } from '@babel/traverse';\nimport type * as t from '@babel/types';\nimport type { RelationType } from './utils';\nimport _traverse from '@babel/traverse';\nimport { babelParse } from '@vue/compiler-sfc';\nimport { getComment, getRelationType, NodeCollection, NodeType } from './utils';\n\nconst traverse: typeof _traverse\n // @ts-expect-error unwarp default\n = _traverse.default?.default || _traverse.default || _traverse;\n\nconst ignoreFunctionsName = ['defineProps', 'defineEmits', 'withDefaults'];\n\nexport const watchHooks = [\n 'watch',\n\n // from `@vueuse/core`\n 'watchArray',\n 'watchAtMost',\n 'watchDebounced',\n 'watchDeep',\n 'watchIgnorable',\n 'watchImmediate',\n 'watchOnce',\n 'watchPausable',\n 'watchThrottled',\n 'watchTriggerable',\n 'watchWithFilter',\n];\n\nexport function processSetup(\n ast: t.Node,\n parentScope?: Scope,\n parentPath?: t.Node,\n _spread?: string[],\n _lineOffset = 0,\n) {\n const spread = _spread || [];\n\n const nodeCollection = new NodeCollection(_lineOffset);\n\n const graph = {\n nodes: new Set<string>(),\n edges: new Map<string, Set<{ label: string, type: RelationType }>>(),\n spread: new Map<string, Set<string>>(),\n };\n\n traverse(ast, {\n VariableDeclaration(path) {\n path.node.declarations.forEach((declaration) => {\n if (declaration.id.type === 'ArrayPattern') {\n declaration.id.elements.forEach((element) => {\n if (element?.type === 'Identifier') {\n const name = element.name;\n const binding = path.scope.getBinding(name);\n\n if (\n binding\n && (path.parent.type === 'Program'\n || (parentPath?.type === 'ObjectMethod' && parentPath.body === path.parent)\n )\n && !(declaration.init?.type === 'CallExpression'\n && declaration.init?.callee.type === 'Identifier'\n && ignoreFunctionsName.includes(declaration.init?.callee.name)\n )\n ) {\n graph.nodes.add(name);\n nodeCollection.addNode(name, element, {\n comment: getComment(path.node),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n if (element?.type === 'RestElement' && element.argument.type === 'Identifier') {\n const name = element.argument.name;\n const binding = path.scope.getBinding(name);\n\n if (\n binding\n && (path.parent.type === 'Program'\n || (parentPath?.type === 'ObjectMethod' && parentPath.body === path.parent)\n )\n && !(declaration.init?.type === 'CallExpression'\n && declaration.init?.callee.type === 'Identifier'\n && ignoreFunctionsName.includes(declaration.init?.callee.name)\n )\n ) {\n graph.nodes.add(name);\n nodeCollection.addNode(name, element.argument, {\n comment: getComment(path.node),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n });\n }\n if (declaration.id.type === 'ObjectPattern') {\n declaration.id.properties.forEach((property) => {\n if (property.type === 'ObjectProperty' && property.value.type === 'Identifier') {\n const name = property.value.name;\n const binding = path.scope.getBinding(name);\n if (\n binding\n && (path.parent.type === 'Program'\n || (parentPath?.type === 'ObjectMethod' && parentPath.body === path.parent)\n )\n && !(declaration.init?.type === 'CallExpression'\n && declaration.init?.callee.type === 'Identifier'\n && ignoreFunctionsName.includes(declaration.init?.callee.name)\n )\n ) {\n graph.nodes.add(name);\n nodeCollection.addNode(name, property.value, {\n comment: getComment(property),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n\n if (property.type === 'RestElement' && property.argument.type === 'Identifier') {\n const name = property.argument.name;\n const binding = path.scope.getBinding(name);\n if (\n binding\n && (path.parent.type === 'Program'\n || (parentPath?.type === 'ObjectMethod' && parentPath.body === path.parent)\n )\n && !(declaration.init?.type === 'CallExpression'\n && declaration.init?.callee.type === 'Identifier'\n && ignoreFunctionsName.includes(declaration.init?.callee.name)\n )\n ) {\n graph.nodes.add(name);\n nodeCollection.addNode(name, property.argument, {\n comment: getComment(property),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n });\n }\n if (declaration.id?.type === 'Identifier') {\n const name = declaration.id.name;\n const binding = path.scope.getBinding(name);\n if (\n binding\n && (path.parent.type === 'Program'\n || (parentPath?.type === 'ObjectMethod' && parentPath.body === path.parent)\n )\n && !(declaration.init?.type === 'CallExpression'\n && declaration.init?.callee.type === 'Identifier'\n && ignoreFunctionsName.includes(declaration.init?.callee.name)\n )\n ) {\n graph.nodes.add(name);\n nodeCollection.addNode(name, declaration, {\n comment: getComment(path.node),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n\n if (spread.includes(name)) {\n if (declaration.init?.type === 'ObjectExpression') {\n declaration.init?.properties.forEach((prop) => {\n if (\n (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod')\n && prop.key.type === 'Identifier'\n ) {\n const keyName = prop.key.name;\n graph.nodes.add(keyName);\n nodeCollection.addNode(keyName, prop, {\n comment: getComment(prop),\n });\n if (!graph.edges.get(keyName)) {\n graph.edges.set(keyName, new Set());\n }\n if (graph.spread.has(name)) {\n graph.spread.get(name)?.add(keyName);\n }\n else {\n graph.spread.set(name, new Set([keyName]));\n }\n }\n else if (prop.type === 'SpreadElement') {\n console.warn('not support spread in spread');\n }\n });\n }\n if (\n declaration.init?.type === 'CallExpression'\n && declaration.init?.callee.type === 'Identifier'\n && declaration.init?.callee.name === 'reactive'\n ) {\n const arg = declaration.init?.arguments[0];\n if (arg.type === 'ObjectExpression') {\n arg.properties.forEach((prop) => {\n if (\n (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod')\n && prop.key.type === 'Identifier'\n ) {\n const keyName = prop.key.name;\n graph.nodes.add(keyName);\n nodeCollection.addNode(keyName, prop, {\n comment: getComment(prop),\n });\n if (!graph.edges.get(keyName)) {\n graph.edges.set(keyName, new Set());\n }\n if (graph.spread.has(name)) {\n graph.spread.get(name)?.add(keyName);\n }\n else {\n graph.spread.set(name, new Set([keyName]));\n }\n }\n else if (prop.type === 'SpreadElement') {\n console.warn('not support spread in spread');\n }\n });\n }\n }\n }\n }\n }\n });\n },\n FunctionDeclaration(path) {\n const name = path.node.id?.name;\n if (name) {\n const binding = path.scope.getBinding(name);\n if (binding && (path.parent.type === 'Program'\n || (parentPath?.type === 'ObjectMethod' && parentPath.body === path.parent)\n )) {\n graph.nodes.add(name);\n nodeCollection.addNode(name, path.node.id!, {\n isMethod: true,\n comment: getComment(path.node),\n });\n if (!graph.edges.get(name)) {\n graph.edges.set(name, new Set());\n }\n }\n }\n },\n }, parentScope, parentPath);\n\n function traverseHooks(node: t.ExpressionStatement | t.CallExpression, patentScope: Scope) {\n if (\n (\n node.type === 'ExpressionStatement'\n && node.expression.type === 'CallExpression'\n && node.expression.callee.type === 'Identifier'\n ) || (\n node.type === 'CallExpression'\n && node.callee.type === 'Identifier'\n )\n ) {\n const hookName = (() => {\n if (node.type === 'ExpressionStatement'\n && node.expression.type === 'CallExpression'\n && node.expression.callee.type === 'Identifier') {\n return node.expression.callee.name;\n }\n if (node.type === 'CallExpression'\n && node.callee.type === 'Identifier') {\n return node.callee.name;\n }\n })() || '';\n\n if (!hookName) {\n return;\n }\n\n const hookBinding = patentScope.getBinding(hookName);\n if (!(hookBinding === undefined || hookBinding?.scope.block.type === 'Program'\n || parentScope === hookBinding?.scope)) {\n return;\n }\n\n const expression = (node.type === 'ExpressionStatement'\n ? node.expression\n : node) as t.CallExpression;\n\n const watchArgs = new Set<t.Identifier>();\n\n if (hookName === 'provide') {\n traverse(expression, {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || parentScope === binding?.scope)\n ) {\n const _node = nodeCollection.getNode(path1.node.name);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n }\n },\n }, patentScope, node);\n }\n else if (watchHooks.includes(hookName)) {\n if (expression.arguments[0].type === 'Identifier') {\n const binding = patentScope.getBinding(expression.arguments[0].name);\n if (\n graph.nodes.has(expression.arguments[0].name)\n && (binding?.scope.block.type === 'Program'\n || parentScope === binding?.scope)\n ) {\n watchArgs.add(expression.arguments[0]);\n }\n }\n else {\n traverse(expression.arguments[0], {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || parentScope === binding?.scope)\n ) {\n watchArgs.add(path1.node);\n }\n },\n }, patentScope, node);\n }\n }\n else if (hookName === 'useEffect' && expression.arguments[1].type === 'ArrayExpression') {\n traverse(expression.arguments[1], {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || parentScope === binding?.scope)\n ) {\n watchArgs.add(path1.node);\n }\n },\n }, patentScope, node);\n }\n expression.arguments.forEach((argNode, index) => {\n if (watchHooks.includes(hookName) && index === 0 && argNode.type === 'Identifier') {\n const _node = nodeCollection.getNode(argNode.name);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n return;\n }\n if (argNode.type === 'Identifier') {\n const binding = patentScope.getBinding(argNode.name);\n if (\n graph.nodes.has(argNode.name)\n && (binding?.scope.block.type === 'Program'\n || parentScope === binding?.scope)\n ) {\n const _node = nodeCollection.getNode(argNode.name);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n }\n }\n else {\n traverse(argNode, {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || parentScope === binding?.scope)\n ) {\n if ([...watchHooks, 'useEffect'].includes(hookName) && watchArgs.size > 0) {\n const watchArgsNames = Array.from(watchArgs).map(arg => arg.name);\n watchArgs.forEach((watchArg) => {\n if (!watchArgsNames.includes(path1.node.name)) {\n graph.edges.get(watchArg.name)?.add({\n label: path1.node.name,\n type: getRelationType(path1),\n });\n }\n });\n }\n const _node = nodeCollection.getNode(path1.node.name);\n if (_node?.info?.used) {\n _node?.info?.used?.add(hookName);\n }\n else if (_node) {\n _node.info = {\n ..._node?.info,\n used: new Set([hookName]),\n };\n }\n }\n },\n }, patentScope, node);\n }\n });\n }\n }\n\n // get the relation between the variable and the function\n traverse(ast, {\n FunctionDeclaration(path) {\n const name = path.node.id?.name;\n if (name && graph.nodes.has(name)) {\n traverse(path.node.body, {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.name,\n type: getRelationType(path1),\n });\n }\n },\n MemberExpression(path1) {\n if (\n path1.node.object.type === 'Identifier'\n && spread.includes(path1.node.object.name)\n ) {\n const binding = path1.scope.getBinding(path1.node.object.name);\n if (\n spread.includes(path1.node.object.name)\n && path1.node.property.type === 'Identifier'\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.property.name,\n type: getRelationType(path1),\n });\n }\n }\n },\n }, path.scope, path);\n }\n },\n\n VariableDeclarator(path) {\n if (path.node.init) {\n if (path.node.id.type === 'ArrayPattern') {\n path.node.id.elements.forEach((element) => {\n if (element?.type === 'Identifier') {\n const name = element.name;\n if (name && graph.nodes.has(name) && path.node.init?.type === 'CallExpression') {\n traverse(path.node.init, {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.name,\n type: getRelationType(path1),\n });\n }\n },\n MemberExpression(path1) {\n if (\n path1.node.object.type === 'Identifier'\n && spread.includes(path1.node.object.name)\n ) {\n const binding = path1.scope.getBinding(path1.node.object.name);\n if (\n spread.includes(path1.node.object.name)\n && path1.node.property.type === 'Identifier'\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.property.name,\n type: getRelationType(path1),\n });\n }\n }\n },\n }, path.scope, path);\n }\n }\n });\n }\n else if (path.node.id.type === 'ObjectPattern') {\n path.node.id.properties.forEach((property) => {\n if (property.type === 'ObjectProperty' && property.value.type === 'Identifier') {\n const name = property.value.name;\n if (name && graph.nodes.has(name) && path.node.init) {\n traverse(path.node.init, {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.name,\n type: getRelationType(path1),\n });\n }\n },\n MemberExpression(path1) {\n if (\n path1.node.object.type === 'Identifier'\n && spread.includes(path1.node.object.name)\n ) {\n const binding = path1.scope.getBinding(path1.node.object.name);\n if (\n spread.includes(path1.node.object.name)\n && path1.node.property.type === 'Identifier'\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.property.name,\n type: getRelationType(path1),\n });\n }\n }\n },\n }, path.scope, path);\n }\n }\n });\n }\n else if ([\n 'CallExpression',\n 'ArrowFunctionExpression',\n 'FunctionDeclaration',\n ].includes(path.node.init.type)\n && path.node.id.type === 'Identifier'\n ) {\n if (path.node.init.type === 'CallExpression' && path.node.init.callee.type === 'Identifier' && [...watchHooks, 'watchEffect'].includes(path.node.init.callee.name)) {\n traverseHooks(path.node.init, path.scope);\n }\n const name = path.node.id?.name;\n if (name && graph.nodes.has(name)) {\n traverse(path.node.init, {\n Identifier(path1) {\n const binding = path1.scope.getBinding(path1.node.name);\n if (\n graph.nodes.has(path1.node.name)\n && (\n (path1.parent.type !== 'MemberExpression'\n && path1.parent.type !== 'OptionalMemberExpression')\n || path1.parent.object === path1.node\n )\n && (binding?.scope.block.type === 'Program'\n || (parentScope === binding?.scope)\n )\n ) {\n graph.edges.get(name)?.add({\n label: path1.node.name,\n type: getRelationType(path1),\n });\n }\n },\n MemberExpression(path1) {\n if (\n path1.node.object.type === 'Identifier'\n && spread.includes(path1.node.object.name)\n ) {\n const binding = path1.scope.getBinding(path1.node.object.name);\n if (\n spread.includes(path1.node.object.name)\n && path1.node.property.type === 'Identifier'\n && (binding?.scope.block.type === 'Program'\n || (parentScope === bind