api-tests-coverage 1.0.12 → 1.0.14
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/dashboard/dist/assets/_basePickBy-CErN2e4K.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-DzHorfx6.js +1 -0
- package/dist/dashboard/dist/assets/arc-g03p1JXB.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-CLCqS7Lv.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-V4vxrfzX.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-Bs4R4b6P.js +10 -0
- package/dist/dashboard/dist/assets/channel-BSnIwwuG.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-oleArqPu.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-BIFhHB94.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-BChq1Wly.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-D7eiRvhB.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-BgEzYy_c.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-BNX7uRa_.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-mq-zucvn.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-DGTVcqTS.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-Be0F7AG8.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-Be0F7AG8.js +1 -0
- package/dist/dashboard/dist/assets/clone-B5PF81Z3.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-xVw-THr_.js +1 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-Bjadb8g_.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-BPFtplp4.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CYxueP7U.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-CRUXUQeh.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-DvI8ycM6.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-BAHshMEg.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-BS4JnN-M.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-Uz3nRdju.js +65 -0
- package/dist/dashboard/dist/assets/graph-Cwn7jgQS.js +1 -0
- package/dist/dashboard/dist/assets/index-D3_88Gr5.js +777 -0
- package/dist/dashboard/dist/assets/index-D_begBP0.css +1 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-HLwGZBHJ.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-CFgbI9OH.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-D3u5Ov-5.js +89 -0
- package/dist/dashboard/dist/assets/layout-DgtRACDS.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-BJ5xCmsL.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-BIOVbZG_.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-Crgu5WqK.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-ClNSNeYe.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-VcdyPlVE.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-97qjzqIO.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-BWqoDymf.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-K_qkvHaB.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-DR6HNny6.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-MpQr6tee.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-bS40I4IT.js +7 -0
- package/dist/dashboard/dist/index.html +2 -2
- package/dist/src/ast/astAnalysisOrchestrator.d.ts +28 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts.map +1 -1
- package/dist/src/ast/astAnalysisOrchestrator.js +97 -27
- package/dist/src/languages/javascript/httpInteractionExtractor.js +10 -4
- package/dist/src/pipeline/confidence.d.ts +70 -0
- package/dist/src/pipeline/confidence.d.ts.map +1 -0
- package/dist/src/pipeline/confidence.js +198 -0
- package/dist/src/pipeline/graph.d.ts +58 -0
- package/dist/src/pipeline/graph.d.ts.map +1 -0
- package/dist/src/pipeline/graph.js +199 -0
- package/dist/src/pipeline/index.d.ts +24 -0
- package/dist/src/pipeline/index.d.ts.map +1 -0
- package/dist/src/pipeline/index.js +41 -0
- package/dist/src/pipeline/orchestrator.d.ts +42 -0
- package/dist/src/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/src/pipeline/orchestrator.js +115 -0
- package/dist/src/pipeline/stageInterface.d.ts +45 -0
- package/dist/src/pipeline/stageInterface.d.ts.map +1 -0
- package/dist/src/pipeline/stageInterface.js +17 -0
- package/dist/src/pipeline/stages/ast/abstractLayerTraversal.d.ts +38 -0
- package/dist/src/pipeline/stages/ast/abstractLayerTraversal.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/abstractLayerTraversal.js +203 -0
- package/dist/src/pipeline/stages/ast/astStage.d.ts +19 -0
- package/dist/src/pipeline/stages/ast/astStage.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/astStage.js +238 -0
- package/dist/src/pipeline/stages/ast/crossFileResolver.d.ts +23 -0
- package/dist/src/pipeline/stages/ast/crossFileResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/crossFileResolver.js +183 -0
- package/dist/src/pipeline/stages/ast/graphBuilder.d.ts +15 -0
- package/dist/src/pipeline/stages/ast/graphBuilder.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/graphBuilder.js +268 -0
- package/dist/src/pipeline/stages/ast/importResolver.d.ts +22 -0
- package/dist/src/pipeline/stages/ast/importResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/importResolver.js +186 -0
- package/dist/src/pipeline/stages/ast/types.d.ts +85 -0
- package/dist/src/pipeline/stages/ast/types.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/types.js +5 -0
- package/dist/src/pipeline/stages/dast/conflictEmitter.d.ts +25 -0
- package/dist/src/pipeline/stages/dast/conflictEmitter.d.ts.map +1 -0
- package/dist/src/pipeline/stages/dast/conflictEmitter.js +90 -0
- package/dist/src/pipeline/stages/dast/dastStage.d.ts +17 -0
- package/dist/src/pipeline/stages/dast/dastStage.d.ts.map +1 -0
- package/dist/src/pipeline/stages/dast/dastStage.js +203 -0
- package/dist/src/pipeline/stages/dast/types.d.ts +49 -0
- package/dist/src/pipeline/stages/dast/types.d.ts.map +1 -0
- package/dist/src/pipeline/stages/dast/types.js +9 -0
- package/dist/src/pipeline/stages/iast/iastStage.d.ts +17 -0
- package/dist/src/pipeline/stages/iast/iastStage.d.ts.map +1 -0
- package/dist/src/pipeline/stages/iast/iastStage.js +191 -0
- package/dist/src/pipeline/stages/iast/types.d.ts +48 -0
- package/dist/src/pipeline/stages/iast/types.d.ts.map +1 -0
- package/dist/src/pipeline/stages/iast/types.js +8 -0
- package/dist/src/pipeline/stages/merge/conflictDetector.d.ts +17 -0
- package/dist/src/pipeline/stages/merge/conflictDetector.d.ts.map +1 -0
- package/dist/src/pipeline/stages/merge/conflictDetector.js +60 -0
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.d.ts +15 -0
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.d.ts.map +1 -0
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.js +141 -0
- package/dist/src/pipeline/stages/merge/mergeRules.d.ts +39 -0
- package/dist/src/pipeline/stages/merge/mergeRules.d.ts.map +1 -0
- package/dist/src/pipeline/stages/merge/mergeRules.js +90 -0
- package/dist/src/pipeline/stages/merge/mergeStage.d.ts +20 -0
- package/dist/src/pipeline/stages/merge/mergeStage.d.ts.map +1 -0
- package/dist/src/pipeline/stages/merge/mergeStage.js +145 -0
- package/dist/src/pipeline/stages/merge/summaryComputer.d.ts +11 -0
- package/dist/src/pipeline/stages/merge/summaryComputer.d.ts.map +1 -0
- package/dist/src/pipeline/stages/merge/summaryComputer.js +46 -0
- package/dist/src/pipeline/stages/sca/ciDetector.d.ts +15 -0
- package/dist/src/pipeline/stages/sca/ciDetector.d.ts.map +1 -0
- package/dist/src/pipeline/stages/sca/ciDetector.js +87 -0
- package/dist/src/pipeline/stages/sca/dependencyClassification.d.ts +31 -0
- package/dist/src/pipeline/stages/sca/dependencyClassification.d.ts.map +1 -0
- package/dist/src/pipeline/stages/sca/dependencyClassification.js +296 -0
- package/dist/src/pipeline/stages/sca/dependencyDetector.d.ts +25 -0
- package/dist/src/pipeline/stages/sca/dependencyDetector.d.ts.map +1 -0
- package/dist/src/pipeline/stages/sca/dependencyDetector.js +416 -0
- package/dist/src/pipeline/stages/sca/scaStage.d.ts +21 -0
- package/dist/src/pipeline/stages/sca/scaStage.d.ts.map +1 -0
- package/dist/src/pipeline/stages/sca/scaStage.js +208 -0
- package/dist/src/pipeline/stages/sca/types.d.ts +61 -0
- package/dist/src/pipeline/stages/sca/types.d.ts.map +1 -0
- package/dist/src/pipeline/stages/sca/types.js +9 -0
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.d.ts +19 -0
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.d.ts.map +1 -0
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.js +118 -0
- package/dist/src/pipeline/stages/tia/parameterizedTestExpander.d.ts +20 -0
- package/dist/src/pipeline/stages/tia/parameterizedTestExpander.d.ts.map +1 -0
- package/dist/src/pipeline/stages/tia/parameterizedTestExpander.js +238 -0
- package/dist/src/pipeline/stages/tia/testEndpointMapper.d.ts +22 -0
- package/dist/src/pipeline/stages/tia/testEndpointMapper.d.ts.map +1 -0
- package/dist/src/pipeline/stages/tia/testEndpointMapper.js +134 -0
- package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts +16 -0
- package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts.map +1 -0
- package/dist/src/pipeline/stages/tia/testLayerClassifier.js +191 -0
- package/dist/src/pipeline/stages/tia/tiaStage.d.ts +20 -0
- package/dist/src/pipeline/stages/tia/tiaStage.d.ts.map +1 -0
- package/dist/src/pipeline/stages/tia/tiaStage.js +215 -0
- package/dist/src/pipeline/stages/tia/types.d.ts +52 -0
- package/dist/src/pipeline/stages/tia/types.d.ts.map +1 -0
- package/dist/src/pipeline/stages/tia/types.js +5 -0
- package/dist/src/pipeline/types.d.ts +128 -0
- package/dist/src/pipeline/types.d.ts.map +1 -0
- package/dist/src/pipeline/types.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract layer traversal — resolves inherited assertions, imported helpers,
|
|
4
|
+
* fixture injections, and page objects across the inheritance chain.
|
|
5
|
+
*
|
|
6
|
+
* Implements the spec's abstract layer resolution with a configurable depth cap
|
|
7
|
+
* (default 5, RULE-05). When the cap is reached, resolution is marked as 'partial'.
|
|
8
|
+
* Cycles are detected and broken (RULE-15).
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.traverseInheritanceChain = traverseInheritanceChain;
|
|
12
|
+
exports.resolveImportedHelper = resolveImportedHelper;
|
|
13
|
+
exports.resolveFixtureInjection = resolveFixtureInjection;
|
|
14
|
+
/**
|
|
15
|
+
* Traverse the inheritance chain starting from a test class to resolve
|
|
16
|
+
* assertions, HTTP calls, and helper references from base classes.
|
|
17
|
+
*
|
|
18
|
+
* @param className - The test class/suite to start from
|
|
19
|
+
* @param table - The cross-file symbol table
|
|
20
|
+
* @param depthCap - Maximum traversal depth (RULE-05: default 5)
|
|
21
|
+
*/
|
|
22
|
+
function traverseInheritanceChain(className, table, depthCap = 5) {
|
|
23
|
+
const visited = new Set();
|
|
24
|
+
const resolvedAssertions = [];
|
|
25
|
+
const resolvedHttpCalls = [];
|
|
26
|
+
const unresolvedChain = [];
|
|
27
|
+
let maxDepth = 0;
|
|
28
|
+
let cycleDetected = false;
|
|
29
|
+
function traverse(name, depth) {
|
|
30
|
+
var _a;
|
|
31
|
+
if (depth > depthCap) {
|
|
32
|
+
unresolvedChain.push(`${name} (depth cap ${depthCap} reached)`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (visited.has(name)) {
|
|
36
|
+
cycleDetected = true;
|
|
37
|
+
unresolvedChain.push(`${name} (cycle detected)`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
visited.add(name);
|
|
41
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
42
|
+
// Find the class declaration
|
|
43
|
+
const classDecl = table.classes.get(name);
|
|
44
|
+
if (!classDecl) {
|
|
45
|
+
if (depth > 0) {
|
|
46
|
+
unresolvedChain.push(`${name} (class not found)`);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Get the semantic model for this class's file
|
|
51
|
+
const model = table.models.get(classDecl.filePath);
|
|
52
|
+
if (!model) {
|
|
53
|
+
unresolvedChain.push(`${name} (model not found for ${classDecl.filePath})`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Collect assertions from this class's model
|
|
57
|
+
for (const assertion of model.assertions) {
|
|
58
|
+
resolvedAssertions.push(assertion);
|
|
59
|
+
}
|
|
60
|
+
// Collect HTTP calls from this class's functions
|
|
61
|
+
for (const methodName of classDecl.methods) {
|
|
62
|
+
const func = (_a = model.functions.get(methodName)) !== null && _a !== void 0 ? _a : model.functions.get(`${name}.${methodName}`);
|
|
63
|
+
if (func) {
|
|
64
|
+
resolvedHttpCalls.push(...func.bodyHttpCalls);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Recurse into base class
|
|
68
|
+
if (classDecl.extendsClass) {
|
|
69
|
+
traverse(classDecl.extendsClass, depth + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
traverse(className, 0);
|
|
73
|
+
const resolution = unresolvedChain.length > 0 ? 'partial' : 'full';
|
|
74
|
+
const assertionSource = maxDepth === 0
|
|
75
|
+
? 'direct'
|
|
76
|
+
: resolvedAssertions.length > 0
|
|
77
|
+
? 'inherited'
|
|
78
|
+
: 'unresolved';
|
|
79
|
+
return {
|
|
80
|
+
resolvedAssertions,
|
|
81
|
+
resolvedHttpCalls,
|
|
82
|
+
assertionSource,
|
|
83
|
+
traversalDepth: maxDepth,
|
|
84
|
+
resolution,
|
|
85
|
+
unresolvedChain: unresolvedChain.length > 0 ? unresolvedChain : undefined,
|
|
86
|
+
cycleDetected,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve an imported helper function by following the import chain.
|
|
91
|
+
*
|
|
92
|
+
* @param functionName - The helper function to resolve
|
|
93
|
+
* @param fromFile - The file importing the helper
|
|
94
|
+
* @param table - The cross-file symbol table
|
|
95
|
+
* @param depthCap - Maximum traversal depth
|
|
96
|
+
*/
|
|
97
|
+
function resolveImportedHelper(functionName, fromFile, table, depthCap = 5) {
|
|
98
|
+
const visited = new Set();
|
|
99
|
+
const resolvedAssertions = [];
|
|
100
|
+
const resolvedHttpCalls = [];
|
|
101
|
+
const unresolvedChain = [];
|
|
102
|
+
let maxDepth = 0;
|
|
103
|
+
let cycleDetected = false;
|
|
104
|
+
function resolve(funcName, file, depth) {
|
|
105
|
+
var _a;
|
|
106
|
+
if (depth > depthCap) {
|
|
107
|
+
unresolvedChain.push(`${funcName} in ${file} (depth cap reached)`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const key = `${file}::${funcName}`;
|
|
111
|
+
if (visited.has(key)) {
|
|
112
|
+
cycleDetected = true;
|
|
113
|
+
unresolvedChain.push(`${funcName} in ${file} (cycle detected)`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
visited.add(key);
|
|
117
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
118
|
+
const model = table.models.get(file);
|
|
119
|
+
if (!model) {
|
|
120
|
+
unresolvedChain.push(`${funcName} (model not found for ${file})`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Find the function in the model
|
|
124
|
+
const func = model.functions.get(funcName);
|
|
125
|
+
if (!func) {
|
|
126
|
+
// Try imported files
|
|
127
|
+
const importedFiles = (_a = table.importGraph.get(file)) !== null && _a !== void 0 ? _a : [];
|
|
128
|
+
for (const importedFile of importedFiles) {
|
|
129
|
+
const importedModel = table.models.get(importedFile);
|
|
130
|
+
if (importedModel === null || importedModel === void 0 ? void 0 : importedModel.functions.has(funcName)) {
|
|
131
|
+
resolve(funcName, importedFile, depth + 1);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
unresolvedChain.push(`${funcName} (function not found in ${file})`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Collect HTTP calls from this function
|
|
139
|
+
resolvedHttpCalls.push(...func.bodyHttpCalls);
|
|
140
|
+
// Collect any assertions from the model that relate to this function
|
|
141
|
+
for (const assertion of model.assertions) {
|
|
142
|
+
resolvedAssertions.push(assertion);
|
|
143
|
+
}
|
|
144
|
+
// Follow called functions recursively
|
|
145
|
+
for (const calledFunc of func.calledFunctions) {
|
|
146
|
+
resolve(calledFunc, file, depth + 1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
resolve(functionName, fromFile, 0);
|
|
150
|
+
const resolution = unresolvedChain.length > 0 ? 'partial' : 'full';
|
|
151
|
+
const assertionSource = resolvedAssertions.length > 0 ? 'helper' : 'unresolved';
|
|
152
|
+
return {
|
|
153
|
+
resolvedAssertions,
|
|
154
|
+
resolvedHttpCalls,
|
|
155
|
+
assertionSource,
|
|
156
|
+
traversalDepth: maxDepth,
|
|
157
|
+
resolution,
|
|
158
|
+
unresolvedChain: unresolvedChain.length > 0 ? unresolvedChain : undefined,
|
|
159
|
+
cycleDetected,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Resolve fixture-injected assertions and HTTP calls.
|
|
164
|
+
* Traces @BeforeEach/beforeEach/pytest fixtures/@Autowired patterns.
|
|
165
|
+
*
|
|
166
|
+
* @param fixtureName - The fixture function name
|
|
167
|
+
* @param table - The cross-file symbol table
|
|
168
|
+
* @param depthCap - Maximum traversal depth
|
|
169
|
+
*/
|
|
170
|
+
function resolveFixtureInjection(fixtureName, table, depthCap = 5) {
|
|
171
|
+
const resolvedAssertions = [];
|
|
172
|
+
const resolvedHttpCalls = [];
|
|
173
|
+
const unresolvedChain = [];
|
|
174
|
+
let maxDepth = 0;
|
|
175
|
+
let found = false;
|
|
176
|
+
// Search all models for the fixture function
|
|
177
|
+
for (const [filePath, model] of table.models) {
|
|
178
|
+
const func = model.functions.get(fixtureName);
|
|
179
|
+
if (!func)
|
|
180
|
+
continue;
|
|
181
|
+
found = true;
|
|
182
|
+
maxDepth = 1;
|
|
183
|
+
// Collect HTTP calls from the fixture
|
|
184
|
+
resolvedHttpCalls.push(...func.bodyHttpCalls);
|
|
185
|
+
// Check if fixture has assertions
|
|
186
|
+
for (const assertion of model.assertions) {
|
|
187
|
+
resolvedAssertions.push(assertion);
|
|
188
|
+
}
|
|
189
|
+
break; // Use first match
|
|
190
|
+
}
|
|
191
|
+
if (!found) {
|
|
192
|
+
unresolvedChain.push(`${fixtureName} (fixture not found)`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
resolvedAssertions,
|
|
196
|
+
resolvedHttpCalls,
|
|
197
|
+
assertionSource: found ? 'fixture' : 'unresolved',
|
|
198
|
+
traversalDepth: maxDepth,
|
|
199
|
+
resolution: found ? 'full' : 'partial',
|
|
200
|
+
unresolvedChain: unresolvedChain.length > 0 ? unresolvedChain : undefined,
|
|
201
|
+
cycleDetected: false,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Stage — Stage 2 of the coverage pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing AST analysis orchestrator and extends it with:
|
|
5
|
+
* - Multi-file analysis: parses all discovered files and builds a cross-file symbol table
|
|
6
|
+
* - Abstract layer traversal: resolves inherited assertions, helpers, fixtures
|
|
7
|
+
* - Graph building: converts analysis results into GraphNode[] and GraphEdge[]
|
|
8
|
+
*
|
|
9
|
+
* The AST stage reads the SCA output (from context.stageOutputs) to determine
|
|
10
|
+
* which languages and frameworks are present, enabling targeted analysis heuristics.
|
|
11
|
+
*/
|
|
12
|
+
import type { PipelineStage, PipelineContext } from '../../stageInterface';
|
|
13
|
+
import type { AstStageOutput } from './types';
|
|
14
|
+
export declare class AstStage implements PipelineStage<AstStageOutput> {
|
|
15
|
+
readonly name: "ast";
|
|
16
|
+
readonly optional = false;
|
|
17
|
+
execute(context: PipelineContext): Promise<AstStageOutput>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=astStage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astStage.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/astStage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAkB9C,qBAAa,QAAS,YAAW,aAAa,CAAC,cAAc,CAAC;IAC5D,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,QAAQ,SAAS;IAEpB,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;CAyKjE"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AST Stage — Stage 2 of the coverage pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the existing AST analysis orchestrator and extends it with:
|
|
6
|
+
* - Multi-file analysis: parses all discovered files and builds a cross-file symbol table
|
|
7
|
+
* - Abstract layer traversal: resolves inherited assertions, helpers, fixtures
|
|
8
|
+
* - Graph building: converts analysis results into GraphNode[] and GraphEdge[]
|
|
9
|
+
*
|
|
10
|
+
* The AST stage reads the SCA output (from context.stageOutputs) to determine
|
|
11
|
+
* which languages and frameworks are present, enabling targeted analysis heuristics.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.AstStage = void 0;
|
|
48
|
+
const astAnalysisOrchestrator_1 = require("../../../ast/astAnalysisOrchestrator");
|
|
49
|
+
const crossFileResolver_1 = require("./crossFileResolver");
|
|
50
|
+
const abstractLayerTraversal_1 = require("./abstractLayerTraversal");
|
|
51
|
+
const graphBuilder_1 = require("./graphBuilder");
|
|
52
|
+
const fileClassifier_1 = require("../../../discovery/fileClassifier");
|
|
53
|
+
const projectDiscovery_1 = require("../../../discovery/projectDiscovery");
|
|
54
|
+
const parserRegistry_1 = require("../../../ast/parserRegistry");
|
|
55
|
+
const fs = __importStar(require("fs"));
|
|
56
|
+
const path = __importStar(require("path"));
|
|
57
|
+
class AstStage {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.name = 'ast';
|
|
60
|
+
this.optional = false;
|
|
61
|
+
}
|
|
62
|
+
async execute(context) {
|
|
63
|
+
var _a;
|
|
64
|
+
const startTime = Date.now();
|
|
65
|
+
const filesScanned = [];
|
|
66
|
+
const filesSkipped = [];
|
|
67
|
+
// Ensure all language analyzers are registered
|
|
68
|
+
(0, astAnalysisOrchestrator_1.registerAllAnalyzers)();
|
|
69
|
+
// Get SCA output for language/framework context
|
|
70
|
+
const scaOutput = context.stageOutputs.get('sca');
|
|
71
|
+
// Build analysis context
|
|
72
|
+
const analysisContext = (0, astAnalysisOrchestrator_1.buildAnalysisContext)(context.config.astConfig);
|
|
73
|
+
// Discover all files
|
|
74
|
+
const artifacts = (0, projectDiscovery_1.discoverProject)({ rootDir: context.projectRoot });
|
|
75
|
+
const allSourceFiles = [
|
|
76
|
+
...artifacts.testFiles,
|
|
77
|
+
...artifacts.serviceFiles,
|
|
78
|
+
];
|
|
79
|
+
// Phase 1: Parse all files and build semantic models
|
|
80
|
+
const models = new Map();
|
|
81
|
+
const interactionsMap = new Map();
|
|
82
|
+
for (const filePath of allSourceFiles) {
|
|
83
|
+
// Enforce per-file timeout (RULE-16)
|
|
84
|
+
const fileStart = Date.now();
|
|
85
|
+
let content;
|
|
86
|
+
try {
|
|
87
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
filesSkipped.push({ file: filePath, reason: 'read-error' });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Detect language from file extension
|
|
94
|
+
const language = detectLanguage(filePath, scaOutput);
|
|
95
|
+
if (!language) {
|
|
96
|
+
filesSkipped.push({ file: filePath, reason: 'unsupported-language' });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// Check timeout
|
|
100
|
+
if (Date.now() - fileStart > context.config.fileTimeoutMs) {
|
|
101
|
+
filesSkipped.push({ file: filePath, reason: 'timeout' });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
// Use existing AST analysis
|
|
106
|
+
const result = (0, astAnalysisOrchestrator_1.analyzeFileDetailed)(content, filePath, language, analysisContext);
|
|
107
|
+
interactionsMap.set(filePath, result.interactions);
|
|
108
|
+
// Build semantic model if we have an analyzer
|
|
109
|
+
const analyzer = (0, parserRegistry_1.getAnalyzer)(language, analysisContext.astConfig);
|
|
110
|
+
if (analyzer) {
|
|
111
|
+
const parsed = analyzer.parse(filePath, content);
|
|
112
|
+
if (!parsed.parseError) {
|
|
113
|
+
const model = analyzer.buildSemanticModel(parsed, analysisContext);
|
|
114
|
+
models.set(filePath, model);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// RULE-02: File not skipped on parse error — we already have fallback interactions
|
|
118
|
+
filesSkipped.push({ file: filePath, reason: 'parse-error' });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
filesScanned.push(filePath);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// RULE-02: Never skip a file entirely on error
|
|
125
|
+
filesSkipped.push({
|
|
126
|
+
file: filePath,
|
|
127
|
+
reason: `analysis-error: ${err instanceof Error ? err.message : String(err)}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Phase 2: Build cross-file symbol table
|
|
132
|
+
const crossFileTable = (0, crossFileResolver_1.buildCrossFileSymbolTable)(models, context.projectRoot);
|
|
133
|
+
// Phase 3: Run abstract layer traversal for test files
|
|
134
|
+
const traversalResults = new Map();
|
|
135
|
+
const depthCap = context.config.traversalDepthCap;
|
|
136
|
+
for (const [filePath] of models) {
|
|
137
|
+
const basename = path.basename(filePath);
|
|
138
|
+
if (!(0, fileClassifier_1.isTestFile)(basename))
|
|
139
|
+
continue;
|
|
140
|
+
// Try inheritance chain traversal for classes in this test file
|
|
141
|
+
for (const [className, classDecl] of crossFileTable.classes) {
|
|
142
|
+
if (classDecl.filePath === filePath && classDecl.extendsClass) {
|
|
143
|
+
const result = (0, abstractLayerTraversal_1.traverseInheritanceChain)(className, crossFileTable, depthCap);
|
|
144
|
+
traversalResults.set(filePath, result);
|
|
145
|
+
break; // One traversal result per test file
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// If no inheritance found, try resolving imported helpers
|
|
149
|
+
if (!traversalResults.has(filePath)) {
|
|
150
|
+
const model = models.get(filePath);
|
|
151
|
+
if (model) {
|
|
152
|
+
for (const [funcName, func] of model.functions) {
|
|
153
|
+
for (const calledFunc of func.calledFunctions) {
|
|
154
|
+
// Check if this is an imported function
|
|
155
|
+
const importedFiles = (_a = crossFileTable.importGraph.get(filePath)) !== null && _a !== void 0 ? _a : [];
|
|
156
|
+
for (const importedFile of importedFiles) {
|
|
157
|
+
const importedModel = models.get(importedFile);
|
|
158
|
+
if (importedModel === null || importedModel === void 0 ? void 0 : importedModel.functions.has(calledFunc)) {
|
|
159
|
+
const result = (0, abstractLayerTraversal_1.resolveImportedHelper)(calledFunc, importedFile, crossFileTable, depthCap);
|
|
160
|
+
if (result.resolvedAssertions.length > 0 || result.resolvedHttpCalls.length > 0) {
|
|
161
|
+
traversalResults.set(filePath, result);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (traversalResults.has(filePath))
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
if (traversalResults.has(filePath))
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Phase 4: Build graph nodes and edges
|
|
176
|
+
const { nodes, edges } = (0, graphBuilder_1.buildAstGraph)(models, crossFileTable, traversalResults, interactionsMap);
|
|
177
|
+
// Add nodes and edges to the context graph
|
|
178
|
+
for (const node of nodes) {
|
|
179
|
+
if (!context.graph.hasNode(node.id)) {
|
|
180
|
+
context.graph.addNode(node);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const edge of edges) {
|
|
184
|
+
if (!context.graph.hasEdge(edge.id)) {
|
|
185
|
+
context.graph.addEdge(edge);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Record diagnostics (RULE-12)
|
|
189
|
+
const diagnostics = {
|
|
190
|
+
stageName: 'ast',
|
|
191
|
+
filesScanned,
|
|
192
|
+
filesSkipped,
|
|
193
|
+
durationMs: Date.now() - startTime,
|
|
194
|
+
metadata: {
|
|
195
|
+
totalModels: models.size,
|
|
196
|
+
totalInteractions: Array.from(interactionsMap.values()).reduce((sum, arr) => sum + arr.length, 0),
|
|
197
|
+
totalClasses: crossFileTable.classes.size,
|
|
198
|
+
totalExportedSymbols: crossFileTable.exportedSymbols.size,
|
|
199
|
+
traversalResultCount: traversalResults.size,
|
|
200
|
+
graphNodesAdded: nodes.length,
|
|
201
|
+
graphEdgesAdded: edges.length,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
context.diagnostics.set('ast', diagnostics);
|
|
205
|
+
// Build output
|
|
206
|
+
const output = {
|
|
207
|
+
models,
|
|
208
|
+
crossFileTable,
|
|
209
|
+
traversalResults,
|
|
210
|
+
analyzedFiles: filesScanned,
|
|
211
|
+
skippedFiles: filesSkipped,
|
|
212
|
+
};
|
|
213
|
+
context.stageOutputs.set('ast', output);
|
|
214
|
+
return output;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.AstStage = AstStage;
|
|
218
|
+
/**
|
|
219
|
+
* Detect the language of a file from its extension.
|
|
220
|
+
*/
|
|
221
|
+
function detectLanguage(filePath, scaOutput) {
|
|
222
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
223
|
+
const extensionMap = {
|
|
224
|
+
'.ts': 'typescript',
|
|
225
|
+
'.tsx': 'typescript',
|
|
226
|
+
'.js': 'javascript',
|
|
227
|
+
'.jsx': 'javascript',
|
|
228
|
+
'.mjs': 'javascript',
|
|
229
|
+
'.cjs': 'javascript',
|
|
230
|
+
'.java': 'java',
|
|
231
|
+
'.kt': 'kotlin',
|
|
232
|
+
'.kts': 'kotlin',
|
|
233
|
+
'.py': 'python',
|
|
234
|
+
'.rb': 'ruby',
|
|
235
|
+
'.feature': 'cucumber',
|
|
236
|
+
};
|
|
237
|
+
return extensionMap[ext];
|
|
238
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-file resolver — builds a multi-file symbol table from all parsed semantic models.
|
|
3
|
+
*
|
|
4
|
+
* Aggregates per-file symbol tables, export declarations, class declarations,
|
|
5
|
+
* and import graphs into a unified `CrossFileSymbolTable` that enables
|
|
6
|
+
* cross-file constant resolution, inheritance chain traversal, and
|
|
7
|
+
* import-based helper resolution.
|
|
8
|
+
*/
|
|
9
|
+
import type { SemanticModel } from '../../../ast/astTypes';
|
|
10
|
+
import type { CrossFileSymbolTable } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* Build a cross-file symbol table from all parsed semantic models.
|
|
13
|
+
*
|
|
14
|
+
* @param models - Map of filePath → SemanticModel
|
|
15
|
+
* @param projectRoot - The project root directory
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildCrossFileSymbolTable(models: Map<string, SemanticModel>, projectRoot: string): CrossFileSymbolTable;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a symbol name using the cross-file symbol table.
|
|
20
|
+
* Tries: direct lookup, qualified lookup (filePath::name), and import-chain following.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveSymbolCrossFile(symbolName: string, fromFile: string, table: CrossFileSymbolTable): string | undefined;
|
|
23
|
+
//# sourceMappingURL=crossFileResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossFileResolver.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/crossFileResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAuC,MAAM,uBAAuB,CAAC;AAChG,OAAO,KAAK,EAAE,oBAAoB,EAAuC,MAAM,SAAS,CAAC;AAGzF;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAClC,WAAW,EAAE,MAAM,GAClB,oBAAoB,CAuDtB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,oBAAoB,GAC1B,MAAM,GAAG,SAAS,CA4BpB"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-file resolver — builds a multi-file symbol table from all parsed semantic models.
|
|
4
|
+
*
|
|
5
|
+
* Aggregates per-file symbol tables, export declarations, class declarations,
|
|
6
|
+
* and import graphs into a unified `CrossFileSymbolTable` that enables
|
|
7
|
+
* cross-file constant resolution, inheritance chain traversal, and
|
|
8
|
+
* import-based helper resolution.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.buildCrossFileSymbolTable = buildCrossFileSymbolTable;
|
|
12
|
+
exports.resolveSymbolCrossFile = resolveSymbolCrossFile;
|
|
13
|
+
const importResolver_1 = require("./importResolver");
|
|
14
|
+
/**
|
|
15
|
+
* Build a cross-file symbol table from all parsed semantic models.
|
|
16
|
+
*
|
|
17
|
+
* @param models - Map of filePath → SemanticModel
|
|
18
|
+
* @param projectRoot - The project root directory
|
|
19
|
+
*/
|
|
20
|
+
function buildCrossFileSymbolTable(models, projectRoot) {
|
|
21
|
+
const exportedSymbols = new Map();
|
|
22
|
+
const classes = new Map();
|
|
23
|
+
const importGraph = new Map();
|
|
24
|
+
// Pass 1: Collect all exported symbols and class declarations
|
|
25
|
+
for (const [filePath, model] of models) {
|
|
26
|
+
// Collect constants as exported symbols
|
|
27
|
+
for (const [name, symbol] of model.constants) {
|
|
28
|
+
if (symbol.value !== undefined) {
|
|
29
|
+
exportedSymbols.set(`${filePath}::${name}`, { filePath, value: symbol.value });
|
|
30
|
+
// Also register as global name for simple lookups
|
|
31
|
+
if (!exportedSymbols.has(name)) {
|
|
32
|
+
exportedSymbols.set(name, { filePath, value: symbol.value });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Collect local variables that are const-like with string values
|
|
37
|
+
for (const [name, symbol] of model.localVariables) {
|
|
38
|
+
if (symbol.kind === 'const' && symbol.value !== undefined) {
|
|
39
|
+
exportedSymbols.set(`${filePath}::${name}`, { filePath, value: symbol.value });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Collect enum members as exported symbols
|
|
43
|
+
for (const [enumName, members] of model.enums) {
|
|
44
|
+
for (const [memberName, value] of members) {
|
|
45
|
+
const qualifiedName = `${enumName}.${memberName}`;
|
|
46
|
+
exportedSymbols.set(`${filePath}::${qualifiedName}`, { filePath, value });
|
|
47
|
+
if (!exportedSymbols.has(qualifiedName)) {
|
|
48
|
+
exportedSymbols.set(qualifiedName, { filePath, value });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Extract class declarations from functions (heuristic: capitalize name + has methods)
|
|
53
|
+
extractClassDeclarations(filePath, model, classes);
|
|
54
|
+
}
|
|
55
|
+
// Pass 2: Build import graph
|
|
56
|
+
for (const [filePath, model] of models) {
|
|
57
|
+
const imports = extractImportsFromModel(filePath, model, projectRoot);
|
|
58
|
+
const resolvedPaths = [];
|
|
59
|
+
for (const imp of imports) {
|
|
60
|
+
if (imp.resolvedPath) {
|
|
61
|
+
resolvedPaths.push(imp.resolvedPath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
importGraph.set(filePath, resolvedPaths);
|
|
65
|
+
}
|
|
66
|
+
return { models, exportedSymbols, classes, importGraph };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a symbol name using the cross-file symbol table.
|
|
70
|
+
* Tries: direct lookup, qualified lookup (filePath::name), and import-chain following.
|
|
71
|
+
*/
|
|
72
|
+
function resolveSymbolCrossFile(symbolName, fromFile, table) {
|
|
73
|
+
var _a;
|
|
74
|
+
// Try qualified lookup for the current file
|
|
75
|
+
const qualified = table.exportedSymbols.get(`${fromFile}::${symbolName}`);
|
|
76
|
+
if (qualified)
|
|
77
|
+
return qualified.value;
|
|
78
|
+
// Try global lookup
|
|
79
|
+
const global = table.exportedSymbols.get(symbolName);
|
|
80
|
+
if (global)
|
|
81
|
+
return global.value;
|
|
82
|
+
// Try following imports from the current file
|
|
83
|
+
const importedFiles = (_a = table.importGraph.get(fromFile)) !== null && _a !== void 0 ? _a : [];
|
|
84
|
+
for (const importedFile of importedFiles) {
|
|
85
|
+
const fromImported = table.exportedSymbols.get(`${importedFile}::${symbolName}`);
|
|
86
|
+
if (fromImported)
|
|
87
|
+
return fromImported.value;
|
|
88
|
+
}
|
|
89
|
+
// Try dotted name resolution (e.g. "Config.BASE_URL")
|
|
90
|
+
const dotParts = symbolName.split('.');
|
|
91
|
+
if (dotParts.length >= 2) {
|
|
92
|
+
// Try each possible combination
|
|
93
|
+
for (const [key, entry] of table.exportedSymbols) {
|
|
94
|
+
if (key.endsWith(`::${symbolName}`) || key === symbolName) {
|
|
95
|
+
return entry.value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Extract import declarations from a semantic model.
|
|
103
|
+
* This is a heuristic extraction — real import extraction would come from the AST.
|
|
104
|
+
*/
|
|
105
|
+
function extractImportsFromModel(filePath, model, projectRoot) {
|
|
106
|
+
const imports = [];
|
|
107
|
+
// Use functions' calledFunctions to infer cross-file references
|
|
108
|
+
for (const [, func] of model.functions) {
|
|
109
|
+
for (const calledName of func.calledFunctions) {
|
|
110
|
+
// If the function name contains a dot, it might be an import reference
|
|
111
|
+
const dotIndex = calledName.indexOf('.');
|
|
112
|
+
if (dotIndex > 0) {
|
|
113
|
+
const modulePart = calledName.substring(0, dotIndex);
|
|
114
|
+
const resolvedPath = (0, importResolver_1.resolveImportPath)(`./${modulePart}`, filePath, projectRoot, model.language);
|
|
115
|
+
if (resolvedPath) {
|
|
116
|
+
imports.push({
|
|
117
|
+
source: modulePart,
|
|
118
|
+
resolvedPath,
|
|
119
|
+
importedNames: [calledName.substring(dotIndex + 1)],
|
|
120
|
+
isDefault: false,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return imports;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Extract class declarations from a semantic model (heuristic).
|
|
130
|
+
*
|
|
131
|
+
* Looks for functions that follow class patterns:
|
|
132
|
+
* - Capitalized names
|
|
133
|
+
* - Constructor patterns
|
|
134
|
+
* - Extends/implements annotations
|
|
135
|
+
*/
|
|
136
|
+
function extractClassDeclarations(filePath, model, classes) {
|
|
137
|
+
const classMethods = new Map();
|
|
138
|
+
for (const [name, func] of model.functions) {
|
|
139
|
+
// Look for constructor patterns to detect class names
|
|
140
|
+
if (name === 'constructor' || name.startsWith('__init__'))
|
|
141
|
+
continue;
|
|
142
|
+
// Methods prefixed with "ClassName." or "ClassName#"
|
|
143
|
+
const dotIndex = name.indexOf('.');
|
|
144
|
+
const hashIndex = name.indexOf('#');
|
|
145
|
+
const separator = dotIndex >= 0 ? dotIndex : hashIndex;
|
|
146
|
+
if (separator > 0) {
|
|
147
|
+
const className = name.substring(0, separator);
|
|
148
|
+
const methodName = name.substring(separator + 1);
|
|
149
|
+
if (!classMethods.has(className)) {
|
|
150
|
+
classMethods.set(className, []);
|
|
151
|
+
}
|
|
152
|
+
classMethods.get(className).push(methodName);
|
|
153
|
+
}
|
|
154
|
+
// Check annotations for class membership
|
|
155
|
+
if (func.annotations) {
|
|
156
|
+
for (const ann of func.annotations) {
|
|
157
|
+
// Spring/Java annotations like @Controller, @Service, @RestController
|
|
158
|
+
if (ann.match(/^@(Controller|Service|Repository|Component|RestController|SpringBootTest)/)) {
|
|
159
|
+
const className = extractClassNameFromAnnotation(name, func);
|
|
160
|
+
if (className && !classMethods.has(className)) {
|
|
161
|
+
classMethods.set(className, []);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Convert to ClassDeclaration objects
|
|
168
|
+
for (const [className, methods] of classMethods) {
|
|
169
|
+
if (!classes.has(className)) {
|
|
170
|
+
classes.set(className, {
|
|
171
|
+
name: className,
|
|
172
|
+
methods,
|
|
173
|
+
filePath,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function extractClassNameFromAnnotation(functionName, _func) {
|
|
179
|
+
const dotIndex = functionName.indexOf('.');
|
|
180
|
+
if (dotIndex > 0)
|
|
181
|
+
return functionName.substring(0, dotIndex);
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|