api-tests-coverage 1.0.15 → 1.0.16

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.
Files changed (118) hide show
  1. package/dist/dashboard/dist/assets/_basePickBy-C2jmWITn.js +1 -0
  2. package/dist/dashboard/dist/assets/_baseUniq-DE6cyzJb.js +1 -0
  3. package/dist/dashboard/dist/assets/arc-B-Q4nGPT.js +1 -0
  4. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-C_5dqWCI.js +36 -0
  5. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-DbGIO6Kt.js +122 -0
  6. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-CAFpcejP.js +10 -0
  7. package/dist/dashboard/dist/assets/channel-Di9el3wE.js +1 -0
  8. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-DY1boKsq.js +1 -0
  9. package/dist/dashboard/dist/assets/chunk-55IACEB6-BSL35gyW.js +1 -0
  10. package/dist/dashboard/dist/assets/chunk-B4BG7PRW-eTDXrKrv.js +165 -0
  11. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-M-8I3jEy.js +220 -0
  12. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-bSA0XiS0.js +15 -0
  13. package/dist/dashboard/dist/assets/chunk-QN33PNHL-BrOIYUBs.js +1 -0
  14. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-CliaQGD4.js +1 -0
  15. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-CyhcxGB1.js +1 -0
  16. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-BkGN4Cpz.js +1 -0
  17. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-BkGN4Cpz.js +1 -0
  18. package/dist/dashboard/dist/assets/clone-Cvq8JuOb.js +1 -0
  19. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-BUkL7Wtq.js +1 -0
  20. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-B8oEROJc.js +4 -0
  21. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-5uki9Dw8.js +24 -0
  22. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-BRNhmby2.js +43 -0
  23. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-D-ku_X8U.js +24 -0
  24. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-DGl6gPe2.js +60 -0
  25. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Co89qYBD.js +162 -0
  26. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-2r3WpWQC.js +267 -0
  27. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-CuJ5l3TK.js +65 -0
  28. package/dist/dashboard/dist/assets/graph-ZtgwAPQj.js +1 -0
  29. package/dist/dashboard/dist/assets/index-D3sRJga7.js +777 -0
  30. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-ujnMqVz3.js +2 -0
  31. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-DQzfeBIo.js +139 -0
  32. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-ueIaoeks.js +89 -0
  33. package/dist/dashboard/dist/assets/layout-B1fTYUMj.js +1 -0
  34. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-B7wYeLe1.js +68 -0
  35. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-Bf8vKEOf.js +30 -0
  36. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-CM8qiFLR.js +7 -0
  37. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-DPTtP4Ve.js +64 -0
  38. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-DEVTdH0h.js +10 -0
  39. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-Bjr5wgXg.js +145 -0
  40. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DDrhZYly.js +1 -0
  41. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-Im6pH8C-.js +1 -0
  42. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-DAT3r9va.js +61 -0
  43. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-BlA8rg0m.js +162 -0
  44. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-7aSkQtVu.js +7 -0
  45. package/dist/dashboard/dist/index.html +1 -1
  46. package/dist/src/ast/astTypes.d.ts +86 -0
  47. package/dist/src/ast/astTypes.d.ts.map +1 -1
  48. package/dist/src/discovery/frameworkDetector.d.ts +28 -0
  49. package/dist/src/discovery/frameworkDetector.d.ts.map +1 -0
  50. package/dist/src/discovery/frameworkDetector.js +189 -0
  51. package/dist/src/discovery/projectDiscovery.d.ts +5 -1
  52. package/dist/src/discovery/projectDiscovery.d.ts.map +1 -1
  53. package/dist/src/discovery/projectDiscovery.js +4 -0
  54. package/dist/src/inference/routeInference.d.ts.map +1 -1
  55. package/dist/src/inference/routeInference.js +224 -1
  56. package/dist/src/languages/java/graphqlSchemaParser.d.ts +65 -0
  57. package/dist/src/languages/java/graphqlSchemaParser.d.ts.map +1 -0
  58. package/dist/src/languages/java/graphqlSchemaParser.js +164 -0
  59. package/dist/src/languages/java/mybatisXmlParser.d.ts +52 -0
  60. package/dist/src/languages/java/mybatisXmlParser.d.ts.map +1 -0
  61. package/dist/src/languages/java/mybatisXmlParser.js +107 -0
  62. package/dist/src/languages/javascript/angularDetector.d.ts +74 -0
  63. package/dist/src/languages/javascript/angularDetector.d.ts.map +1 -0
  64. package/dist/src/languages/javascript/angularDetector.js +194 -0
  65. package/dist/src/languages/javascript/hapiDetector.d.ts +40 -0
  66. package/dist/src/languages/javascript/hapiDetector.d.ts.map +1 -0
  67. package/dist/src/languages/javascript/hapiDetector.js +131 -0
  68. package/dist/src/languages/javascript/mongooseDetector.d.ts +65 -0
  69. package/dist/src/languages/javascript/mongooseDetector.d.ts.map +1 -0
  70. package/dist/src/languages/javascript/mongooseDetector.js +237 -0
  71. package/dist/src/languages/javascript/vueDetector.d.ts +40 -0
  72. package/dist/src/languages/javascript/vueDetector.d.ts.map +1 -0
  73. package/dist/src/languages/javascript/vueDetector.js +87 -0
  74. package/dist/src/languages/python/index.d.ts +5 -1
  75. package/dist/src/languages/python/index.d.ts.map +1 -1
  76. package/dist/src/languages/python/index.js +167 -2
  77. package/dist/src/languages/python/testPatternDetector.d.ts +70 -0
  78. package/dist/src/languages/python/testPatternDetector.d.ts.map +1 -0
  79. package/dist/src/languages/python/testPatternDetector.js +201 -0
  80. package/dist/src/pipeline/stages/ast/astStage.d.ts.map +1 -1
  81. package/dist/src/pipeline/stages/ast/astStage.js +6 -0
  82. package/dist/src/pipeline/stages/ast/baseUrlComposer.d.ts +44 -0
  83. package/dist/src/pipeline/stages/ast/baseUrlComposer.d.ts.map +1 -0
  84. package/dist/src/pipeline/stages/ast/baseUrlComposer.js +83 -0
  85. package/dist/src/pipeline/stages/ast/crossFileResolutionPass.d.ts +54 -0
  86. package/dist/src/pipeline/stages/ast/crossFileResolutionPass.d.ts.map +1 -0
  87. package/dist/src/pipeline/stages/ast/crossFileResolutionPass.js +88 -0
  88. package/dist/src/pipeline/stages/ast/crossFileResolver.d.ts.map +1 -1
  89. package/dist/src/pipeline/stages/ast/crossFileResolver.js +10 -1
  90. package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts +39 -0
  91. package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts.map +1 -0
  92. package/dist/src/pipeline/stages/ast/optionalAuthUnifier.js +81 -0
  93. package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.d.ts +18 -0
  94. package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.d.ts.map +1 -0
  95. package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.js +77 -0
  96. package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.d.ts +46 -0
  97. package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.d.ts.map +1 -0
  98. package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.js +238 -0
  99. package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.d.ts +17 -0
  100. package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.d.ts.map +1 -0
  101. package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.js +65 -0
  102. package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.d.ts +17 -0
  103. package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.d.ts.map +1 -0
  104. package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.js +114 -0
  105. package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.d.ts +27 -0
  106. package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.d.ts.map +1 -0
  107. package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.js +74 -0
  108. package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.d.ts +17 -0
  109. package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.d.ts.map +1 -0
  110. package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.js +55 -0
  111. package/dist/src/pipeline/stages/ast/rulesEnforcer.d.ts +24 -0
  112. package/dist/src/pipeline/stages/ast/rulesEnforcer.d.ts.map +1 -0
  113. package/dist/src/pipeline/stages/ast/rulesEnforcer.js +120 -0
  114. package/dist/src/pipeline/stages/ast/types.d.ts +114 -1
  115. package/dist/src/pipeline/stages/ast/types.d.ts.map +1 -1
  116. package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts.map +1 -1
  117. package/dist/src/pipeline/stages/tia/testLayerClassifier.js +5 -0
  118. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testPatternDetector.d.ts","sourceRoot":"","sources":["../../../../src/languages/python/testPatternDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,oBAAoB,CAAC;AAK5E,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oCAAoC;IACpC,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAiEnG;AAID,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAiBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CA6BzF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,gBAAgB,EAAE,CAUnF;AAID,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAyC1F;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAO7D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE1D"}
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ /**
3
+ * Python test pattern detector (Feature 27, Sub-PR 4)
4
+ *
5
+ * Detects:
6
+ * 1. Factory Boy factories (factory.Factory subclasses, SubFactory, Meta.model)
7
+ * 2. webtest API calls (TestApp, testapp.get/post_json/put_json/delete)
8
+ * 3. pytest fixture chains (@pytest.fixture, fixture dependencies)
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.detectFactoryBoyFactories = detectFactoryBoyFactories;
12
+ exports.detectWebtestCalls = detectWebtestCalls;
13
+ exports.webtestCallsToHttpCalls = webtestCallsToHttpCalls;
14
+ exports.detectPytestFixtures = detectPytestFixtures;
15
+ exports.hasWebtestTestApp = hasWebtestTestApp;
16
+ exports.hasRealDbFixtures = hasRealDbFixtures;
17
+ exports.usesFactoryBoy = usesFactoryBoy;
18
+ /**
19
+ * Detect Factory Boy factory classes from source text.
20
+ * Looks for `class XFactory(factory.Factory):` and parses Meta.model, SubFactory, RelatedFactoryList.
21
+ */
22
+ function detectFactoryBoyFactories(sourceText, filePath) {
23
+ const factories = [];
24
+ const lines = sourceText.split('\n');
25
+ // Regex for class definition inheriting from factory.Factory or factory.DjangoModelFactory etc.
26
+ const classPattern = /^class\s+(\w+)\s*\(\s*(?:factory\.(?:Factory|DjangoModelFactory|SQLAlchemyModelFactory|MongoEngineFactory)|Factory)\s*\)/;
27
+ const metaModelPattern = /model\s*=\s*(\w+)/;
28
+ const subFactoryPattern = /=\s*(?:factory\.)?SubFactory\s*\(\s*['"]?(\w+)/g;
29
+ const relatedFactoryPattern = /=\s*(?:factory\.)?RelatedFactoryList\s*\(\s*['"]?(\w+)/g;
30
+ let currentFactory = null;
31
+ let classIndent = -1;
32
+ for (let i = 0; i < lines.length; i++) {
33
+ const line = lines[i];
34
+ const indent = line.search(/\S/);
35
+ // If we're inside a factory class, check for end of class
36
+ if (currentFactory && indent >= 0 && indent <= classIndent && line.trim().length > 0) {
37
+ factories.push(currentFactory);
38
+ currentFactory = null;
39
+ classIndent = -1;
40
+ }
41
+ const classMatch = line.match(classPattern);
42
+ if (classMatch) {
43
+ if (currentFactory)
44
+ factories.push(currentFactory);
45
+ currentFactory = {
46
+ className: classMatch[1],
47
+ subFactories: [],
48
+ relatedFactoryLists: [],
49
+ sourceFile: filePath,
50
+ line: i + 1,
51
+ };
52
+ classIndent = indent;
53
+ continue;
54
+ }
55
+ if (currentFactory) {
56
+ // Meta.model
57
+ const metaMatch = line.match(metaModelPattern);
58
+ if (metaMatch) {
59
+ currentFactory.modelName = metaMatch[1];
60
+ }
61
+ // SubFactory
62
+ let sfMatch;
63
+ subFactoryPattern.lastIndex = 0;
64
+ while ((sfMatch = subFactoryPattern.exec(line)) !== null) {
65
+ currentFactory.subFactories.push(sfMatch[1]);
66
+ }
67
+ // RelatedFactoryList
68
+ let rfMatch;
69
+ relatedFactoryPattern.lastIndex = 0;
70
+ while ((rfMatch = relatedFactoryPattern.exec(line)) !== null) {
71
+ currentFactory.relatedFactoryLists.push(rfMatch[1]);
72
+ }
73
+ }
74
+ }
75
+ // Push last factory if still open
76
+ if (currentFactory)
77
+ factories.push(currentFactory);
78
+ return factories;
79
+ }
80
+ /** Map webtest methods to HTTP methods */
81
+ const WEBTEST_METHOD_MAP = {
82
+ get: 'GET',
83
+ post: 'POST',
84
+ post_json: 'POST',
85
+ put: 'PUT',
86
+ put_json: 'PUT',
87
+ patch: 'PATCH',
88
+ patch_json: 'PATCH',
89
+ delete: 'DELETE',
90
+ delete_json: 'DELETE',
91
+ head: 'HEAD',
92
+ options: 'OPTIONS',
93
+ };
94
+ /**
95
+ * Detect webtest HTTP calls from source text.
96
+ * Looks for `testapp.get('/path')`, `testapp.post_json('/path', {...})`, etc.
97
+ */
98
+ function detectWebtestCalls(sourceText, filePath) {
99
+ const calls = [];
100
+ const lines = sourceText.split('\n');
101
+ // Pattern: any_var.METHOD('/path'...) where METHOD is a webtest HTTP method
102
+ const webtestMethods = Object.keys(WEBTEST_METHOD_MAP).join('|');
103
+ const callPattern = new RegExp(`\\w+\\.(${webtestMethods})\\s*\\(\\s*['"]([^'"]+)['"]`, 'g');
104
+ for (let i = 0; i < lines.length; i++) {
105
+ const line = lines[i];
106
+ callPattern.lastIndex = 0;
107
+ let match;
108
+ while ((match = callPattern.exec(line)) !== null) {
109
+ const method = WEBTEST_METHOD_MAP[match[1]];
110
+ if (method) {
111
+ calls.push({
112
+ method,
113
+ path: match[2],
114
+ sourceFile: filePath,
115
+ line: i + 1,
116
+ });
117
+ }
118
+ }
119
+ }
120
+ return calls;
121
+ }
122
+ /**
123
+ * Convert webtest API calls into SemanticHttpCall objects.
124
+ */
125
+ function webtestCallsToHttpCalls(calls) {
126
+ return calls.map((call) => ({
127
+ method: call.method,
128
+ rawPathArg: call.path,
129
+ resolvedPath: call.path,
130
+ normalizedPath: call.path.startsWith('/') ? call.path : undefined,
131
+ resolutionType: 'direct',
132
+ confidence: 'high',
133
+ line: call.line,
134
+ }));
135
+ }
136
+ /**
137
+ * Detect @pytest.fixture functions and their parameter dependencies.
138
+ */
139
+ function detectPytestFixtures(sourceText, filePath) {
140
+ const fixtures = [];
141
+ const lines = sourceText.split('\n');
142
+ for (let i = 0; i < lines.length; i++) {
143
+ const line = lines[i];
144
+ // @pytest.fixture or @pytest.fixture(scope='...')
145
+ const fixtureMatch = line.match(/@pytest\.fixture(?:\s*\(([^)]*)\))?/);
146
+ if (!fixtureMatch)
147
+ continue;
148
+ // Parse scope from decorator args
149
+ let scope = 'function';
150
+ if (fixtureMatch[1]) {
151
+ const scopeMatch = fixtureMatch[1].match(/scope\s*=\s*['"](\w+)['"]/);
152
+ if (scopeMatch)
153
+ scope = scopeMatch[1];
154
+ }
155
+ // Find the next def line (typically the line right after the decorator)
156
+ for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
157
+ const defMatch = lines[j].match(/def\s+(\w+)\s*\(([^)]*)\)/);
158
+ if (defMatch) {
159
+ const name = defMatch[1];
160
+ const params = defMatch[2]
161
+ .split(',')
162
+ .map((p) => p.trim().split(':')[0].split('=')[0].trim())
163
+ .filter((p) => p.length > 0 && p !== 'request');
164
+ fixtures.push({
165
+ name,
166
+ scope,
167
+ dependencies: params,
168
+ sourceFile: filePath,
169
+ line: j + 1,
170
+ });
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ return fixtures;
176
+ }
177
+ // ─── TestApp Detection ──────────────────────────────────────────────────────
178
+ /**
179
+ * Detect if a file creates a webtest TestApp instance.
180
+ * Looks for `TestApp(app)` or `TestApp(...)`.
181
+ */
182
+ function hasWebtestTestApp(sourceText) {
183
+ return /TestApp\s*\(/.test(sourceText);
184
+ }
185
+ /**
186
+ * Check if a file uses real DB fixtures (not mocked).
187
+ * Looks for pytest fixtures that reference db, database, session, engine, etc.
188
+ */
189
+ function hasRealDbFixtures(sourceText) {
190
+ // Check for DB fixture names in function parameters
191
+ const dbFixturePattern = /def\s+\w+\s*\([^)]*\b(db|database|dbsession|db_session|engine|session|connection)\b/;
192
+ // Also check for common DB setup patterns
193
+ const dbSetupPattern = /(?:create_all|Base\.metadata|sessionmaker|create_engine|init_db|setup_database)/;
194
+ return dbFixturePattern.test(sourceText) || dbSetupPattern.test(sourceText);
195
+ }
196
+ /**
197
+ * Check if a file uses Factory Boy factories.
198
+ */
199
+ function usesFactoryBoy(sourceText) {
200
+ return /(?:import\s+factory|from\s+factory\s+import|factory\.Factory|factory\.SubFactory)/.test(sourceText);
201
+ }
@@ -1 +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"}
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;AAmB9C,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;CAoLjE"}
@@ -47,6 +47,7 @@ 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");
@@ -130,6 +131,8 @@ class AstStage {
130
131
  }
131
132
  // Phase 2: Build cross-file symbol table
132
133
  const crossFileTable = (0, crossFileResolver_1.buildCrossFileSymbolTable)(models, context.projectRoot);
134
+ // Phase 2b: Run cross-file resolution pass (Feature 27)
135
+ const crossFileResolutionResult = (0, crossFileResolutionPass_1.runCrossFileResolution)(crossFileTable, context.projectRoot, artifacts.apiFrameworks, allSourceFiles);
133
136
  // Phase 3: Run abstract layer traversal for test files
134
137
  const traversalResults = new Map();
135
138
  const depthCap = context.config.traversalDepthCap;
@@ -199,6 +202,8 @@ class AstStage {
199
202
  traversalResultCount: traversalResults.size,
200
203
  graphNodesAdded: nodes.length,
201
204
  graphEdgesAdded: edges.length,
205
+ crossFileResolversRun: crossFileResolutionResult.resolverResults.length,
206
+ crossFileEntriesAdded: crossFileResolutionResult.totalEntriesAdded,
202
207
  },
203
208
  };
204
209
  context.diagnostics.set('ast', diagnostics);
@@ -209,6 +214,7 @@ class AstStage {
209
214
  traversalResults,
210
215
  analyzedFiles: filesScanned,
211
216
  skippedFiles: filesSkipped,
217
+ crossFileResolutionDiagnostics: crossFileResolutionResult.resolverResults,
212
218
  };
213
219
  context.stageOutputs.set('ast', output);
214
220
  return output;
@@ -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,CAStE;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,83 @@
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
+ .map((s) => s.replace(/^\/+|\/+$/g, ''));
29
+ const joined = parts.join('/');
30
+ const normalized = '/' + joined.replace(/\/+/g, '/');
31
+ return normalized === '/' ? '/' : normalized.replace(/\/+$/, '');
32
+ }
33
+ /**
34
+ * Normalize a path template: ensure leading slash, remove double slashes,
35
+ * standardize parameter syntax.
36
+ */
37
+ function normalizePath(path) {
38
+ if (!path)
39
+ return '/';
40
+ // Ensure leading slash
41
+ let normalized = path.startsWith('/') ? path : '/' + path;
42
+ // Remove double slashes (but not from protocol like http://)
43
+ // First handle leading double slashes
44
+ normalized = normalized.replace(/^\/\/+/, '/');
45
+ // Then handle double slashes in the middle
46
+ normalized = normalized.replace(/([^:])\/\/+/g, '$1/');
47
+ // Remove trailing slash (unless it's just "/")
48
+ if (normalized.length > 1) {
49
+ normalized = normalized.replace(/\/+$/, '');
50
+ }
51
+ return normalized;
52
+ }
53
+ /**
54
+ * Convert framework-specific parameter syntax to OpenAPI-style {param}.
55
+ *
56
+ * Flask: <param>, <int:param> → {param}
57
+ * Express: :param → {param}
58
+ * Slim PHP: {param} (already correct)
59
+ * HapiJS: {param} (already correct)
60
+ * Spring: {param} (already correct)
61
+ */
62
+ function normalizePathParams(path) {
63
+ // Flask/Python: <param> or <type:param>
64
+ let normalized = path.replace(/<(?:\w+:)?(\w+)>/g, '{$1}');
65
+ // Express: :param (but not :// from URLs)
66
+ normalized = normalized.replace(/(?<=\/):([\w]+)/g, '{$1}');
67
+ return normalized;
68
+ }
69
+ /**
70
+ * Compose full URL for a specific framework's route.
71
+ */
72
+ function composeFrameworkUrl(framework, parts) {
73
+ const segments = [];
74
+ if (parts.baseUrl)
75
+ segments.push(parts.baseUrl);
76
+ if (parts.classPrefix)
77
+ segments.push(parts.classPrefix);
78
+ if (parts.mountPrefix)
79
+ segments.push(parts.mountPrefix);
80
+ segments.push(parts.routePath);
81
+ const raw = composeUrl(...segments);
82
+ return normalizePathParams(raw);
83
+ }
@@ -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,CAuDtB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,oBAAoB,GAC1B,MAAM,GAAG,SAAS,CA4BpB"}
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 { models, exportedSymbols, classes, importGraph };
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.
@@ -0,0 +1,39 @@
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
+ * Angular route guard → depends on guard type
11
+ */
12
+ import type { SecurityClassification } from '../../../ast/astTypes';
13
+ export interface AuthClassificationEntry {
14
+ framework: string;
15
+ sourcePattern: string;
16
+ classification: SecurityClassification;
17
+ }
18
+ /**
19
+ * Framework-specific auth pattern → unified SecurityClassification.
20
+ */
21
+ export declare function unifyAuthClassification(framework: string, pattern: string): SecurityClassification | undefined;
22
+ /**
23
+ * Detect auth coverage gaps.
24
+ */
25
+ export interface AuthCoverageGap {
26
+ type: 'missing-401-test' | 'missing-optional-dual-path';
27
+ endpointPath: string;
28
+ security: SecurityClassification;
29
+ sourceFile: string;
30
+ }
31
+ /**
32
+ * Analyze auth coverage gaps in endpoints vs tests.
33
+ */
34
+ export declare function detectAuthCoverageGaps(endpoints: Array<{
35
+ path: string;
36
+ security?: SecurityClassification;
37
+ sourceFile: string;
38
+ }>, testAssertionPaths: Set<string>): AuthCoverageGap[];
39
+ //# 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;;;;;;;;;;GAUG;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,CAwCpC;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"}