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.
- 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/dashboard/dist/index.html +1 -1
- package/dist/src/ast/astTypes.d.ts +86 -0
- package/dist/src/ast/astTypes.d.ts.map +1 -1
- 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 +4 -0
- 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/javascript/angularDetector.d.ts +74 -0
- package/dist/src/languages/javascript/angularDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/angularDetector.js +194 -0
- 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 +131 -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 +40 -0
- package/dist/src/languages/javascript/vueDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/vueDetector.js +87 -0
- package/dist/src/languages/python/index.d.ts +5 -1
- package/dist/src/languages/python/index.d.ts.map +1 -1
- package/dist/src/languages/python/index.js +167 -2
- 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/stages/ast/astStage.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/astStage.js +6 -0
- 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 +83 -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 +10 -1
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts +39 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.js +81 -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 +77 -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 +238 -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 +74 -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 +55 -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 +120 -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/tia/testLayerClassifier.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/testLayerClassifier.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mongoose schema/model detector (Feature 27, Sub-PR 6)
|
|
4
|
+
*
|
|
5
|
+
* Detects:
|
|
6
|
+
* 1. mongoose.Schema({...}) — field definitions, validation rules
|
|
7
|
+
* 2. mongoose.model('Name', schema) — model creation
|
|
8
|
+
* 3. .methods.* — instance method definitions
|
|
9
|
+
* 4. .pre()/.post() hooks — lifecycle hooks
|
|
10
|
+
* 5. .plugin(validator) — plugin registrations
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.detectMongooseModels = detectMongooseModels;
|
|
14
|
+
exports.detectExpressErrorHandlers = detectExpressErrorHandlers;
|
|
15
|
+
exports.detectExpressAuthMiddleware = detectExpressAuthMiddleware;
|
|
16
|
+
/**
|
|
17
|
+
* Detect Mongoose model/schema definitions from JS/TS source text.
|
|
18
|
+
*/
|
|
19
|
+
function detectMongooseModels(sourceText, filePath) {
|
|
20
|
+
var _a, _b, _c, _d;
|
|
21
|
+
const models = [];
|
|
22
|
+
const lines = sourceText.split('\n');
|
|
23
|
+
// Track schema variables and their fields
|
|
24
|
+
const schemaVars = new Map();
|
|
25
|
+
// Track methods, hooks, plugins per schema var
|
|
26
|
+
const schemaMethods = new Map();
|
|
27
|
+
const schemaHooks = new Map();
|
|
28
|
+
const schemaPlugins = new Map();
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
// new mongoose.Schema({...}) or new Schema({...})
|
|
32
|
+
const schemaMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\.)?Schema\s*\(/);
|
|
33
|
+
if (schemaMatch) {
|
|
34
|
+
const varName = schemaMatch[1];
|
|
35
|
+
const fields = extractSchemaFields(sourceText, i);
|
|
36
|
+
schemaVars.set(varName, { fields, line: i + 1 });
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// mongoose.model('Name', schema)
|
|
40
|
+
const modelMatch = line.match(/mongoose\.model\s*\(\s*['"](\w+)['"]\s*,\s*(\w+)/);
|
|
41
|
+
if (modelMatch) {
|
|
42
|
+
const modelName = modelMatch[1];
|
|
43
|
+
const schemaVar = modelMatch[2];
|
|
44
|
+
const schemaInfo = schemaVars.get(schemaVar);
|
|
45
|
+
models.push({
|
|
46
|
+
modelName,
|
|
47
|
+
schemaVariable: schemaVar,
|
|
48
|
+
fields: (_a = schemaInfo === null || schemaInfo === void 0 ? void 0 : schemaInfo.fields) !== null && _a !== void 0 ? _a : [],
|
|
49
|
+
methods: (_b = schemaMethods.get(schemaVar)) !== null && _b !== void 0 ? _b : [],
|
|
50
|
+
hooks: (_c = schemaHooks.get(schemaVar)) !== null && _c !== void 0 ? _c : [],
|
|
51
|
+
plugins: (_d = schemaPlugins.get(schemaVar)) !== null && _d !== void 0 ? _d : [],
|
|
52
|
+
sourceFile: filePath,
|
|
53
|
+
line: i + 1,
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// schema.methods.methodName = function
|
|
58
|
+
const methodMatch = line.match(/(\w+)\.methods\.(\w+)\s*=/);
|
|
59
|
+
if (methodMatch) {
|
|
60
|
+
const varName = methodMatch[1];
|
|
61
|
+
if (!schemaMethods.has(varName))
|
|
62
|
+
schemaMethods.set(varName, []);
|
|
63
|
+
schemaMethods.get(varName).push(methodMatch[2]);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// schema.pre('save', ...) or schema.post('save', ...)
|
|
67
|
+
const hookMatch = line.match(/(\w+)\.(pre|post)\s*\(\s*['"](\w+)['"]/);
|
|
68
|
+
if (hookMatch) {
|
|
69
|
+
const varName = hookMatch[1];
|
|
70
|
+
if (!schemaHooks.has(varName))
|
|
71
|
+
schemaHooks.set(varName, []);
|
|
72
|
+
schemaHooks.get(varName).push({
|
|
73
|
+
stage: hookMatch[2],
|
|
74
|
+
event: hookMatch[3],
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// schema.plugin(uniqueValidator)
|
|
79
|
+
const pluginMatch = line.match(/(\w+)\.plugin\s*\(\s*(\w+)/);
|
|
80
|
+
if (pluginMatch) {
|
|
81
|
+
const varName = pluginMatch[1];
|
|
82
|
+
if (!schemaPlugins.has(varName))
|
|
83
|
+
schemaPlugins.set(varName, []);
|
|
84
|
+
schemaPlugins.get(varName).push(pluginMatch[2]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return models;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extract field definitions from a schema constructor.
|
|
91
|
+
* Simple regex extraction — handles the most common patterns.
|
|
92
|
+
*/
|
|
93
|
+
function extractSchemaFields(source, startLine) {
|
|
94
|
+
var _a;
|
|
95
|
+
const fields = [];
|
|
96
|
+
const lines = source.split('\n');
|
|
97
|
+
// Find the opening brace of the schema definition
|
|
98
|
+
let braceDepth = 0;
|
|
99
|
+
let inSchema = false;
|
|
100
|
+
let schemaBody = '';
|
|
101
|
+
for (let i = startLine; i < Math.min(startLine + 50, lines.length); i++) {
|
|
102
|
+
const line = lines[i];
|
|
103
|
+
for (const ch of line) {
|
|
104
|
+
if (ch === '(' || ch === '{')
|
|
105
|
+
braceDepth++;
|
|
106
|
+
if (ch === ')' || ch === '}')
|
|
107
|
+
braceDepth--;
|
|
108
|
+
if (braceDepth >= 2 && !inSchema) {
|
|
109
|
+
inSchema = true;
|
|
110
|
+
}
|
|
111
|
+
if (inSchema)
|
|
112
|
+
schemaBody += ch;
|
|
113
|
+
if (inSchema && braceDepth < 2) {
|
|
114
|
+
inSchema = false;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (schemaBody && !inSchema)
|
|
119
|
+
break;
|
|
120
|
+
if (inSchema)
|
|
121
|
+
schemaBody += '\n';
|
|
122
|
+
}
|
|
123
|
+
// Parse field definitions from the schema body
|
|
124
|
+
// Pattern: fieldName: { type: Type, required: true, unique: true, enum: [...] }
|
|
125
|
+
const fieldPattern = /(\w+)\s*:\s*\{([^}]+)\}/g;
|
|
126
|
+
let match;
|
|
127
|
+
while ((match = fieldPattern.exec(schemaBody)) !== null) {
|
|
128
|
+
const name = match[1];
|
|
129
|
+
const body = match[2];
|
|
130
|
+
const typeMatch = body.match(/type\s*:\s*(\w+)/);
|
|
131
|
+
const required = /required\s*:\s*true/.test(body);
|
|
132
|
+
const unique = /unique\s*:\s*true/.test(body);
|
|
133
|
+
const enumMatch = body.match(/enum\s*:\s*\[([^\]]+)\]/);
|
|
134
|
+
const field = {
|
|
135
|
+
name,
|
|
136
|
+
type: (_a = typeMatch === null || typeMatch === void 0 ? void 0 : typeMatch[1]) !== null && _a !== void 0 ? _a : 'Mixed',
|
|
137
|
+
};
|
|
138
|
+
if (required)
|
|
139
|
+
field.required = true;
|
|
140
|
+
if (unique)
|
|
141
|
+
field.unique = true;
|
|
142
|
+
if (enumMatch) {
|
|
143
|
+
field.enumValues = enumMatch[1].split(',').map((s) => s.trim().replace(/['"]/g, '')).filter(Boolean);
|
|
144
|
+
}
|
|
145
|
+
fields.push(field);
|
|
146
|
+
}
|
|
147
|
+
// Also handle shorthand: fieldName: Type (e.g., fieldName: String)
|
|
148
|
+
const shorthandPattern = /(\w+)\s*:\s*(String|Number|Boolean|Date|ObjectId|Buffer|Mixed|Map)\b/g;
|
|
149
|
+
while ((match = shorthandPattern.exec(schemaBody)) !== null) {
|
|
150
|
+
const name = match[1];
|
|
151
|
+
// Skip if already captured as an object definition
|
|
152
|
+
if (!fields.some((f) => f.name === name)) {
|
|
153
|
+
fields.push({ name, type: match[2] });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return fields;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Detect Express 4-argument error handler middleware.
|
|
160
|
+
* Pattern: function(err, req, res, next) or (err, req, res, next) =>
|
|
161
|
+
*/
|
|
162
|
+
function detectExpressErrorHandlers(sourceText, filePath) {
|
|
163
|
+
const handlers = [];
|
|
164
|
+
const lines = sourceText.split('\n');
|
|
165
|
+
// Pattern: matches 4-argument functions (err, req, res, next)
|
|
166
|
+
const errorHandlerPattern = /(?:function\s+(\w+))?\s*\(\s*(?:err|error)\s*,\s*req\s*,\s*res\s*,\s*next\s*\)/;
|
|
167
|
+
for (let i = 0; i < lines.length; i++) {
|
|
168
|
+
const line = lines[i];
|
|
169
|
+
const match = line.match(errorHandlerPattern);
|
|
170
|
+
if (!match)
|
|
171
|
+
continue;
|
|
172
|
+
const functionName = match[1];
|
|
173
|
+
const errorTypes = [];
|
|
174
|
+
const statusCodes = [];
|
|
175
|
+
// Scan ahead for error type checks and status codes
|
|
176
|
+
for (let j = i; j < Math.min(i + 30, lines.length); j++) {
|
|
177
|
+
const scanLine = lines[j];
|
|
178
|
+
// err.name === 'ValidationError'
|
|
179
|
+
const errorTypeMatch = scanLine.match(/err(?:or)?\.name\s*===?\s*['"](\w+)['"]/);
|
|
180
|
+
if (errorTypeMatch)
|
|
181
|
+
errorTypes.push(errorTypeMatch[1]);
|
|
182
|
+
// res.status(404)
|
|
183
|
+
const statusMatch = scanLine.match(/res\.status\s*\(\s*(\d{3})\s*\)/);
|
|
184
|
+
if (statusMatch)
|
|
185
|
+
statusCodes.push(parseInt(statusMatch[1], 10));
|
|
186
|
+
}
|
|
187
|
+
handlers.push({
|
|
188
|
+
functionName,
|
|
189
|
+
errorTypes,
|
|
190
|
+
statusCodes,
|
|
191
|
+
sourceFile: filePath,
|
|
192
|
+
line: i + 1,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return handlers;
|
|
196
|
+
}
|
|
197
|
+
function detectExpressAuthMiddleware(sourceText, filePath) {
|
|
198
|
+
const middleware = [];
|
|
199
|
+
const lines = sourceText.split('\n');
|
|
200
|
+
for (let i = 0; i < lines.length; i++) {
|
|
201
|
+
const line = lines[i];
|
|
202
|
+
// router.use(auth.required) or app.use(auth.required)
|
|
203
|
+
const simpleAuth = line.match(/(?:router|app)\.use\s*\(\s*auth\.(required|optional)/);
|
|
204
|
+
if (simpleAuth) {
|
|
205
|
+
middleware.push({
|
|
206
|
+
authType: simpleAuth[1],
|
|
207
|
+
sourcePattern: simpleAuth[0],
|
|
208
|
+
sourceFile: filePath,
|
|
209
|
+
line: i + 1,
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// router.use('/path', auth.required, router)
|
|
214
|
+
const pathAuth = line.match(/(?:router|app)\.use\s*\(\s*['"]([^'"]+)['"]\s*,\s*auth\.(required|optional)/);
|
|
215
|
+
if (pathAuth) {
|
|
216
|
+
middleware.push({
|
|
217
|
+
authType: pathAuth[2],
|
|
218
|
+
pathPrefix: pathAuth[1],
|
|
219
|
+
sourcePattern: pathAuth[0],
|
|
220
|
+
sourceFile: filePath,
|
|
221
|
+
line: i + 1,
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
// passport.authenticate('jwt', { session: false })
|
|
226
|
+
const passportAuth = line.match(/passport\.authenticate\s*\(\s*['"](\w+)['"]/);
|
|
227
|
+
if (passportAuth) {
|
|
228
|
+
middleware.push({
|
|
229
|
+
authType: 'required',
|
|
230
|
+
sourcePattern: passportAuth[0],
|
|
231
|
+
sourceFile: filePath,
|
|
232
|
+
line: i + 1,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return middleware;
|
|
237
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue.js/Vuex pattern detector (Feature 27, Sub-PR 8)
|
|
3
|
+
*
|
|
4
|
+
* Detects:
|
|
5
|
+
* 1. axios.defaults.baseURL — global base URL
|
|
6
|
+
* 2. Vuex actions with API calls (ApiService.get, axios.get, etc.)
|
|
7
|
+
* 3. this.$store.dispatch('actionName') — component dispatches
|
|
8
|
+
* 4. ApiService.get('resource') — service-layer API calls
|
|
9
|
+
*/
|
|
10
|
+
export interface VueApiCall {
|
|
11
|
+
actionName?: string;
|
|
12
|
+
method: string;
|
|
13
|
+
urlPattern: string;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
sourceFile: string;
|
|
16
|
+
line?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface VuexDispatch {
|
|
19
|
+
actionName: string;
|
|
20
|
+
sourceFile: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface VueBaseUrl {
|
|
24
|
+
url: string;
|
|
25
|
+
sourceFile: string;
|
|
26
|
+
line?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Detect axios.defaults.baseURL settings.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectAxiosBaseUrl(sourceText: string, filePath: string): VueBaseUrl | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Detect Vuex action definitions with API calls.
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectVuexActions(sourceText: string, filePath: string): VueApiCall[];
|
|
36
|
+
/**
|
|
37
|
+
* Detect this.$store.dispatch() or store.dispatch() calls.
|
|
38
|
+
*/
|
|
39
|
+
export declare function detectVuexDispatches(sourceText: string, filePath: string): VuexDispatch[];
|
|
40
|
+
//# sourceMappingURL=vueDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vueDetector.d.ts","sourceRoot":"","sources":["../../../../src/languages/javascript/vueDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAO/F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CA4CpF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,CAkBzF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vue.js/Vuex pattern detector (Feature 27, Sub-PR 8)
|
|
4
|
+
*
|
|
5
|
+
* Detects:
|
|
6
|
+
* 1. axios.defaults.baseURL — global base URL
|
|
7
|
+
* 2. Vuex actions with API calls (ApiService.get, axios.get, etc.)
|
|
8
|
+
* 3. this.$store.dispatch('actionName') — component dispatches
|
|
9
|
+
* 4. ApiService.get('resource') — service-layer API calls
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.detectAxiosBaseUrl = detectAxiosBaseUrl;
|
|
13
|
+
exports.detectVuexActions = detectVuexActions;
|
|
14
|
+
exports.detectVuexDispatches = detectVuexDispatches;
|
|
15
|
+
/**
|
|
16
|
+
* Detect axios.defaults.baseURL settings.
|
|
17
|
+
*/
|
|
18
|
+
function detectAxiosBaseUrl(sourceText, filePath) {
|
|
19
|
+
const match = sourceText.match(/axios\.defaults\.baseURL\s*=\s*['"`]([^'"`]+)['"`]/);
|
|
20
|
+
if (match) {
|
|
21
|
+
const line = sourceText.substring(0, match.index).split('\n').length;
|
|
22
|
+
return { url: match[1], sourceFile: filePath, line };
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Detect Vuex action definitions with API calls.
|
|
28
|
+
*/
|
|
29
|
+
function detectVuexActions(sourceText, filePath) {
|
|
30
|
+
const calls = [];
|
|
31
|
+
const lines = sourceText.split('\n');
|
|
32
|
+
let currentAction = '';
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
// Vuex action definition: [ACTION_NAME]({ commit }, payload) {
|
|
36
|
+
// or: actionName({ commit }) {
|
|
37
|
+
const actionMatch = line.match(/(?:\[(\w+)\]|(\w+))\s*\(\s*\{\s*(?:commit|dispatch|state|getters|rootState)/);
|
|
38
|
+
if (actionMatch) {
|
|
39
|
+
currentAction = actionMatch[1] || actionMatch[2] || '';
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// ApiService.get/post/put/delete('resource')
|
|
43
|
+
const apiServiceMatch = line.match(/ApiService\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/i);
|
|
44
|
+
if (apiServiceMatch) {
|
|
45
|
+
calls.push({
|
|
46
|
+
actionName: currentAction || undefined,
|
|
47
|
+
method: apiServiceMatch[1].toUpperCase(),
|
|
48
|
+
urlPattern: apiServiceMatch[2],
|
|
49
|
+
sourceFile: filePath,
|
|
50
|
+
line: i + 1,
|
|
51
|
+
});
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// axios.get/post/put/delete('url')
|
|
55
|
+
const axiosMatch = line.match(/axios\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/i);
|
|
56
|
+
if (axiosMatch) {
|
|
57
|
+
calls.push({
|
|
58
|
+
actionName: currentAction || undefined,
|
|
59
|
+
method: axiosMatch[1].toUpperCase(),
|
|
60
|
+
urlPattern: axiosMatch[2],
|
|
61
|
+
sourceFile: filePath,
|
|
62
|
+
line: i + 1,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return calls;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Detect this.$store.dispatch() or store.dispatch() calls.
|
|
70
|
+
*/
|
|
71
|
+
function detectVuexDispatches(sourceText, filePath) {
|
|
72
|
+
const dispatches = [];
|
|
73
|
+
const lines = sourceText.split('\n');
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const line = lines[i];
|
|
76
|
+
// this.$store.dispatch('ACTION_NAME') or store.dispatch('actionName')
|
|
77
|
+
const match = line.match(/(?:this\.\$store|store)\.dispatch\s*\(\s*['"](\w+)['"]/);
|
|
78
|
+
if (match) {
|
|
79
|
+
dispatches.push({
|
|
80
|
+
actionName: match[1],
|
|
81
|
+
sourceFile: filePath,
|
|
82
|
+
line: i + 1,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return dispatches;
|
|
87
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Python language analyzer using tree-sitter-python.
|
|
3
3
|
*/
|
|
4
4
|
import type { LanguageAnalyzer } from '../../ast/languageAnalyzer';
|
|
5
|
-
import type { SupportedLanguage, ParsedSourceFile, SemanticModel, ResolvedHttpInteraction, SemanticAssertion, BusinessRuleRef, FlowRef, AnalysisContext } from '../../ast/astTypes';
|
|
5
|
+
import type { SupportedLanguage, ParsedSourceFile, SemanticModel, ResolvedHttpInteraction, SemanticAssertion, BusinessRuleRef, FlowRef, AnalysisContext, DecoratorInfo, SecurityClassification } from '../../ast/astTypes';
|
|
6
6
|
export declare class PythonAnalyzer implements LanguageAnalyzer {
|
|
7
7
|
readonly language: SupportedLanguage;
|
|
8
8
|
parse(filePath: string, content: string): ParsedSourceFile;
|
|
@@ -12,4 +12,8 @@ export declare class PythonAnalyzer implements LanguageAnalyzer {
|
|
|
12
12
|
extractBusinessRuleRefs(model: SemanticModel): BusinessRuleRef[];
|
|
13
13
|
extractFlowRefs(_model: SemanticModel): FlowRef[];
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Classify JWT/auth security from decorator information.
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifyFlaskSecurity(decorators: DecoratorInfo[]): SecurityClassification | undefined;
|
|
15
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/languages/python/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EAIb,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,OAAO,EACP,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/languages/python/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EAIb,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,OAAO,EACP,eAAe,EAEf,aAAa,EAEb,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAkC5B,qBAAa,cAAe,YAAW,gBAAgB;IACrD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAY;IAEhD,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB;IAc1D,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,GAAG,aAAa;IA6BtF,uBAAuB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,eAAe,GAAG,uBAAuB,EAAE;IAwB/F,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB,EAAE;IAG5D,uBAAuB,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,EAAE;IAGhE,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,EAAE;CAGlD;AA2WD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,sBAAsB,GAAG,SAAS,CAarG"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PythonAnalyzer = void 0;
|
|
7
|
+
exports.classifyFlaskSecurity = classifyFlaskSecurity;
|
|
7
8
|
const parserRegistry_1 = require("../../ast/parserRegistry");
|
|
8
9
|
const treeSitterUtils_1 = require("../shared/treeSitterUtils");
|
|
9
10
|
const resolvePaths_1 = require("../../coverage/deep-analysis/resolvePaths");
|
|
@@ -52,6 +53,9 @@ class PythonAnalyzer {
|
|
|
52
53
|
const functions = extractPythonFunctions(root, constants);
|
|
53
54
|
const assertions = extractPythonAssertions(root);
|
|
54
55
|
const businessRuleRefs = extractPythonBusinessRefs(root);
|
|
56
|
+
// Feature 27: Flask/FastAPI pattern detection
|
|
57
|
+
const decoratorStacks = extractFlaskDecoratorStacks(root, parsed.filePath);
|
|
58
|
+
const routeRegistrations = extractFlaskRouteRegistrations(root, parsed.filePath);
|
|
55
59
|
return {
|
|
56
60
|
filePath: parsed.filePath,
|
|
57
61
|
language: 'python',
|
|
@@ -63,6 +67,8 @@ class PythonAnalyzer {
|
|
|
63
67
|
assertions,
|
|
64
68
|
businessRuleRefs,
|
|
65
69
|
flowRefs: [],
|
|
70
|
+
decoratorStacks,
|
|
71
|
+
routeRegistrations,
|
|
66
72
|
};
|
|
67
73
|
}
|
|
68
74
|
extractHttpInteractions(model, _ctx) {
|
|
@@ -179,10 +185,12 @@ function extractPythonHttpCalls(body, constants, out) {
|
|
|
179
185
|
if (!funcNode)
|
|
180
186
|
continue;
|
|
181
187
|
const funcText = (_c = funcNode.text) !== null && _c !== void 0 ? _c : '';
|
|
182
|
-
const match = funcText.match(/(?:requests|httpx|self\.client|client|self\.app|app)\.(get|post|put|patch|delete|head|options)/i);
|
|
188
|
+
const match = funcText.match(/(?:requests|httpx|self\.client|client|self\.app|app|testapp|self\.testapp)\.(get|post|post_json|put|put_json|patch|patch_json|delete|delete_json|head|options)/i);
|
|
183
189
|
if (!match)
|
|
184
190
|
continue;
|
|
185
191
|
const [, methodName] = match;
|
|
192
|
+
// Normalize webtest methods: post_json → POST, put_json → PUT, etc.
|
|
193
|
+
const normalizedMethod = methodName.replace(/_json$/i, '').toUpperCase();
|
|
186
194
|
const argList = (_e = (_d = call.childForFieldName) === null || _d === void 0 ? void 0 : _d.call(call, 'arguments')) !== null && _e !== void 0 ? _e : (0, treeSitterUtils_1.firstChildOfType)(call, 'argument_list');
|
|
187
195
|
if (!argList)
|
|
188
196
|
continue;
|
|
@@ -195,7 +203,7 @@ function extractPythonHttpCalls(body, constants, out) {
|
|
|
195
203
|
// Resolve f-string variables against constants
|
|
196
204
|
const resolvedPath = rawPath.includes('{') ? resolveFString(rawPath, constants) : rawPath;
|
|
197
205
|
out.push({
|
|
198
|
-
method:
|
|
206
|
+
method: normalizedMethod,
|
|
199
207
|
rawPathArg: rawPath,
|
|
200
208
|
resolvedPath,
|
|
201
209
|
normalizedPath: resolvedPath.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(resolvedPath) : undefined,
|
|
@@ -290,4 +298,161 @@ function emptyModel(filePath) {
|
|
|
290
298
|
flowRefs: [],
|
|
291
299
|
};
|
|
292
300
|
}
|
|
301
|
+
// ─── Flask / FastAPI pattern extraction (Feature 27) ─────────────────────────
|
|
302
|
+
/**
|
|
303
|
+
* Extract decorator stacks for Flask/FastAPI route functions.
|
|
304
|
+
* Groups decorators by the function they decorate:
|
|
305
|
+
* @blueprint.route('/articles', methods=['GET'])
|
|
306
|
+
* @use_kwargs({...})
|
|
307
|
+
* @marshal_with(ArticleSchema)
|
|
308
|
+
* @jwt_required
|
|
309
|
+
* def get_articles():
|
|
310
|
+
* → DecoratorStack for 'get_articles' with 4 decorators
|
|
311
|
+
*/
|
|
312
|
+
function extractFlaskDecoratorStacks(root, filePath) {
|
|
313
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
314
|
+
const stacks = [];
|
|
315
|
+
const fnDefs = (0, treeSitterUtils_1.findNodes)(root, ['function_definition']);
|
|
316
|
+
for (const fn of fnDefs) {
|
|
317
|
+
const nameNode = (_b = (_a = fn.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(fn, 'name')) !== null && _b !== void 0 ? _b : (0, treeSitterUtils_1.firstChildOfType)(fn, 'identifier');
|
|
318
|
+
const funcName = (_c = nameNode === null || nameNode === void 0 ? void 0 : nameNode.text) !== null && _c !== void 0 ? _c : '';
|
|
319
|
+
if (!funcName)
|
|
320
|
+
continue;
|
|
321
|
+
const decorators = [];
|
|
322
|
+
for (let i = 0; i < ((_d = fn === null || fn === void 0 ? void 0 : fn.childCount) !== null && _d !== void 0 ? _d : 0); i++) {
|
|
323
|
+
const child = fn.child(i);
|
|
324
|
+
if ((child === null || child === void 0 ? void 0 : child.type) !== 'decorator')
|
|
325
|
+
continue;
|
|
326
|
+
const decText = (_e = child.text) !== null && _e !== void 0 ? _e : '';
|
|
327
|
+
const dec = parseFlaskDecorator(decText, (_f = child.startPosition) === null || _f === void 0 ? void 0 : _f.row);
|
|
328
|
+
if (dec)
|
|
329
|
+
decorators.push(dec);
|
|
330
|
+
}
|
|
331
|
+
if (decorators.length > 0) {
|
|
332
|
+
stacks.push({
|
|
333
|
+
functionName: funcName,
|
|
334
|
+
decorators,
|
|
335
|
+
sourceFile: filePath,
|
|
336
|
+
line: ((_g = fn.startPosition) === null || _g === void 0 ? void 0 : _g.row) ? fn.startPosition.row + 1 : undefined,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return stacks;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Parse a single Flask/FastAPI decorator into a DecoratorInfo.
|
|
344
|
+
*/
|
|
345
|
+
function parseFlaskDecorator(text, line) {
|
|
346
|
+
// @blueprint.route('/path', methods=['GET', 'POST'])
|
|
347
|
+
const routeMatch = text.match(/@(\w+)\.route\s*\(\s*['"]([^'"]+)['"]/);
|
|
348
|
+
if (routeMatch) {
|
|
349
|
+
const args = { path: routeMatch[2] };
|
|
350
|
+
const methodsMatch = text.match(/methods\s*=\s*\[([^\]]+)\]/);
|
|
351
|
+
if (methodsMatch)
|
|
352
|
+
args.methods = methodsMatch[1].replace(/['"]/g, '').trim();
|
|
353
|
+
return { name: 'route', fullText: text, args, line };
|
|
354
|
+
}
|
|
355
|
+
// @use_kwargs({...}) or @use_kwargs(SchemaClass)
|
|
356
|
+
const useKwargsMatch = text.match(/@use_kwargs\s*\((.+)\)/s);
|
|
357
|
+
if (useKwargsMatch) {
|
|
358
|
+
return { name: 'use_kwargs', fullText: text, args: { schema: useKwargsMatch[1].trim() }, line };
|
|
359
|
+
}
|
|
360
|
+
// @marshal_with(SchemaClass)
|
|
361
|
+
const marshalMatch = text.match(/@marshal_with\s*\(\s*(\w+)/);
|
|
362
|
+
if (marshalMatch) {
|
|
363
|
+
return { name: 'marshal_with', fullText: text, args: { schema: marshalMatch[1] }, line };
|
|
364
|
+
}
|
|
365
|
+
// @jwt_required, @jwt_required(), @jwt_required(optional=True), @jwt_optional
|
|
366
|
+
if (text.includes('@jwt_required') || text.includes('@jwt_optional')) {
|
|
367
|
+
const isOptional = text.includes('jwt_optional') || text.includes('optional=True') || text.includes('optional = True');
|
|
368
|
+
return {
|
|
369
|
+
name: isOptional ? 'jwt_optional' : 'jwt_required',
|
|
370
|
+
fullText: text,
|
|
371
|
+
args: { optional: isOptional ? 'true' : 'false' },
|
|
372
|
+
line,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// @login_required
|
|
376
|
+
if (text.includes('@login_required')) {
|
|
377
|
+
return { name: 'login_required', fullText: text, args: {}, line };
|
|
378
|
+
}
|
|
379
|
+
// Generic decorator
|
|
380
|
+
const genericMatch = text.match(/@(\w[\w.]*)/);
|
|
381
|
+
if (genericMatch) {
|
|
382
|
+
return { name: genericMatch[1], fullText: text, line };
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Extract Flask Blueprint route registrations.
|
|
388
|
+
* Detects: Blueprint() constructors, register_blueprint() calls,
|
|
389
|
+
* @blueprint.route() registrations.
|
|
390
|
+
*/
|
|
391
|
+
function extractFlaskRouteRegistrations(root, filePath) {
|
|
392
|
+
var _a, _b, _c;
|
|
393
|
+
const registrations = [];
|
|
394
|
+
// Use regex on the full source text since tree-sitter node traversal
|
|
395
|
+
// for call arguments is complex. This is a targeted pattern match.
|
|
396
|
+
const sourceText = (_a = root.text) !== null && _a !== void 0 ? _a : '';
|
|
397
|
+
const lines = sourceText.split('\n');
|
|
398
|
+
for (let i = 0; i < lines.length; i++) {
|
|
399
|
+
const line = lines[i];
|
|
400
|
+
// Blueprint constructor: bp = Blueprint('name', __name__, url_prefix='/api')
|
|
401
|
+
const bpMatch = line.match(/(\w+)\s*=\s*Blueprint\s*\(\s*['"](\w+)['"](?:[^)]*?url_prefix\s*=\s*['"]([^'"]+)['"])?\s*\)/);
|
|
402
|
+
if (bpMatch) {
|
|
403
|
+
registrations.push({
|
|
404
|
+
registrarName: bpMatch[1],
|
|
405
|
+
path: (_b = bpMatch[3]) !== null && _b !== void 0 ? _b : '',
|
|
406
|
+
sourceFile: filePath,
|
|
407
|
+
line: i + 1,
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
// register_blueprint(bp, url_prefix='/api/articles')
|
|
412
|
+
const regMatch = line.match(/register_blueprint\s*\(\s*(\w+)(?:\s*,\s*url_prefix\s*=\s*['"]([^'"]+)['"])?\s*\)/);
|
|
413
|
+
if (regMatch) {
|
|
414
|
+
registrations.push({
|
|
415
|
+
registrarName: regMatch[1],
|
|
416
|
+
path: (_c = regMatch[2]) !== null && _c !== void 0 ? _c : '',
|
|
417
|
+
targetModule: regMatch[1],
|
|
418
|
+
sourceFile: filePath,
|
|
419
|
+
line: i + 1,
|
|
420
|
+
});
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
// @blueprint.route('/path', methods=[...])
|
|
424
|
+
const routeMatch = line.match(/@(\w+)\.route\s*\(\s*['"]([^'"]+)['"]/);
|
|
425
|
+
if (routeMatch) {
|
|
426
|
+
const methodsMatch = line.match(/methods\s*=\s*\[([^\]]+)\]/);
|
|
427
|
+
const methods = methodsMatch
|
|
428
|
+
? methodsMatch[1].replace(/['"]/g, '').split(',').map((m) => m.trim().toUpperCase())
|
|
429
|
+
: ['GET'];
|
|
430
|
+
registrations.push({
|
|
431
|
+
registrarName: routeMatch[1],
|
|
432
|
+
path: routeMatch[2],
|
|
433
|
+
methods,
|
|
434
|
+
sourceFile: filePath,
|
|
435
|
+
line: i + 1,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return registrations;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Classify JWT/auth security from decorator information.
|
|
443
|
+
*/
|
|
444
|
+
function classifyFlaskSecurity(decorators) {
|
|
445
|
+
for (const dec of decorators) {
|
|
446
|
+
if (dec.name === 'jwt_required') {
|
|
447
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: '@jwt_required' };
|
|
448
|
+
}
|
|
449
|
+
if (dec.name === 'jwt_optional') {
|
|
450
|
+
return { type: 'jwt', required: false, optional: true, sourcePattern: '@jwt_optional' };
|
|
451
|
+
}
|
|
452
|
+
if (dec.name === 'login_required') {
|
|
453
|
+
return { type: 'session', required: true, optional: false, sourcePattern: '@login_required' };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
293
458
|
(0, parserRegistry_1.registerAnalyzer)('python', () => new PythonAnalyzer());
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python test pattern detector (Feature 27, Sub-PR 4)
|
|
3
|
+
*
|
|
4
|
+
* Detects:
|
|
5
|
+
* 1. Factory Boy factories (factory.Factory subclasses, SubFactory, Meta.model)
|
|
6
|
+
* 2. webtest API calls (TestApp, testapp.get/post_json/put_json/delete)
|
|
7
|
+
* 3. pytest fixture chains (@pytest.fixture, fixture dependencies)
|
|
8
|
+
*/
|
|
9
|
+
import type { SemanticHttpCall } from '../../ast/astTypes';
|
|
10
|
+
export interface FactoryBoyFactory {
|
|
11
|
+
/** The factory class name (e.g. 'ArticleFactory') */
|
|
12
|
+
className: string;
|
|
13
|
+
/** The model the factory creates (from Meta.model) */
|
|
14
|
+
modelName?: string;
|
|
15
|
+
/** SubFactory references */
|
|
16
|
+
subFactories: string[];
|
|
17
|
+
/** RelatedFactoryList references */
|
|
18
|
+
relatedFactoryLists: string[];
|
|
19
|
+
sourceFile: string;
|
|
20
|
+
line?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect Factory Boy factory classes from source text.
|
|
24
|
+
* Looks for `class XFactory(factory.Factory):` and parses Meta.model, SubFactory, RelatedFactoryList.
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectFactoryBoyFactories(sourceText: string, filePath: string): FactoryBoyFactory[];
|
|
27
|
+
export interface WebtestApiCall {
|
|
28
|
+
method: string;
|
|
29
|
+
path: string;
|
|
30
|
+
sourceFile: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect webtest HTTP calls from source text.
|
|
35
|
+
* Looks for `testapp.get('/path')`, `testapp.post_json('/path', {...})`, etc.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectWebtestCalls(sourceText: string, filePath: string): WebtestApiCall[];
|
|
38
|
+
/**
|
|
39
|
+
* Convert webtest API calls into SemanticHttpCall objects.
|
|
40
|
+
*/
|
|
41
|
+
export declare function webtestCallsToHttpCalls(calls: WebtestApiCall[]): SemanticHttpCall[];
|
|
42
|
+
export interface PytestFixture {
|
|
43
|
+
/** Name of the fixture function */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Scope: 'function' (default), 'class', 'module', 'session' */
|
|
46
|
+
scope: string;
|
|
47
|
+
/** Parameter names that are dependencies on other fixtures */
|
|
48
|
+
dependencies: string[];
|
|
49
|
+
sourceFile: string;
|
|
50
|
+
line?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect @pytest.fixture functions and their parameter dependencies.
|
|
54
|
+
*/
|
|
55
|
+
export declare function detectPytestFixtures(sourceText: string, filePath: string): PytestFixture[];
|
|
56
|
+
/**
|
|
57
|
+
* Detect if a file creates a webtest TestApp instance.
|
|
58
|
+
* Looks for `TestApp(app)` or `TestApp(...)`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function hasWebtestTestApp(sourceText: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a file uses real DB fixtures (not mocked).
|
|
63
|
+
* Looks for pytest fixtures that reference db, database, session, engine, etc.
|
|
64
|
+
*/
|
|
65
|
+
export declare function hasRealDbFixtures(sourceText: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Check if a file uses Factory Boy factories.
|
|
68
|
+
*/
|
|
69
|
+
export declare function usesFactoryBoy(sourceText: string): boolean;
|
|
70
|
+
//# sourceMappingURL=testPatternDetector.d.ts.map
|