api-tests-coverage 1.0.0
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/README.md +703 -0
- package/config.yaml.example +227 -0
- package/dist/action/src/index.d.ts +2 -0
- package/dist/action/src/index.d.ts.map +1 -0
- package/dist/action/src/index.js +349 -0
- package/dist/action/src/prComment.d.ts +34 -0
- package/dist/action/src/prComment.d.ts.map +1 -0
- package/dist/action/src/prComment.js +146 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts +36 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts.map +1 -0
- package/dist/src/ast/astAnalysisOrchestrator.js +123 -0
- package/dist/src/ast/astTypes.d.ts +105 -0
- package/dist/src/ast/astTypes.d.ts.map +1 -0
- package/dist/src/ast/astTypes.js +9 -0
- package/dist/src/ast/languageAnalyzer.d.ts +46 -0
- package/dist/src/ast/languageAnalyzer.d.ts.map +1 -0
- package/dist/src/ast/languageAnalyzer.js +9 -0
- package/dist/src/ast/languageCapabilities.d.ts +24 -0
- package/dist/src/ast/languageCapabilities.d.ts.map +1 -0
- package/dist/src/ast/languageCapabilities.js +92 -0
- package/dist/src/ast/parseFile.d.ts +16 -0
- package/dist/src/ast/parseFile.d.ts.map +1 -0
- package/dist/src/ast/parseFile.js +65 -0
- package/dist/src/ast/parserRegistry.d.ts +39 -0
- package/dist/src/ast/parserRegistry.d.ts.map +1 -0
- package/dist/src/ast/parserRegistry.js +66 -0
- package/dist/src/buildSummary.d.ts +26 -0
- package/dist/src/buildSummary.d.ts.map +1 -0
- package/dist/src/buildSummary.js +193 -0
- package/dist/src/businessCoverage.d.ts +68 -0
- package/dist/src/businessCoverage.d.ts.map +1 -0
- package/dist/src/businessCoverage.js +290 -0
- package/dist/src/compatibilityCoverage.d.ts +83 -0
- package/dist/src/compatibilityCoverage.d.ts.map +1 -0
- package/dist/src/compatibilityCoverage.js +501 -0
- package/dist/src/config/defaultConfig.d.ts +9 -0
- package/dist/src/config/defaultConfig.d.ts.map +1 -0
- package/dist/src/config/defaultConfig.js +97 -0
- package/dist/src/config/index.d.ts +12 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +37 -0
- package/dist/src/config/loadConfig.d.ts +29 -0
- package/dist/src/config/loadConfig.d.ts.map +1 -0
- package/dist/src/config/loadConfig.js +135 -0
- package/dist/src/config/mergeConfig.d.ts +15 -0
- package/dist/src/config/mergeConfig.d.ts.map +1 -0
- package/dist/src/config/mergeConfig.js +57 -0
- package/dist/src/config/schema.d.ts +15 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +30 -0
- package/dist/src/config/types.d.ts +175 -0
- package/dist/src/config/types.d.ts.map +1 -0
- package/dist/src/config/types.js +9 -0
- package/dist/src/config/validateConfig.d.ts +22 -0
- package/dist/src/config/validateConfig.d.ts.map +1 -0
- package/dist/src/config/validateConfig.js +171 -0
- package/dist/src/config.d.ts +168 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +204 -0
- package/dist/src/coverage/deep-analysis/callGraph.d.ts +67 -0
- package/dist/src/coverage/deep-analysis/callGraph.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/callGraph.js +275 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts +23 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.js +394 -0
- package/dist/src/coverage/deep-analysis/index.d.ts +17 -0
- package/dist/src/coverage/deep-analysis/index.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/index.js +63 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts +60 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.js +121 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.d.ts +36 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.js +92 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.d.ts +55 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.js +152 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts +70 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.js +152 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.d.ts +80 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.js +216 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts +71 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.js +226 -0
- package/dist/src/coverage/deep-analysis/symbolTable.d.ts +58 -0
- package/dist/src/coverage/deep-analysis/symbolTable.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/symbolTable.js +230 -0
- package/dist/src/coverage/deep-analysis/types.d.ts +122 -0
- package/dist/src/coverage/deep-analysis/types.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/types.js +21 -0
- package/dist/src/discovery/fileClassifier.d.ts +50 -0
- package/dist/src/discovery/fileClassifier.d.ts.map +1 -0
- package/dist/src/discovery/fileClassifier.js +238 -0
- package/dist/src/discovery/projectDiscovery.d.ts +66 -0
- package/dist/src/discovery/projectDiscovery.d.ts.map +1 -0
- package/dist/src/discovery/projectDiscovery.js +287 -0
- package/dist/src/endpointCoverage.d.ts +70 -0
- package/dist/src/endpointCoverage.d.ts.map +1 -0
- package/dist/src/endpointCoverage.js +381 -0
- package/dist/src/errorCoverage.d.ts +93 -0
- package/dist/src/errorCoverage.d.ts.map +1 -0
- package/dist/src/errorCoverage.js +698 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +1441 -0
- package/dist/src/inference/businessRuleInference.d.ts +63 -0
- package/dist/src/inference/businessRuleInference.d.ts.map +1 -0
- package/dist/src/inference/businessRuleInference.js +268 -0
- package/dist/src/inference/integrationFlowInference.d.ts +56 -0
- package/dist/src/inference/integrationFlowInference.d.ts.map +1 -0
- package/dist/src/inference/integrationFlowInference.js +266 -0
- package/dist/src/integrationCoverage.d.ts +72 -0
- package/dist/src/integrationCoverage.d.ts.map +1 -0
- package/dist/src/integrationCoverage.js +317 -0
- package/dist/src/intelligence/index.d.ts +20 -0
- package/dist/src/intelligence/index.d.ts.map +1 -0
- package/dist/src/intelligence/index.js +105 -0
- package/dist/src/intelligence/linkageEngine.d.ts +20 -0
- package/dist/src/intelligence/linkageEngine.d.ts.map +1 -0
- package/dist/src/intelligence/linkageEngine.js +522 -0
- package/dist/src/intelligence/markdownReporter.d.ts +12 -0
- package/dist/src/intelligence/markdownReporter.d.ts.map +1 -0
- package/dist/src/intelligence/markdownReporter.js +265 -0
- package/dist/src/intelligence/riskScoring.d.ts +53 -0
- package/dist/src/intelligence/riskScoring.d.ts.map +1 -0
- package/dist/src/intelligence/riskScoring.js +181 -0
- package/dist/src/intelligence/types.d.ts +121 -0
- package/dist/src/intelligence/types.d.ts.map +1 -0
- package/dist/src/intelligence/types.js +8 -0
- package/dist/src/languageDetection.d.ts +100 -0
- package/dist/src/languageDetection.d.ts.map +1 -0
- package/dist/src/languageDetection.js +349 -0
- package/dist/src/languages/java/index.d.ts +16 -0
- package/dist/src/languages/java/index.d.ts.map +1 -0
- package/dist/src/languages/java/index.js +103 -0
- package/dist/src/languages/java/parser.d.ts +7 -0
- package/dist/src/languages/java/parser.d.ts.map +1 -0
- package/dist/src/languages/java/parser.js +50 -0
- package/dist/src/languages/java/semanticBuilder.d.ts +21 -0
- package/dist/src/languages/java/semanticBuilder.d.ts.map +1 -0
- package/dist/src/languages/java/semanticBuilder.js +358 -0
- package/dist/src/languages/javascript/annotationExtractor.d.ts +20 -0
- package/dist/src/languages/javascript/annotationExtractor.d.ts.map +1 -0
- package/dist/src/languages/javascript/annotationExtractor.js +94 -0
- package/dist/src/languages/javascript/assertionResolver.d.ts +18 -0
- package/dist/src/languages/javascript/assertionResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/assertionResolver.js +150 -0
- package/dist/src/languages/javascript/callResolver.d.ts +23 -0
- package/dist/src/languages/javascript/callResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/callResolver.js +236 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.d.ts +23 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.d.ts.map +1 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.js +205 -0
- package/dist/src/languages/javascript/index.d.ts +20 -0
- package/dist/src/languages/javascript/index.d.ts.map +1 -0
- package/dist/src/languages/javascript/index.js +136 -0
- package/dist/src/languages/javascript/parser.d.ts +14 -0
- package/dist/src/languages/javascript/parser.d.ts.map +1 -0
- package/dist/src/languages/javascript/parser.js +38 -0
- package/dist/src/languages/javascript/symbolResolver.d.ts +31 -0
- package/dist/src/languages/javascript/symbolResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/symbolResolver.js +183 -0
- package/dist/src/languages/kotlin/index.d.ts +16 -0
- package/dist/src/languages/kotlin/index.d.ts.map +1 -0
- package/dist/src/languages/kotlin/index.js +151 -0
- package/dist/src/languages/kotlin/parser.d.ts +11 -0
- package/dist/src/languages/kotlin/parser.d.ts.map +1 -0
- package/dist/src/languages/kotlin/parser.js +74 -0
- package/dist/src/languages/python/index.d.ts +15 -0
- package/dist/src/languages/python/index.d.ts.map +1 -0
- package/dist/src/languages/python/index.js +293 -0
- package/dist/src/languages/ruby/index.d.ts +15 -0
- package/dist/src/languages/ruby/index.d.ts.map +1 -0
- package/dist/src/languages/ruby/index.js +274 -0
- package/dist/src/languages/shared/treeSitterUtils.d.ts +43 -0
- package/dist/src/languages/shared/treeSitterUtils.d.ts.map +1 -0
- package/dist/src/languages/shared/treeSitterUtils.js +100 -0
- package/dist/src/languages/typescript/index.d.ts +14 -0
- package/dist/src/languages/typescript/index.d.ts.map +1 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/lib/index.d.ts +228 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js +486 -0
- package/dist/src/mcp/client/index.d.ts +37 -0
- package/dist/src/mcp/client/index.d.ts.map +1 -0
- package/dist/src/mcp/client/index.js +235 -0
- package/dist/src/mcp/config.d.ts +50 -0
- package/dist/src/mcp/config.d.ts.map +1 -0
- package/dist/src/mcp/config.js +125 -0
- package/dist/src/mcp/events.d.ts +24 -0
- package/dist/src/mcp/events.d.ts.map +1 -0
- package/dist/src/mcp/events.js +48 -0
- package/dist/src/mcp/fallback/index.d.ts +50 -0
- package/dist/src/mcp/fallback/index.d.ts.map +1 -0
- package/dist/src/mcp/fallback/index.js +216 -0
- package/dist/src/mcp/index.d.ts +67 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +212 -0
- package/dist/src/mcp/normalizer.d.ts +21 -0
- package/dist/src/mcp/normalizer.d.ts.map +1 -0
- package/dist/src/mcp/normalizer.js +99 -0
- package/dist/src/mcp/prompts/index.d.ts +86 -0
- package/dist/src/mcp/prompts/index.d.ts.map +1 -0
- package/dist/src/mcp/prompts/index.js +304 -0
- package/dist/src/mcp/templates/index.d.ts +35 -0
- package/dist/src/mcp/templates/index.d.ts.map +1 -0
- package/dist/src/mcp/templates/index.js +143 -0
- package/dist/src/mcp/testing/mock-server/index.d.ts +47 -0
- package/dist/src/mcp/testing/mock-server/index.d.ts.map +1 -0
- package/dist/src/mcp/testing/mock-server/index.js +157 -0
- package/dist/src/mcp/types.d.ts +127 -0
- package/dist/src/mcp/types.d.ts.map +1 -0
- package/dist/src/mcp/types.js +8 -0
- package/dist/src/observability.d.ts +138 -0
- package/dist/src/observability.d.ts.map +1 -0
- package/dist/src/observability.js +519 -0
- package/dist/src/parameterCoverage.d.ts +75 -0
- package/dist/src/parameterCoverage.d.ts.map +1 -0
- package/dist/src/parameterCoverage.js +629 -0
- package/dist/src/perfResilienceCoverage.d.ts +155 -0
- package/dist/src/perfResilienceCoverage.d.ts.map +1 -0
- package/dist/src/perfResilienceCoverage.js +670 -0
- package/dist/src/pluginLoader.d.ts +51 -0
- package/dist/src/pluginLoader.d.ts.map +1 -0
- package/dist/src/pluginLoader.js +72 -0
- package/dist/src/publishing.d.ts +63 -0
- package/dist/src/publishing.d.ts.map +1 -0
- package/dist/src/publishing.js +379 -0
- package/dist/src/qualityGate.d.ts +58 -0
- package/dist/src/qualityGate.d.ts.map +1 -0
- package/dist/src/qualityGate.js +118 -0
- package/dist/src/reporting.d.ts +41 -0
- package/dist/src/reporting.d.ts.map +1 -0
- package/dist/src/reporting.js +278 -0
- package/dist/src/screenshots.d.ts +71 -0
- package/dist/src/screenshots.d.ts.map +1 -0
- package/dist/src/screenshots.js +141 -0
- package/dist/src/security/gate/index.d.ts +11 -0
- package/dist/src/security/gate/index.d.ts.map +1 -0
- package/dist/src/security/gate/index.js +65 -0
- package/dist/src/security/index.d.ts +30 -0
- package/dist/src/security/index.d.ts.map +1 -0
- package/dist/src/security/index.js +342 -0
- package/dist/src/security/normalizers/semgrep.d.ts +10 -0
- package/dist/src/security/normalizers/semgrep.d.ts.map +1 -0
- package/dist/src/security/normalizers/semgrep.js +104 -0
- package/dist/src/security/normalizers/trivy.d.ts +10 -0
- package/dist/src/security/normalizers/trivy.d.ts.map +1 -0
- package/dist/src/security/normalizers/trivy.js +78 -0
- package/dist/src/security/normalizers/zap.d.ts +10 -0
- package/dist/src/security/normalizers/zap.d.ts.map +1 -0
- package/dist/src/security/normalizers/zap.js +104 -0
- package/dist/src/security/scanners/semgrep.d.ts +6 -0
- package/dist/src/security/scanners/semgrep.d.ts.map +1 -0
- package/dist/src/security/scanners/semgrep.js +125 -0
- package/dist/src/security/scanners/trivy.d.ts +6 -0
- package/dist/src/security/scanners/trivy.d.ts.map +1 -0
- package/dist/src/security/scanners/trivy.js +115 -0
- package/dist/src/security/scanners/zap.d.ts +6 -0
- package/dist/src/security/scanners/zap.d.ts.map +1 -0
- package/dist/src/security/scanners/zap.js +135 -0
- package/dist/src/security/types.d.ts +146 -0
- package/dist/src/security/types.d.ts.map +1 -0
- package/dist/src/security/types.js +6 -0
- package/dist/src/securityCoverage.d.ts +116 -0
- package/dist/src/securityCoverage.d.ts.map +1 -0
- package/dist/src/securityCoverage.js +725 -0
- package/dist/src/summary/buildSummary.d.ts +28 -0
- package/dist/src/summary/buildSummary.d.ts.map +1 -0
- package/dist/src/summary/buildSummary.js +257 -0
- package/dist/src/summary/evaluateMetrics.d.ts +31 -0
- package/dist/src/summary/evaluateMetrics.d.ts.map +1 -0
- package/dist/src/summary/evaluateMetrics.js +118 -0
- package/dist/src/summary/index.d.ts +10 -0
- package/dist/src/summary/index.d.ts.map +1 -0
- package/dist/src/summary/index.js +22 -0
- package/dist/src/summary/markdownRenderer.d.ts +139 -0
- package/dist/src/summary/markdownRenderer.d.ts.map +1 -0
- package/dist/src/summary/markdownRenderer.js +459 -0
- package/dist/src/summary/prSummary.d.ts +24 -0
- package/dist/src/summary/prSummary.d.ts.map +1 -0
- package/dist/src/summary/prSummary.js +233 -0
- package/dist/src/summary/summaryTypes.d.ts +35 -0
- package/dist/src/summary/summaryTypes.d.ts.map +1 -0
- package/dist/src/summary/summaryTypes.js +27 -0
- package/package.json +84 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Python language analyzer using tree-sitter-python.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PythonAnalyzer = void 0;
|
|
7
|
+
const parserRegistry_1 = require("../../ast/parserRegistry");
|
|
8
|
+
const treeSitterUtils_1 = require("../shared/treeSitterUtils");
|
|
9
|
+
const resolvePaths_1 = require("../../coverage/deep-analysis/resolvePaths");
|
|
10
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
11
|
+
let cachedParser = undefined;
|
|
12
|
+
let parserLoaded = false;
|
|
13
|
+
function getPythonParser() {
|
|
14
|
+
if (!parserLoaded) {
|
|
15
|
+
parserLoaded = true;
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const grammar = require('tree-sitter-python');
|
|
19
|
+
cachedParser = (0, treeSitterUtils_1.createParser)(grammar);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
cachedParser = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return cachedParser;
|
|
26
|
+
}
|
|
27
|
+
// ─── Analyzer ─────────────────────────────────────────────────────────────────
|
|
28
|
+
class PythonAnalyzer {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.language = 'python';
|
|
31
|
+
}
|
|
32
|
+
parse(filePath, content) {
|
|
33
|
+
const parser = getPythonParser();
|
|
34
|
+
if (!parser) {
|
|
35
|
+
return { filePath, language: 'python', ast: null, content, parseError: new Error('tree-sitter-python not available') };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const tree = parser.parse(content);
|
|
40
|
+
return { filePath, language: 'python', ast: tree, content };
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { filePath, language: 'python', ast: null, content, parseError: err instanceof Error ? err : new Error(String(err)) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
buildSemanticModel(parsed, _context) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const root = (_b = (_a = parsed.ast) === null || _a === void 0 ? void 0 : _a.rootNode) !== null && _b !== void 0 ? _b : parsed.ast;
|
|
49
|
+
if (!root)
|
|
50
|
+
return emptyModel(parsed.filePath);
|
|
51
|
+
const constants = extractPythonConstants(root);
|
|
52
|
+
const functions = extractPythonFunctions(root, constants);
|
|
53
|
+
const assertions = extractPythonAssertions(root);
|
|
54
|
+
const businessRuleRefs = extractPythonBusinessRefs(root);
|
|
55
|
+
return {
|
|
56
|
+
filePath: parsed.filePath,
|
|
57
|
+
language: 'python',
|
|
58
|
+
localVariables: new Map(),
|
|
59
|
+
constants,
|
|
60
|
+
enums: new Map(),
|
|
61
|
+
functions,
|
|
62
|
+
httpInteractions: [],
|
|
63
|
+
assertions,
|
|
64
|
+
businessRuleRefs,
|
|
65
|
+
flowRefs: [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
extractHttpInteractions(model, _ctx) {
|
|
69
|
+
var _a;
|
|
70
|
+
const results = [];
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
for (const fn of model.functions.values()) {
|
|
73
|
+
for (const c of fn.bodyHttpCalls) {
|
|
74
|
+
const path = (_a = c.resolvedPath) !== null && _a !== void 0 ? _a : c.rawPathArg;
|
|
75
|
+
if (!path)
|
|
76
|
+
continue;
|
|
77
|
+
const key = `${c.method}:${path}`;
|
|
78
|
+
if (seen.has(key))
|
|
79
|
+
continue;
|
|
80
|
+
seen.add(key);
|
|
81
|
+
results.push({
|
|
82
|
+
method: c.method,
|
|
83
|
+
path,
|
|
84
|
+
normalizedPath: path.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(path) : undefined,
|
|
85
|
+
sourceFile: model.filePath,
|
|
86
|
+
sourceLanguage: 'python',
|
|
87
|
+
resolutionType: c.resolutionType,
|
|
88
|
+
confidence: c.confidence,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
extractAssertions(model) {
|
|
95
|
+
return model.assertions;
|
|
96
|
+
}
|
|
97
|
+
extractBusinessRuleRefs(model) {
|
|
98
|
+
return model.businessRuleRefs;
|
|
99
|
+
}
|
|
100
|
+
extractFlowRefs(_model) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.PythonAnalyzer = PythonAnalyzer;
|
|
105
|
+
// ─── Symbol extraction ────────────────────────────────────────────────────────
|
|
106
|
+
function extractPythonConstants(root) {
|
|
107
|
+
var _a, _b, _c, _d;
|
|
108
|
+
const map = new Map();
|
|
109
|
+
// Module-level assignments: USERS = '/users' or USERS: str = '/users'
|
|
110
|
+
const assignments = (0, treeSitterUtils_1.findNodes)(root, ['assignment', 'augmented_assignment']);
|
|
111
|
+
for (const assign of assignments) {
|
|
112
|
+
const nameNode = (_b = (_a = assign.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(assign, 'left')) !== null && _b !== void 0 ? _b : (0, treeSitterUtils_1.firstChildOfType)(assign, 'identifier');
|
|
113
|
+
if (!nameNode)
|
|
114
|
+
continue;
|
|
115
|
+
const name = (_c = nameNode.text) !== null && _c !== void 0 ? _c : '';
|
|
116
|
+
// Only module-level UPPER_CASE constants
|
|
117
|
+
if (name === name.toUpperCase() && name.length > 1) {
|
|
118
|
+
const valueNode = (_d = assign.childForFieldName) === null || _d === void 0 ? void 0 : _d.call(assign, 'right');
|
|
119
|
+
const strValue = extractPythonString(valueNode);
|
|
120
|
+
if (strValue !== undefined) {
|
|
121
|
+
map.set(name, { name, kind: 'const', value: strValue, resolvedValue: strValue });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return map;
|
|
126
|
+
}
|
|
127
|
+
function extractPythonFunctions(root, constants) {
|
|
128
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
129
|
+
const graph = new Map();
|
|
130
|
+
const fnDefs = (0, treeSitterUtils_1.findNodes)(root, ['function_definition']);
|
|
131
|
+
for (const fn of fnDefs) {
|
|
132
|
+
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');
|
|
133
|
+
const name = (_c = nameNode === null || nameNode === void 0 ? void 0 : nameNode.text) !== null && _c !== void 0 ? _c : '';
|
|
134
|
+
if (!name)
|
|
135
|
+
continue;
|
|
136
|
+
const decorators = extractPythonDecorators(fn);
|
|
137
|
+
const body = (_e = (_d = fn.childForFieldName) === null || _d === void 0 ? void 0 : _d.call(fn, 'body')) !== null && _e !== void 0 ? _e : (0, treeSitterUtils_1.firstChildOfType)(fn, 'block');
|
|
138
|
+
const bodyHttpCalls = [];
|
|
139
|
+
const calledFunctions = [];
|
|
140
|
+
if (body) {
|
|
141
|
+
extractPythonHttpCalls(body, constants, bodyHttpCalls);
|
|
142
|
+
extractPythonCalls(body, calledFunctions);
|
|
143
|
+
}
|
|
144
|
+
graph.set(name, {
|
|
145
|
+
name,
|
|
146
|
+
parameters: [],
|
|
147
|
+
bodyHttpCalls,
|
|
148
|
+
calledFunctions,
|
|
149
|
+
annotations: decorators,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Also check class methods
|
|
153
|
+
const classDefs = (0, treeSitterUtils_1.findNodes)(root, ['class_definition']);
|
|
154
|
+
for (const cls of classDefs) {
|
|
155
|
+
const clsBody = (_g = (_f = cls.childForFieldName) === null || _f === void 0 ? void 0 : _f.call(cls, 'body')) !== null && _g !== void 0 ? _g : (0, treeSitterUtils_1.firstChildOfType)(cls, 'block');
|
|
156
|
+
if (!clsBody)
|
|
157
|
+
continue;
|
|
158
|
+
const methods = (0, treeSitterUtils_1.findNodes)(clsBody, ['function_definition']);
|
|
159
|
+
for (const method of methods) {
|
|
160
|
+
const nameNode = (_j = (_h = method.childForFieldName) === null || _h === void 0 ? void 0 : _h.call(method, 'name')) !== null && _j !== void 0 ? _j : (0, treeSitterUtils_1.firstChildOfType)(method, 'identifier');
|
|
161
|
+
const name = (_k = nameNode === null || nameNode === void 0 ? void 0 : nameNode.text) !== null && _k !== void 0 ? _k : '';
|
|
162
|
+
if (!name || name === '__init__')
|
|
163
|
+
continue;
|
|
164
|
+
const methodBody = (_m = (_l = method.childForFieldName) === null || _l === void 0 ? void 0 : _l.call(method, 'body')) !== null && _m !== void 0 ? _m : (0, treeSitterUtils_1.firstChildOfType)(method, 'block');
|
|
165
|
+
const bodyHttpCalls = [];
|
|
166
|
+
if (methodBody)
|
|
167
|
+
extractPythonHttpCalls(methodBody, constants, bodyHttpCalls);
|
|
168
|
+
graph.set(name, { name, parameters: [], bodyHttpCalls, calledFunctions: [] });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return graph;
|
|
172
|
+
}
|
|
173
|
+
function extractPythonHttpCalls(body, constants, out) {
|
|
174
|
+
var _a, _b, _c, _d, _e, _f;
|
|
175
|
+
// requests.get(url), httpx.post(url), self.client.get(url), client.get(url)
|
|
176
|
+
const calls = (0, treeSitterUtils_1.findNodes)(body, ['call']);
|
|
177
|
+
for (const call of calls) {
|
|
178
|
+
const funcNode = (_b = (_a = call.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(call, 'function')) !== null && _b !== void 0 ? _b : (0, treeSitterUtils_1.firstChildOfType)(call, 'attribute');
|
|
179
|
+
if (!funcNode)
|
|
180
|
+
continue;
|
|
181
|
+
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);
|
|
183
|
+
if (!match)
|
|
184
|
+
continue;
|
|
185
|
+
const [, methodName] = match;
|
|
186
|
+
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
|
+
if (!argList)
|
|
188
|
+
continue;
|
|
189
|
+
const pathArg = (_f = (0, treeSitterUtils_1.firstChildOfType)(argList, 'string')) !== null && _f !== void 0 ? _f : (0, treeSitterUtils_1.firstChildOfType)(argList, 'concatenated_string');
|
|
190
|
+
if (!pathArg)
|
|
191
|
+
continue;
|
|
192
|
+
const rawPath = extractPythonString(pathArg);
|
|
193
|
+
if (!rawPath)
|
|
194
|
+
continue;
|
|
195
|
+
// Resolve f-string variables against constants
|
|
196
|
+
const resolvedPath = rawPath.includes('{') ? resolveFString(rawPath, constants) : rawPath;
|
|
197
|
+
out.push({
|
|
198
|
+
method: methodName.toUpperCase(),
|
|
199
|
+
rawPathArg: rawPath,
|
|
200
|
+
resolvedPath,
|
|
201
|
+
normalizedPath: resolvedPath.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(resolvedPath) : undefined,
|
|
202
|
+
resolutionType: rawPath === resolvedPath ? 'direct' : 'interpolated-path',
|
|
203
|
+
confidence: rawPath === resolvedPath ? 'high' : 'medium',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function extractPythonAssertions(root) {
|
|
208
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
209
|
+
const assertions = [];
|
|
210
|
+
const assertStmts = (0, treeSitterUtils_1.findNodes)(root, ['assert_statement']);
|
|
211
|
+
for (const stmt of assertStmts) {
|
|
212
|
+
const text = (_a = stmt.text) !== null && _a !== void 0 ? _a : '';
|
|
213
|
+
const type = text.includes('status_code') || text.includes('status') ? 'status-code' : 'body-field';
|
|
214
|
+
const varMatch = text.match(/assert\s+(\w+)\./);
|
|
215
|
+
assertions.push({ assertionType: type, subjectVariable: varMatch === null || varMatch === void 0 ? void 0 : varMatch[1] });
|
|
216
|
+
}
|
|
217
|
+
// self.assertEqual(response.status_code, 200)
|
|
218
|
+
const calls = (0, treeSitterUtils_1.findNodes)(root, ['call']);
|
|
219
|
+
for (const call of calls) {
|
|
220
|
+
const funcText = (_d = (_c = (_b = call.childForFieldName) === null || _b === void 0 ? void 0 : _b.call(call, 'function')) === null || _c === void 0 ? void 0 : _c.text) !== null && _d !== void 0 ? _d : '';
|
|
221
|
+
if (funcText.includes('assertEqual') || funcText.includes('assertStatus')) {
|
|
222
|
+
const argText = (_g = (_f = (_e = call.childForFieldName) === null || _e === void 0 ? void 0 : _e.call(call, 'arguments')) === null || _f === void 0 ? void 0 : _f.text) !== null && _g !== void 0 ? _g : '';
|
|
223
|
+
const varMatch = argText.match(/(\w+)\.status/);
|
|
224
|
+
assertions.push({
|
|
225
|
+
assertionType: 'status-code',
|
|
226
|
+
subjectVariable: varMatch === null || varMatch === void 0 ? void 0 : varMatch[1],
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return assertions;
|
|
231
|
+
}
|
|
232
|
+
function extractPythonBusinessRefs(root) {
|
|
233
|
+
var _a;
|
|
234
|
+
const refs = [];
|
|
235
|
+
const decorators = (0, treeSitterUtils_1.findNodes)(root, ['decorator']);
|
|
236
|
+
for (const dec of decorators) {
|
|
237
|
+
const text = (_a = dec.text) !== null && _a !== void 0 ? _a : '';
|
|
238
|
+
const match = text.match(/@(?:pytest\.mark\.|mark\.)?(?:businessRule|business_rule)\s*\(\s*["']([^"']+)/);
|
|
239
|
+
if (match)
|
|
240
|
+
refs.push({ ruleId: match[1], source: 'decorator' });
|
|
241
|
+
}
|
|
242
|
+
return refs;
|
|
243
|
+
}
|
|
244
|
+
function extractPythonDecorators(fn) {
|
|
245
|
+
var _a, _b;
|
|
246
|
+
const decorators = [];
|
|
247
|
+
for (let i = 0; i < ((_a = fn === null || fn === void 0 ? void 0 : fn.childCount) !== null && _a !== void 0 ? _a : 0); i++) {
|
|
248
|
+
const child = fn.child(i);
|
|
249
|
+
if ((child === null || child === void 0 ? void 0 : child.type) === 'decorator')
|
|
250
|
+
decorators.push((_b = child.text) !== null && _b !== void 0 ? _b : '');
|
|
251
|
+
}
|
|
252
|
+
return decorators;
|
|
253
|
+
}
|
|
254
|
+
function extractPythonCalls(body, out) {
|
|
255
|
+
var _a, _b, _c;
|
|
256
|
+
const calls = (0, treeSitterUtils_1.findNodes)(body, ['call']);
|
|
257
|
+
for (const call of calls) {
|
|
258
|
+
const func = (_c = (_b = (_a = call.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(call, 'function')) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : '';
|
|
259
|
+
if (func && !out.includes(func))
|
|
260
|
+
out.push(func);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function extractPythonString(node) {
|
|
264
|
+
var _a;
|
|
265
|
+
if (!node)
|
|
266
|
+
return undefined;
|
|
267
|
+
const raw = (_a = node.text) !== null && _a !== void 0 ? _a : '';
|
|
268
|
+
if ((raw.startsWith('"') || raw.startsWith("'") || raw.startsWith('f"') || raw.startsWith("f'"))) {
|
|
269
|
+
return raw.replace(/^f?["']|["']$/g, '');
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
function resolveFString(template, constants) {
|
|
274
|
+
return template.replace(/\{(\w+)\}/g, (_match, name) => {
|
|
275
|
+
var _a, _b;
|
|
276
|
+
return (_b = (_a = constants.get(name)) === null || _a === void 0 ? void 0 : _a.resolvedValue) !== null && _b !== void 0 ? _b : `{${name}}`;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function emptyModel(filePath) {
|
|
280
|
+
return {
|
|
281
|
+
filePath,
|
|
282
|
+
language: 'python',
|
|
283
|
+
localVariables: new Map(),
|
|
284
|
+
constants: new Map(),
|
|
285
|
+
enums: new Map(),
|
|
286
|
+
functions: new Map(),
|
|
287
|
+
httpInteractions: [],
|
|
288
|
+
assertions: [],
|
|
289
|
+
businessRuleRefs: [],
|
|
290
|
+
flowRefs: [],
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
(0, parserRegistry_1.registerAnalyzer)('python', () => new PythonAnalyzer());
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruby language analyzer using tree-sitter-ruby.
|
|
3
|
+
*/
|
|
4
|
+
import type { LanguageAnalyzer } from '../../ast/languageAnalyzer';
|
|
5
|
+
import type { SupportedLanguage, ParsedSourceFile, SemanticModel, ResolvedHttpInteraction, SemanticAssertion, BusinessRuleRef, FlowRef, AnalysisContext } from '../../ast/astTypes';
|
|
6
|
+
export declare class RubyAnalyzer implements LanguageAnalyzer {
|
|
7
|
+
readonly language: SupportedLanguage;
|
|
8
|
+
parse(filePath: string, content: string): ParsedSourceFile;
|
|
9
|
+
buildSemanticModel(parsed: ParsedSourceFile, _context: AnalysisContext): SemanticModel;
|
|
10
|
+
extractHttpInteractions(model: SemanticModel, _ctx: AnalysisContext): ResolvedHttpInteraction[];
|
|
11
|
+
extractAssertions(model: SemanticModel): SemanticAssertion[];
|
|
12
|
+
extractBusinessRuleRefs(_model: SemanticModel): BusinessRuleRef[];
|
|
13
|
+
extractFlowRefs(_model: SemanticModel): FlowRef[];
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/languages/ruby/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,EAChB,MAAM,oBAAoB,CAAC;AA8B5B,qBAAa,YAAa,YAAW,gBAAgB;IACnD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAU;IAE9C,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB;IAc1D,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,GAAG,aAAa;IAsBtF,uBAAuB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,eAAe,GAAG,uBAAuB,EAAE;IAyB/F,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB,EAAE;IAG5D,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,EAAE;IAGjE,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,EAAE;CAGlD"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ruby language analyzer using tree-sitter-ruby.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RubyAnalyzer = void 0;
|
|
7
|
+
const parserRegistry_1 = require("../../ast/parserRegistry");
|
|
8
|
+
const treeSitterUtils_1 = require("../shared/treeSitterUtils");
|
|
9
|
+
const resolvePaths_1 = require("../../coverage/deep-analysis/resolvePaths");
|
|
10
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
11
|
+
let cachedParser = undefined;
|
|
12
|
+
let parserLoaded = false;
|
|
13
|
+
function getRubyParser() {
|
|
14
|
+
if (!parserLoaded) {
|
|
15
|
+
parserLoaded = true;
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const grammar = require('tree-sitter-ruby');
|
|
19
|
+
cachedParser = (0, treeSitterUtils_1.createParser)(grammar);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
cachedParser = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return cachedParser;
|
|
26
|
+
}
|
|
27
|
+
// ─── Analyzer ─────────────────────────────────────────────────────────────────
|
|
28
|
+
class RubyAnalyzer {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.language = 'ruby';
|
|
31
|
+
}
|
|
32
|
+
parse(filePath, content) {
|
|
33
|
+
const parser = getRubyParser();
|
|
34
|
+
if (!parser) {
|
|
35
|
+
return { filePath, language: 'ruby', ast: null, content, parseError: new Error('tree-sitter-ruby not available') };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const tree = parser.parse(content);
|
|
40
|
+
return { filePath, language: 'ruby', ast: tree, content };
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { filePath, language: 'ruby', ast: null, content, parseError: err instanceof Error ? err : new Error(String(err)) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
buildSemanticModel(parsed, _context) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const root = (_b = (_a = parsed.ast) === null || _a === void 0 ? void 0 : _a.rootNode) !== null && _b !== void 0 ? _b : parsed.ast;
|
|
49
|
+
if (!root)
|
|
50
|
+
return emptyModel(parsed.filePath);
|
|
51
|
+
const constants = extractRubyConstants(root);
|
|
52
|
+
const functions = extractRubyFunctions(root, constants);
|
|
53
|
+
const assertions = extractRubyAssertions(root);
|
|
54
|
+
return {
|
|
55
|
+
filePath: parsed.filePath,
|
|
56
|
+
language: 'ruby',
|
|
57
|
+
localVariables: new Map(),
|
|
58
|
+
constants,
|
|
59
|
+
enums: new Map(),
|
|
60
|
+
functions,
|
|
61
|
+
httpInteractions: [],
|
|
62
|
+
assertions,
|
|
63
|
+
businessRuleRefs: [],
|
|
64
|
+
flowRefs: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
extractHttpInteractions(model, _ctx) {
|
|
68
|
+
var _a;
|
|
69
|
+
const results = [];
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const fn of model.functions.values()) {
|
|
72
|
+
for (const c of fn.bodyHttpCalls) {
|
|
73
|
+
const path = (_a = c.resolvedPath) !== null && _a !== void 0 ? _a : c.rawPathArg;
|
|
74
|
+
if (!path)
|
|
75
|
+
continue;
|
|
76
|
+
const key = `${c.method}:${path}`;
|
|
77
|
+
if (seen.has(key))
|
|
78
|
+
continue;
|
|
79
|
+
seen.add(key);
|
|
80
|
+
results.push({
|
|
81
|
+
method: c.method,
|
|
82
|
+
path,
|
|
83
|
+
normalizedPath: path.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(path) : undefined,
|
|
84
|
+
sourceFile: model.filePath,
|
|
85
|
+
sourceLanguage: 'ruby',
|
|
86
|
+
resolutionType: fn.cucumberPattern ? 'cucumber-step' : c.resolutionType,
|
|
87
|
+
confidence: c.confidence,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
extractAssertions(model) {
|
|
94
|
+
return model.assertions;
|
|
95
|
+
}
|
|
96
|
+
extractBusinessRuleRefs(_model) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
extractFlowRefs(_model) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.RubyAnalyzer = RubyAnalyzer;
|
|
104
|
+
// ─── Symbol extraction ────────────────────────────────────────────────────────
|
|
105
|
+
function extractRubyConstants(root) {
|
|
106
|
+
var _a, _b, _c, _d, _e, _f;
|
|
107
|
+
const map = new Map();
|
|
108
|
+
// Ruby constants: USERS = '/users'
|
|
109
|
+
const assigns = (0, treeSitterUtils_1.findNodes)(root, ['assignment']);
|
|
110
|
+
for (const assign of assigns) {
|
|
111
|
+
const left = (_b = (_a = assign.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(assign, 'left')) !== null && _b !== void 0 ? _b : (0, treeSitterUtils_1.firstChildOfType)(assign, 'constant');
|
|
112
|
+
const right = (_c = assign.childForFieldName) === null || _c === void 0 ? void 0 : _c.call(assign, 'right');
|
|
113
|
+
if (!left || !right)
|
|
114
|
+
continue;
|
|
115
|
+
const name = (_d = left.text) !== null && _d !== void 0 ? _d : '';
|
|
116
|
+
if (!name || name !== name.toUpperCase())
|
|
117
|
+
continue;
|
|
118
|
+
const value = (_e = (0, treeSitterUtils_1.extractStringValue)(right)) !== null && _e !== void 0 ? _e : (_f = right.text) === null || _f === void 0 ? void 0 : _f.replace(/^["']|["']$/g, '');
|
|
119
|
+
if (value) {
|
|
120
|
+
map.set(name, { name, kind: 'const', value, resolvedValue: value });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return map;
|
|
124
|
+
}
|
|
125
|
+
function extractRubyFunctions(root, constants) {
|
|
126
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
127
|
+
const graph = new Map();
|
|
128
|
+
// Regular method definitions: def method_name; end
|
|
129
|
+
const methodDefs = (0, treeSitterUtils_1.findNodes)(root, ['method', 'singleton_method']);
|
|
130
|
+
for (const method of methodDefs) {
|
|
131
|
+
const nameNode = (_b = (_a = method.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(method, 'name')) !== null && _b !== void 0 ? _b : (0, treeSitterUtils_1.firstChildOfType)(method, 'identifier');
|
|
132
|
+
const name = (_c = nameNode === null || nameNode === void 0 ? void 0 : nameNode.text) !== null && _c !== void 0 ? _c : '';
|
|
133
|
+
if (!name)
|
|
134
|
+
continue;
|
|
135
|
+
const bodyHttpCalls = [];
|
|
136
|
+
extractRubyHttpCalls(method, constants, bodyHttpCalls);
|
|
137
|
+
graph.set(name, { name, parameters: [], bodyHttpCalls, calledFunctions: [] });
|
|
138
|
+
}
|
|
139
|
+
// RSpec blocks: it 'does something' do ... end
|
|
140
|
+
const calls = (0, treeSitterUtils_1.findNodes)(root, ['call']);
|
|
141
|
+
for (const call of calls) {
|
|
142
|
+
const methodName = (_f = (_e = (_d = call.childForFieldName) === null || _d === void 0 ? void 0 : _d.call(call, 'method')) === null || _e === void 0 ? void 0 : _e.text) !== null && _f !== void 0 ? _f : '';
|
|
143
|
+
const isCucumber = ['Given', 'When', 'Then', 'And', 'But'].includes(methodName);
|
|
144
|
+
const isRSpec = ['it', 'specify', 'example', 'before', 'after', 'context', 'describe', 'scenario'].includes(methodName);
|
|
145
|
+
if (!isCucumber && !isRSpec)
|
|
146
|
+
continue;
|
|
147
|
+
const blockNode = (_g = (0, treeSitterUtils_1.firstChildOfType)(call, 'do_block')) !== null && _g !== void 0 ? _g : (0, treeSitterUtils_1.firstChildOfType)(call, 'block');
|
|
148
|
+
if (!blockNode)
|
|
149
|
+
continue;
|
|
150
|
+
// Extract label for Cucumber (first string arg)
|
|
151
|
+
let blockName = methodName;
|
|
152
|
+
let cucumberPattern;
|
|
153
|
+
if (call.arguments) {
|
|
154
|
+
const firstArg = (_h = (0, treeSitterUtils_1.firstChildOfType)(call.arguments, 'string')) !== null && _h !== void 0 ? _h : (0, treeSitterUtils_1.firstChildOfType)(call.arguments, 'regex');
|
|
155
|
+
if (firstArg) {
|
|
156
|
+
const label = (_k = (_j = (0, treeSitterUtils_1.extractStringValue)(firstArg)) !== null && _j !== void 0 ? _j : firstArg.text) !== null && _k !== void 0 ? _k : '';
|
|
157
|
+
blockName = `${methodName}:${label}`;
|
|
158
|
+
if (isCucumber)
|
|
159
|
+
cucumberPattern = label;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const bodyHttpCalls = [];
|
|
163
|
+
extractRubyHttpCalls(blockNode, constants, bodyHttpCalls);
|
|
164
|
+
graph.set(blockName, {
|
|
165
|
+
name: blockName,
|
|
166
|
+
parameters: [],
|
|
167
|
+
bodyHttpCalls,
|
|
168
|
+
calledFunctions: [],
|
|
169
|
+
cucumberPattern,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return graph;
|
|
173
|
+
}
|
|
174
|
+
function extractRubyHttpCalls(node, constants, out) {
|
|
175
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
176
|
+
const calls = (0, treeSitterUtils_1.findNodes)(node, ['call']);
|
|
177
|
+
for (const call of calls) {
|
|
178
|
+
const methodName = (_c = (_b = (_a = call.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(call, 'method')) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : '';
|
|
179
|
+
const lowerMethod = methodName.toLowerCase();
|
|
180
|
+
const HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
|
|
181
|
+
if (!HTTP_METHODS.has(lowerMethod))
|
|
182
|
+
continue;
|
|
183
|
+
// Get the args — first arg is path
|
|
184
|
+
const args = (_d = call.childForFieldName) === null || _d === void 0 ? void 0 : _d.call(call, 'arguments');
|
|
185
|
+
if (!args) {
|
|
186
|
+
// Rails style: just `get '/path'` with no explicit parentheses
|
|
187
|
+
// Try: call text is "get '/path'"
|
|
188
|
+
const callText = (_e = call.text) !== null && _e !== void 0 ? _e : '';
|
|
189
|
+
const railsMatch = callText.match(/^(?:get|post|put|patch|delete)\s+["'`]([^"'`]+)["'`]/);
|
|
190
|
+
if (railsMatch) {
|
|
191
|
+
const path = railsMatch[1];
|
|
192
|
+
out.push({
|
|
193
|
+
method: lowerMethod.toUpperCase(),
|
|
194
|
+
rawPathArg: path,
|
|
195
|
+
resolvedPath: path,
|
|
196
|
+
normalizedPath: path.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(path) : undefined,
|
|
197
|
+
resolutionType: 'direct',
|
|
198
|
+
confidence: 'high',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const firstArg = (_g = (_f = (0, treeSitterUtils_1.firstChildOfType)(args, 'string')) !== null && _f !== void 0 ? _f : (0, treeSitterUtils_1.firstChildOfType)(args, 'simple_string')) !== null && _g !== void 0 ? _g : (0, treeSitterUtils_1.firstChildOfType)(args, 'constant');
|
|
204
|
+
if (!firstArg)
|
|
205
|
+
continue;
|
|
206
|
+
let rawPath = (_j = (_h = (0, treeSitterUtils_1.extractStringValue)(firstArg)) !== null && _h !== void 0 ? _h : firstArg.text) !== null && _j !== void 0 ? _j : '';
|
|
207
|
+
rawPath = rawPath.replace(/^["'`]|["'`]$/g, '');
|
|
208
|
+
// Resolve Ruby constants
|
|
209
|
+
if (firstArg.type === 'constant') {
|
|
210
|
+
const resolved = (_l = (_k = constants.get(rawPath)) === null || _k === void 0 ? void 0 : _k.resolvedValue) !== null && _l !== void 0 ? _l : rawPath;
|
|
211
|
+
out.push({
|
|
212
|
+
method: lowerMethod.toUpperCase(),
|
|
213
|
+
rawPathArg: rawPath,
|
|
214
|
+
resolvedPath: resolved,
|
|
215
|
+
normalizedPath: resolved.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(resolved) : undefined,
|
|
216
|
+
resolutionType: rawPath === resolved ? 'direct' : 'constant',
|
|
217
|
+
confidence: rawPath === resolved ? 'high' : 'medium',
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// Resolve Ruby string interpolation: "/users/#{user.id}"
|
|
222
|
+
const resolvedPath = resolveRubyInterpolation(rawPath, constants);
|
|
223
|
+
out.push({
|
|
224
|
+
method: lowerMethod.toUpperCase(),
|
|
225
|
+
rawPathArg: rawPath,
|
|
226
|
+
resolvedPath,
|
|
227
|
+
normalizedPath: resolvedPath.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(resolvedPath) : undefined,
|
|
228
|
+
resolutionType: rawPath.includes('#{') ? 'interpolated-path' : 'direct',
|
|
229
|
+
confidence: 'high',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function extractRubyAssertions(root) {
|
|
234
|
+
var _a;
|
|
235
|
+
const assertions = [];
|
|
236
|
+
// expect(response.status).to eq(200) or expect(response.code).to eq('200')
|
|
237
|
+
const calls = (0, treeSitterUtils_1.findNodes)(root, ['call']);
|
|
238
|
+
for (const call of calls) {
|
|
239
|
+
const text = (_a = call.text) !== null && _a !== void 0 ? _a : '';
|
|
240
|
+
if (text.includes('expect(') && (text.includes('.status') || text.includes('.code'))) {
|
|
241
|
+
const varMatch = text.match(/expect\((\w+)\./);
|
|
242
|
+
assertions.push({
|
|
243
|
+
assertionType: 'status-code',
|
|
244
|
+
subjectVariable: varMatch === null || varMatch === void 0 ? void 0 : varMatch[1],
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (text.includes('should have_http_status') || text.includes('have_http_status')) {
|
|
248
|
+
assertions.push({ assertionType: 'status-code' });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return assertions;
|
|
252
|
+
}
|
|
253
|
+
function resolveRubyInterpolation(template, constants) {
|
|
254
|
+
return template.replace(/#\{([^}]+)\}/g, (_match, expr) => {
|
|
255
|
+
var _a, _b;
|
|
256
|
+
const trimmed = expr.trim();
|
|
257
|
+
return (_b = (_a = constants.get(trimmed)) === null || _a === void 0 ? void 0 : _a.resolvedValue) !== null && _b !== void 0 ? _b : `{${trimmed}}`;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
function emptyModel(filePath) {
|
|
261
|
+
return {
|
|
262
|
+
filePath,
|
|
263
|
+
language: 'ruby',
|
|
264
|
+
localVariables: new Map(),
|
|
265
|
+
constants: new Map(),
|
|
266
|
+
enums: new Map(),
|
|
267
|
+
functions: new Map(),
|
|
268
|
+
httpInteractions: [],
|
|
269
|
+
assertions: [],
|
|
270
|
+
businessRuleRefs: [],
|
|
271
|
+
flowRefs: [],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
(0, parserRegistry_1.registerAnalyzer)('ruby', () => new RubyAnalyzer());
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tree-sitter utilities.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the tree-sitter native module with lazy loading and graceful
|
|
5
|
+
* fallback. Every tree-sitter language module calls `createParser()` and
|
|
6
|
+
* treats a null return as "fall back to regex analysis".
|
|
7
|
+
*/
|
|
8
|
+
export type TsNode = any;
|
|
9
|
+
export type TsTree = any;
|
|
10
|
+
export type TsParser = any;
|
|
11
|
+
/**
|
|
12
|
+
* Create a tree-sitter parser for the given language grammar.
|
|
13
|
+
*
|
|
14
|
+
* @param grammarModule The `require()`-resolved grammar object from tree-sitter-*
|
|
15
|
+
* @returns A configured Parser instance, or null if native loading fails.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createParser(grammarModule: unknown): TsParser | null;
|
|
18
|
+
/**
|
|
19
|
+
* Recursively walk a tree-sitter node tree, calling `visitor` on each node.
|
|
20
|
+
* Pass `nodeTypes` to filter — visitor is only called when node.type matches.
|
|
21
|
+
*/
|
|
22
|
+
export declare function walkTree(node: TsNode, visitor: (n: TsNode) => void, nodeTypes?: Set<string>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Collect all descendant nodes whose type is in `nodeTypes`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findNodes(root: TsNode, nodeTypes: string[]): TsNode[];
|
|
27
|
+
/**
|
|
28
|
+
* Get the text of a named child field, or undefined.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fieldText(node: TsNode, fieldName: string): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Get the first child of a given type.
|
|
33
|
+
*/
|
|
34
|
+
export declare function firstChildOfType(node: TsNode, type: string): TsNode | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a node has a child of the given type.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasChildOfType(node: TsNode, type: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Extract an unquoted string value from a string_literal / string node.
|
|
41
|
+
*/
|
|
42
|
+
export declare function extractStringValue(node: TsNode): string | undefined;
|
|
43
|
+
//# sourceMappingURL=treeSitterUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"treeSitterUtils.d.ts","sourceRoot":"","sources":["../../../../src/languages/shared/treeSitterUtils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,MAAM,MAAM,MAAM,GAAG,GAAG,CAAC;AAEzB,MAAM,MAAM,MAAM,GAAG,GAAG,CAAC;AAEzB,MAAM,MAAM,QAAQ,GAAG,GAAG,CAAC;AAE3B;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,aAAa,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAUpE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC5B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,IAAI,CAQN;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKrE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM/E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAYnE"}
|