@rigour-labs/core 5.0.1 → 5.1.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 +9 -1
- package/dist/gates/agent-team.d.ts +0 -1
- package/dist/gates/agent-team.js +0 -1
- package/dist/gates/checkpoint.d.ts +0 -2
- package/dist/gates/checkpoint.js +0 -2
- package/dist/gates/context-window-artifacts.d.ts +6 -2
- package/dist/gates/context-window-artifacts.js +107 -31
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +41 -11
- package/dist/gates/dependency.d.ts +0 -2
- package/dist/gates/dependency.js +23 -5
- package/dist/gates/deprecated-apis.d.ts +0 -2
- package/dist/gates/deprecated-apis.js +33 -20
- package/dist/gates/duplication-drift/index.d.ts +61 -0
- package/dist/gates/duplication-drift/index.js +240 -0
- package/dist/gates/duplication-drift/similarity.d.ts +68 -0
- package/dist/gates/duplication-drift/similarity.js +177 -0
- package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
- package/dist/gates/duplication-drift/tokenizer.js +195 -0
- package/dist/gates/frontend-secret-exposure.d.ts +0 -3
- package/dist/gates/frontend-secret-exposure.js +1 -114
- package/dist/gates/frontend-secret-patterns.d.ts +33 -0
- package/dist/gates/frontend-secret-patterns.js +119 -0
- package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
- package/dist/gates/hallucinated-imports/index.js +174 -0
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
- package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
- package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
- package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
- package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
- package/dist/gates/hallucinated-imports-lang.js +269 -34
- package/dist/gates/hallucinated-imports.test.js +1 -2
- package/dist/gates/inconsistent-error-handling.d.ts +0 -5
- package/dist/gates/inconsistent-error-handling.js +15 -144
- package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/csharp-adapter.js +211 -0
- package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/go-adapter.js +195 -0
- package/dist/gates/language-adapters/index.d.ts +15 -0
- package/dist/gates/language-adapters/index.js +16 -0
- package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/java-adapter.js +237 -0
- package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/js-adapter.js +279 -0
- package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/python-adapter.js +183 -0
- package/dist/gates/language-adapters/registry.d.ts +26 -0
- package/dist/gates/language-adapters/registry.js +65 -0
- package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/ruby-adapter.js +217 -0
- package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
- package/dist/gates/language-adapters/rust-adapter.js +235 -0
- package/dist/gates/language-adapters/types.d.ts +60 -0
- package/dist/gates/language-adapters/types.js +22 -0
- package/dist/gates/logic-drift-extractors.d.ts +15 -0
- package/dist/gates/logic-drift-extractors.js +34 -0
- package/dist/gates/logic-drift.d.ts +0 -30
- package/dist/gates/logic-drift.js +39 -129
- package/dist/gates/phantom-apis.d.ts +0 -2
- package/dist/gates/phantom-apis.js +49 -20
- package/dist/gates/promise-safety.d.ts +0 -1
- package/dist/gates/promise-safety.js +14 -2
- package/dist/gates/runner.js +51 -22
- package/dist/gates/security-patterns-data.d.ts +14 -0
- package/dist/gates/security-patterns-data.js +235 -0
- package/dist/gates/security-patterns.d.ts +17 -3
- package/dist/gates/security-patterns.js +80 -211
- package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
- package/dist/gates/side-effect-analysis/categorizer.js +83 -0
- package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
- package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
- package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
- package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
- package/dist/gates/side-effect-helpers/index.d.ts +4 -0
- package/dist/gates/side-effect-helpers/index.js +4 -0
- package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
- package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
- package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
- package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
- package/dist/gates/side-effect-helpers/types.d.ts +38 -0
- package/dist/gates/side-effect-helpers/types.js +41 -0
- package/dist/gates/side-effect-rules.d.ts +0 -1
- package/dist/gates/side-effect-rules.js +0 -1
- package/dist/gates/style-drift-rules.d.ts +86 -0
- package/dist/gates/style-drift-rules.js +103 -0
- package/dist/gates/style-drift.d.ts +7 -16
- package/dist/gates/style-drift.js +101 -119
- package/dist/gates/test-quality-matchers.d.ts +53 -0
- package/dist/gates/test-quality-matchers.js +86 -0
- package/dist/gates/test-quality.d.ts +0 -3
- package/dist/gates/test-quality.js +47 -44
- package/dist/hooks/checker.d.ts +0 -1
- package/dist/hooks/checker.js +0 -2
- package/dist/hooks/dlp-templates.d.ts +0 -1
- package/dist/hooks/dlp-templates.js +0 -4
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/input-validator.d.ts +0 -1
- package/dist/hooks/input-validator.js +0 -1
- package/dist/hooks/input-validator.test.js +0 -1
- package/dist/hooks/standalone-checker.d.ts +0 -1
- package/dist/hooks/standalone-checker.js +0 -1
- package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
- package/dist/hooks/standalone-dlp-checker.js +0 -1
- package/dist/hooks/templates.d.ts +0 -1
- package/dist/hooks/templates.js +0 -1
- package/dist/hooks/types.d.ts +0 -1
- package/dist/hooks/types.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/services/adaptive-thresholds.d.ts +0 -2
- package/dist/services/adaptive-thresholds.js +0 -2
- package/dist/services/filesystem-cache.d.ts +0 -1
- package/dist/services/filesystem-cache.js +0 -1
- package/dist/services/score-history.d.ts +0 -1
- package/dist/services/score-history.js +0 -1
- package/dist/services/temporal-drift.d.ts +1 -2
- package/dist/services/temporal-drift.js +7 -8
- package/dist/storage/db.d.ts +23 -7
- package/dist/storage/db.js +116 -55
- package/dist/storage/findings.d.ts +4 -3
- package/dist/storage/findings.js +13 -20
- package/dist/storage/local-memory.d.ts +4 -4
- package/dist/storage/local-memory.js +20 -22
- package/dist/storage/patterns.d.ts +5 -5
- package/dist/storage/patterns.js +20 -26
- package/dist/storage/scans.d.ts +6 -6
- package/dist/storage/scans.js +12 -21
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/scanner.js +1 -1
- package/package.json +7 -8
- package/dist/gates/duplication-drift.d.ts +0 -128
- package/dist/gates/duplication-drift.js +0 -585
- package/dist/gates/hallucinated-imports.js +0 -641
- package/dist/gates/side-effect-helpers.d.ts +0 -260
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { classifyCasing } from './types.js';
|
|
2
|
+
class JavaAdapter {
|
|
3
|
+
id = 'java';
|
|
4
|
+
name = 'Java/Kotlin';
|
|
5
|
+
extensions = ['.java', '.kt'];
|
|
6
|
+
extractFunctions(source) {
|
|
7
|
+
const lines = source.split('\n');
|
|
8
|
+
const functions = [];
|
|
9
|
+
// Java: public/private/protected static? ReturnType methodName(
|
|
10
|
+
const javaMethodRegex = /^\s*(public|private|protected)?\s*(static)?\s*(\w+)\s+(\w+)\s*\(/;
|
|
11
|
+
// Kotlin: fun methodName( or suspend fun methodName(
|
|
12
|
+
const kotlinFunRegex = /^\s*(suspend\s+)?fun\s+(\w+)\s*\(/;
|
|
13
|
+
let i = 0;
|
|
14
|
+
while (i < lines.length) {
|
|
15
|
+
let match = lines[i].match(javaMethodRegex);
|
|
16
|
+
let isKotlin = false;
|
|
17
|
+
let isAsync = false;
|
|
18
|
+
let methodName = '';
|
|
19
|
+
if (!match) {
|
|
20
|
+
match = lines[i].match(kotlinFunRegex);
|
|
21
|
+
if (match) {
|
|
22
|
+
isKotlin = true;
|
|
23
|
+
isAsync = !!match[1];
|
|
24
|
+
methodName = match[2];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
isAsync = false;
|
|
29
|
+
methodName = match[4];
|
|
30
|
+
}
|
|
31
|
+
if (match && methodName) {
|
|
32
|
+
const startLine = i + 1;
|
|
33
|
+
// Find closing brace
|
|
34
|
+
let braceCount = 0;
|
|
35
|
+
let foundOpening = false;
|
|
36
|
+
let j = i;
|
|
37
|
+
let body = '';
|
|
38
|
+
while (j < lines.length) {
|
|
39
|
+
const line = lines[j];
|
|
40
|
+
for (let k = 0; k < line.length; k++) {
|
|
41
|
+
if (line[k] === '{') {
|
|
42
|
+
braceCount++;
|
|
43
|
+
foundOpening = true;
|
|
44
|
+
}
|
|
45
|
+
else if (line[k] === '}') {
|
|
46
|
+
braceCount--;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
body += line + '\n';
|
|
50
|
+
if (foundOpening && braceCount === 0)
|
|
51
|
+
break;
|
|
52
|
+
j++;
|
|
53
|
+
}
|
|
54
|
+
functions.push({
|
|
55
|
+
name: methodName,
|
|
56
|
+
startLine,
|
|
57
|
+
endLine: j + 1,
|
|
58
|
+
body: body.trim(),
|
|
59
|
+
isAsync,
|
|
60
|
+
isExported: lines[i].includes('public'),
|
|
61
|
+
});
|
|
62
|
+
i = j + 1;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return functions;
|
|
69
|
+
}
|
|
70
|
+
extractImports(source) {
|
|
71
|
+
const imports = [];
|
|
72
|
+
const lines = source.split('\n');
|
|
73
|
+
// Java/Kotlin: import package.Class; or import package.*
|
|
74
|
+
const importRegex = /^\s*import\s+([^;]+);/;
|
|
75
|
+
lines.forEach((line, idx) => {
|
|
76
|
+
const match = line.match(importRegex);
|
|
77
|
+
if (match) {
|
|
78
|
+
const fullModule = match[1].trim();
|
|
79
|
+
// Check if wildcard import
|
|
80
|
+
const isWildcard = fullModule.endsWith('.*');
|
|
81
|
+
const module = isWildcard
|
|
82
|
+
? fullModule.slice(0, -2)
|
|
83
|
+
: fullModule.split('.').slice(0, -1).join('.');
|
|
84
|
+
const className = isWildcard
|
|
85
|
+
? '*'
|
|
86
|
+
: fullModule.split('.').pop() || '';
|
|
87
|
+
imports.push({
|
|
88
|
+
module,
|
|
89
|
+
names: className ? [className] : [],
|
|
90
|
+
line: idx + 1,
|
|
91
|
+
isDynamic: false,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return imports;
|
|
96
|
+
}
|
|
97
|
+
extractErrorHandlers(source) {
|
|
98
|
+
const handlers = [];
|
|
99
|
+
const lines = source.split('\n');
|
|
100
|
+
// Match: catch (ExceptionType e) {
|
|
101
|
+
const catchRegex = /catch\s*\(\s*(\w+)\s+\w+\s*\)\s*{/;
|
|
102
|
+
let i = 0;
|
|
103
|
+
while (i < lines.length) {
|
|
104
|
+
const match = lines[i].match(catchRegex);
|
|
105
|
+
if (match) {
|
|
106
|
+
const exceptionType = match[1];
|
|
107
|
+
const startLine = i + 1;
|
|
108
|
+
// Extract catch block
|
|
109
|
+
let braceCount = 0;
|
|
110
|
+
let foundOpening = false;
|
|
111
|
+
let j = i;
|
|
112
|
+
let body = '';
|
|
113
|
+
while (j < lines.length) {
|
|
114
|
+
const line = lines[j];
|
|
115
|
+
for (let k = 0; k < line.length; k++) {
|
|
116
|
+
if (line[k] === '{') {
|
|
117
|
+
braceCount++;
|
|
118
|
+
foundOpening = true;
|
|
119
|
+
}
|
|
120
|
+
else if (line[k] === '}') {
|
|
121
|
+
braceCount--;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
body += line + '\n';
|
|
125
|
+
if (foundOpening && braceCount === 0)
|
|
126
|
+
break;
|
|
127
|
+
j++;
|
|
128
|
+
}
|
|
129
|
+
// Classify strategy
|
|
130
|
+
let strategy = 'swallow';
|
|
131
|
+
if (/\bthrow\s*;/.test(body)) {
|
|
132
|
+
strategy = 'rethrow';
|
|
133
|
+
}
|
|
134
|
+
else if (/\bthrow\s+new\s+/.test(body)) {
|
|
135
|
+
strategy = 'wrap';
|
|
136
|
+
}
|
|
137
|
+
else if (/System\.out\.(print|println)|Log\.(d|e|i|w)/.test(body)) {
|
|
138
|
+
strategy = 'log';
|
|
139
|
+
}
|
|
140
|
+
handlers.push({
|
|
141
|
+
type: 'try-catch',
|
|
142
|
+
strategy,
|
|
143
|
+
startLine,
|
|
144
|
+
body: body.trim(),
|
|
145
|
+
});
|
|
146
|
+
i = j + 1;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
i++;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return handlers;
|
|
153
|
+
}
|
|
154
|
+
extractNamingPatterns(source) {
|
|
155
|
+
const patterns = [];
|
|
156
|
+
// Extract camelCase methods
|
|
157
|
+
const methodRegex = /\b(public|private|protected)?\s*(static)?\s*\w+\s+([a-z]\w*)\s*\(/g;
|
|
158
|
+
let match;
|
|
159
|
+
while ((match = methodRegex.exec(source)) !== null) {
|
|
160
|
+
patterns.push({
|
|
161
|
+
name: match[3],
|
|
162
|
+
kind: 'method',
|
|
163
|
+
convention: classifyCasing(match[3]),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Extract PascalCase class declarations
|
|
167
|
+
const classRegex = /\b(class|interface)\s+([A-Z]\w+)/g;
|
|
168
|
+
while ((match = classRegex.exec(source)) !== null) {
|
|
169
|
+
patterns.push({
|
|
170
|
+
name: match[2],
|
|
171
|
+
kind: 'class',
|
|
172
|
+
convention: classifyCasing(match[2]),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Extract camelCase variables
|
|
176
|
+
const varRegex = /\b(private|protected)?\s*\w+\s+([a-z]\w+)\s*[=;]/g;
|
|
177
|
+
while ((match = varRegex.exec(source)) !== null) {
|
|
178
|
+
patterns.push({
|
|
179
|
+
name: match[2],
|
|
180
|
+
kind: 'variable',
|
|
181
|
+
convention: classifyCasing(match[2]),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// Extract SCREAMING_SNAKE constants
|
|
185
|
+
const constRegex = /\bfinal\s+\w+\s+([A-Z][A-Z0-9_]*)\s*=/g;
|
|
186
|
+
while ((match = constRegex.exec(source)) !== null) {
|
|
187
|
+
patterns.push({
|
|
188
|
+
name: match[1],
|
|
189
|
+
kind: 'constant',
|
|
190
|
+
convention: classifyCasing(match[1]),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return patterns;
|
|
194
|
+
}
|
|
195
|
+
stripComments(source) {
|
|
196
|
+
// Remove // comments
|
|
197
|
+
let result = source.replace(/\/\/.*$/gm, '');
|
|
198
|
+
// Remove /* */ comments (handles multiline)
|
|
199
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
200
|
+
// Kotlin: remove /** */ doc comments
|
|
201
|
+
result = result.replace(/\/\*\*[\s\S]*?\*\//g, '');
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
extractComparisonOps(source) {
|
|
205
|
+
const ops = new Set();
|
|
206
|
+
const opsRegex = /(>=|<=|==|!=|>|<)/g;
|
|
207
|
+
let match;
|
|
208
|
+
while ((match = opsRegex.exec(source)) !== null) {
|
|
209
|
+
ops.add(match[1]);
|
|
210
|
+
}
|
|
211
|
+
return Array.from(ops);
|
|
212
|
+
}
|
|
213
|
+
countBranches(source) {
|
|
214
|
+
const stripped = this.stripComments(source);
|
|
215
|
+
const ifMatches = stripped.match(/\bif\s*\(/g) || [];
|
|
216
|
+
const elseIfMatches = stripped.match(/\belse\s+if\s*\(/g) || [];
|
|
217
|
+
const elseMatches = stripped.match(/\belse\s*{/g) || [];
|
|
218
|
+
const switchMatches = stripped.match(/\bswitch\s*\(/g) || [];
|
|
219
|
+
const caseMatches = stripped.match(/\bcase\s+/g) || [];
|
|
220
|
+
// Kotlin-specific
|
|
221
|
+
const whenMatches = stripped.match(/\bwhen\s*\(/g) || [];
|
|
222
|
+
const isMatches = stripped.match(/\bis\s+/g) || [];
|
|
223
|
+
return (ifMatches.length +
|
|
224
|
+
elseIfMatches.length +
|
|
225
|
+
elseMatches.length +
|
|
226
|
+
switchMatches.length +
|
|
227
|
+
caseMatches.length +
|
|
228
|
+
whenMatches.length +
|
|
229
|
+
isMatches.length);
|
|
230
|
+
}
|
|
231
|
+
countReturns(source) {
|
|
232
|
+
const stripped = this.stripComments(source);
|
|
233
|
+
const returnMatches = stripped.match(/\breturn\b/g) || [];
|
|
234
|
+
return returnMatches.length;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export const javaAdapter = new JavaAdapter();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript/TypeScript Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles: .ts, .tsx, .js, .jsx, .mjs, .cjs
|
|
5
|
+
*
|
|
6
|
+
* Regex-based static analysis for JS/TS code patterns.
|
|
7
|
+
*/
|
|
8
|
+
import type { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
|
|
9
|
+
declare class JsAdapter implements LanguageAdapter {
|
|
10
|
+
readonly id = "js";
|
|
11
|
+
readonly name = "JavaScript/TypeScript";
|
|
12
|
+
readonly extensions: string[];
|
|
13
|
+
extractFunctions(source: string, filePath?: string): FunctionFact[];
|
|
14
|
+
extractImports(source: string): ImportFact[];
|
|
15
|
+
extractErrorHandlers(source: string): ErrorHandlerFact[];
|
|
16
|
+
extractNamingPatterns(source: string): NamingPattern[];
|
|
17
|
+
stripComments(source: string): string;
|
|
18
|
+
extractComparisonOps(source: string): string[];
|
|
19
|
+
countBranches(source: string): number;
|
|
20
|
+
countReturns(source: string): number;
|
|
21
|
+
private extractBraceBody;
|
|
22
|
+
private extractCatchCallbackBody;
|
|
23
|
+
private classifyJsErrorStrategy;
|
|
24
|
+
}
|
|
25
|
+
export declare const jsAdapter: JsAdapter;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript/TypeScript Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles: .ts, .tsx, .js, .jsx, .mjs, .cjs
|
|
5
|
+
*
|
|
6
|
+
* Regex-based static analysis for JS/TS code patterns.
|
|
7
|
+
*/
|
|
8
|
+
import { classifyCasing } from './types.js';
|
|
9
|
+
class JsAdapter {
|
|
10
|
+
id = 'js';
|
|
11
|
+
name = 'JavaScript/TypeScript';
|
|
12
|
+
extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
13
|
+
extractFunctions(source, filePath) {
|
|
14
|
+
const functions = [];
|
|
15
|
+
const lines = source.split('\n');
|
|
16
|
+
// Match: function foo() { ... }, async function foo() { ... }
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const line = lines[i];
|
|
19
|
+
// Traditional function declaration
|
|
20
|
+
let match = line.match(/(?:^|\s)(async\s+)?function\s+(\w+)\s*\(/);
|
|
21
|
+
if (match) {
|
|
22
|
+
const isAsync = !!match[1];
|
|
23
|
+
const name = match[2];
|
|
24
|
+
const body = this.extractBraceBody(lines, i);
|
|
25
|
+
const endLine = i + body.length;
|
|
26
|
+
const isExported = /\bexport\b/.test(lines.slice(Math.max(0, i - 2), i + 1).join(' '));
|
|
27
|
+
functions.push({
|
|
28
|
+
name, startLine: i + 1, endLine: endLine + 1,
|
|
29
|
+
body: body.join('\n'), isAsync, isExported,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Arrow function: const foo = () => { ... }
|
|
33
|
+
match = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(async\s+)?\([^)]*\)\s*=>\s*\{/);
|
|
34
|
+
if (match) {
|
|
35
|
+
const name = match[1];
|
|
36
|
+
const isAsync = !!match[2];
|
|
37
|
+
const body = this.extractBraceBody(lines, i);
|
|
38
|
+
const endLine = i + body.length;
|
|
39
|
+
const isExported = /\bexport\b/.test(lines.slice(Math.max(0, i - 2), i + 1).join(' '));
|
|
40
|
+
functions.push({
|
|
41
|
+
name, startLine: i + 1, endLine: endLine + 1,
|
|
42
|
+
body: body.join('\n'), isAsync, isExported,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Export function: export function foo() { ... }
|
|
46
|
+
match = line.match(/\bexport\s+(?:async\s+)?function\s+(\w+)\s*\(/);
|
|
47
|
+
if (match) {
|
|
48
|
+
const name = match[1];
|
|
49
|
+
const isAsync = /\basync\b/.test(line);
|
|
50
|
+
const body = this.extractBraceBody(lines, i);
|
|
51
|
+
const endLine = i + body.length;
|
|
52
|
+
functions.push({
|
|
53
|
+
name, startLine: i + 1, endLine: endLine + 1,
|
|
54
|
+
body: body.join('\n'), isAsync, isExported: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Class method: methodName() { ... }
|
|
58
|
+
match = line.match(/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/);
|
|
59
|
+
if (match && i > 0) {
|
|
60
|
+
const prevLine = lines[i - 1];
|
|
61
|
+
if (/\bclass\s+\w+/.test(prevLine) || /^\s+constructor\s*\(/.test(line) || !prevLine.includes('function')) {
|
|
62
|
+
const name = match[1];
|
|
63
|
+
const isAsync = /\basync\b/.test(line);
|
|
64
|
+
const body = this.extractBraceBody(lines, i);
|
|
65
|
+
const endLine = i + body.length;
|
|
66
|
+
functions.push({
|
|
67
|
+
name, startLine: i + 1, endLine: endLine + 1,
|
|
68
|
+
body: body.join('\n'), isAsync, isExported: false,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return functions;
|
|
74
|
+
}
|
|
75
|
+
extractImports(source) {
|
|
76
|
+
const imports = [];
|
|
77
|
+
const lines = source.split('\n');
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
const line = lines[i];
|
|
80
|
+
// import x from 'module'
|
|
81
|
+
let match = line.match(/^\s*import\s+(?:\{([^}]+)\}|(\w+)|(\*\s+as\s+(\w+)))\s+from\s+['"]([^'"]+)['"]/);
|
|
82
|
+
if (match) {
|
|
83
|
+
const names = match[1] ? match[1].split(',').map(s => s.trim()) : (match[4] ? [match[4]] : (match[2] ? [match[2]] : []));
|
|
84
|
+
imports.push({
|
|
85
|
+
module: match[5], names, line: i + 1, isDynamic: false,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// require('module')
|
|
89
|
+
match = line.match(/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
90
|
+
if (match) {
|
|
91
|
+
const names = match[1] ? match[1].split(',').map(s => s.trim()) : [match[2]];
|
|
92
|
+
imports.push({
|
|
93
|
+
module: match[3], names, line: i + 1, isDynamic: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// import('module') - dynamic
|
|
97
|
+
match = line.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
98
|
+
if (match) {
|
|
99
|
+
imports.push({
|
|
100
|
+
module: match[1], names: [], line: i + 1, isDynamic: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return imports;
|
|
105
|
+
}
|
|
106
|
+
extractErrorHandlers(source) {
|
|
107
|
+
const handlers = [];
|
|
108
|
+
const lines = source.split('\n');
|
|
109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
110
|
+
const line = lines[i];
|
|
111
|
+
// try { ... } catch (e) { ... }
|
|
112
|
+
if (/\btry\s*\{/.test(line)) {
|
|
113
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
114
|
+
if (/\bcatch\s*\(/.test(lines[j])) {
|
|
115
|
+
const body = this.extractBraceBody(lines, j);
|
|
116
|
+
const strategy = this.classifyJsErrorStrategy(body.join('\n'));
|
|
117
|
+
handlers.push({
|
|
118
|
+
type: 'try-catch', strategy,
|
|
119
|
+
startLine: j + 1, body: body.join('\n'),
|
|
120
|
+
});
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// .catch(e => { ... })
|
|
126
|
+
if (/\.catch\s*\(/.test(line)) {
|
|
127
|
+
const body = this.extractCatchCallbackBody(lines, i);
|
|
128
|
+
if (body) {
|
|
129
|
+
const strategy = this.classifyJsErrorStrategy(body);
|
|
130
|
+
handlers.push({
|
|
131
|
+
type: 'try-catch', strategy,
|
|
132
|
+
startLine: i + 1, body,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return handlers;
|
|
138
|
+
}
|
|
139
|
+
extractNamingPatterns(source) {
|
|
140
|
+
const patterns = [];
|
|
141
|
+
const lines = source.split('\n');
|
|
142
|
+
for (let i = 0; i < lines.length; i++) {
|
|
143
|
+
const line = lines[i];
|
|
144
|
+
// function foo() { ... }
|
|
145
|
+
let match = line.match(/\bfunction\s+(\w+)\s*\(/);
|
|
146
|
+
if (match) {
|
|
147
|
+
patterns.push({
|
|
148
|
+
name: match[1], kind: 'function',
|
|
149
|
+
convention: classifyCasing(match[1]),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// class Foo { ... }
|
|
153
|
+
match = line.match(/\bclass\s+(\w+)/);
|
|
154
|
+
if (match) {
|
|
155
|
+
patterns.push({
|
|
156
|
+
name: match[1], kind: 'class',
|
|
157
|
+
convention: classifyCasing(match[1]),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// const foo = ..., let foo = ..., var foo = ...
|
|
161
|
+
match = line.match(/(?:const|let|var)\s+(\w+)\s*=/);
|
|
162
|
+
if (match && !line.includes('function')) {
|
|
163
|
+
const isFunctionExpr = /=\s*(?:async\s+)?\(/.test(line) || /=>\s*/.test(line);
|
|
164
|
+
const kind = isFunctionExpr ? 'function' : 'variable';
|
|
165
|
+
patterns.push({
|
|
166
|
+
name: match[1], kind,
|
|
167
|
+
convention: classifyCasing(match[1]),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Method names in classes
|
|
171
|
+
match = line.match(/^\s+(\w+)\s*\([^)]*\)\s*\{/);
|
|
172
|
+
if (match && /\bclass\b/.test(lines.slice(Math.max(0, i - 10), i).join(' '))) {
|
|
173
|
+
patterns.push({
|
|
174
|
+
name: match[1], kind: 'method',
|
|
175
|
+
convention: classifyCasing(match[1]),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return patterns;
|
|
180
|
+
}
|
|
181
|
+
stripComments(source) {
|
|
182
|
+
// Remove line comments
|
|
183
|
+
let result = source.replace(/\/\/.*/g, '');
|
|
184
|
+
// Remove block comments
|
|
185
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
extractComparisonOps(source) {
|
|
189
|
+
const ops = [];
|
|
190
|
+
const cleaned = this.stripComments(source);
|
|
191
|
+
const matches = cleaned.matchAll(/(===|!==|==|!=|>=|<=|>(?!=)|<(?!=))/g);
|
|
192
|
+
for (const m of matches) {
|
|
193
|
+
ops.push(m[1]);
|
|
194
|
+
}
|
|
195
|
+
return ops;
|
|
196
|
+
}
|
|
197
|
+
countBranches(source) {
|
|
198
|
+
let count = 0;
|
|
199
|
+
const cleaned = this.stripComments(source);
|
|
200
|
+
count += (cleaned.match(/\bif\s*\(/g) || []).length;
|
|
201
|
+
count += (cleaned.match(/\belse\s+if\s*\(/g) || []).length;
|
|
202
|
+
count += (cleaned.match(/\belse\s*\{/g) || []).length;
|
|
203
|
+
count += (cleaned.match(/\bswitch\s*\(/g) || []).length;
|
|
204
|
+
count += (cleaned.match(/\bcase\s+/g) || []).length;
|
|
205
|
+
count += (cleaned.match(/\?\s*[^?]/g) || []).length;
|
|
206
|
+
return count;
|
|
207
|
+
}
|
|
208
|
+
countReturns(source) {
|
|
209
|
+
return (source.match(/\breturn\b/g) || []).length;
|
|
210
|
+
}
|
|
211
|
+
extractBraceBody(lines, startIndex) {
|
|
212
|
+
let braceDepth = 0;
|
|
213
|
+
let started = false;
|
|
214
|
+
const body = [];
|
|
215
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
216
|
+
const line = lines[i];
|
|
217
|
+
for (const ch of line) {
|
|
218
|
+
if (ch === '{') {
|
|
219
|
+
braceDepth++;
|
|
220
|
+
started = true;
|
|
221
|
+
}
|
|
222
|
+
if (ch === '}')
|
|
223
|
+
braceDepth--;
|
|
224
|
+
}
|
|
225
|
+
if (started)
|
|
226
|
+
body.push(line);
|
|
227
|
+
if (started && braceDepth === 0)
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
return body;
|
|
231
|
+
}
|
|
232
|
+
extractCatchCallbackBody(lines, startLine) {
|
|
233
|
+
const hasArrow = lines[startLine]?.includes('=>');
|
|
234
|
+
let depth = 0;
|
|
235
|
+
let started = false;
|
|
236
|
+
const body = [];
|
|
237
|
+
for (let i = startLine; i < Math.min(startLine + 20, lines.length); i++) {
|
|
238
|
+
for (const ch of lines[i]) {
|
|
239
|
+
if (hasArrow) {
|
|
240
|
+
if (ch === '{') {
|
|
241
|
+
depth++;
|
|
242
|
+
started = true;
|
|
243
|
+
}
|
|
244
|
+
if (ch === '}')
|
|
245
|
+
depth--;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
if (ch === '{' || ch === '(') {
|
|
249
|
+
depth++;
|
|
250
|
+
started = true;
|
|
251
|
+
}
|
|
252
|
+
if (ch === '}' || ch === ')')
|
|
253
|
+
depth--;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (started && i > startLine)
|
|
257
|
+
body.push(lines[i]);
|
|
258
|
+
if (started && depth <= 0)
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
return body.length > 0 ? body.join('\n') : null;
|
|
262
|
+
}
|
|
263
|
+
classifyJsErrorStrategy(body) {
|
|
264
|
+
const trimmed = body.trim();
|
|
265
|
+
if (!trimmed || trimmed === '{}')
|
|
266
|
+
return 'swallow';
|
|
267
|
+
if (/\bthrow\b/.test(trimmed)) {
|
|
268
|
+
if (/\bthrow\s+new\b/.test(trimmed))
|
|
269
|
+
return 'wrap';
|
|
270
|
+
return 'rethrow';
|
|
271
|
+
}
|
|
272
|
+
if (/\breturn\s+null\b|\breturn\s+undefined\b|\breturn\s*;/.test(trimmed))
|
|
273
|
+
return 'return-error';
|
|
274
|
+
if (/console\.(error|warn|log)\b/.test(trimmed))
|
|
275
|
+
return 'log';
|
|
276
|
+
return 'other';
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
export const jsAdapter = new JsAdapter();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles: .py, .pyw
|
|
5
|
+
*
|
|
6
|
+
* Regex-based static analysis for Python code patterns.
|
|
7
|
+
*/
|
|
8
|
+
import type { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
|
|
9
|
+
declare class PythonAdapter implements LanguageAdapter {
|
|
10
|
+
readonly id = "python";
|
|
11
|
+
readonly name = "Python";
|
|
12
|
+
readonly extensions: string[];
|
|
13
|
+
extractFunctions(source: string, filePath?: string): FunctionFact[];
|
|
14
|
+
extractImports(source: string): ImportFact[];
|
|
15
|
+
extractErrorHandlers(source: string): ErrorHandlerFact[];
|
|
16
|
+
extractNamingPatterns(source: string): NamingPattern[];
|
|
17
|
+
stripComments(source: string): string;
|
|
18
|
+
extractComparisonOps(source: string): string[];
|
|
19
|
+
countBranches(source: string): number;
|
|
20
|
+
countReturns(source: string): number;
|
|
21
|
+
private extractIndentBody;
|
|
22
|
+
private classifyPythonErrorStrategy;
|
|
23
|
+
}
|
|
24
|
+
export declare const pythonAdapter: PythonAdapter;
|
|
25
|
+
export {};
|