arcvision 0.2.3 → 0.2.4
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/dist/index.js +1017 -636
- package/package.json +12 -2
- package/arcvision.context.json +0 -533
- package/docs/blast-radius-implementation.md +0 -76
- package/docs/blast-radius.md +0 -44
- package/output1.json +0 -281
- package/output2.json +0 -281
- package/scan_output.txt +0 -0
- package/schema/arcvision_context_schema_v1.json +0 -84
- package/src/core/blastRadius.js +0 -249
- package/src/core/di-detector.js +0 -202
- package/src/core/method-tracker.js +0 -174
- package/src/core/parser-enhanced.js +0 -73
- package/src/core/parser.js +0 -343
- package/src/core/path-resolver.js +0 -174
- package/src/core/react-nextjs-detector.js +0 -245
- package/src/core/scanner.js +0 -518
- package/src/core/semantic-analyzer.js +0 -204
- package/src/core/tsconfig-utils.js +0 -35
- package/src/core/type-analyzer.js +0 -272
- package/src/core/watcher.js +0 -18
- package/src/engine/context_builder.js +0 -153
- package/src/engine/context_sorter.js +0 -41
- package/src/engine/context_validator.js +0 -75
- package/src/engine/id-generator.js +0 -16
- package/src/index.js +0 -325
- package/src/plugins/express-plugin.js +0 -48
- package/src/plugins/plugin-manager.js +0 -58
- package/src/plugins/react-plugin.js +0 -54
- package/test/determinism-test.js +0 -65
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Semantic Analyzer
|
|
3
|
-
*
|
|
4
|
-
* Builds cross-file symbol resolution and tracks actual usage of imported symbols.
|
|
5
|
-
* This enables detection of which files actually USE imported functions/classes,
|
|
6
|
-
* not just which files import them.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Build a global symbol table from all file exports
|
|
11
|
-
* @param {Array} nodes - All scanned file nodes with metadata
|
|
12
|
-
* @returns {Map} Symbol table mapping symbol names to their source files
|
|
13
|
-
*/
|
|
14
|
-
function buildSymbolTable(nodes) {
|
|
15
|
-
const symbolTable = new Map();
|
|
16
|
-
|
|
17
|
-
nodes.forEach(node => {
|
|
18
|
-
const filePath = node.id;
|
|
19
|
-
const metadata = node.metadata;
|
|
20
|
-
|
|
21
|
-
if (!metadata) return;
|
|
22
|
-
|
|
23
|
-
// Register all exports from this file
|
|
24
|
-
if (metadata.exports && Array.isArray(metadata.exports)) {
|
|
25
|
-
metadata.exports.forEach(exp => {
|
|
26
|
-
const symbolName = exp.name;
|
|
27
|
-
if (!symbolTable.has(symbolName)) {
|
|
28
|
-
symbolTable.set(symbolName, []);
|
|
29
|
-
}
|
|
30
|
-
symbolTable.get(symbolName).push({
|
|
31
|
-
file: filePath,
|
|
32
|
-
type: exp.type,
|
|
33
|
-
loc: exp.loc
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Register all functions
|
|
39
|
-
if (metadata.functions && Array.isArray(metadata.functions)) {
|
|
40
|
-
metadata.functions.forEach(func => {
|
|
41
|
-
const symbolName = func.name;
|
|
42
|
-
if (!symbolTable.has(symbolName)) {
|
|
43
|
-
symbolTable.set(symbolName, []);
|
|
44
|
-
}
|
|
45
|
-
symbolTable.get(symbolName).push({
|
|
46
|
-
file: filePath,
|
|
47
|
-
type: 'function',
|
|
48
|
-
loc: func.loc
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Register all classes
|
|
54
|
-
if (metadata.classes && Array.isArray(metadata.classes)) {
|
|
55
|
-
metadata.classes.forEach(cls => {
|
|
56
|
-
const symbolName = cls.name;
|
|
57
|
-
if (!symbolTable.has(symbolName)) {
|
|
58
|
-
symbolTable.set(symbolName, []);
|
|
59
|
-
}
|
|
60
|
-
symbolTable.get(symbolName).push({
|
|
61
|
-
file: filePath,
|
|
62
|
-
type: 'class',
|
|
63
|
-
loc: cls.loc
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return symbolTable;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Resolve symbol usage to their definitions
|
|
74
|
-
* @param {Object} node - File node with metadata
|
|
75
|
-
* @param {Map} symbolTable - Global symbol table
|
|
76
|
-
* @returns {Array} Array of resolved symbol usages
|
|
77
|
-
*/
|
|
78
|
-
function resolveSymbolUsage(node, symbolTable) {
|
|
79
|
-
const resolvedUsages = [];
|
|
80
|
-
const metadata = node.metadata;
|
|
81
|
-
|
|
82
|
-
if (!metadata) return resolvedUsages;
|
|
83
|
-
|
|
84
|
-
// Track which symbols this file imports
|
|
85
|
-
const importedSymbols = new Map();
|
|
86
|
-
if (metadata.imports && Array.isArray(metadata.imports)) {
|
|
87
|
-
metadata.imports.forEach(imp => {
|
|
88
|
-
if (imp.specifiers && Array.isArray(imp.specifiers)) {
|
|
89
|
-
imp.specifiers.forEach(spec => {
|
|
90
|
-
const localName = spec.local || spec.imported || spec;
|
|
91
|
-
const importedName = spec.imported || spec.local || spec;
|
|
92
|
-
importedSymbols.set(localName, {
|
|
93
|
-
source: imp.source,
|
|
94
|
-
originalName: importedName
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Check function calls against imported symbols
|
|
102
|
-
if (metadata.functionCalls && Array.isArray(metadata.functionCalls)) {
|
|
103
|
-
metadata.functionCalls.forEach(call => {
|
|
104
|
-
const callName = call.name;
|
|
105
|
-
if (importedSymbols.has(callName)) {
|
|
106
|
-
const importInfo = importedSymbols.get(callName);
|
|
107
|
-
resolvedUsages.push({
|
|
108
|
-
type: 'function_call',
|
|
109
|
-
symbol: callName,
|
|
110
|
-
source: importInfo.source,
|
|
111
|
-
loc: call.loc
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Check constructor calls against imported symbols
|
|
118
|
-
if (metadata.constructorCalls && Array.isArray(metadata.constructorCalls)) {
|
|
119
|
-
metadata.constructorCalls.forEach(call => {
|
|
120
|
-
const className = call.className;
|
|
121
|
-
if (importedSymbols.has(className)) {
|
|
122
|
-
const importInfo = importedSymbols.get(className);
|
|
123
|
-
resolvedUsages.push({
|
|
124
|
-
type: 'constructor_call',
|
|
125
|
-
symbol: className,
|
|
126
|
-
source: importInfo.source,
|
|
127
|
-
loc: call.loc
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check component usage (React/JSX)
|
|
134
|
-
if (metadata.componentUsage && Array.isArray(metadata.componentUsage)) {
|
|
135
|
-
metadata.componentUsage.forEach(comp => {
|
|
136
|
-
const componentName = comp.component;
|
|
137
|
-
if (importedSymbols.has(componentName)) {
|
|
138
|
-
const importInfo = importedSymbols.get(componentName);
|
|
139
|
-
resolvedUsages.push({
|
|
140
|
-
type: 'component_usage',
|
|
141
|
-
symbol: componentName,
|
|
142
|
-
source: importInfo.source,
|
|
143
|
-
loc: comp.loc
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return resolvedUsages;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Track cross-file references and build usage edges
|
|
154
|
-
* @param {Array} nodes - All scanned file nodes
|
|
155
|
-
* @param {Map} symbolTable - Global symbol table
|
|
156
|
-
* @returns {Array} Array of usage edges
|
|
157
|
-
*/
|
|
158
|
-
function trackCrossFileReferences(nodes, symbolTable) {
|
|
159
|
-
const usageEdges = [];
|
|
160
|
-
|
|
161
|
-
nodes.forEach(node => {
|
|
162
|
-
const filePath = node.id;
|
|
163
|
-
const resolvedUsages = resolveSymbolUsage(node, symbolTable);
|
|
164
|
-
|
|
165
|
-
resolvedUsages.forEach(usage => {
|
|
166
|
-
// Create an edge from this file to the source of the symbol
|
|
167
|
-
usageEdges.push({
|
|
168
|
-
source: filePath,
|
|
169
|
-
target: usage.source,
|
|
170
|
-
type: usage.type,
|
|
171
|
-
symbol: usage.symbol,
|
|
172
|
-
loc: usage.loc
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
return usageEdges;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Analyze semantic relationships in the codebase
|
|
182
|
-
* @param {Array} nodes - All scanned file nodes
|
|
183
|
-
* @returns {Object} Semantic analysis results
|
|
184
|
-
*/
|
|
185
|
-
function analyzeSemantics(nodes) {
|
|
186
|
-
const symbolTable = buildSymbolTable(nodes);
|
|
187
|
-
const usageEdges = trackCrossFileReferences(nodes, symbolTable);
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
symbolTable,
|
|
191
|
-
usageEdges,
|
|
192
|
-
stats: {
|
|
193
|
-
totalSymbols: symbolTable.size,
|
|
194
|
-
totalUsages: usageEdges.length
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
module.exports = {
|
|
200
|
-
buildSymbolTable,
|
|
201
|
-
resolveSymbolUsage,
|
|
202
|
-
trackCrossFileReferences,
|
|
203
|
-
analyzeSemantics
|
|
204
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Load and parse tsconfig.json from project root
|
|
6
|
-
* @param {string} projectRoot - The root directory of the project
|
|
7
|
-
* @returns {Object|null} The compilerOptions from tsconfig.json or null if not found
|
|
8
|
-
*/
|
|
9
|
-
function loadTSConfig(projectRoot) {
|
|
10
|
-
const tsconfigPaths = [
|
|
11
|
-
path.join(projectRoot, 'tsconfig.json'),
|
|
12
|
-
path.join(projectRoot, 'jsconfig.json')
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
for (const tsconfigPath of tsconfigPaths) {
|
|
16
|
-
if (fs.existsSync(tsconfigPath)) {
|
|
17
|
-
try {
|
|
18
|
-
let content = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
19
|
-
// Remove BOM if present (0xFEFF)
|
|
20
|
-
if (content.charCodeAt(0) === 0xFEFF) {
|
|
21
|
-
content = content.slice(1);
|
|
22
|
-
}
|
|
23
|
-
const raw = JSON.parse(content);
|
|
24
|
-
return raw.compilerOptions || {};
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.warn(`Warning: Could not parse ${tsconfigPath}:`, error.message);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
module.exports = { loadTSConfig };
|
|
@@ -1,272 +0,0 @@
|
|
|
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
|
-
};
|
package/src/core/watcher.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const chokidar = require('chokidar');
|
|
2
|
-
|
|
3
|
-
function watch(directory, callback) {
|
|
4
|
-
const watcher = chokidar.watch(directory, {
|
|
5
|
-
ignored: [/(^|[\/\\])\../, '**/node_modules/**'], // ignore dotfiles
|
|
6
|
-
persistent: true,
|
|
7
|
-
ignoreInitial: true
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
watcher
|
|
11
|
-
.on('add', path => callback('add', path))
|
|
12
|
-
.on('change', path => callback('change', path))
|
|
13
|
-
.on('unlink', path => callback('unlink', path));
|
|
14
|
-
|
|
15
|
-
return watcher;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = { watch };
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const { stableId } = require('./id-generator');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Build the context object that conforms to the Arcvision schema
|
|
6
|
-
* @param {Array} files - List of files from scanner
|
|
7
|
-
* @param {Array} edges - List of edges from scanner
|
|
8
|
-
* @param {Object} options - Options including directory path and project name
|
|
9
|
-
* @returns {Object} - Context object conforming to schema
|
|
10
|
-
*/
|
|
11
|
-
function buildContext(files, edges, options = {}) {
|
|
12
|
-
const {
|
|
13
|
-
directory = '.',
|
|
14
|
-
projectName = 'arcvision',
|
|
15
|
-
language = 'javascript'
|
|
16
|
-
} = options;
|
|
17
|
-
|
|
18
|
-
// Build nodes from files
|
|
19
|
-
const nodes = files.map(file => {
|
|
20
|
-
// Use the node's id field which is already the normalized relative path
|
|
21
|
-
const normalizedPath = file.id;
|
|
22
|
-
|
|
23
|
-
// Determine role based on multiple factors for better accuracy
|
|
24
|
-
const hasFunctions = file.metadata.functions && file.metadata.functions.length > 0;
|
|
25
|
-
const hasClasses = file.metadata.classes && file.metadata.classes.length > 0;
|
|
26
|
-
const hasExports = file.metadata.exports && file.metadata.exports.length > 0;
|
|
27
|
-
const hasApiCalls = file.metadata.apiCalls && file.metadata.apiCalls.length > 0;
|
|
28
|
-
|
|
29
|
-
let role = 'Structure';
|
|
30
|
-
if (hasFunctions || hasClasses || hasApiCalls) {
|
|
31
|
-
role = 'Implementation';
|
|
32
|
-
} else if (hasExports) {
|
|
33
|
-
role = 'Interface';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Extract dependencies more accurately and remove duplicates
|
|
37
|
-
const dependencies = [];
|
|
38
|
-
const uniqueDeps = new Set();
|
|
39
|
-
if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
|
|
40
|
-
file.metadata.imports.forEach(imp => {
|
|
41
|
-
if (imp.source && typeof imp.source === 'string') {
|
|
42
|
-
// Only add unique dependencies
|
|
43
|
-
if (!uniqueDeps.has(imp.source)) {
|
|
44
|
-
uniqueDeps.add(imp.source);
|
|
45
|
-
dependencies.push(imp.source);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
id: stableId(normalizedPath),
|
|
53
|
-
type: 'file',
|
|
54
|
-
path: normalizedPath,
|
|
55
|
-
role: role,
|
|
56
|
-
dependencies: dependencies,
|
|
57
|
-
blast_radius: file.metadata.blast_radius || 0
|
|
58
|
-
};
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Build edges from dependencies
|
|
62
|
-
const schemaEdges = [];
|
|
63
|
-
for (const edge of edges) {
|
|
64
|
-
// Find source and target nodes to get their stable IDs
|
|
65
|
-
const sourceNode = nodes.find(n => n.path === edge.source);
|
|
66
|
-
const targetNode = nodes.find(n => n.path === edge.target);
|
|
67
|
-
|
|
68
|
-
// Include edges where source exists (even if target doesn't exist as a node)
|
|
69
|
-
if (sourceNode) {
|
|
70
|
-
// Normalize the edge type to fit the schema
|
|
71
|
-
let relationType = 'imports';
|
|
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') {
|
|
88
|
-
relationType = 'imports';
|
|
89
|
-
}
|
|
90
|
-
// Handle existing types
|
|
91
|
-
else if (edge.type === 'calls') {
|
|
92
|
-
relationType = 'calls';
|
|
93
|
-
} else if (edge.type === 'owns') {
|
|
94
|
-
relationType = 'owns';
|
|
95
|
-
} else if (edge.type === 'depends_on') {
|
|
96
|
-
relationType = 'depends_on';
|
|
97
|
-
} else {
|
|
98
|
-
// Default to 'imports' for any unknown edge types to ensure schema compliance
|
|
99
|
-
relationType = 'imports';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (targetNode) {
|
|
103
|
-
// Both source and target nodes exist
|
|
104
|
-
schemaEdges.push({
|
|
105
|
-
from: sourceNode.id,
|
|
106
|
-
to: targetNode.id,
|
|
107
|
-
relation: relationType
|
|
108
|
-
});
|
|
109
|
-
} else {
|
|
110
|
-
// Source exists but target doesn't (external dependency like node_modules)
|
|
111
|
-
// We'll still include the edge to preserve the relationship
|
|
112
|
-
// However, since the schema requires valid node IDs, we'll only add if the target is a known node
|
|
113
|
-
// For now, we'll skip edges to non-existent targets to maintain schema compliance
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Calculate metrics
|
|
119
|
-
const metrics = {
|
|
120
|
-
total_files: nodes.length,
|
|
121
|
-
total_nodes: nodes.length,
|
|
122
|
-
total_edges: 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,
|
|
127
|
-
files_with_functions: nodes.filter(n => n.role === 'Implementation').length
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Build and return the context object
|
|
131
|
-
const context = {
|
|
132
|
-
schema_version: '1.0.0',
|
|
133
|
-
generated_at: new Date().toISOString(),
|
|
134
|
-
system: {
|
|
135
|
-
name: projectName,
|
|
136
|
-
root_path: path.resolve(directory),
|
|
137
|
-
language: language
|
|
138
|
-
},
|
|
139
|
-
nodes,
|
|
140
|
-
edges: schemaEdges,
|
|
141
|
-
metrics,
|
|
142
|
-
contextSurface: options.contextSurface || {}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// Add additional computed metrics
|
|
146
|
-
context.metrics.files_with_functions = nodes.filter(n => n.role === 'Implementation').length;
|
|
147
|
-
context.metrics.files_with_high_blast_radius = nodes.filter(n => n.blast_radius > 5).length;
|
|
148
|
-
context.metrics.total_dependencies = schemaEdges.length;
|
|
149
|
-
|
|
150
|
-
return context;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
module.exports = { buildContext };
|