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.
- package/arcvision.context.json +273 -7610
- package/dist/index.js +873 -3
- package/package.json +1 -1
- package/src/core/di-detector.js +202 -0
- package/src/core/method-tracker.js +174 -0
- package/src/core/parser-enhanced.js +73 -0
- package/src/core/react-nextjs-detector.js +245 -0
- package/src/core/scanner.js +84 -58
- package/src/core/semantic-analyzer.js +204 -0
- package/src/core/type-analyzer.js +272 -0
- package/src/engine/context_builder.js +34 -11
|
@@ -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
|
-
|
|
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
|
-
}
|
|
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
|
|