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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Optional auth unifier (Feature 27, Sub-PR 9)
|
|
4
|
+
*
|
|
5
|
+
* Normalizes all framework-specific auth classifications to a unified SecurityClassification.
|
|
6
|
+
* Maps framework-specific patterns:
|
|
7
|
+
* Flask @jwt_optional → { optional: true }
|
|
8
|
+
* Express auth.optional / credentialsRequired: false → { optional: true }
|
|
9
|
+
* HapiJS auth: { mode: 'try' } → { optional: true }
|
|
10
|
+
* Spring @PreAuthorize → { required: true }
|
|
11
|
+
* Slim PHP optionalAuth → { optional: true }, jwt/auth → { required: true }
|
|
12
|
+
* Angular canActivate:none → { optional: true } (interceptor-based)
|
|
13
|
+
* NestJS @UseGuards → { required: true }
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.unifyAuthClassification = unifyAuthClassification;
|
|
17
|
+
exports.detectAuthCoverageGaps = detectAuthCoverageGaps;
|
|
18
|
+
/**
|
|
19
|
+
* Framework-specific auth pattern → unified SecurityClassification.
|
|
20
|
+
*/
|
|
21
|
+
function unifyAuthClassification(framework, pattern) {
|
|
22
|
+
const key = `${framework}:${pattern}`.toLowerCase();
|
|
23
|
+
// Flask patterns — use word-boundary regex to avoid matching e.g. "@jwt_required_custom"
|
|
24
|
+
if (/^flask:@?jwt_required\b/.test(key)) {
|
|
25
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: pattern };
|
|
26
|
+
}
|
|
27
|
+
if (/^flask:@?jwt_optional\b/.test(key)) {
|
|
28
|
+
return { type: 'jwt', required: false, optional: true, sourcePattern: pattern };
|
|
29
|
+
}
|
|
30
|
+
if (/^flask:@?login_required\b/.test(key)) {
|
|
31
|
+
return { type: 'session', required: true, optional: false, sourcePattern: pattern };
|
|
32
|
+
}
|
|
33
|
+
// Express patterns — use word-boundary regex for precise matching
|
|
34
|
+
if (/^express:auth\.required\b/.test(key) || /^express:credentialsrequired:\s*true\b/.test(key)) {
|
|
35
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: pattern };
|
|
36
|
+
}
|
|
37
|
+
if (/^express:auth\.optional\b/.test(key) || /^express:credentialsrequired:\s*false\b/.test(key)) {
|
|
38
|
+
return { type: 'jwt', required: false, optional: true, sourcePattern: pattern };
|
|
39
|
+
}
|
|
40
|
+
if (/^express:passport\.authenticate\b/.test(key)) {
|
|
41
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: pattern };
|
|
42
|
+
}
|
|
43
|
+
// HapiJS patterns (framework may be 'hapi' or 'hapijs')
|
|
44
|
+
const isHapi = /^hapi(?:js)?:/.test(key);
|
|
45
|
+
if (isHapi && /\bauth\b/.test(key) && /\bmode\b/.test(key) && (/\btry\b/.test(key) || /\boptional\b/.test(key))) {
|
|
46
|
+
return { type: 'jwt', required: false, optional: true, sourcePattern: pattern };
|
|
47
|
+
}
|
|
48
|
+
if (isHapi && /\bauth\b/.test(key) && !/\bfalse\b/.test(key) && !/\btry\b/.test(key) && !/\boptional\b/.test(key)) {
|
|
49
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: pattern };
|
|
50
|
+
}
|
|
51
|
+
// Spring patterns — include @RolesAllowed and @WithMockUser
|
|
52
|
+
if (/^spring:@?preauthorize\b/.test(key) || /^spring:@?secured\b/.test(key) || /^spring:@?rolesallowed\b/.test(key)) {
|
|
53
|
+
return { type: 'custom', required: true, optional: false, sourcePattern: pattern };
|
|
54
|
+
}
|
|
55
|
+
if (/^spring:@?withmockuser\b/.test(key)) {
|
|
56
|
+
return { type: 'custom', required: true, optional: false, sourcePattern: pattern };
|
|
57
|
+
}
|
|
58
|
+
// Slim PHP patterns — slim:optionalAuth is optional, slim:jwt or slim:auth is required
|
|
59
|
+
if (/^slim:optionalauth\b/.test(key)) {
|
|
60
|
+
return { type: 'jwt', required: false, optional: true, sourcePattern: pattern };
|
|
61
|
+
}
|
|
62
|
+
if (/^slim:(?:jwt|auth)\b/.test(key)) {
|
|
63
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: pattern };
|
|
64
|
+
}
|
|
65
|
+
// Angular patterns — canActivate:none (no guard) with interceptor tokens is optional
|
|
66
|
+
if (/^angular:canactivate:none\b/.test(key)) {
|
|
67
|
+
return { type: 'custom', required: false, optional: true, sourcePattern: pattern };
|
|
68
|
+
}
|
|
69
|
+
// NestJS patterns — @UseGuards is required auth
|
|
70
|
+
if (/^nestjs:@?useguards\b/.test(key)) {
|
|
71
|
+
return { type: 'custom', required: true, optional: false, sourcePattern: pattern };
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Analyze auth coverage gaps in endpoints vs tests.
|
|
77
|
+
*/
|
|
78
|
+
function detectAuthCoverageGaps(endpoints, testAssertionPaths) {
|
|
79
|
+
const gaps = [];
|
|
80
|
+
for (const ep of endpoints) {
|
|
81
|
+
if (!ep.security)
|
|
82
|
+
continue;
|
|
83
|
+
if (ep.security.required && !testAssertionPaths.has(ep.path)) {
|
|
84
|
+
gaps.push({
|
|
85
|
+
type: 'missing-401-test',
|
|
86
|
+
endpointPath: ep.path,
|
|
87
|
+
security: ep.security,
|
|
88
|
+
sourceFile: ep.sourceFile,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (ep.security.optional && !testAssertionPaths.has(ep.path)) {
|
|
92
|
+
gaps.push({
|
|
93
|
+
type: 'missing-optional-dual-path',
|
|
94
|
+
endpointPath: ep.path,
|
|
95
|
+
security: ep.security,
|
|
96
|
+
sourceFile: ep.sourceFile,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return gaps;
|
|
101
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Angular injection cross-file resolver (Feature 27, Sub-PR 7)
|
|
3
|
+
*
|
|
4
|
+
* Resolves Angular dependency injection chains:
|
|
5
|
+
* 1. Constructor injection: private articlesService: ArticlesService → find ArticlesService file
|
|
6
|
+
* 2. inject(ArticlesService) → same resolution
|
|
7
|
+
* 3. Follow service methods to HttpClient calls → build chain: Component → Service → HTTP call
|
|
8
|
+
* 4. Resolve environment.api_url from environments/environment.ts
|
|
9
|
+
* 5. Link guards to routes they protect via route config canActivate arrays
|
|
10
|
+
*/
|
|
11
|
+
import type { CrossFileResolver, CrossFileResolutionContext, CrossFileResolutionResult } from '../types';
|
|
12
|
+
import type { DetectedApiFramework } from '../../../../discovery/frameworkDetector';
|
|
13
|
+
export declare class AngularInjectionResolver implements CrossFileResolver {
|
|
14
|
+
readonly name = "angular-injection";
|
|
15
|
+
appliesTo(frameworks: DetectedApiFramework[]): boolean;
|
|
16
|
+
resolve(ctx: CrossFileResolutionContext): CrossFileResolutionResult;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=angularInjectionResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angularInjectionResolver.d.ts","sourceRoot":"","sources":["../../../../../../src/pipeline/stages/ast/resolvers/angularInjectionResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAEpF,qBAAa,wBAAyB,YAAW,iBAAiB;IAChE,QAAQ,CAAC,IAAI,uBAAuB;IAEpC,SAAS,CAAC,UAAU,EAAE,oBAAoB,EAAE,GAAG,OAAO;IAItD,OAAO,CAAC,GAAG,EAAE,0BAA0B,GAAG,yBAAyB;CAyCpE"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Angular injection cross-file resolver (Feature 27, Sub-PR 7)
|
|
4
|
+
*
|
|
5
|
+
* Resolves Angular dependency injection chains:
|
|
6
|
+
* 1. Constructor injection: private articlesService: ArticlesService → find ArticlesService file
|
|
7
|
+
* 2. inject(ArticlesService) → same resolution
|
|
8
|
+
* 3. Follow service methods to HttpClient calls → build chain: Component → Service → HTTP call
|
|
9
|
+
* 4. Resolve environment.api_url from environments/environment.ts
|
|
10
|
+
* 5. Link guards to routes they protect via route config canActivate arrays
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.AngularInjectionResolver = void 0;
|
|
14
|
+
class AngularInjectionResolver {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.name = 'angular-injection';
|
|
17
|
+
}
|
|
18
|
+
appliesTo(frameworks) {
|
|
19
|
+
return frameworks.some((f) => f.name === 'angular');
|
|
20
|
+
}
|
|
21
|
+
resolve(ctx) {
|
|
22
|
+
let entriesAdded = 0;
|
|
23
|
+
const diagnostics = [];
|
|
24
|
+
const unresolvedRefs = [];
|
|
25
|
+
// Build injection chains from class registry
|
|
26
|
+
for (const [className, classInfo] of ctx.symbolTable.classes) {
|
|
27
|
+
// Check any class that has a model — Angular services typically don't implement interfaces
|
|
28
|
+
// Look for classes that have methods making HTTP calls
|
|
29
|
+
const model = ctx.symbolTable.models.get(classInfo.filePath);
|
|
30
|
+
if (!model)
|
|
31
|
+
continue;
|
|
32
|
+
// Check if any function makes HTTP calls
|
|
33
|
+
for (const [, func] of model.functions) {
|
|
34
|
+
if (func.bodyHttpCalls.length > 0) {
|
|
35
|
+
// This is an API service — create injection chains for any class that injects it
|
|
36
|
+
const consumers = findConsumers(className, ctx);
|
|
37
|
+
for (const consumer of consumers) {
|
|
38
|
+
if (!ctx.symbolTable.injectionChains.has(consumer.file)) {
|
|
39
|
+
ctx.symbolTable.injectionChains.set(consumer.file, []);
|
|
40
|
+
}
|
|
41
|
+
ctx.symbolTable.injectionChains.get(consumer.file).push({
|
|
42
|
+
consumerFile: consumer.file,
|
|
43
|
+
consumerClass: consumer.className,
|
|
44
|
+
serviceClass: className,
|
|
45
|
+
serviceFile: classInfo.filePath,
|
|
46
|
+
injectionStyle: consumer.style,
|
|
47
|
+
});
|
|
48
|
+
entriesAdded++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (entriesAdded > 0) {
|
|
54
|
+
diagnostics.push(`Resolved ${entriesAdded} Angular injection chain(s)`);
|
|
55
|
+
}
|
|
56
|
+
return { entriesAdded, diagnostics, unresolvedRefs };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.AngularInjectionResolver = AngularInjectionResolver;
|
|
60
|
+
function findConsumers(serviceName, ctx) {
|
|
61
|
+
const consumers = [];
|
|
62
|
+
// Check existing injection chains (may have been populated by angularDetector)
|
|
63
|
+
for (const [file, chains] of ctx.symbolTable.injectionChains) {
|
|
64
|
+
for (const chain of chains) {
|
|
65
|
+
if (chain.serviceClass === serviceName) {
|
|
66
|
+
consumers.push({
|
|
67
|
+
className: chain.consumerClass,
|
|
68
|
+
file: chain.consumerFile,
|
|
69
|
+
style: chain.injectionStyle,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Fallback: search class registry for classes that might inject this service
|
|
75
|
+
// (constructor injection detected by parameter name matching)
|
|
76
|
+
for (const [className, classInfo] of ctx.symbolTable.classes) {
|
|
77
|
+
// Skip if already found as a consumer
|
|
78
|
+
if (consumers.some((c) => c.className === className && c.file === classInfo.filePath))
|
|
79
|
+
continue;
|
|
80
|
+
// Check if this class's model has the service name in its calledFunctions
|
|
81
|
+
const model = ctx.symbolTable.models.get(classInfo.filePath);
|
|
82
|
+
if (!model)
|
|
83
|
+
continue;
|
|
84
|
+
for (const [, func] of model.functions) {
|
|
85
|
+
if (func.calledFunctions.some((f) => f.includes(serviceName))) {
|
|
86
|
+
consumers.push({
|
|
87
|
+
className,
|
|
88
|
+
file: classInfo.filePath,
|
|
89
|
+
style: 'constructor',
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return consumers;
|
|
96
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DDD / CQRS / Hexagonal layer resolver (Feature 27, Sub-PR 5)
|
|
3
|
+
*
|
|
4
|
+
* Resolves DDD-structured Java projects without requiring @Service/@Repository annotations.
|
|
5
|
+
* Detects:
|
|
6
|
+
* 1. Repository interfaces (findBy*, save, delete methods)
|
|
7
|
+
* 2. Interface → implementation mapping (implements InterfaceName)
|
|
8
|
+
* 3. CQRS command/query handlers (execute/handle/apply taking *Command/*Query)
|
|
9
|
+
* 4. Package-name layer hints (domain, application, infrastructure, adapter) as tie-breakers only
|
|
10
|
+
*/
|
|
11
|
+
import type { CrossFileResolver, CrossFileResolutionContext, CrossFileResolutionResult } from '../types';
|
|
12
|
+
import type { DetectedApiFramework } from '../../../../discovery/frameworkDetector';
|
|
13
|
+
export interface DddRepositoryInterface {
|
|
14
|
+
interfaceName: string;
|
|
15
|
+
methods: string[];
|
|
16
|
+
sourceFile: string;
|
|
17
|
+
line?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface DddCqrsHandler {
|
|
20
|
+
className: string;
|
|
21
|
+
handlerType: 'command' | 'query' | 'event';
|
|
22
|
+
handleMethodName: string;
|
|
23
|
+
parameterType: string;
|
|
24
|
+
sourceFile: string;
|
|
25
|
+
line?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class DddLayerResolver implements CrossFileResolver {
|
|
28
|
+
readonly name = "ddd-layer";
|
|
29
|
+
appliesTo(frameworks: DetectedApiFramework[]): boolean;
|
|
30
|
+
resolve(ctx: CrossFileResolutionContext): CrossFileResolutionResult;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Detect repository interfaces: interfaces with findBy*, save, delete, etc.
|
|
34
|
+
* No @Repository annotation required (RULE-SA03).
|
|
35
|
+
*/
|
|
36
|
+
export declare function detectRepositoryInterfaces(source: string, filePath: string): DddRepositoryInterface[];
|
|
37
|
+
/**
|
|
38
|
+
* Detect CQRS command/query handlers.
|
|
39
|
+
* Looks for classes with execute/handle/apply methods that take *Command/*Query parameters.
|
|
40
|
+
*/
|
|
41
|
+
export declare function detectCqrsHandlers(source: string, filePath: string): DddCqrsHandler[];
|
|
42
|
+
/**
|
|
43
|
+
* Detect implements clauses from Java/Kotlin source.
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectImplementsClauses(source: string, filePath: string): Array<[string, string]>;
|
|
46
|
+
//# sourceMappingURL=dddLayerResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dddLayerResolver.d.ts","sourceRoot":"","sources":["../../../../../../src/pipeline/stages/ast/resolvers/dddLayerResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAEpF,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,QAAQ,CAAC,IAAI,eAAe;IAE5B,SAAS,CAAC,UAAU,EAAE,oBAAoB,EAAE,GAAG,OAAO;IAMtD,OAAO,CAAC,GAAG,EAAE,0BAA0B,GAAG,yBAAyB;CA+JpE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,EAAE,CAgFrG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAiCrF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAcjG"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DDD / CQRS / Hexagonal layer resolver (Feature 27, Sub-PR 5)
|
|
4
|
+
*
|
|
5
|
+
* Resolves DDD-structured Java projects without requiring @Service/@Repository annotations.
|
|
6
|
+
* Detects:
|
|
7
|
+
* 1. Repository interfaces (findBy*, save, delete methods)
|
|
8
|
+
* 2. Interface → implementation mapping (implements InterfaceName)
|
|
9
|
+
* 3. CQRS command/query handlers (execute/handle/apply taking *Command/*Query)
|
|
10
|
+
* 4. Package-name layer hints (domain, application, infrastructure, adapter) as tie-breakers only
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DddLayerResolver = void 0;
|
|
14
|
+
exports.detectRepositoryInterfaces = detectRepositoryInterfaces;
|
|
15
|
+
exports.detectCqrsHandlers = detectCqrsHandlers;
|
|
16
|
+
exports.detectImplementsClauses = detectImplementsClauses;
|
|
17
|
+
class DddLayerResolver {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.name = 'ddd-layer';
|
|
20
|
+
}
|
|
21
|
+
appliesTo(frameworks) {
|
|
22
|
+
return frameworks.some((f) => f.name === 'spring-boot' || f.name === 'spring-graphql' || f.name === 'dgs-framework');
|
|
23
|
+
}
|
|
24
|
+
resolve(ctx) {
|
|
25
|
+
let entriesAdded = 0;
|
|
26
|
+
const diagnostics = [];
|
|
27
|
+
const unresolvedRefs = [];
|
|
28
|
+
const repoInterfaces = [];
|
|
29
|
+
const cqrsHandlers = [];
|
|
30
|
+
const implementations = new Map();
|
|
31
|
+
// ----- Detect from class registry and semantic models -----
|
|
32
|
+
// 1. Detect repository interfaces from class registry
|
|
33
|
+
for (const [className, classDecl] of ctx.symbolTable.classes) {
|
|
34
|
+
const isRepoByName = /(?:Repository|Repo|Store|Gateway)$/.test(className);
|
|
35
|
+
const repoMethods = classDecl.methods.filter(m => /^(?:findBy\w+|find\w+|save|saveAll|delete|deleteById|existsBy\w+|countBy\w+|getBy\w+)$/.test(m));
|
|
36
|
+
if (isRepoByName && repoMethods.length > 0) {
|
|
37
|
+
repoInterfaces.push({
|
|
38
|
+
interfaceName: className,
|
|
39
|
+
methods: repoMethods,
|
|
40
|
+
sourceFile: classDecl.filePath,
|
|
41
|
+
line: classDecl.line,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else if (!isRepoByName && repoMethods.length >= 2) {
|
|
45
|
+
// Generic interface with enough repository-like methods
|
|
46
|
+
repoInterfaces.push({
|
|
47
|
+
interfaceName: className,
|
|
48
|
+
methods: repoMethods,
|
|
49
|
+
sourceFile: classDecl.filePath,
|
|
50
|
+
line: classDecl.line,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 2. Detect CQRS handlers from class registry + semantic models
|
|
55
|
+
for (const [className, classDecl] of ctx.symbolTable.classes) {
|
|
56
|
+
const model = ctx.symbolTable.models.get(classDecl.filePath);
|
|
57
|
+
if (!model)
|
|
58
|
+
continue;
|
|
59
|
+
for (const methodName of classDecl.methods) {
|
|
60
|
+
if (methodName !== 'execute' && methodName !== 'handle' && methodName !== 'apply')
|
|
61
|
+
continue;
|
|
62
|
+
// Determine handler type from class name or method context
|
|
63
|
+
let handlerType = 'command';
|
|
64
|
+
if (/Query/.test(className))
|
|
65
|
+
handlerType = 'query';
|
|
66
|
+
else if (/Event/.test(className))
|
|
67
|
+
handlerType = 'event';
|
|
68
|
+
// Try to get parameter type from the function's annotations or the class name
|
|
69
|
+
const func = model.functions.get(methodName);
|
|
70
|
+
let parameterType = `${className.replace(/Handler$/, '')}`;
|
|
71
|
+
if (func === null || func === void 0 ? void 0 : func.annotations) {
|
|
72
|
+
// Check for annotations that hint at the command/query type
|
|
73
|
+
for (const ann of func.annotations) {
|
|
74
|
+
const typeMatch = ann.match(/(\w+(?:Command|Query|Event))/);
|
|
75
|
+
if (typeMatch) {
|
|
76
|
+
parameterType = typeMatch[1];
|
|
77
|
+
if (/Command$/.test(parameterType))
|
|
78
|
+
handlerType = 'command';
|
|
79
|
+
else if (/Query$/.test(parameterType))
|
|
80
|
+
handlerType = 'query';
|
|
81
|
+
else if (/Event$/.test(parameterType))
|
|
82
|
+
handlerType = 'event';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
cqrsHandlers.push({
|
|
87
|
+
className,
|
|
88
|
+
handlerType,
|
|
89
|
+
handleMethodName: methodName,
|
|
90
|
+
parameterType,
|
|
91
|
+
sourceFile: classDecl.filePath,
|
|
92
|
+
line: classDecl.line,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 3. Detect implements clauses from class registry (already parsed by tree-sitter)
|
|
97
|
+
for (const [className, classDecl] of ctx.symbolTable.classes) {
|
|
98
|
+
if (!classDecl.implementsInterfaces || classDecl.implementsInterfaces.length === 0)
|
|
99
|
+
continue;
|
|
100
|
+
for (const iface of classDecl.implementsInterfaces) {
|
|
101
|
+
if (!implementations.has(iface)) {
|
|
102
|
+
implementations.set(iface, []);
|
|
103
|
+
}
|
|
104
|
+
implementations.get(iface).push(className);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Map interface → implementation in symbolTable
|
|
108
|
+
for (const [ifaceName, implNames] of implementations) {
|
|
109
|
+
for (const implName of implNames) {
|
|
110
|
+
const ifaceFile = findFileForClass(ifaceName, ctx);
|
|
111
|
+
const implFile = findFileForClass(implName, ctx);
|
|
112
|
+
if (ifaceFile && implFile) {
|
|
113
|
+
if (!ctx.symbolTable.interfaceImplementations.has(ifaceFile)) {
|
|
114
|
+
ctx.symbolTable.interfaceImplementations.set(ifaceFile, []);
|
|
115
|
+
}
|
|
116
|
+
ctx.symbolTable.interfaceImplementations.get(ifaceFile).push({
|
|
117
|
+
interfaceName: ifaceName,
|
|
118
|
+
interfaceFile: ifaceFile,
|
|
119
|
+
implName,
|
|
120
|
+
implFile,
|
|
121
|
+
});
|
|
122
|
+
entriesAdded++;
|
|
123
|
+
}
|
|
124
|
+
else if (!implFile) {
|
|
125
|
+
unresolvedRefs.push({
|
|
126
|
+
ref: implName,
|
|
127
|
+
reason: `Implementation class '${implName}' not found in parsed models`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Write repository interfaces into symbolTable (Section 5.3)
|
|
133
|
+
for (const repo of repoInterfaces) {
|
|
134
|
+
const key = repo.sourceFile;
|
|
135
|
+
if (!ctx.symbolTable.interfaceImplementations.has(key)) {
|
|
136
|
+
ctx.symbolTable.interfaceImplementations.set(key, []);
|
|
137
|
+
}
|
|
138
|
+
const existing = ctx.symbolTable.interfaceImplementations.get(key);
|
|
139
|
+
const alreadyMapped = existing.some(e => e.interfaceName === repo.interfaceName);
|
|
140
|
+
if (!alreadyMapped) {
|
|
141
|
+
existing.push({
|
|
142
|
+
interfaceName: repo.interfaceName,
|
|
143
|
+
interfaceFile: repo.sourceFile,
|
|
144
|
+
implName: '',
|
|
145
|
+
implFile: '',
|
|
146
|
+
});
|
|
147
|
+
entriesAdded++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Write CQRS handlers as service-layer entries into symbolTable (Section 5.2)
|
|
151
|
+
for (const handler of cqrsHandlers) {
|
|
152
|
+
const key = handler.sourceFile;
|
|
153
|
+
if (!ctx.symbolTable.interfaceImplementations.has(key)) {
|
|
154
|
+
ctx.symbolTable.interfaceImplementations.set(key, []);
|
|
155
|
+
}
|
|
156
|
+
ctx.symbolTable.interfaceImplementations.get(key).push({
|
|
157
|
+
interfaceName: handler.parameterType,
|
|
158
|
+
interfaceFile: handler.sourceFile,
|
|
159
|
+
implName: handler.className,
|
|
160
|
+
implFile: handler.sourceFile,
|
|
161
|
+
});
|
|
162
|
+
entriesAdded++;
|
|
163
|
+
}
|
|
164
|
+
if (repoInterfaces.length > 0) {
|
|
165
|
+
diagnostics.push(`Found ${repoInterfaces.length} repository interface(s)`);
|
|
166
|
+
}
|
|
167
|
+
if (cqrsHandlers.length > 0) {
|
|
168
|
+
diagnostics.push(`Found ${cqrsHandlers.length} CQRS handler(s)`);
|
|
169
|
+
}
|
|
170
|
+
if (implementations.size > 0) {
|
|
171
|
+
diagnostics.push(`Found ${implementations.size} interface→implementation mapping(s)`);
|
|
172
|
+
}
|
|
173
|
+
return { entriesAdded, diagnostics, unresolvedRefs };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.DddLayerResolver = DddLayerResolver;
|
|
177
|
+
/**
|
|
178
|
+
* Detect repository interfaces: interfaces with findBy*, save, delete, etc.
|
|
179
|
+
* No @Repository annotation required (RULE-SA03).
|
|
180
|
+
*/
|
|
181
|
+
function detectRepositoryInterfaces(source, filePath) {
|
|
182
|
+
const repos = [];
|
|
183
|
+
const lines = source.split('\n');
|
|
184
|
+
// Find interfaces
|
|
185
|
+
const interfacePattern = /(?:public\s+)?interface\s+(\w+(?:Repository|Repo|Store|Gateway))\b/;
|
|
186
|
+
let currentInterface = null;
|
|
187
|
+
let braceDepth = 0;
|
|
188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
189
|
+
const line = lines[i];
|
|
190
|
+
if (!currentInterface) {
|
|
191
|
+
const match = line.match(interfacePattern);
|
|
192
|
+
if (match) {
|
|
193
|
+
currentInterface = {
|
|
194
|
+
interfaceName: match[1],
|
|
195
|
+
methods: [],
|
|
196
|
+
sourceFile: filePath,
|
|
197
|
+
line: i + 1,
|
|
198
|
+
};
|
|
199
|
+
braceDepth = 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (currentInterface) {
|
|
203
|
+
braceDepth += (line.match(/\{/g) || []).length;
|
|
204
|
+
braceDepth -= (line.match(/\}/g) || []).length;
|
|
205
|
+
// Detect repository methods: findByX, save, delete, existsBy, countBy
|
|
206
|
+
const methodMatch = line.match(/\b(findBy\w+|find\w+|save|saveAll|delete|deleteById|existsBy\w+|countBy\w+|getBy\w+)\s*\(/);
|
|
207
|
+
if (methodMatch) {
|
|
208
|
+
currentInterface.methods.push(methodMatch[1]);
|
|
209
|
+
}
|
|
210
|
+
if (braceDepth <= 0 && currentInterface.methods.length > 0) {
|
|
211
|
+
repos.push(currentInterface);
|
|
212
|
+
currentInterface = null;
|
|
213
|
+
}
|
|
214
|
+
else if (braceDepth <= 0) {
|
|
215
|
+
currentInterface = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Also detect interfaces with standard CRUD method signatures without naming convention
|
|
220
|
+
const genericInterfacePattern = /(?:public\s+)?interface\s+(\w+)\b/g;
|
|
221
|
+
let genMatch;
|
|
222
|
+
while ((genMatch = genericInterfacePattern.exec(source)) !== null) {
|
|
223
|
+
const name = genMatch[1];
|
|
224
|
+
// Skip if already detected
|
|
225
|
+
if (repos.some((r) => r.interfaceName === name))
|
|
226
|
+
continue;
|
|
227
|
+
// Check if this interface has findBy, save, delete methods
|
|
228
|
+
const startIdx = genMatch.index;
|
|
229
|
+
const braceIdx = source.indexOf('{', startIdx);
|
|
230
|
+
if (braceIdx < 0)
|
|
231
|
+
continue;
|
|
232
|
+
let depth = 1;
|
|
233
|
+
let endIdx = braceIdx + 1;
|
|
234
|
+
while (endIdx < source.length && depth > 0) {
|
|
235
|
+
if (source[endIdx] === '{')
|
|
236
|
+
depth++;
|
|
237
|
+
if (source[endIdx] === '}')
|
|
238
|
+
depth--;
|
|
239
|
+
endIdx++;
|
|
240
|
+
}
|
|
241
|
+
const body = source.substring(braceIdx, endIdx);
|
|
242
|
+
const methods = [];
|
|
243
|
+
const repoMethodPattern = /\b(findBy\w+|save|delete|deleteById)\s*\(/g;
|
|
244
|
+
let rm;
|
|
245
|
+
while ((rm = repoMethodPattern.exec(body)) !== null) {
|
|
246
|
+
methods.push(rm[1]);
|
|
247
|
+
}
|
|
248
|
+
if (methods.length >= 2) {
|
|
249
|
+
const lineNum = source.substring(0, startIdx).split('\n').length;
|
|
250
|
+
repos.push({ interfaceName: name, methods, sourceFile: filePath, line: lineNum });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return repos;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Detect CQRS command/query handlers.
|
|
257
|
+
* Looks for classes with execute/handle/apply methods that take *Command/*Query parameters.
|
|
258
|
+
*/
|
|
259
|
+
function detectCqrsHandlers(source, filePath) {
|
|
260
|
+
const handlers = [];
|
|
261
|
+
const lines = source.split('\n');
|
|
262
|
+
// Pattern: handle(CreateArticleCommand cmd) or execute(GetArticlesQuery query)
|
|
263
|
+
const handlerMethodPattern = /(?:public\s+\S+\s+)?(execute|handle|apply)\s*\(\s*(\w+(Command|Query|Event))\s+\w+\s*\)/;
|
|
264
|
+
// Find the enclosing class name
|
|
265
|
+
let currentClass = '';
|
|
266
|
+
for (let i = 0; i < lines.length; i++) {
|
|
267
|
+
const line = lines[i];
|
|
268
|
+
const classMatch = line.match(/class\s+(\w+)/);
|
|
269
|
+
if (classMatch) {
|
|
270
|
+
currentClass = classMatch[1];
|
|
271
|
+
}
|
|
272
|
+
const methodMatch = line.match(handlerMethodPattern);
|
|
273
|
+
if (methodMatch && currentClass) {
|
|
274
|
+
const handlerType = methodMatch[3].toLowerCase();
|
|
275
|
+
handlers.push({
|
|
276
|
+
className: currentClass,
|
|
277
|
+
handlerType,
|
|
278
|
+
handleMethodName: methodMatch[1],
|
|
279
|
+
parameterType: methodMatch[2],
|
|
280
|
+
sourceFile: filePath,
|
|
281
|
+
line: i + 1,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return handlers;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Detect implements clauses from Java/Kotlin source.
|
|
289
|
+
*/
|
|
290
|
+
function detectImplementsClauses(source, filePath) {
|
|
291
|
+
const impls = [];
|
|
292
|
+
const pattern = /class\s+(\w+)(?:\s+extends\s+\w+)?\s+implements\s+([\w,\s]+)/g;
|
|
293
|
+
let match;
|
|
294
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
295
|
+
const className = match[1];
|
|
296
|
+
const interfaces = match[2].split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
297
|
+
for (const iface of interfaces) {
|
|
298
|
+
impls.push([iface, className]);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return impls;
|
|
302
|
+
}
|
|
303
|
+
function findFileForClass(className, ctx) {
|
|
304
|
+
// Search through exported symbols and class registry
|
|
305
|
+
const classInfo = ctx.symbolTable.classes.get(className);
|
|
306
|
+
if (classInfo)
|
|
307
|
+
return classInfo.filePath;
|
|
308
|
+
// Search through models for any mention
|
|
309
|
+
for (const [filePath, model] of ctx.symbolTable.models) {
|
|
310
|
+
if (model.functions.has(className))
|
|
311
|
+
return filePath;
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express router cross-file resolver (Feature 27, Sub-PR 6)
|
|
3
|
+
*
|
|
4
|
+
* Resolves Express `app.use('/prefix', router)` mount paths across files:
|
|
5
|
+
* 1. Find all app.use('/path', require('./routes')) calls → mount registry
|
|
6
|
+
* 2. Follow require('./routes') to resolve sub-router files
|
|
7
|
+
* 3. Propagate auth middleware through nested routers
|
|
8
|
+
* 4. Build complete URL: base mount + sub-router mount + route path
|
|
9
|
+
*/
|
|
10
|
+
import type { CrossFileResolver, CrossFileResolutionContext, CrossFileResolutionResult } from '../types';
|
|
11
|
+
import type { DetectedApiFramework } from '../../../../discovery/frameworkDetector';
|
|
12
|
+
export declare class ExpressRouterResolver implements CrossFileResolver {
|
|
13
|
+
readonly name = "express-router";
|
|
14
|
+
appliesTo(frameworks: DetectedApiFramework[]): boolean;
|
|
15
|
+
resolve(ctx: CrossFileResolutionContext): CrossFileResolutionResult;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=expressRouterResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expressRouterResolver.d.ts","sourceRoot":"","sources":["../../../../../../src/pipeline/stages/ast/resolvers/expressRouterResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAEpF,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,QAAQ,CAAC,IAAI,oBAAoB;IAEjC,SAAS,CAAC,UAAU,EAAE,oBAAoB,EAAE,GAAG,OAAO;IAMtD,OAAO,CAAC,GAAG,EAAE,0BAA0B,GAAG,yBAAyB;CAiDpE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express router cross-file resolver (Feature 27, Sub-PR 6)
|
|
4
|
+
*
|
|
5
|
+
* Resolves Express `app.use('/prefix', router)` mount paths across files:
|
|
6
|
+
* 1. Find all app.use('/path', require('./routes')) calls → mount registry
|
|
7
|
+
* 2. Follow require('./routes') to resolve sub-router files
|
|
8
|
+
* 3. Propagate auth middleware through nested routers
|
|
9
|
+
* 4. Build complete URL: base mount + sub-router mount + route path
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ExpressRouterResolver = void 0;
|
|
13
|
+
class ExpressRouterResolver {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.name = 'express-router';
|
|
16
|
+
}
|
|
17
|
+
appliesTo(frameworks) {
|
|
18
|
+
return frameworks.some((f) => f.name === 'express' || f.name === 'nestjs' || f.name === 'koa' || f.name === 'fastify');
|
|
19
|
+
}
|
|
20
|
+
resolve(ctx) {
|
|
21
|
+
let entriesAdded = 0;
|
|
22
|
+
const diagnostics = [];
|
|
23
|
+
const unresolvedRefs = [];
|
|
24
|
+
// Scan models for Express router mount patterns
|
|
25
|
+
for (const [filePath, model] of ctx.symbolTable.models) {
|
|
26
|
+
if (!model.routeRegistrations)
|
|
27
|
+
continue;
|
|
28
|
+
for (const reg of model.routeRegistrations) {
|
|
29
|
+
// app.use('/prefix', require('./router'))
|
|
30
|
+
if (reg.targetModule && reg.path) {
|
|
31
|
+
if (!ctx.symbolTable.routerMounts.has(filePath)) {
|
|
32
|
+
ctx.symbolTable.routerMounts.set(filePath, []);
|
|
33
|
+
}
|
|
34
|
+
ctx.symbolTable.routerMounts.get(filePath).push({
|
|
35
|
+
prefix: reg.path,
|
|
36
|
+
targetModulePath: reg.targetModule,
|
|
37
|
+
middleware: [],
|
|
38
|
+
sourceFile: filePath,
|
|
39
|
+
line: reg.line,
|
|
40
|
+
});
|
|
41
|
+
entriesAdded++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Propagate middleware from router mounts to sub-routes
|
|
46
|
+
for (const [filePath, mounts] of ctx.symbolTable.routerMounts) {
|
|
47
|
+
for (const mount of mounts) {
|
|
48
|
+
// Check if auth middleware is applied to this mount
|
|
49
|
+
const middleware = ctx.symbolTable.middlewareInheritance.get(filePath);
|
|
50
|
+
if (middleware) {
|
|
51
|
+
for (const mw of middleware) {
|
|
52
|
+
if (mw.appliedTo === 'router') {
|
|
53
|
+
mount.middleware.push(mw);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (entriesAdded > 0) {
|
|
60
|
+
diagnostics.push(`Resolved ${entriesAdded} Express router mount(s)`);
|
|
61
|
+
}
|
|
62
|
+
return { entriesAdded, diagnostics, unresolvedRefs };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ExpressRouterResolver = ExpressRouterResolver;
|