api-tests-coverage 1.0.15 → 1.0.17
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-C2jmWITn.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-DE6cyzJb.js +1 -0
- package/dist/dashboard/dist/assets/arc-B-Q4nGPT.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-C_5dqWCI.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-DbGIO6Kt.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-CAFpcejP.js +10 -0
- package/dist/dashboard/dist/assets/channel-Di9el3wE.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-DY1boKsq.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-BSL35gyW.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-eTDXrKrv.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-M-8I3jEy.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-bSA0XiS0.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-BrOIYUBs.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-CliaQGD4.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-CyhcxGB1.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-BkGN4Cpz.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-BkGN4Cpz.js +1 -0
- package/dist/dashboard/dist/assets/clone-Cvq8JuOb.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-BUkL7Wtq.js +1 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-B8oEROJc.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-5uki9Dw8.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-BRNhmby2.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-D-ku_X8U.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-DGl6gPe2.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Co89qYBD.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-2r3WpWQC.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-CuJ5l3TK.js +65 -0
- package/dist/dashboard/dist/assets/graph-ZtgwAPQj.js +1 -0
- package/dist/dashboard/dist/assets/index-D3sRJga7.js +777 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-ujnMqVz3.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-DQzfeBIo.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-ueIaoeks.js +89 -0
- package/dist/dashboard/dist/assets/layout-B1fTYUMj.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-B7wYeLe1.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-Bf8vKEOf.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-CM8qiFLR.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-DPTtP4Ve.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-DEVTdH0h.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-Bjr5wgXg.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DDrhZYly.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-Im6pH8C-.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-DAT3r9va.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-BlA8rg0m.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-7aSkQtVu.js +7 -0
- package/dist/src/ast/astTypes.d.ts +86 -0
- package/dist/src/ast/astTypes.d.ts.map +1 -1
- package/dist/src/discovery/fileClassifier.d.ts.map +1 -1
- package/dist/src/discovery/fileClassifier.js +15 -16
- package/dist/src/discovery/frameworkDetector.d.ts +28 -0
- package/dist/src/discovery/frameworkDetector.d.ts.map +1 -0
- package/dist/src/discovery/frameworkDetector.js +189 -0
- package/dist/src/discovery/projectDiscovery.d.ts +5 -1
- package/dist/src/discovery/projectDiscovery.d.ts.map +1 -1
- package/dist/src/discovery/projectDiscovery.js +8 -1
- package/dist/src/inference/routeInference.d.ts.map +1 -1
- package/dist/src/inference/routeInference.js +224 -1
- package/dist/src/languages/java/graphqlSchemaParser.d.ts +65 -0
- package/dist/src/languages/java/graphqlSchemaParser.d.ts.map +1 -0
- package/dist/src/languages/java/graphqlSchemaParser.js +164 -0
- package/dist/src/languages/java/mybatisXmlParser.d.ts +52 -0
- package/dist/src/languages/java/mybatisXmlParser.d.ts.map +1 -0
- package/dist/src/languages/java/mybatisXmlParser.js +107 -0
- package/dist/src/languages/java/semanticBuilder.d.ts.map +1 -1
- package/dist/src/languages/java/semanticBuilder.js +69 -12
- package/dist/src/languages/javascript/angularDetector.d.ts +74 -0
- package/dist/src/languages/javascript/angularDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/angularDetector.js +227 -0
- package/dist/src/languages/javascript/assertionResolver.js +6 -4
- package/dist/src/languages/javascript/hapiDetector.d.ts +40 -0
- package/dist/src/languages/javascript/hapiDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/hapiDetector.js +174 -0
- package/dist/src/languages/javascript/mongooseDetector.d.ts +65 -0
- package/dist/src/languages/javascript/mongooseDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/mongooseDetector.js +237 -0
- package/dist/src/languages/javascript/vueDetector.d.ts +42 -0
- package/dist/src/languages/javascript/vueDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/vueDetector.js +109 -0
- package/dist/src/languages/python/index.d.ts +6 -2
- package/dist/src/languages/python/index.d.ts.map +1 -1
- package/dist/src/languages/python/index.js +200 -5
- package/dist/src/languages/python/testPatternDetector.d.ts +70 -0
- package/dist/src/languages/python/testPatternDetector.d.ts.map +1 -0
- package/dist/src/languages/python/testPatternDetector.js +201 -0
- package/dist/src/pipeline/confidence.d.ts +6 -1
- package/dist/src/pipeline/confidence.d.ts.map +1 -1
- package/dist/src/pipeline/confidence.js +8 -3
- package/dist/src/pipeline/graph.d.ts.map +1 -1
- package/dist/src/pipeline/graph.js +16 -4
- package/dist/src/pipeline/stages/ast/astStage.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/astStage.js +51 -1
- package/dist/src/pipeline/stages/ast/baseUrlComposer.d.ts +44 -0
- package/dist/src/pipeline/stages/ast/baseUrlComposer.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/baseUrlComposer.js +97 -0
- package/dist/src/pipeline/stages/ast/crossFileResolutionPass.d.ts +54 -0
- package/dist/src/pipeline/stages/ast/crossFileResolutionPass.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/crossFileResolutionPass.js +88 -0
- package/dist/src/pipeline/stages/ast/crossFileResolver.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/crossFileResolver.js +39 -1
- package/dist/src/pipeline/stages/ast/graphBuilder.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/graphBuilder.js +81 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts +41 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.js +101 -0
- package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.d.ts +18 -0
- package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.js +96 -0
- package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.d.ts +46 -0
- package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.js +314 -0
- package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.d.ts +17 -0
- package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.js +65 -0
- package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.d.ts +17 -0
- package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.js +114 -0
- package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.d.ts +27 -0
- package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.js +130 -0
- package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.d.ts +17 -0
- package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.js +80 -0
- package/dist/src/pipeline/stages/ast/rulesEnforcer.d.ts +24 -0
- package/dist/src/pipeline/stages/ast/rulesEnforcer.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/rulesEnforcer.js +411 -0
- package/dist/src/pipeline/stages/ast/types.d.ts +114 -1
- package/dist/src/pipeline/stages/ast/types.d.ts.map +1 -1
- package/dist/src/pipeline/stages/merge/conflictDetector.d.ts +2 -0
- package/dist/src/pipeline/stages/merge/conflictDetector.d.ts.map +1 -1
- package/dist/src/pipeline/stages/merge/conflictDetector.js +54 -2
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.d.ts.map +1 -1
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.js +67 -3
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.js +8 -1
- package/dist/src/pipeline/stages/tia/parameterizedTestExpander.js +8 -4
- package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/testLayerClassifier.js +41 -10
- package/dist/src/pipeline/types.d.ts +1 -1
- package/dist/src/pipeline/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -47,11 +47,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
47
47
|
exports.AstStage = void 0;
|
|
48
48
|
const astAnalysisOrchestrator_1 = require("../../../ast/astAnalysisOrchestrator");
|
|
49
49
|
const crossFileResolver_1 = require("./crossFileResolver");
|
|
50
|
+
const crossFileResolutionPass_1 = require("./crossFileResolutionPass");
|
|
50
51
|
const abstractLayerTraversal_1 = require("./abstractLayerTraversal");
|
|
51
52
|
const graphBuilder_1 = require("./graphBuilder");
|
|
52
53
|
const fileClassifier_1 = require("../../../discovery/fileClassifier");
|
|
53
54
|
const projectDiscovery_1 = require("../../../discovery/projectDiscovery");
|
|
54
55
|
const parserRegistry_1 = require("../../../ast/parserRegistry");
|
|
56
|
+
const rulesEnforcer_1 = require("./rulesEnforcer");
|
|
57
|
+
const optionalAuthUnifier_1 = require("./optionalAuthUnifier");
|
|
58
|
+
const expressRouterResolver_1 = require("./resolvers/expressRouterResolver");
|
|
59
|
+
const flaskBlueprintResolver_1 = require("./resolvers/flaskBlueprintResolver");
|
|
60
|
+
const angularInjectionResolver_1 = require("./resolvers/angularInjectionResolver");
|
|
61
|
+
const vuexActionResolver_1 = require("./resolvers/vuexActionResolver");
|
|
62
|
+
const mybatisResolver_1 = require("./resolvers/mybatisResolver");
|
|
63
|
+
const dddLayerResolver_1 = require("./resolvers/dddLayerResolver");
|
|
55
64
|
const fs = __importStar(require("fs"));
|
|
56
65
|
const path = __importStar(require("path"));
|
|
57
66
|
class AstStage {
|
|
@@ -70,7 +79,8 @@ class AstStage {
|
|
|
70
79
|
const scaOutput = context.stageOutputs.get('sca');
|
|
71
80
|
// Build analysis context
|
|
72
81
|
const analysisContext = (0, astAnalysisOrchestrator_1.buildAnalysisContext)(context.config.astConfig);
|
|
73
|
-
// Discover all files
|
|
82
|
+
// Discover all files — XML, GraphQL, and PHP files are included in serviceFiles
|
|
83
|
+
// via SERVICE_CODE_EXTS in fileClassifier.ts (RULE-SA05, SA06)
|
|
74
84
|
const artifacts = (0, projectDiscovery_1.discoverProject)({ rootDir: context.projectRoot });
|
|
75
85
|
const allSourceFiles = [
|
|
76
86
|
...artifacts.testFiles,
|
|
@@ -130,6 +140,33 @@ class AstStage {
|
|
|
130
140
|
}
|
|
131
141
|
// Phase 2: Build cross-file symbol table
|
|
132
142
|
const crossFileTable = (0, crossFileResolver_1.buildCrossFileSymbolTable)(models, context.projectRoot);
|
|
143
|
+
// Phase 2b: Register and run cross-file resolvers (Feature 27)
|
|
144
|
+
// Clear any stale registrations from prior runs, then register all 6 resolvers
|
|
145
|
+
(0, crossFileResolutionPass_1.clearCrossFileResolvers)();
|
|
146
|
+
(0, crossFileResolutionPass_1.registerCrossFileResolver)(new expressRouterResolver_1.ExpressRouterResolver());
|
|
147
|
+
(0, crossFileResolutionPass_1.registerCrossFileResolver)(new flaskBlueprintResolver_1.FlaskBlueprintResolver());
|
|
148
|
+
(0, crossFileResolutionPass_1.registerCrossFileResolver)(new angularInjectionResolver_1.AngularInjectionResolver());
|
|
149
|
+
(0, crossFileResolutionPass_1.registerCrossFileResolver)(new vuexActionResolver_1.VuexActionResolver());
|
|
150
|
+
(0, crossFileResolutionPass_1.registerCrossFileResolver)(new mybatisResolver_1.MyBatisResolver());
|
|
151
|
+
(0, crossFileResolutionPass_1.registerCrossFileResolver)(new dddLayerResolver_1.DddLayerResolver());
|
|
152
|
+
const crossFileResolutionResult = (0, crossFileResolutionPass_1.runCrossFileResolution)(crossFileTable, context.projectRoot, artifacts.apiFrameworks, allSourceFiles);
|
|
153
|
+
// Phase 2c: Detect auth coverage gaps (Feature 27)
|
|
154
|
+
// Collect all endpoints with their security classifications for gap analysis
|
|
155
|
+
const endpointsForAuthGaps = [];
|
|
156
|
+
for (const [filePath, model] of crossFileTable.models) {
|
|
157
|
+
if (!model.routeRegistrations)
|
|
158
|
+
continue;
|
|
159
|
+
for (const reg of model.routeRegistrations) {
|
|
160
|
+
endpointsForAuthGaps.push({
|
|
161
|
+
path: reg.path,
|
|
162
|
+
security: reg.security,
|
|
163
|
+
sourceFile: filePath,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const authCoverageGaps = (0, optionalAuthUnifier_1.detectAuthCoverageGaps)(endpointsForAuthGaps, new Set());
|
|
168
|
+
// Phase 2d: Enforce structure-agnostic rules (Feature 27)
|
|
169
|
+
const rulesResult = (0, rulesEnforcer_1.enforceStructureAgnosticRules)(crossFileTable, context.projectRoot);
|
|
133
170
|
// Phase 3: Run abstract layer traversal for test files
|
|
134
171
|
const traversalResults = new Map();
|
|
135
172
|
const depthCap = context.config.traversalDepthCap;
|
|
@@ -199,6 +236,12 @@ class AstStage {
|
|
|
199
236
|
traversalResultCount: traversalResults.size,
|
|
200
237
|
graphNodesAdded: nodes.length,
|
|
201
238
|
graphEdgesAdded: edges.length,
|
|
239
|
+
crossFileResolversRun: crossFileResolutionResult.resolverResults.length,
|
|
240
|
+
crossFileEntriesAdded: crossFileResolutionResult.totalEntriesAdded,
|
|
241
|
+
rulesChecked: rulesResult.rulesChecked,
|
|
242
|
+
rulesPassed: rulesResult.rulesPassed,
|
|
243
|
+
ruleViolations: rulesResult.violations.length,
|
|
244
|
+
authCoverageGaps: authCoverageGaps.length,
|
|
202
245
|
},
|
|
203
246
|
};
|
|
204
247
|
context.diagnostics.set('ast', diagnostics);
|
|
@@ -209,6 +252,7 @@ class AstStage {
|
|
|
209
252
|
traversalResults,
|
|
210
253
|
analyzedFiles: filesScanned,
|
|
211
254
|
skippedFiles: filesSkipped,
|
|
255
|
+
crossFileResolutionDiagnostics: crossFileResolutionResult.resolverResults,
|
|
212
256
|
};
|
|
213
257
|
context.stageOutputs.set('ast', output);
|
|
214
258
|
return output;
|
|
@@ -232,7 +276,13 @@ function detectLanguage(filePath, scaOutput) {
|
|
|
232
276
|
'.kts': 'kotlin',
|
|
233
277
|
'.py': 'python',
|
|
234
278
|
'.rb': 'ruby',
|
|
279
|
+
'.php': 'php',
|
|
235
280
|
'.feature': 'cucumber',
|
|
236
281
|
};
|
|
282
|
+
// Handle GraphQL schema files and XML as special-case languages
|
|
283
|
+
if (ext === '.graphqls' || ext === '.graphql')
|
|
284
|
+
return 'graphql';
|
|
285
|
+
if (ext === '.xml')
|
|
286
|
+
return 'xml';
|
|
237
287
|
return extensionMap[ext];
|
|
238
288
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base URL composer (Feature 27, Sub-PR 9)
|
|
3
|
+
*
|
|
4
|
+
* Unified URL composition across all frameworks:
|
|
5
|
+
* - Flask: Blueprint url_prefix + route path
|
|
6
|
+
* - Express: app.use mount prefix + router path + route path
|
|
7
|
+
* - Spring: @RequestMapping class-level + method-level
|
|
8
|
+
* - Angular: environment.apiUrl from environments/environment.ts
|
|
9
|
+
* - Vue: axios.defaults.baseURL
|
|
10
|
+
* - Slim PHP: $app->group prefix + route path
|
|
11
|
+
* - HapiJS: explicit path (no composition needed)
|
|
12
|
+
*
|
|
13
|
+
* Normalizes: remove double slashes, ensure single leading slash.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Compose a full URL from multiple path segments.
|
|
17
|
+
* Handles: double slashes, missing leading slashes, trailing slashes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function composeUrl(...segments: (string | undefined)[]): string;
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a path template: ensure leading slash, remove double slashes,
|
|
22
|
+
* standardize parameter syntax.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizePath(path: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Convert framework-specific parameter syntax to OpenAPI-style {param}.
|
|
27
|
+
*
|
|
28
|
+
* Flask: <param>, <int:param> → {param}
|
|
29
|
+
* Express: :param → {param}
|
|
30
|
+
* Slim PHP: {param} (already correct)
|
|
31
|
+
* HapiJS: {param} (already correct)
|
|
32
|
+
* Spring: {param} (already correct)
|
|
33
|
+
*/
|
|
34
|
+
export declare function normalizePathParams(path: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Compose full URL for a specific framework's route.
|
|
37
|
+
*/
|
|
38
|
+
export declare function composeFrameworkUrl(framework: string, parts: {
|
|
39
|
+
baseUrl?: string;
|
|
40
|
+
classPrefix?: string;
|
|
41
|
+
mountPrefix?: string;
|
|
42
|
+
routePath: string;
|
|
43
|
+
}): string;
|
|
44
|
+
//# sourceMappingURL=baseUrlComposer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseUrlComposer.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/baseUrlComposer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,QAAQ,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,CAyBtE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE;IACL,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,GACA,MAAM,CAUR"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Base URL composer (Feature 27, Sub-PR 9)
|
|
4
|
+
*
|
|
5
|
+
* Unified URL composition across all frameworks:
|
|
6
|
+
* - Flask: Blueprint url_prefix + route path
|
|
7
|
+
* - Express: app.use mount prefix + router path + route path
|
|
8
|
+
* - Spring: @RequestMapping class-level + method-level
|
|
9
|
+
* - Angular: environment.apiUrl from environments/environment.ts
|
|
10
|
+
* - Vue: axios.defaults.baseURL
|
|
11
|
+
* - Slim PHP: $app->group prefix + route path
|
|
12
|
+
* - HapiJS: explicit path (no composition needed)
|
|
13
|
+
*
|
|
14
|
+
* Normalizes: remove double slashes, ensure single leading slash.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.composeUrl = composeUrl;
|
|
18
|
+
exports.normalizePath = normalizePath;
|
|
19
|
+
exports.normalizePathParams = normalizePathParams;
|
|
20
|
+
exports.composeFrameworkUrl = composeFrameworkUrl;
|
|
21
|
+
/**
|
|
22
|
+
* Compose a full URL from multiple path segments.
|
|
23
|
+
* Handles: double slashes, missing leading slashes, trailing slashes.
|
|
24
|
+
*/
|
|
25
|
+
function composeUrl(...segments) {
|
|
26
|
+
const parts = segments
|
|
27
|
+
.filter((s) => typeof s === 'string' && s.length > 0);
|
|
28
|
+
if (parts.length === 0)
|
|
29
|
+
return '/';
|
|
30
|
+
// Detect protocol in the first segment (e.g. http://, https://)
|
|
31
|
+
const protocolMatch = parts[0].match(/^(\w+:\/\/)/);
|
|
32
|
+
let protocol = '';
|
|
33
|
+
if (protocolMatch) {
|
|
34
|
+
protocol = protocolMatch[1];
|
|
35
|
+
parts[0] = parts[0].slice(protocol.length);
|
|
36
|
+
}
|
|
37
|
+
const stripped = parts.map((s) => s.replace(/^\/+|\/+$/g, ''));
|
|
38
|
+
const joined = stripped.join('/');
|
|
39
|
+
const deduped = joined.replace(/\/+/g, '/');
|
|
40
|
+
if (protocol) {
|
|
41
|
+
const normalized = protocol + deduped;
|
|
42
|
+
return normalized.replace(/\/+$/, '') || protocol;
|
|
43
|
+
}
|
|
44
|
+
const normalized = '/' + deduped;
|
|
45
|
+
return normalized === '/' ? '/' : normalized.replace(/\/+$/, '');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a path template: ensure leading slash, remove double slashes,
|
|
49
|
+
* standardize parameter syntax.
|
|
50
|
+
*/
|
|
51
|
+
function normalizePath(path) {
|
|
52
|
+
if (!path)
|
|
53
|
+
return '/';
|
|
54
|
+
// Ensure leading slash
|
|
55
|
+
let normalized = path.startsWith('/') ? path : '/' + path;
|
|
56
|
+
// Remove double slashes (but not from protocol like http://)
|
|
57
|
+
// First handle leading double slashes
|
|
58
|
+
normalized = normalized.replace(/^\/\/+/, '/');
|
|
59
|
+
// Then handle double slashes in the middle
|
|
60
|
+
normalized = normalized.replace(/([^:])\/\/+/g, '$1/');
|
|
61
|
+
// Remove trailing slash (unless it's just "/")
|
|
62
|
+
if (normalized.length > 1) {
|
|
63
|
+
normalized = normalized.replace(/\/+$/, '');
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Convert framework-specific parameter syntax to OpenAPI-style {param}.
|
|
69
|
+
*
|
|
70
|
+
* Flask: <param>, <int:param> → {param}
|
|
71
|
+
* Express: :param → {param}
|
|
72
|
+
* Slim PHP: {param} (already correct)
|
|
73
|
+
* HapiJS: {param} (already correct)
|
|
74
|
+
* Spring: {param} (already correct)
|
|
75
|
+
*/
|
|
76
|
+
function normalizePathParams(path) {
|
|
77
|
+
// Flask/Python: <param> or <type:param>
|
|
78
|
+
let normalized = path.replace(/<(?:\w+:)?(\w+)>/g, '{$1}');
|
|
79
|
+
// Express: :param (but not :// from URLs)
|
|
80
|
+
normalized = normalized.replace(/(?<=\/):([\w]+)/g, '{$1}');
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Compose full URL for a specific framework's route.
|
|
85
|
+
*/
|
|
86
|
+
function composeFrameworkUrl(framework, parts) {
|
|
87
|
+
const segments = [];
|
|
88
|
+
if (parts.baseUrl)
|
|
89
|
+
segments.push(parts.baseUrl);
|
|
90
|
+
if (parts.classPrefix)
|
|
91
|
+
segments.push(parts.classPrefix);
|
|
92
|
+
if (parts.mountPrefix)
|
|
93
|
+
segments.push(parts.mountPrefix);
|
|
94
|
+
segments.push(parts.routePath);
|
|
95
|
+
const raw = composeUrl(...segments);
|
|
96
|
+
return normalizePathParams(raw);
|
|
97
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-file resolution pass (Feature 27)
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates all registered cross-file resolvers after the initial AST
|
|
5
|
+
* parsing pass. Each resolver enriches the CrossFileSymbolTable with
|
|
6
|
+
* framework-specific resolution data (Blueprint prefixes, Angular injection
|
|
7
|
+
* chains, DDD interface mappings, etc.).
|
|
8
|
+
*
|
|
9
|
+
* Resolvers are registered via `registerCrossFileResolver()` and discovered
|
|
10
|
+
* via `getCrossFileResolvers()`. Each resolver implements the `CrossFileResolver`
|
|
11
|
+
* interface and self-declares which API frameworks it applies to.
|
|
12
|
+
*/
|
|
13
|
+
import type { CrossFileResolver, CrossFileSymbolTable } from './types';
|
|
14
|
+
import type { DetectedApiFramework } from '../../../discovery/frameworkDetector';
|
|
15
|
+
/**
|
|
16
|
+
* Register a cross-file resolver. Resolvers are run in registration order.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerCrossFileResolver(resolver: CrossFileResolver): void;
|
|
19
|
+
/**
|
|
20
|
+
* Return all registered cross-file resolvers.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCrossFileResolvers(): readonly CrossFileResolver[];
|
|
23
|
+
/**
|
|
24
|
+
* Clear all registered resolvers (for testing).
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearCrossFileResolvers(): void;
|
|
27
|
+
export interface CrossFileResolutionPassResult {
|
|
28
|
+
/** Per-resolver diagnostics */
|
|
29
|
+
resolverResults: Array<{
|
|
30
|
+
resolverName: string;
|
|
31
|
+
entriesAdded: number;
|
|
32
|
+
diagnostics: string[];
|
|
33
|
+
unresolvedRefs: Array<{
|
|
34
|
+
ref: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
}>;
|
|
37
|
+
}>;
|
|
38
|
+
/** Total entries added across all resolvers */
|
|
39
|
+
totalEntriesAdded: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run all applicable cross-file resolvers against the symbol table.
|
|
43
|
+
*
|
|
44
|
+
* Each resolver mutates the symbol table in place, adding router mounts,
|
|
45
|
+
* injection chains, interface implementations, and middleware inheritance data.
|
|
46
|
+
*
|
|
47
|
+
* @param symbolTable - The cross-file symbol table to enrich
|
|
48
|
+
* @param projectRoot - Project root directory
|
|
49
|
+
* @param apiFrameworks - Detected API frameworks
|
|
50
|
+
* @param allSourceFiles - All source file paths
|
|
51
|
+
* @returns Aggregated diagnostics from all resolvers
|
|
52
|
+
*/
|
|
53
|
+
export declare function runCrossFileResolution(symbolTable: CrossFileSymbolTable, projectRoot: string, apiFrameworks: DetectedApiFramework[], allSourceFiles: string[]): CrossFileResolutionPassResult;
|
|
54
|
+
//# sourceMappingURL=crossFileResolutionPass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossFileResolutionPass.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/crossFileResolutionPass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAGjB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAMjF;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAK3E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,SAAS,iBAAiB,EAAE,CAEpE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAID,MAAM,WAAW,6BAA6B;IAC5C,+BAA+B;IAC/B,eAAe,EAAE,KAAK,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,cAAc,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACxD,CAAC,CAAC;IACH,+CAA+C;IAC/C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,oBAAoB,EACjC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,oBAAoB,EAAE,EACrC,cAAc,EAAE,MAAM,EAAE,GACvB,6BAA6B,CAoC/B"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-file resolution pass (Feature 27)
|
|
4
|
+
*
|
|
5
|
+
* Orchestrates all registered cross-file resolvers after the initial AST
|
|
6
|
+
* parsing pass. Each resolver enriches the CrossFileSymbolTable with
|
|
7
|
+
* framework-specific resolution data (Blueprint prefixes, Angular injection
|
|
8
|
+
* chains, DDD interface mappings, etc.).
|
|
9
|
+
*
|
|
10
|
+
* Resolvers are registered via `registerCrossFileResolver()` and discovered
|
|
11
|
+
* via `getCrossFileResolvers()`. Each resolver implements the `CrossFileResolver`
|
|
12
|
+
* interface and self-declares which API frameworks it applies to.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.registerCrossFileResolver = registerCrossFileResolver;
|
|
16
|
+
exports.getCrossFileResolvers = getCrossFileResolvers;
|
|
17
|
+
exports.clearCrossFileResolvers = clearCrossFileResolvers;
|
|
18
|
+
exports.runCrossFileResolution = runCrossFileResolution;
|
|
19
|
+
// ─── Resolver registry ────────────────────────────────────────────────────────
|
|
20
|
+
const resolverRegistry = [];
|
|
21
|
+
/**
|
|
22
|
+
* Register a cross-file resolver. Resolvers are run in registration order.
|
|
23
|
+
*/
|
|
24
|
+
function registerCrossFileResolver(resolver) {
|
|
25
|
+
// Avoid duplicate registrations
|
|
26
|
+
if (!resolverRegistry.some((r) => r.name === resolver.name)) {
|
|
27
|
+
resolverRegistry.push(resolver);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Return all registered cross-file resolvers.
|
|
32
|
+
*/
|
|
33
|
+
function getCrossFileResolvers() {
|
|
34
|
+
return resolverRegistry;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clear all registered resolvers (for testing).
|
|
38
|
+
*/
|
|
39
|
+
function clearCrossFileResolvers() {
|
|
40
|
+
resolverRegistry.length = 0;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Run all applicable cross-file resolvers against the symbol table.
|
|
44
|
+
*
|
|
45
|
+
* Each resolver mutates the symbol table in place, adding router mounts,
|
|
46
|
+
* injection chains, interface implementations, and middleware inheritance data.
|
|
47
|
+
*
|
|
48
|
+
* @param symbolTable - The cross-file symbol table to enrich
|
|
49
|
+
* @param projectRoot - Project root directory
|
|
50
|
+
* @param apiFrameworks - Detected API frameworks
|
|
51
|
+
* @param allSourceFiles - All source file paths
|
|
52
|
+
* @returns Aggregated diagnostics from all resolvers
|
|
53
|
+
*/
|
|
54
|
+
function runCrossFileResolution(symbolTable, projectRoot, apiFrameworks, allSourceFiles) {
|
|
55
|
+
const ctx = {
|
|
56
|
+
symbolTable,
|
|
57
|
+
projectRoot,
|
|
58
|
+
apiFrameworks,
|
|
59
|
+
allSourceFiles,
|
|
60
|
+
};
|
|
61
|
+
const resolverResults = [];
|
|
62
|
+
let totalEntriesAdded = 0;
|
|
63
|
+
for (const resolver of resolverRegistry) {
|
|
64
|
+
// Only run resolvers that apply to the detected frameworks
|
|
65
|
+
if (!resolver.appliesTo(apiFrameworks))
|
|
66
|
+
continue;
|
|
67
|
+
try {
|
|
68
|
+
const result = resolver.resolve(ctx);
|
|
69
|
+
resolverResults.push({
|
|
70
|
+
resolverName: resolver.name,
|
|
71
|
+
entriesAdded: result.entriesAdded,
|
|
72
|
+
diagnostics: result.diagnostics,
|
|
73
|
+
unresolvedRefs: result.unresolvedRefs,
|
|
74
|
+
});
|
|
75
|
+
totalEntriesAdded += result.entriesAdded;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// Never let a resolver crash the pipeline
|
|
79
|
+
resolverResults.push({
|
|
80
|
+
resolverName: resolver.name,
|
|
81
|
+
entriesAdded: 0,
|
|
82
|
+
diagnostics: [`resolver-error: ${err instanceof Error ? err.message : String(err)}`],
|
|
83
|
+
unresolvedRefs: [],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { resolverResults, totalEntriesAdded };
|
|
88
|
+
}
|
|
@@ -1 +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,
|
|
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,CAgEtB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,oBAAoB,GAC1B,MAAM,GAAG,SAAS,CA4BpB"}
|
|
@@ -63,7 +63,16 @@ function buildCrossFileSymbolTable(models, projectRoot) {
|
|
|
63
63
|
}
|
|
64
64
|
importGraph.set(filePath, resolvedPaths);
|
|
65
65
|
}
|
|
66
|
-
return {
|
|
66
|
+
return {
|
|
67
|
+
models,
|
|
68
|
+
exportedSymbols,
|
|
69
|
+
classes,
|
|
70
|
+
importGraph,
|
|
71
|
+
routerMounts: new Map(),
|
|
72
|
+
injectionChains: new Map(),
|
|
73
|
+
interfaceImplementations: new Map(),
|
|
74
|
+
middlewareInheritance: new Map(),
|
|
75
|
+
};
|
|
67
76
|
}
|
|
68
77
|
/**
|
|
69
78
|
* Resolve a symbol name using the cross-file symbol table.
|
|
@@ -98,12 +107,31 @@ function resolveSymbolCrossFile(symbolName, fromFile, table) {
|
|
|
98
107
|
}
|
|
99
108
|
return undefined;
|
|
100
109
|
}
|
|
110
|
+
/** Built-in objects that should never be treated as import module names */
|
|
111
|
+
const BUILTIN_OBJECTS = new Set([
|
|
112
|
+
'console', 'math', 'json', 'object', 'array', 'promise', 'date',
|
|
113
|
+
'string', 'number', 'boolean', 'symbol', 'regexp', 'error', 'map',
|
|
114
|
+
'set', 'weakmap', 'weakset', 'proxy', 'reflect', 'intl',
|
|
115
|
+
'arraybuffer', 'sharedarraybuffer', 'atomics', 'dataview',
|
|
116
|
+
'this', 'self', 'super', 'window', 'document', 'process',
|
|
117
|
+
'global', 'globalthis', 'module', 'exports', 'require',
|
|
118
|
+
'__dirname', '__filename',
|
|
119
|
+
]);
|
|
101
120
|
/**
|
|
102
121
|
* Extract import declarations from a semantic model.
|
|
103
122
|
* This is a heuristic extraction — real import extraction would come from the AST.
|
|
104
123
|
*/
|
|
105
124
|
function extractImportsFromModel(filePath, model, projectRoot) {
|
|
106
125
|
const imports = [];
|
|
126
|
+
// Build a set of known identifiers from the model (constants and local variables)
|
|
127
|
+
// These are the names that could plausibly be import aliases
|
|
128
|
+
const knownIdentifiers = new Set();
|
|
129
|
+
for (const [name] of model.constants) {
|
|
130
|
+
knownIdentifiers.add(name);
|
|
131
|
+
}
|
|
132
|
+
for (const [name] of model.localVariables) {
|
|
133
|
+
knownIdentifiers.add(name);
|
|
134
|
+
}
|
|
107
135
|
// Use functions' calledFunctions to infer cross-file references
|
|
108
136
|
for (const [, func] of model.functions) {
|
|
109
137
|
for (const calledName of func.calledFunctions) {
|
|
@@ -111,6 +139,16 @@ function extractImportsFromModel(filePath, model, projectRoot) {
|
|
|
111
139
|
const dotIndex = calledName.indexOf('.');
|
|
112
140
|
if (dotIndex > 0) {
|
|
113
141
|
const modulePart = calledName.substring(0, dotIndex);
|
|
142
|
+
// Skip common built-in objects that are never import sources
|
|
143
|
+
if (BUILTIN_OBJECTS.has(modulePart.toLowerCase())) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Only treat as an import if the prefix matches a known identifier
|
|
147
|
+
// (constant or local variable) in this file, which is how import
|
|
148
|
+
// aliases typically appear in the semantic model
|
|
149
|
+
if (!knownIdentifiers.has(modulePart)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
114
152
|
const resolvedPath = (0, importResolver_1.resolveImportPath)(`./${modulePart}`, filePath, projectRoot, model.language);
|
|
115
153
|
if (resolvedPath) {
|
|
116
154
|
imports.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphBuilder.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/graphBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAoB,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"graphBuilder.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/graphBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAoB,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAiB,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAIrE;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAClC,cAAc,EAAE,oBAAoB,EACpC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,GACnD;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,KAAK,EAAE,SAAS,EAAE,CAAA;CAAE,CA4S5C"}
|
|
@@ -60,6 +60,28 @@ function buildAstGraph(models, crossFileTable, traversalResults, interactions) {
|
|
|
60
60
|
addedEdgeIds.add(edge.id);
|
|
61
61
|
edges.push(edge);
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Ensure a node exists in the graph. If the node ID is not yet present,
|
|
65
|
+
* create a stub node flagged as cross-file-unresolved (spec §2.3).
|
|
66
|
+
* Confidence on stub nodes is capped at 'low'.
|
|
67
|
+
*/
|
|
68
|
+
function ensureNodeExists(nodeId, missingFilePath, nodeType) {
|
|
69
|
+
if (addedNodeIds.has(nodeId))
|
|
70
|
+
return nodeId;
|
|
71
|
+
addNode({
|
|
72
|
+
id: nodeId,
|
|
73
|
+
type: nodeType,
|
|
74
|
+
label: `[unresolved] ${path.basename(missingFilePath)}`,
|
|
75
|
+
sourceStage: 'ast',
|
|
76
|
+
filePath: missingFilePath,
|
|
77
|
+
metadata: {
|
|
78
|
+
resolution: 'cross-file-unresolved',
|
|
79
|
+
confidence: 'low',
|
|
80
|
+
diagnostic: `Cross-file resolution failed: file "${missingFilePath}" was not found in parsed models`,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
return nodeId;
|
|
84
|
+
}
|
|
63
85
|
// 1. Create file nodes for every parsed file
|
|
64
86
|
for (const [filePath, model] of models) {
|
|
65
87
|
const basename = path.basename(filePath);
|
|
@@ -241,6 +263,65 @@ function buildAstGraph(models, crossFileTable, traversalResults, interactions) {
|
|
|
241
263
|
});
|
|
242
264
|
}
|
|
243
265
|
}
|
|
266
|
+
// 7. Feature 27: Create router mount edges
|
|
267
|
+
for (const [filePath, mounts] of crossFileTable.routerMounts) {
|
|
268
|
+
const sourceId = `file:${filePath}`;
|
|
269
|
+
if (!addedNodeIds.has(sourceId))
|
|
270
|
+
continue;
|
|
271
|
+
for (const mount of mounts) {
|
|
272
|
+
const targetId = `file:${mount.targetModulePath}`;
|
|
273
|
+
// Create edge even if target not parsed (creates stub node for missing files)
|
|
274
|
+
addEdge({
|
|
275
|
+
id: `${sourceId}->mounts->${mount.prefix}:${mount.targetModulePath}`,
|
|
276
|
+
type: 'router-mount',
|
|
277
|
+
sourceNodeId: sourceId,
|
|
278
|
+
targetNodeId: ensureNodeExists(targetId, mount.targetModulePath, 'file'),
|
|
279
|
+
sourceStage: 'ast',
|
|
280
|
+
metadata: {
|
|
281
|
+
prefix: mount.prefix,
|
|
282
|
+
targetModule: mount.targetModulePath,
|
|
283
|
+
middlewareCount: mount.middleware.length,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// 8. Feature 27: Create injection chain edges
|
|
289
|
+
for (const [consumerFile, chains] of crossFileTable.injectionChains) {
|
|
290
|
+
for (const chain of chains) {
|
|
291
|
+
const consumerNodeId = `file:${chain.consumerFile}`;
|
|
292
|
+
const serviceNodeId = `file:${chain.serviceFile}`;
|
|
293
|
+
addEdge({
|
|
294
|
+
id: `${consumerNodeId}->injects->${chain.serviceClass}:${chain.serviceFile}`,
|
|
295
|
+
type: 'injects',
|
|
296
|
+
sourceNodeId: ensureNodeExists(consumerNodeId, chain.consumerFile, 'file'),
|
|
297
|
+
targetNodeId: ensureNodeExists(serviceNodeId, chain.serviceFile, 'file'),
|
|
298
|
+
sourceStage: 'ast',
|
|
299
|
+
metadata: {
|
|
300
|
+
consumerClass: chain.consumerClass,
|
|
301
|
+
serviceClass: chain.serviceClass,
|
|
302
|
+
injectionStyle: chain.injectionStyle,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// 9. Feature 27: Create interface implementation edges
|
|
308
|
+
for (const [ifaceFile, impls] of crossFileTable.interfaceImplementations) {
|
|
309
|
+
for (const impl of impls) {
|
|
310
|
+
const ifaceNodeId = `file:${impl.interfaceFile}`;
|
|
311
|
+
const implNodeId = `file:${impl.implFile}`;
|
|
312
|
+
addEdge({
|
|
313
|
+
id: `${implNodeId}->implements->${impl.interfaceName}:${impl.interfaceFile}`,
|
|
314
|
+
type: 'implements',
|
|
315
|
+
sourceNodeId: ensureNodeExists(implNodeId, impl.implFile, 'file'),
|
|
316
|
+
targetNodeId: ensureNodeExists(ifaceNodeId, impl.interfaceFile, 'file'),
|
|
317
|
+
sourceStage: 'ast',
|
|
318
|
+
metadata: {
|
|
319
|
+
interfaceName: impl.interfaceName,
|
|
320
|
+
implName: impl.implName,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
244
325
|
return { nodes, edges };
|
|
245
326
|
}
|
|
246
327
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional auth unifier (Feature 27, Sub-PR 9)
|
|
3
|
+
*
|
|
4
|
+
* Normalizes all framework-specific auth classifications to a unified SecurityClassification.
|
|
5
|
+
* Maps framework-specific patterns:
|
|
6
|
+
* Flask @jwt_optional → { optional: true }
|
|
7
|
+
* Express auth.optional / credentialsRequired: false → { optional: true }
|
|
8
|
+
* HapiJS auth: { mode: 'try' } → { optional: true }
|
|
9
|
+
* Spring @PreAuthorize → { required: true }
|
|
10
|
+
* Slim PHP optionalAuth → { optional: true }, jwt/auth → { required: true }
|
|
11
|
+
* Angular canActivate:none → { optional: true } (interceptor-based)
|
|
12
|
+
* NestJS @UseGuards → { required: true }
|
|
13
|
+
*/
|
|
14
|
+
import type { SecurityClassification } from '../../../ast/astTypes';
|
|
15
|
+
export interface AuthClassificationEntry {
|
|
16
|
+
framework: string;
|
|
17
|
+
sourcePattern: string;
|
|
18
|
+
classification: SecurityClassification;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Framework-specific auth pattern → unified SecurityClassification.
|
|
22
|
+
*/
|
|
23
|
+
export declare function unifyAuthClassification(framework: string, pattern: string): SecurityClassification | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Detect auth coverage gaps.
|
|
26
|
+
*/
|
|
27
|
+
export interface AuthCoverageGap {
|
|
28
|
+
type: 'missing-401-test' | 'missing-optional-dual-path';
|
|
29
|
+
endpointPath: string;
|
|
30
|
+
security: SecurityClassification;
|
|
31
|
+
sourceFile: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Analyze auth coverage gaps in endpoints vs tests.
|
|
35
|
+
*/
|
|
36
|
+
export declare function detectAuthCoverageGaps(endpoints: Array<{
|
|
37
|
+
path: string;
|
|
38
|
+
security?: SecurityClassification;
|
|
39
|
+
sourceFile: string;
|
|
40
|
+
}>, testAssertionPaths: Set<string>): AuthCoverageGap[];
|
|
41
|
+
//# sourceMappingURL=optionalAuthUnifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optionalAuthUnifier.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/optionalAuthUnifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,sBAAsB,CAAC;CACxC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,sBAAsB,GAAG,SAAS,CA6DpC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,kBAAkB,GAAG,4BAA4B,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,EACzF,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC9B,eAAe,EAAE,CA0BnB"}
|