@rigour-labs/core 5.0.0 → 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 +1 -3
- 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 +6 -1
- package/dist/hooks/templates.js +6 -1
- package/dist/hooks/types.d.ts +1 -2
- package/dist/hooks/types.js +1 -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,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles: .py, .pyw
|
|
5
|
+
*
|
|
6
|
+
* Regex-based static analysis for Python code patterns.
|
|
7
|
+
*/
|
|
8
|
+
import { classifyCasing } from './types.js';
|
|
9
|
+
class PythonAdapter {
|
|
10
|
+
id = 'python';
|
|
11
|
+
name = 'Python';
|
|
12
|
+
extensions = ['.py', '.pyw'];
|
|
13
|
+
extractFunctions(source, filePath) {
|
|
14
|
+
const functions = [];
|
|
15
|
+
const lines = source.split('\n');
|
|
16
|
+
for (let i = 0; i < lines.length; i++) {
|
|
17
|
+
const line = lines[i];
|
|
18
|
+
// def foo(...): or async def foo(...):
|
|
19
|
+
const match = line.match(/^\s*(async\s+)?def\s+(\w+)\s*\(/);
|
|
20
|
+
if (match) {
|
|
21
|
+
const isAsync = !!match[1];
|
|
22
|
+
const name = match[2];
|
|
23
|
+
const body = this.extractIndentBody(lines, i);
|
|
24
|
+
const endLine = i + body.length;
|
|
25
|
+
const isExported = false; // Python doesn't have explicit exports in the traditional sense
|
|
26
|
+
functions.push({
|
|
27
|
+
name, startLine: i + 1, endLine: endLine + 1,
|
|
28
|
+
body: body.join('\n'), isAsync, isExported,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return functions;
|
|
33
|
+
}
|
|
34
|
+
extractImports(source) {
|
|
35
|
+
const imports = [];
|
|
36
|
+
const lines = source.split('\n');
|
|
37
|
+
for (let i = 0; i < lines.length; i++) {
|
|
38
|
+
const line = lines[i];
|
|
39
|
+
// import x or import x as y
|
|
40
|
+
let match = line.match(/^\s*import\s+([\w.]+)(?:\s+as\s+(\w+))?/);
|
|
41
|
+
if (match) {
|
|
42
|
+
const names = match[2] ? [match[2]] : [match[1]];
|
|
43
|
+
imports.push({
|
|
44
|
+
module: match[1], names, line: i + 1, isDynamic: false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// from x import y, z
|
|
48
|
+
match = line.match(/^\s*from\s+([\w.]+)\s+import\s+(.+)/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const names = match[2]
|
|
51
|
+
.split(',')
|
|
52
|
+
.map(s => s.trim())
|
|
53
|
+
.filter(s => s && s !== '*');
|
|
54
|
+
imports.push({
|
|
55
|
+
module: match[1], names: names.length > 0 ? names : [], line: i + 1, isDynamic: false,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return imports;
|
|
60
|
+
}
|
|
61
|
+
extractErrorHandlers(source) {
|
|
62
|
+
const handlers = [];
|
|
63
|
+
const lines = source.split('\n');
|
|
64
|
+
for (let i = 0; i < lines.length; i++) {
|
|
65
|
+
const line = lines[i];
|
|
66
|
+
// except ExceptionType as e: OR bare except:
|
|
67
|
+
const match = line.match(/^\s*except\s*(?:(\w[\w.]*)(?:\s+as\s+(\w+))?)?\s*:/);
|
|
68
|
+
if (match) {
|
|
69
|
+
const body = this.extractIndentBody(lines, i);
|
|
70
|
+
const exceptionType = match[1] || null; // null = bare except
|
|
71
|
+
const strategy = this.classifyPythonErrorStrategy(body.join('\n'), exceptionType);
|
|
72
|
+
handlers.push({
|
|
73
|
+
type: exceptionType ? 'except' : 'bare-except',
|
|
74
|
+
strategy,
|
|
75
|
+
startLine: i + 1, body: body.join('\n'),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return handlers;
|
|
80
|
+
}
|
|
81
|
+
extractNamingPatterns(source) {
|
|
82
|
+
const patterns = [];
|
|
83
|
+
const lines = source.split('\n');
|
|
84
|
+
for (let i = 0; i < lines.length; i++) {
|
|
85
|
+
const line = lines[i];
|
|
86
|
+
// def foo(...):
|
|
87
|
+
let match = line.match(/^\s*def\s+(\w+)\s*\(/);
|
|
88
|
+
if (match) {
|
|
89
|
+
patterns.push({
|
|
90
|
+
name: match[1], kind: 'function',
|
|
91
|
+
convention: classifyCasing(match[1]),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// class Foo:
|
|
95
|
+
match = line.match(/^\s*class\s+(\w+)/);
|
|
96
|
+
if (match) {
|
|
97
|
+
patterns.push({
|
|
98
|
+
name: match[1], kind: 'class',
|
|
99
|
+
convention: classifyCasing(match[1]),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// foo = ... (variable assignment at module level)
|
|
103
|
+
match = line.match(/^(\w+)\s*=/);
|
|
104
|
+
if (match && !line.includes('def') && !line.includes('class')) {
|
|
105
|
+
const name = match[1];
|
|
106
|
+
// Distinguish constants from regular variables
|
|
107
|
+
const kind = /^[A-Z][A-Z0-9_]*$/.test(name) ? 'constant' : 'variable';
|
|
108
|
+
patterns.push({
|
|
109
|
+
name, kind,
|
|
110
|
+
convention: classifyCasing(name),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return patterns;
|
|
115
|
+
}
|
|
116
|
+
stripComments(source) {
|
|
117
|
+
// Remove line comments (#)
|
|
118
|
+
let result = source.replace(/#.*/g, '');
|
|
119
|
+
// Remove docstrings (""" or ''')
|
|
120
|
+
result = result.replace(/"""[\s\S]*?"""/g, '');
|
|
121
|
+
result = result.replace(/'''[\s\S]*?'''/g, '');
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
extractComparisonOps(source) {
|
|
125
|
+
const ops = [];
|
|
126
|
+
const cleaned = this.stripComments(source);
|
|
127
|
+
const matches = cleaned.matchAll(/(==|!=|>=|<=|>(?!=)|<(?!=)|\bis\s+not\b|\bis\b|\bnot\s+in\b|\bin\b)/g);
|
|
128
|
+
for (const m of matches) {
|
|
129
|
+
ops.push(m[1].trim());
|
|
130
|
+
}
|
|
131
|
+
return ops;
|
|
132
|
+
}
|
|
133
|
+
countBranches(source) {
|
|
134
|
+
let count = 0;
|
|
135
|
+
const cleaned = this.stripComments(source);
|
|
136
|
+
count += (cleaned.match(/\bif\s+/g) || []).length;
|
|
137
|
+
count += (cleaned.match(/\belif\s+/g) || []).length;
|
|
138
|
+
count += (cleaned.match(/\belse\s*:/g) || []).length;
|
|
139
|
+
count += (cleaned.match(/\bmatch\s+/g) || []).length;
|
|
140
|
+
count += (cleaned.match(/\bcase\s+/g) || []).length;
|
|
141
|
+
return count;
|
|
142
|
+
}
|
|
143
|
+
countReturns(source) {
|
|
144
|
+
return (source.match(/\breturn\b/g) || []).length;
|
|
145
|
+
}
|
|
146
|
+
extractIndentBody(lines, startIndex) {
|
|
147
|
+
const body = [];
|
|
148
|
+
const defLine = lines[startIndex];
|
|
149
|
+
const defIndent = defLine.match(/^(\s*)/)?.[1]?.length || 0;
|
|
150
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
151
|
+
const line = lines[i];
|
|
152
|
+
if (line.trim() === '') {
|
|
153
|
+
body.push(line);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const currentIndent = line.match(/^(\s*)/)?.[1]?.length || 0;
|
|
157
|
+
if (currentIndent <= defIndent)
|
|
158
|
+
break;
|
|
159
|
+
body.push(line);
|
|
160
|
+
}
|
|
161
|
+
return body;
|
|
162
|
+
}
|
|
163
|
+
classifyPythonErrorStrategy(body, exceptionType) {
|
|
164
|
+
const trimmed = body.trim();
|
|
165
|
+
// Bare except with just pass/... is always a swallow anti-pattern
|
|
166
|
+
if (!trimmed || trimmed === 'pass' || trimmed === '...')
|
|
167
|
+
return 'swallow';
|
|
168
|
+
// `except Exception: pass` is also a swallow (catches everything, does nothing useful)
|
|
169
|
+
if (exceptionType === 'Exception' && (trimmed === 'pass' || trimmed === '...' || !trimmed))
|
|
170
|
+
return 'swallow';
|
|
171
|
+
if (/\braise\b/.test(trimmed)) {
|
|
172
|
+
if (/\braise\s+\w+Error\b/.test(trimmed))
|
|
173
|
+
return 'wrap';
|
|
174
|
+
return 'rethrow';
|
|
175
|
+
}
|
|
176
|
+
if (/\breturn\s+None\b|\breturn\s*$/.test(trimmed))
|
|
177
|
+
return 'return-error';
|
|
178
|
+
if (/\blogging\.\w+|\blogger\.\w+|\bprint\s*\(/.test(trimmed))
|
|
179
|
+
return 'log';
|
|
180
|
+
return 'other';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export const pythonAdapter = new PythonAdapter();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Adapter Registry
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for language detection and adapter resolution.
|
|
5
|
+
* Gates call `registry.getAdapter(filePath)` instead of branching on extensions.
|
|
6
|
+
*/
|
|
7
|
+
import type { LanguageAdapter } from './types.js';
|
|
8
|
+
export declare class LanguageAdapterRegistry {
|
|
9
|
+
private extensionMap;
|
|
10
|
+
private adapters;
|
|
11
|
+
constructor(adapters?: LanguageAdapter[]);
|
|
12
|
+
/** Auto-detect language from file extension and return the appropriate adapter */
|
|
13
|
+
getAdapter(filePath: string): LanguageAdapter | null;
|
|
14
|
+
/** Check if a file extension is supported */
|
|
15
|
+
isSupported(filePath: string): boolean;
|
|
16
|
+
/** Get all registered language IDs */
|
|
17
|
+
getSupportedLanguages(): string[];
|
|
18
|
+
/** Get all registered adapters */
|
|
19
|
+
getAllAdapters(): LanguageAdapter[];
|
|
20
|
+
/** Get scan glob patterns that cover all registered languages */
|
|
21
|
+
getScanPatterns(): string[];
|
|
22
|
+
/** Get all supported file extensions */
|
|
23
|
+
getSupportedExtensions(): string[];
|
|
24
|
+
}
|
|
25
|
+
/** Singleton registry instance — import this in gates */
|
|
26
|
+
export declare const languageAdapters: LanguageAdapterRegistry;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Adapter Registry
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for language detection and adapter resolution.
|
|
5
|
+
* Gates call `registry.getAdapter(filePath)` instead of branching on extensions.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { jsAdapter } from './js-adapter.js';
|
|
9
|
+
import { pythonAdapter } from './python-adapter.js';
|
|
10
|
+
import { goAdapter } from './go-adapter.js';
|
|
11
|
+
import { rubyAdapter } from './ruby-adapter.js';
|
|
12
|
+
import { rustAdapter } from './rust-adapter.js';
|
|
13
|
+
import { csharpAdapter } from './csharp-adapter.js';
|
|
14
|
+
import { javaAdapter } from './java-adapter.js';
|
|
15
|
+
const ALL_ADAPTERS = [
|
|
16
|
+
jsAdapter,
|
|
17
|
+
pythonAdapter,
|
|
18
|
+
goAdapter,
|
|
19
|
+
rubyAdapter,
|
|
20
|
+
rustAdapter,
|
|
21
|
+
csharpAdapter,
|
|
22
|
+
javaAdapter,
|
|
23
|
+
];
|
|
24
|
+
export class LanguageAdapterRegistry {
|
|
25
|
+
extensionMap = new Map();
|
|
26
|
+
adapters;
|
|
27
|
+
constructor(adapters = ALL_ADAPTERS) {
|
|
28
|
+
this.adapters = adapters;
|
|
29
|
+
for (const adapter of adapters) {
|
|
30
|
+
for (const ext of adapter.extensions) {
|
|
31
|
+
this.extensionMap.set(ext.toLowerCase(), adapter);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Auto-detect language from file extension and return the appropriate adapter */
|
|
36
|
+
getAdapter(filePath) {
|
|
37
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
38
|
+
return this.extensionMap.get(ext) ?? null;
|
|
39
|
+
}
|
|
40
|
+
/** Check if a file extension is supported */
|
|
41
|
+
isSupported(filePath) {
|
|
42
|
+
return this.getAdapter(filePath) !== null;
|
|
43
|
+
}
|
|
44
|
+
/** Get all registered language IDs */
|
|
45
|
+
getSupportedLanguages() {
|
|
46
|
+
return this.adapters.map(a => a.id);
|
|
47
|
+
}
|
|
48
|
+
/** Get all registered adapters */
|
|
49
|
+
getAllAdapters() {
|
|
50
|
+
return [...this.adapters];
|
|
51
|
+
}
|
|
52
|
+
/** Get scan glob patterns that cover all registered languages */
|
|
53
|
+
getScanPatterns() {
|
|
54
|
+
const allExts = this.adapters
|
|
55
|
+
.flatMap(a => a.extensions)
|
|
56
|
+
.map(e => e.replace('.', ''));
|
|
57
|
+
return [`**/*.{${allExts.join(',')}}`];
|
|
58
|
+
}
|
|
59
|
+
/** Get all supported file extensions */
|
|
60
|
+
getSupportedExtensions() {
|
|
61
|
+
return [...this.extensionMap.keys()];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Singleton registry instance — import this in gates */
|
|
65
|
+
export const languageAdapters = new LanguageAdapterRegistry();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruby Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles Ruby-specific code analysis:
|
|
5
|
+
* - Function extraction (def method_name with indent-based body)
|
|
6
|
+
* - Import parsing (require, require_relative, gem)
|
|
7
|
+
* - Error handling (rescue blocks)
|
|
8
|
+
* - Naming conventions (snake_case methods, PascalCase classes)
|
|
9
|
+
*/
|
|
10
|
+
import { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
|
|
11
|
+
export declare class RubyAdapter implements LanguageAdapter {
|
|
12
|
+
readonly id = "ruby";
|
|
13
|
+
readonly name = "Ruby";
|
|
14
|
+
readonly extensions: string[];
|
|
15
|
+
extractFunctions(source: string, filePath?: string): FunctionFact[];
|
|
16
|
+
extractImports(source: string): ImportFact[];
|
|
17
|
+
extractErrorHandlers(source: string): ErrorHandlerFact[];
|
|
18
|
+
extractNamingPatterns(source: string): NamingPattern[];
|
|
19
|
+
stripComments(source: string): string;
|
|
20
|
+
extractComparisonOps(source: string): string[];
|
|
21
|
+
countBranches(source: string): number;
|
|
22
|
+
countReturns(source: string): number;
|
|
23
|
+
private classifyRubyStrategy;
|
|
24
|
+
}
|
|
25
|
+
export declare const rubyAdapter: RubyAdapter;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruby Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles Ruby-specific code analysis:
|
|
5
|
+
* - Function extraction (def method_name with indent-based body)
|
|
6
|
+
* - Import parsing (require, require_relative, gem)
|
|
7
|
+
* - Error handling (rescue blocks)
|
|
8
|
+
* - Naming conventions (snake_case methods, PascalCase classes)
|
|
9
|
+
*/
|
|
10
|
+
import { classifyCasing } from './types.js';
|
|
11
|
+
export class RubyAdapter {
|
|
12
|
+
id = 'ruby';
|
|
13
|
+
name = 'Ruby';
|
|
14
|
+
extensions = ['.rb', '.rake'];
|
|
15
|
+
extractFunctions(source, filePath) {
|
|
16
|
+
const functions = [];
|
|
17
|
+
const lines = source.split('\n');
|
|
18
|
+
// Match: def method_name(...) or def self.method_name(...)
|
|
19
|
+
const defRegex = /^\s*def\s+(?:self\.)?(\w+)\s*(?:\(|$)/;
|
|
20
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21
|
+
const match = lines[i].match(defRegex);
|
|
22
|
+
if (!match)
|
|
23
|
+
continue;
|
|
24
|
+
const name = match[1];
|
|
25
|
+
const startLine = i + 1;
|
|
26
|
+
const baseIndent = lines[i].match(/^(\s*)/)?.[1]?.length || 0;
|
|
27
|
+
const body = [];
|
|
28
|
+
// Extract indent-based body until 'end' at same indent level
|
|
29
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
30
|
+
const currentLine = lines[j];
|
|
31
|
+
if (currentLine.trim() === '') {
|
|
32
|
+
body.push(currentLine);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const currentIndent = currentLine.match(/^(\s*)/)?.[1]?.length || 0;
|
|
36
|
+
if (currentIndent <= baseIndent && currentLine.trim() === 'end') {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (currentIndent <= baseIndent && currentLine.trim() !== '') {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
body.push(currentLine);
|
|
43
|
+
}
|
|
44
|
+
functions.push({
|
|
45
|
+
name,
|
|
46
|
+
startLine,
|
|
47
|
+
endLine: startLine + body.length,
|
|
48
|
+
body: body.join('\n'),
|
|
49
|
+
isAsync: false,
|
|
50
|
+
isExported: true, // Ruby doesn't have true exports; all methods accessible
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return functions;
|
|
54
|
+
}
|
|
55
|
+
extractImports(source) {
|
|
56
|
+
const imports = [];
|
|
57
|
+
const lines = source.split('\n');
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i].trim();
|
|
60
|
+
// require 'foo'
|
|
61
|
+
let match = line.match(/^\s*require\s+['"]([^'"]+)['"]/);
|
|
62
|
+
if (match) {
|
|
63
|
+
imports.push({
|
|
64
|
+
module: match[1],
|
|
65
|
+
names: [],
|
|
66
|
+
line: i + 1,
|
|
67
|
+
isDynamic: false,
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// require_relative 'foo'
|
|
72
|
+
match = line.match(/^\s*require_relative\s+['"]([^'"]+)['"]/);
|
|
73
|
+
if (match) {
|
|
74
|
+
imports.push({
|
|
75
|
+
module: match[1],
|
|
76
|
+
names: [],
|
|
77
|
+
line: i + 1,
|
|
78
|
+
isDynamic: false,
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// gem 'baz'
|
|
83
|
+
match = line.match(/^\s*gem\s+['"]([^'"]+)['"]/);
|
|
84
|
+
if (match) {
|
|
85
|
+
imports.push({
|
|
86
|
+
module: match[1],
|
|
87
|
+
names: [],
|
|
88
|
+
line: i + 1,
|
|
89
|
+
isDynamic: false,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return imports;
|
|
94
|
+
}
|
|
95
|
+
extractErrorHandlers(source) {
|
|
96
|
+
const handlers = [];
|
|
97
|
+
const lines = source.split('\n');
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const line = lines[i];
|
|
100
|
+
// Match: rescue ExceptionClass => var
|
|
101
|
+
if (/^\s*rescue\s+/.test(line)) {
|
|
102
|
+
const baseIndent = line.match(/^(\s*)/)?.[1]?.length || 0;
|
|
103
|
+
const body = [];
|
|
104
|
+
// Collect body until next rescue/else/ensure/end at same level
|
|
105
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
106
|
+
const currentLine = lines[j];
|
|
107
|
+
if (currentLine.trim() === '') {
|
|
108
|
+
body.push(currentLine);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const currentIndent = currentLine.match(/^(\s*)/)?.[1]?.length || 0;
|
|
112
|
+
if (currentIndent <= baseIndent && /^\s*(?:rescue|else|ensure|end)\b/.test(currentLine)) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
body.push(currentLine);
|
|
116
|
+
}
|
|
117
|
+
const bodyStr = body.join('\n');
|
|
118
|
+
const strategy = this.classifyRubyStrategy(bodyStr);
|
|
119
|
+
handlers.push({
|
|
120
|
+
type: 'rescue',
|
|
121
|
+
strategy,
|
|
122
|
+
startLine: i + 1,
|
|
123
|
+
body: bodyStr,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return handlers;
|
|
128
|
+
}
|
|
129
|
+
extractNamingPatterns(source) {
|
|
130
|
+
const patterns = [];
|
|
131
|
+
const lines = source.split('\n');
|
|
132
|
+
for (let i = 0; i < lines.length; i++) {
|
|
133
|
+
const line = lines[i];
|
|
134
|
+
// Method definitions
|
|
135
|
+
let match = line.match(/^\s*def\s+(?:self\.)?(\w+)/);
|
|
136
|
+
if (match) {
|
|
137
|
+
const name = match[1];
|
|
138
|
+
patterns.push({
|
|
139
|
+
name,
|
|
140
|
+
kind: 'method',
|
|
141
|
+
convention: classifyCasing(name),
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// Class definitions
|
|
146
|
+
match = line.match(/^\s*class\s+(\w+)/);
|
|
147
|
+
if (match) {
|
|
148
|
+
const name = match[1];
|
|
149
|
+
patterns.push({
|
|
150
|
+
name,
|
|
151
|
+
kind: 'class',
|
|
152
|
+
convention: classifyCasing(name),
|
|
153
|
+
});
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
// Constant assignments
|
|
157
|
+
match = line.match(/^\s*([A-Z_]\w*)\s*=/);
|
|
158
|
+
if (match) {
|
|
159
|
+
const name = match[1];
|
|
160
|
+
patterns.push({
|
|
161
|
+
name,
|
|
162
|
+
kind: 'constant',
|
|
163
|
+
convention: classifyCasing(name),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return patterns;
|
|
168
|
+
}
|
|
169
|
+
stripComments(source) {
|
|
170
|
+
// Remove line comments: #
|
|
171
|
+
let result = source.replace(/#.*/g, '');
|
|
172
|
+
// Remove block comments: =begin...=end
|
|
173
|
+
result = result.replace(/^\s*=begin\b[\s\S]*?^\s*=end\b/m, '');
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
extractComparisonOps(source) {
|
|
177
|
+
const ops = [];
|
|
178
|
+
const cleaned = this.stripComments(source);
|
|
179
|
+
const matches = cleaned.matchAll(/(===|==|!=|>=|<=|<=>|>(?!=)|<(?!=))/g);
|
|
180
|
+
for (const m of matches) {
|
|
181
|
+
ops.push(m[1]);
|
|
182
|
+
}
|
|
183
|
+
return ops;
|
|
184
|
+
}
|
|
185
|
+
countBranches(source) {
|
|
186
|
+
let count = 0;
|
|
187
|
+
const cleaned = this.stripComments(source);
|
|
188
|
+
count += (cleaned.match(/\bif\s+/g) || []).length;
|
|
189
|
+
count += (cleaned.match(/\belsif\s+/g) || []).length;
|
|
190
|
+
count += (cleaned.match(/\belse\b/g) || []).length;
|
|
191
|
+
count += (cleaned.match(/\bunless\s+/g) || []).length;
|
|
192
|
+
count += (cleaned.match(/\bcase\s+/g) || []).length;
|
|
193
|
+
count += (cleaned.match(/\bwhen\s+/g) || []).length;
|
|
194
|
+
return count;
|
|
195
|
+
}
|
|
196
|
+
countReturns(source) {
|
|
197
|
+
// Count explicit return statements
|
|
198
|
+
return (source.match(/\breturn\b/g) || []).length;
|
|
199
|
+
}
|
|
200
|
+
classifyRubyStrategy(body) {
|
|
201
|
+
const trimmed = body.trim();
|
|
202
|
+
if (!trimmed)
|
|
203
|
+
return 'swallow';
|
|
204
|
+
if (/\braise\b/.test(trimmed))
|
|
205
|
+
return 'rethrow';
|
|
206
|
+
if (/\breturn\s+nil\b/.test(trimmed))
|
|
207
|
+
return 'return-null';
|
|
208
|
+
if (/\breturn\s+false\b/.test(trimmed))
|
|
209
|
+
return 'return-false';
|
|
210
|
+
if (/\breturn\s+/.test(trimmed))
|
|
211
|
+
return 'return-value';
|
|
212
|
+
if (/\bputs\b|\blogger\b|\bRails\.logger\b/i.test(trimmed))
|
|
213
|
+
return 'log';
|
|
214
|
+
return 'other';
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
export const rubyAdapter = new RubyAdapter();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles Rust-specific code analysis:
|
|
5
|
+
* - Function extraction (fn, pub fn, pub async fn)
|
|
6
|
+
* - Import parsing (use statements)
|
|
7
|
+
* - Error handling (match, unwrap_or_else)
|
|
8
|
+
* - Naming conventions (snake_case functions, PascalCase types)
|
|
9
|
+
*/
|
|
10
|
+
import { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
|
|
11
|
+
export declare class RustAdapter implements LanguageAdapter {
|
|
12
|
+
readonly id = "rust";
|
|
13
|
+
readonly name = "Rust";
|
|
14
|
+
readonly extensions: string[];
|
|
15
|
+
extractFunctions(source: string, filePath?: string): FunctionFact[];
|
|
16
|
+
extractImports(source: string): ImportFact[];
|
|
17
|
+
extractErrorHandlers(source: string): ErrorHandlerFact[];
|
|
18
|
+
extractNamingPatterns(source: string): NamingPattern[];
|
|
19
|
+
stripComments(source: string): string;
|
|
20
|
+
extractComparisonOps(source: string): string[];
|
|
21
|
+
countBranches(source: string): number;
|
|
22
|
+
countReturns(source: string): number;
|
|
23
|
+
private extractBraceBody;
|
|
24
|
+
private extractCallbackBody;
|
|
25
|
+
private classifyRustErrorStrategy;
|
|
26
|
+
}
|
|
27
|
+
export declare const rustAdapter: RustAdapter;
|