arcvision 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Type Dependency Analyzer
3
+ *
4
+ * Tracks TypeScript type-only dependencies including:
5
+ * - Type-only imports
6
+ * - Interface extensions
7
+ * - Interface implementations
8
+ * - Generic type parameters
9
+ * - Return type dependencies
10
+ */
11
+
12
+ const traverse = require('@babel/traverse').default;
13
+
14
+ /**
15
+ * Extract type-only imports from AST
16
+ * @param {Object} ast - Babel AST
17
+ * @returns {Array} Array of type imports
18
+ */
19
+ function extractTypeImports(ast) {
20
+ const typeImports = [];
21
+
22
+ if (!ast) return typeImports;
23
+
24
+ traverse(ast, {
25
+ ImportDeclaration(path) {
26
+ const node = path.node;
27
+
28
+ // Check if it's a type-only import: import type { ... }
29
+ if (node.importKind === 'type') {
30
+ const specifiers = [];
31
+ node.specifiers.forEach(spec => {
32
+ if (spec.imported) {
33
+ specifiers.push({
34
+ imported: spec.imported.name || spec.imported.value,
35
+ local: spec.local.name
36
+ });
37
+ }
38
+ });
39
+
40
+ typeImports.push({
41
+ source: node.source.value,
42
+ specifiers,
43
+ importKind: 'type',
44
+ loc: node.loc
45
+ });
46
+ }
47
+
48
+ // Check for individual type specifiers: import { type User } from '...'
49
+ else {
50
+ const typeSpecifiers = [];
51
+ node.specifiers.forEach(spec => {
52
+ if (spec.importKind === 'type' && spec.imported) {
53
+ typeSpecifiers.push({
54
+ imported: spec.imported.name || spec.imported.value,
55
+ local: spec.local.name
56
+ });
57
+ }
58
+ });
59
+
60
+ if (typeSpecifiers.length > 0) {
61
+ typeImports.push({
62
+ source: node.source.value,
63
+ specifiers: typeSpecifiers,
64
+ importKind: 'mixed',
65
+ loc: node.loc
66
+ });
67
+ }
68
+ }
69
+ }
70
+ });
71
+
72
+ return typeImports;
73
+ }
74
+
75
+ /**
76
+ * Extract interface dependencies (extends, implements)
77
+ * @param {Object} ast - Babel AST
78
+ * @returns {Array} Array of interface dependencies
79
+ */
80
+ function extractInterfaceDependencies(ast) {
81
+ const interfaceDeps = [];
82
+
83
+ if (!ast) return interfaceDeps;
84
+
85
+ traverse(ast, {
86
+ // Interface extends
87
+ TSInterfaceDeclaration(path) {
88
+ const node = path.node;
89
+ const interfaceName = node.id.name;
90
+
91
+ if (node.extends && node.extends.length > 0) {
92
+ node.extends.forEach(ext => {
93
+ const extendedInterface = extractTypeName(ext.expression);
94
+ if (extendedInterface) {
95
+ interfaceDeps.push({
96
+ interface: interfaceName,
97
+ extends: extendedInterface,
98
+ type: 'interface_extends',
99
+ loc: node.loc
100
+ });
101
+ }
102
+ });
103
+ }
104
+ },
105
+
106
+ // Class implements
107
+ ClassDeclaration(path) {
108
+ const node = path.node;
109
+ const className = node.id ? node.id.name : 'Anonymous';
110
+
111
+ if (node.implements && node.implements.length > 0) {
112
+ node.implements.forEach(impl => {
113
+ const implementedInterface = extractTypeName(impl.expression);
114
+ if (implementedInterface) {
115
+ interfaceDeps.push({
116
+ class: className,
117
+ implements: implementedInterface,
118
+ type: 'class_implements',
119
+ loc: node.loc
120
+ });
121
+ }
122
+ });
123
+ }
124
+ }
125
+ });
126
+
127
+ return interfaceDeps;
128
+ }
129
+
130
+ /**
131
+ * Extract generic type dependencies
132
+ * @param {Object} ast - Babel AST
133
+ * @returns {Array} Array of generic type usages
134
+ */
135
+ function extractGenericDependencies(ast) {
136
+ const genericDeps = [];
137
+
138
+ if (!ast) return genericDeps;
139
+
140
+ traverse(ast, {
141
+ // Function return types
142
+ FunctionDeclaration(path) {
143
+ const node = path.node;
144
+ const functionName = node.id ? node.id.name : 'anonymous';
145
+
146
+ if (node.returnType) {
147
+ const returnTypeName = extractComplexTypeName(node.returnType.typeAnnotation);
148
+ if (returnTypeName) {
149
+ genericDeps.push({
150
+ function: functionName,
151
+ returnType: returnTypeName,
152
+ type: 'return_type',
153
+ loc: node.loc
154
+ });
155
+ }
156
+ }
157
+ },
158
+
159
+ // Arrow function return types
160
+ ArrowFunctionExpression(path) {
161
+ const node = path.node;
162
+
163
+ if (node.returnType) {
164
+ const returnTypeName = extractComplexTypeName(node.returnType.typeAnnotation);
165
+ if (returnTypeName) {
166
+ genericDeps.push({
167
+ function: 'arrow_function',
168
+ returnType: returnTypeName,
169
+ type: 'return_type',
170
+ loc: node.loc
171
+ });
172
+ }
173
+ }
174
+ },
175
+
176
+ // Variable type annotations
177
+ VariableDeclarator(path) {
178
+ const node = path.node;
179
+ const varName = node.id.name;
180
+
181
+ if (node.id.typeAnnotation) {
182
+ const typeName = extractComplexTypeName(node.id.typeAnnotation.typeAnnotation);
183
+ if (typeName) {
184
+ genericDeps.push({
185
+ variable: varName,
186
+ type: typeName,
187
+ kind: 'variable_type',
188
+ loc: node.loc
189
+ });
190
+ }
191
+ }
192
+ }
193
+ });
194
+
195
+ return genericDeps;
196
+ }
197
+
198
+ /**
199
+ * Analyze all type dependencies in a file
200
+ * @param {Object} ast - Babel AST
201
+ * @returns {Object} All type dependencies
202
+ */
203
+ function analyzeTypeDependencies(ast) {
204
+ if (!ast) {
205
+ return {
206
+ typeImports: [],
207
+ interfaceDependencies: [],
208
+ genericDependencies: []
209
+ };
210
+ }
211
+
212
+ return {
213
+ typeImports: extractTypeImports(ast),
214
+ interfaceDependencies: extractInterfaceDependencies(ast),
215
+ genericDependencies: extractGenericDependencies(ast)
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Helper: Extract type name from TypeScript type node
221
+ */
222
+ function extractTypeName(node) {
223
+ if (!node) return null;
224
+
225
+ if (node.type === 'Identifier') {
226
+ return node.name;
227
+ } else if (node.type === 'TSTypeReference' && node.typeName) {
228
+ if (node.typeName.type === 'Identifier') {
229
+ return node.typeName.name;
230
+ }
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Helper: Extract complex type names (including generics)
238
+ */
239
+ function extractComplexTypeName(node) {
240
+ if (!node) return null;
241
+
242
+ if (node.type === 'TSTypeReference' && node.typeName) {
243
+ let baseName = extractTypeName(node);
244
+
245
+ // Handle generic parameters
246
+ if (node.typeParameters && node.typeParameters.params) {
247
+ const params = node.typeParameters.params
248
+ .map(p => extractComplexTypeName(p))
249
+ .filter(Boolean)
250
+ .join(', ');
251
+ if (params) {
252
+ baseName += `<${params}>`;
253
+ }
254
+ }
255
+
256
+ return baseName;
257
+ } else if (node.type === 'Identifier') {
258
+ return node.name;
259
+ } else if (node.type === 'TSArrayType') {
260
+ const elementType = extractComplexTypeName(node.elementType);
261
+ return elementType ? `${elementType}[]` : null;
262
+ }
263
+
264
+ return null;
265
+ }
266
+
267
+ module.exports = {
268
+ extractTypeImports,
269
+ extractInterfaceDependencies,
270
+ extractGenericDependencies,
271
+ analyzeTypeDependencies
272
+ };
@@ -19,20 +19,20 @@ function buildContext(files, edges, options = {}) {
19
19
  const nodes = files.map(file => {
20
20
  // Use the node's id field which is already the normalized relative path
21
21
  const normalizedPath = file.id;
22
-
22
+
23
23
  // Determine role based on multiple factors for better accuracy
24
24
  const hasFunctions = file.metadata.functions && file.metadata.functions.length > 0;
25
25
  const hasClasses = file.metadata.classes && file.metadata.classes.length > 0;
26
26
  const hasExports = file.metadata.exports && file.metadata.exports.length > 0;
27
27
  const hasApiCalls = file.metadata.apiCalls && file.metadata.apiCalls.length > 0;
28
-
28
+
29
29
  let role = 'Structure';
30
30
  if (hasFunctions || hasClasses || hasApiCalls) {
31
31
  role = 'Implementation';
32
32
  } else if (hasExports) {
33
33
  role = 'Interface';
34
34
  }
35
-
35
+
36
36
  // Extract dependencies more accurately and remove duplicates
37
37
  const dependencies = [];
38
38
  const uniqueDeps = new Set();
@@ -47,7 +47,7 @@ function buildContext(files, edges, options = {}) {
47
47
  }
48
48
  });
49
49
  }
50
-
50
+
51
51
  return {
52
52
  id: stableId(normalizedPath),
53
53
  type: 'file',
@@ -64,21 +64,41 @@ function buildContext(files, edges, options = {}) {
64
64
  // Find source and target nodes to get their stable IDs
65
65
  const sourceNode = nodes.find(n => n.path === edge.source);
66
66
  const targetNode = nodes.find(n => n.path === edge.target);
67
-
67
+
68
68
  // Include edges where source exists (even if target doesn't exist as a node)
69
69
  if (sourceNode) {
70
70
  // Normalize the edge type to fit the schema
71
71
  let relationType = 'imports';
72
- if (edge.type === 'imports' || edge.type === 'require' || edge.type === 'export-from' || edge.type === 'export-all' || edge.type === 'dynamic-import' || edge.type === 'require-assignment') {
72
+
73
+ // Handle import-related types
74
+ if (edge.type === 'imports' || edge.type === 'require' || edge.type === 'export-from' ||
75
+ edge.type === 'export-all' || edge.type === 'dynamic-import' || edge.type === 'require-assignment') {
76
+ relationType = 'imports';
77
+ }
78
+ // Handle call-related types (NEW)
79
+ else if (edge.type === 'function_call' || edge.type === 'method_call') {
80
+ relationType = 'calls';
81
+ }
82
+ // Handle constructor calls (NEW) - map to calls for schema compliance
83
+ else if (edge.type === 'constructor_call') {
84
+ relationType = 'calls';
85
+ }
86
+ // Handle component usage (NEW) - map to imports for schema compliance
87
+ else if (edge.type === 'component_usage' || edge.type === 'jsx_component' || edge.type === 'jsx_member_component') {
73
88
  relationType = 'imports';
74
- } else if (edge.type === 'calls') {
89
+ }
90
+ // Handle existing types
91
+ else if (edge.type === 'calls') {
75
92
  relationType = 'calls';
76
93
  } else if (edge.type === 'owns') {
77
94
  relationType = 'owns';
78
95
  } else if (edge.type === 'depends_on') {
79
96
  relationType = 'depends_on';
97
+ } else {
98
+ // Default to 'imports' for any unknown edge types to ensure schema compliance
99
+ relationType = 'imports';
80
100
  }
81
-
101
+
82
102
  if (targetNode) {
83
103
  // Both source and target nodes exist
84
104
  schemaEdges.push({
@@ -100,7 +120,10 @@ function buildContext(files, edges, options = {}) {
100
120
  total_files: nodes.length,
101
121
  total_nodes: nodes.length,
102
122
  total_edges: schemaEdges.length,
103
- total_imports: schemaEdges.length,
123
+ total_imports: schemaEdges.filter(edge => edge.relation === 'imports').length,
124
+ total_calls: schemaEdges.filter(edge => edge.relation === 'calls').length,
125
+ total_owns: schemaEdges.filter(edge => edge.relation === 'owns').length,
126
+ total_depends_on: schemaEdges.filter(edge => edge.relation === 'depends_on').length,
104
127
  files_with_functions: nodes.filter(n => n.role === 'Implementation').length
105
128
  };
106
129
 
@@ -118,12 +141,12 @@ function buildContext(files, edges, options = {}) {
118
141
  metrics,
119
142
  contextSurface: options.contextSurface || {}
120
143
  };
121
-
144
+
122
145
  // Add additional computed metrics
123
146
  context.metrics.files_with_functions = nodes.filter(n => n.role === 'Implementation').length;
124
147
  context.metrics.files_with_high_blast_radius = nodes.filter(n => n.blast_radius > 5).length;
125
148
  context.metrics.total_dependencies = schemaEdges.length;
126
-
149
+
127
150
  return context;
128
151
  }
129
152