@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,281 @@
|
|
|
1
|
+
import { stripStrings } from './types.js';
|
|
2
|
+
import { findBlockEndBrace } from './scope-analysis.js';
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
4
|
+
// VARIABLE BINDING — Track resource creation → cleanup pairs
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
6
|
+
/**
|
|
7
|
+
* Extract the variable name from an assignment.
|
|
8
|
+
* "const timer = setInterval(...)" → "timer"
|
|
9
|
+
* "let fd = fs.open(...)" → "fd"
|
|
10
|
+
* "self.watcher = chokidar.watch()" → "self.watcher"
|
|
11
|
+
* "timer := time.NewTicker(...)" → "timer" (Go)
|
|
12
|
+
*
|
|
13
|
+
* Returns null if the call result is NOT stored in a variable.
|
|
14
|
+
*/
|
|
15
|
+
export function extractVariableBinding(line, lang) {
|
|
16
|
+
const stripped = stripStrings(line).trim();
|
|
17
|
+
if (lang === 'go') {
|
|
18
|
+
// Go: `ticker := time.NewTicker(...)` or `ticker, _ := ...`
|
|
19
|
+
const goMatch = stripped.match(/^(\w+)(?:\s*,\s*\w+)*\s*:?=\s*/);
|
|
20
|
+
if (goMatch)
|
|
21
|
+
return goMatch[1];
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (lang === 'py') {
|
|
25
|
+
// Python: `timer = threading.Timer(...)` or `self.timer = ...`
|
|
26
|
+
const pyMatch = stripped.match(/^((?:self\.)?[\w.]+)\s*=\s*(?!==)/);
|
|
27
|
+
if (pyMatch)
|
|
28
|
+
return pyMatch[1];
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (lang === 'rb') {
|
|
32
|
+
// Ruby: `@watcher = Listen.to(...)` or `watcher = ...`
|
|
33
|
+
const rbMatch = stripped.match(/^(@?\w+)\s*=\s*/);
|
|
34
|
+
if (rbMatch)
|
|
35
|
+
return rbMatch[1];
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// JS/TS/Java/C#/Rust: `const x = ...`, `let x = ...`, `var x = ...`, `auto x = ...`
|
|
39
|
+
const jsMatch = stripped.match(/^(?:const|let|var|final|auto|val)\s+(\w+)\s*=\s*/);
|
|
40
|
+
if (jsMatch)
|
|
41
|
+
return jsMatch[1];
|
|
42
|
+
// Member assignment: `this.timer = ...`, `self.timer = ...`
|
|
43
|
+
const memberMatch = stripped.match(/^(?:this|self)\.([\w]+)\s*=\s*/);
|
|
44
|
+
if (memberMatch)
|
|
45
|
+
return memberMatch[1];
|
|
46
|
+
// Simple assignment: `timer = ...`
|
|
47
|
+
const simpleMatch = stripped.match(/^(\w+)\s*=\s*(?!==)/);
|
|
48
|
+
if (simpleMatch) {
|
|
49
|
+
// Exclude control flow keywords
|
|
50
|
+
const name = simpleMatch[1];
|
|
51
|
+
if (['if', 'for', 'while', 'switch', 'return', 'throw'].includes(name))
|
|
52
|
+
return null;
|
|
53
|
+
return name;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a specific variable is used in a cleanup call within a scope.
|
|
59
|
+
*
|
|
60
|
+
* Unlike the naive "does clearInterval exist in the file?", this checks:
|
|
61
|
+
* 1. The cleanup function references the specific variable
|
|
62
|
+
* 2. The cleanup is within the correct scope (same function or cleanup callback)
|
|
63
|
+
*
|
|
64
|
+
* Example: for variable "timer" and cleanup patterns [/clearInterval/],
|
|
65
|
+
* matches: `clearInterval(timer)`, `clearInterval(this.timer)`, `timer.close()`
|
|
66
|
+
*/
|
|
67
|
+
export function hasCleanupForVariable(lines, varName, scopeStart, scopeEnd, cleanupPatterns, lang) {
|
|
68
|
+
const scope = lines.slice(scopeStart, scopeEnd);
|
|
69
|
+
for (let i = 0; i < scope.length; i++) {
|
|
70
|
+
const stripped = stripStrings(scope[i]);
|
|
71
|
+
// Check cleanup patterns that reference the specific variable
|
|
72
|
+
for (const pat of cleanupPatterns) {
|
|
73
|
+
if (!pat.test(stripped))
|
|
74
|
+
continue;
|
|
75
|
+
// The cleanup call should reference our variable
|
|
76
|
+
const escapedVar = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
77
|
+
const varRef = new RegExp(`\\b${escapedVar}\\b`);
|
|
78
|
+
if (varRef.test(stripped))
|
|
79
|
+
return true;
|
|
80
|
+
// Also check method calls on the variable: timer.close(), timer.stop()
|
|
81
|
+
// The pattern might match a generic .close() — check if it's on our var
|
|
82
|
+
}
|
|
83
|
+
// Direct method cleanup on the variable: varName.close(), varName.Stop()
|
|
84
|
+
const escapedVar = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
85
|
+
const methodCleanup = new RegExp(`\\b${escapedVar}\\.(?:close|stop|destroy|kill|terminate|dispose|cancel|shutdown|unsubscribe|disconnect|end|release|Clear|Stop|Dispose|Close|Cancel)\\s*\\(`);
|
|
86
|
+
if (methodCleanup.test(stripped))
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a line is inside a cleanup/teardown context.
|
|
93
|
+
*
|
|
94
|
+
* Cleanup contexts where resource cleanup is expected:
|
|
95
|
+
* - JS/TS: useEffect return function, componentWillUnmount, beforeDestroy, ngOnDestroy, dispose()
|
|
96
|
+
* - Python: __del__, __exit__, close(), cleanup(), teardown
|
|
97
|
+
* - Go: defer statement
|
|
98
|
+
* - Java: finally block, close() method, @PreDestroy
|
|
99
|
+
* - C#: Dispose(), using block, finalizer
|
|
100
|
+
* - Ruby: ensure block, at_exit
|
|
101
|
+
*/
|
|
102
|
+
export function isInsideCleanupContext(lines, lineIdx, lang) {
|
|
103
|
+
// Scan backwards up to 30 lines for cleanup context markers
|
|
104
|
+
for (let j = lineIdx; j >= Math.max(0, lineIdx - 30); j--) {
|
|
105
|
+
const trimmed = lines[j].trim();
|
|
106
|
+
switch (lang) {
|
|
107
|
+
case 'js':
|
|
108
|
+
case 'ts':
|
|
109
|
+
// React useEffect cleanup: `return () => { cleanup }`
|
|
110
|
+
if (/\breturn\s+(?:\(\)\s*=>|function\s*\()/.test(trimmed))
|
|
111
|
+
return true;
|
|
112
|
+
// Lifecycle: componentWillUnmount, ngOnDestroy, beforeDestroy
|
|
113
|
+
if (/\b(?:componentWillUnmount|ngOnDestroy|beforeDestroy|dispose)\s*\(/.test(trimmed))
|
|
114
|
+
return true;
|
|
115
|
+
// Event: 'beforeunload', 'unload'
|
|
116
|
+
if (/['"](?:beforeunload|unload)['"]\s*,/.test(trimmed))
|
|
117
|
+
return true;
|
|
118
|
+
break;
|
|
119
|
+
case 'py':
|
|
120
|
+
if (/\bdef\s+(?:__del__|__exit__|close|cleanup|teardown|dispose)\s*\(/.test(trimmed))
|
|
121
|
+
return true;
|
|
122
|
+
if (/\bfinally\s*:/.test(trimmed))
|
|
123
|
+
return true;
|
|
124
|
+
break;
|
|
125
|
+
case 'go':
|
|
126
|
+
if (/\bdefer\b/.test(trimmed))
|
|
127
|
+
return true;
|
|
128
|
+
break;
|
|
129
|
+
case 'java':
|
|
130
|
+
if (/\bfinally\s*\{/.test(trimmed))
|
|
131
|
+
return true;
|
|
132
|
+
if (/\b(?:close|destroy|cleanup|dispose)\s*\(/.test(trimmed))
|
|
133
|
+
return true;
|
|
134
|
+
if (/@PreDestroy/.test(trimmed))
|
|
135
|
+
return true;
|
|
136
|
+
break;
|
|
137
|
+
case 'cs':
|
|
138
|
+
if (/\bDispose\s*\(/.test(trimmed))
|
|
139
|
+
return true;
|
|
140
|
+
if (/\busing\s*\(/.test(trimmed))
|
|
141
|
+
return true;
|
|
142
|
+
if (/~\w+\s*\(/.test(trimmed))
|
|
143
|
+
return true; // finalizer
|
|
144
|
+
break;
|
|
145
|
+
case 'rb':
|
|
146
|
+
if (/\bensure\b/.test(trimmed))
|
|
147
|
+
return true;
|
|
148
|
+
break;
|
|
149
|
+
case 'rs':
|
|
150
|
+
if (/\bimpl\s+Drop\b/.test(trimmed))
|
|
151
|
+
return true;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
// Stop at function boundaries
|
|
155
|
+
if (isFunctionBoundary(trimmed, lang))
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
161
|
+
// FRAMEWORK-AWARE PATTERNS — Detect safe idioms per ecosystem
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
163
|
+
/**
|
|
164
|
+
* Check if a timer/resource creation is inside a React useEffect
|
|
165
|
+
* that returns a cleanup function.
|
|
166
|
+
*
|
|
167
|
+
* Pattern:
|
|
168
|
+
* useEffect(() => {
|
|
169
|
+
* const timer = setInterval(...) ← creation
|
|
170
|
+
* return () => clearInterval(timer) ← cleanup
|
|
171
|
+
* }, [deps])
|
|
172
|
+
*/
|
|
173
|
+
export function isInUseEffectWithCleanup(lines, lineIdx) {
|
|
174
|
+
// Walk backwards to find useEffect
|
|
175
|
+
let braceDepth = 0;
|
|
176
|
+
for (let j = lineIdx; j >= Math.max(0, lineIdx - 30); j--) {
|
|
177
|
+
const stripped = stripStrings(lines[j]);
|
|
178
|
+
for (const ch of stripped) {
|
|
179
|
+
if (ch === '}')
|
|
180
|
+
braceDepth++;
|
|
181
|
+
if (ch === '{')
|
|
182
|
+
braceDepth--;
|
|
183
|
+
}
|
|
184
|
+
if (/\buseEffect\s*\(/.test(stripped) && braceDepth <= 0) {
|
|
185
|
+
// Found enclosing useEffect — now check if it has a return () => ...
|
|
186
|
+
const effectEnd = findBlockEndBrace(lines, j);
|
|
187
|
+
const effectBody = lines.slice(j, effectEnd).join('\n');
|
|
188
|
+
// Look for cleanup return: `return () =>` or `return function`
|
|
189
|
+
if (/\breturn\s+(?:\(\)\s*=>|function\s*\()/.test(effectBody)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Check if a Go resource open is immediately followed by defer close.
|
|
199
|
+
*
|
|
200
|
+
* Idiomatic Go:
|
|
201
|
+
* f, err := os.Open(path)
|
|
202
|
+
* if err != nil { return err }
|
|
203
|
+
* defer f.Close()
|
|
204
|
+
*/
|
|
205
|
+
export function hasGoDefer(lines, openLine, varName) {
|
|
206
|
+
// Check lines between open and open+5 for defer using the variable
|
|
207
|
+
const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
208
|
+
const deferPat = new RegExp(`\\bdefer\\s+${escaped}\\.`);
|
|
209
|
+
for (let j = openLine + 1; j < Math.min(lines.length, openLine + 6); j++) {
|
|
210
|
+
if (deferPat.test(lines[j]))
|
|
211
|
+
return true;
|
|
212
|
+
// Also match: defer func() { varName.Close() }()
|
|
213
|
+
if (/\bdefer\s+func\s*\(\)/.test(lines[j])) {
|
|
214
|
+
const endDefer = findBlockEndBrace(lines, j);
|
|
215
|
+
const body = lines.slice(j, endDefer).join('\n');
|
|
216
|
+
if (new RegExp(`\\b${escaped}\\.`).test(body))
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if a Python open() is inside a `with` statement (context manager).
|
|
224
|
+
*/
|
|
225
|
+
export function isPythonWithStatement(line) {
|
|
226
|
+
return /\bwith\s+/.test(stripStrings(line));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Check if a Java resource open is inside try-with-resources.
|
|
230
|
+
* Pattern: try (var x = new FileStream(...)) { ... }
|
|
231
|
+
*/
|
|
232
|
+
export function isJavaTryWithResources(lines, lineIdx) {
|
|
233
|
+
for (let j = lineIdx; j >= Math.max(0, lineIdx - 3); j--) {
|
|
234
|
+
if (/\btry\s*\(/.test(stripStrings(lines[j])))
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Check if a C# resource is inside a using statement/declaration.
|
|
241
|
+
* Patterns: `using (var x = ...)` or `using var x = ...` (C# 8+)
|
|
242
|
+
*/
|
|
243
|
+
export function isCSharpUsing(line) {
|
|
244
|
+
const stripped = stripStrings(line);
|
|
245
|
+
return /\busing\s*\(/.test(stripped) || /\busing\s+(?:var|await)\b/.test(stripped);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if a Ruby File.open uses block form (auto-closes).
|
|
249
|
+
* Pattern: File.open(path) do |f| ... end
|
|
250
|
+
* File.open(path) { |f| ... }
|
|
251
|
+
*/
|
|
252
|
+
export function isRubyBlockForm(line) {
|
|
253
|
+
return /\bdo\s*\|/.test(line) || /\{\s*\|/.test(line);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Check if a Rust resource is automatically dropped (RAII).
|
|
257
|
+
* In Rust, all resources are dropped when they go out of scope,
|
|
258
|
+
* so we only flag resources in unsafe blocks or static/global context.
|
|
259
|
+
*/
|
|
260
|
+
export function isRustAutoDropped(lines, lineIdx) {
|
|
261
|
+
// Check if inside unsafe block (manual memory management)
|
|
262
|
+
for (let j = lineIdx; j >= Math.max(0, lineIdx - 20); j--) {
|
|
263
|
+
if (/\bunsafe\s*\{/.test(lines[j]))
|
|
264
|
+
return false; // Not auto-dropped in unsafe
|
|
265
|
+
}
|
|
266
|
+
return true; // Normal Rust = RAII applies
|
|
267
|
+
}
|
|
268
|
+
function isFunctionBoundary(trimmed, lang) {
|
|
269
|
+
switch (lang) {
|
|
270
|
+
case 'py':
|
|
271
|
+
return /^(?:def|class|async\s+def)\s/.test(trimmed);
|
|
272
|
+
case 'go':
|
|
273
|
+
return /^func\s/.test(trimmed);
|
|
274
|
+
case 'rb':
|
|
275
|
+
return /^(?:def|class|module)\s/.test(trimmed);
|
|
276
|
+
case 'rs':
|
|
277
|
+
return /^(?:fn|impl|pub\s+fn|pub\s+async\s+fn)\s/.test(trimmed);
|
|
278
|
+
default:
|
|
279
|
+
return /^(?:export\s+)?(?:async\s+)?(?:function|class)\s/.test(trimmed);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SideEffectLang } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Find the enclosing function scope for a given line.
|
|
4
|
+
* Returns { start, end } of the function body.
|
|
5
|
+
* For module-level code, returns { start: 0, end: lines.length }.
|
|
6
|
+
*
|
|
7
|
+
* Follows promise-safety's approach of backward scanning with brace tracking.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findEnclosingFunction(lines: string[], lineIdx: number, lang: SideEffectLang): {
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Find the end of a brace-delimited block starting at `start`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findBlockEndBrace(lines: string[], start: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Find the end of an indentation-delimited block (Python).
|
|
19
|
+
*/
|
|
20
|
+
export declare function findBlockEndIndent(lines: string[], start: number): number;
|
|
21
|
+
export declare function getFunctionPatterns(lang: SideEffectLang): RegExp[];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { stripStrings } from './types.js';
|
|
2
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
3
|
+
// SCOPE ANALYSIS — Find function/block boundaries
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
/**
|
|
6
|
+
* Find the enclosing function scope for a given line.
|
|
7
|
+
* Returns { start, end } of the function body.
|
|
8
|
+
* For module-level code, returns { start: 0, end: lines.length }.
|
|
9
|
+
*
|
|
10
|
+
* Follows promise-safety's approach of backward scanning with brace tracking.
|
|
11
|
+
*/
|
|
12
|
+
export function findEnclosingFunction(lines, lineIdx, lang) {
|
|
13
|
+
if (lang === 'py')
|
|
14
|
+
return findEnclosingFunctionPython(lines, lineIdx);
|
|
15
|
+
if (lang === 'rb')
|
|
16
|
+
return findEnclosingFunctionRuby(lines, lineIdx);
|
|
17
|
+
return findEnclosingFunctionBrace(lines, lineIdx, lang);
|
|
18
|
+
}
|
|
19
|
+
function findEnclosingFunctionBrace(lines, lineIdx, lang) {
|
|
20
|
+
// Walk backwards tracking brace depth to find function definition
|
|
21
|
+
let braceDepth = 0;
|
|
22
|
+
const funcPatterns = getFunctionPatterns(lang);
|
|
23
|
+
for (let j = lineIdx; j >= Math.max(0, lineIdx - 200); j--) {
|
|
24
|
+
const stripped = stripStrings(lines[j]);
|
|
25
|
+
// Count braces (reverse direction: } increases, { decreases)
|
|
26
|
+
for (const ch of stripped) {
|
|
27
|
+
if (ch === '}')
|
|
28
|
+
braceDepth++;
|
|
29
|
+
if (ch === '{')
|
|
30
|
+
braceDepth--;
|
|
31
|
+
}
|
|
32
|
+
// If braceDepth < 0, we've exited the enclosing block going backwards
|
|
33
|
+
if (braceDepth < 0) {
|
|
34
|
+
// Check if this line is a function definition
|
|
35
|
+
for (const pat of funcPatterns) {
|
|
36
|
+
if (pat.test(stripped)) {
|
|
37
|
+
const end = findBlockEndBrace(lines, j);
|
|
38
|
+
return { start: j, end };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// It's some other block (if/for/etc), keep looking
|
|
42
|
+
braceDepth = 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Module level
|
|
46
|
+
return { start: 0, end: lines.length };
|
|
47
|
+
}
|
|
48
|
+
function findEnclosingFunctionPython(lines, lineIdx) {
|
|
49
|
+
const lineIndent = lines[lineIdx].length - lines[lineIdx].trimStart().length;
|
|
50
|
+
for (let j = lineIdx - 1; j >= 0; j--) {
|
|
51
|
+
const trimmed = lines[j].trim();
|
|
52
|
+
if (trimmed === '' || trimmed.startsWith('#'))
|
|
53
|
+
continue;
|
|
54
|
+
const indent = lines[j].length - lines[j].trimStart().length;
|
|
55
|
+
if (indent < lineIndent && /^\s*(?:async\s+)?def\s+\w+/.test(lines[j])) {
|
|
56
|
+
const end = findBlockEndIndent(lines, j);
|
|
57
|
+
return { start: j, end };
|
|
58
|
+
}
|
|
59
|
+
if (indent === 0 && /^\s*(?:class|def|async\s+def)\s/.test(lines[j])) {
|
|
60
|
+
const end = findBlockEndIndent(lines, j);
|
|
61
|
+
return { start: j, end };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { start: 0, end: lines.length };
|
|
65
|
+
}
|
|
66
|
+
function findEnclosingFunctionRuby(lines, lineIdx) {
|
|
67
|
+
for (let j = lineIdx - 1; j >= Math.max(0, lineIdx - 100); j--) {
|
|
68
|
+
const trimmed = lines[j].trim();
|
|
69
|
+
if (/^def\s+\w+/.test(trimmed)) {
|
|
70
|
+
const end = findBlockEndRuby(lines, j);
|
|
71
|
+
return { start: j, end };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { start: 0, end: lines.length };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Find the end of a brace-delimited block starting at `start`.
|
|
78
|
+
*/
|
|
79
|
+
export function findBlockEndBrace(lines, start) {
|
|
80
|
+
let braces = 0;
|
|
81
|
+
let started = false;
|
|
82
|
+
const maxScan = Math.min(lines.length, start + 300);
|
|
83
|
+
for (let j = start; j < maxScan; j++) {
|
|
84
|
+
const stripped = stripStrings(lines[j]);
|
|
85
|
+
for (const ch of stripped) {
|
|
86
|
+
if (ch === '{') {
|
|
87
|
+
braces++;
|
|
88
|
+
started = true;
|
|
89
|
+
}
|
|
90
|
+
if (ch === '}')
|
|
91
|
+
braces--;
|
|
92
|
+
}
|
|
93
|
+
if (started && braces <= 0)
|
|
94
|
+
return j + 1;
|
|
95
|
+
}
|
|
96
|
+
return maxScan;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Find the end of an indentation-delimited block (Python).
|
|
100
|
+
*/
|
|
101
|
+
export function findBlockEndIndent(lines, start) {
|
|
102
|
+
const baseIndent = lines[start].length - lines[start].trimStart().length;
|
|
103
|
+
const maxScan = Math.min(lines.length, start + 300);
|
|
104
|
+
for (let j = start + 1; j < maxScan; j++) {
|
|
105
|
+
const trimmed = lines[j].trim();
|
|
106
|
+
if (trimmed === '' || trimmed.startsWith('#'))
|
|
107
|
+
continue;
|
|
108
|
+
const indent = lines[j].length - lines[j].trimStart().length;
|
|
109
|
+
if (indent <= baseIndent)
|
|
110
|
+
return j;
|
|
111
|
+
}
|
|
112
|
+
return maxScan;
|
|
113
|
+
}
|
|
114
|
+
function findBlockEndRuby(lines, start) {
|
|
115
|
+
let depth = 0;
|
|
116
|
+
const maxScan = Math.min(lines.length, start + 300);
|
|
117
|
+
const openers = /\b(?:def|do|class|module|if|unless|while|until|for|begin|case)\b/;
|
|
118
|
+
for (let j = start; j < maxScan; j++) {
|
|
119
|
+
const trimmed = lines[j].trim();
|
|
120
|
+
if (openers.test(trimmed))
|
|
121
|
+
depth++;
|
|
122
|
+
if (/^\s*end\b/.test(trimmed)) {
|
|
123
|
+
depth--;
|
|
124
|
+
if (depth <= 0)
|
|
125
|
+
return j + 1;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return maxScan;
|
|
129
|
+
}
|
|
130
|
+
export function getFunctionPatterns(lang) {
|
|
131
|
+
switch (lang) {
|
|
132
|
+
case 'go':
|
|
133
|
+
return [/\bfunc\s+/];
|
|
134
|
+
case 'rs':
|
|
135
|
+
return [/\bfn\s+\w+/];
|
|
136
|
+
case 'java':
|
|
137
|
+
case 'cs':
|
|
138
|
+
return [/(?:public|private|protected|static|async|void|int|string|Task|var)\s+\w+\s*\(/];
|
|
139
|
+
default: // js, ts
|
|
140
|
+
return [
|
|
141
|
+
/(?:export\s+)?(?:async\s+)?function\s+\w+/,
|
|
142
|
+
/(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
|
|
143
|
+
/\w+\s*\([^)]*\)\s*\{/, // method shorthand
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-Effect Analysis Helpers
|
|
3
|
+
*
|
|
4
|
+
* Context-aware utilities for smart side-effect detection.
|
|
5
|
+
* Follows the same architectural patterns as promise-safety-helpers.ts:
|
|
6
|
+
* - Scope-aware analysis (brace/indent tracking)
|
|
7
|
+
* - Variable binding tracking (pair resource creation with cleanup)
|
|
8
|
+
* - Framework detection (React useEffect, Go defer, Python with, etc.)
|
|
9
|
+
* - Path overlap analysis (circular file watcher detection)
|
|
10
|
+
*
|
|
11
|
+
* These helpers make side-effect detection SMART — instead of asking
|
|
12
|
+
* "does clearInterval exist anywhere in the file?", we ask
|
|
13
|
+
* "is the specific timer variable cleaned up in the right scope?"
|
|
14
|
+
*/
|
|
15
|
+
export type SideEffectLang = 'js' | 'ts' | 'py' | 'go' | 'rs' | 'cs' | 'java' | 'rb';
|
|
16
|
+
export interface SideEffectViolation {
|
|
17
|
+
rule: string;
|
|
18
|
+
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
19
|
+
file: string;
|
|
20
|
+
line: number;
|
|
21
|
+
match: string;
|
|
22
|
+
description: string;
|
|
23
|
+
hint: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Tracks a resource creation and its expected cleanup.
|
|
27
|
+
* E.g.: { varName: 'timer', createLine: 5, createCall: 'setInterval' }
|
|
28
|
+
*/
|
|
29
|
+
export interface ResourceBinding {
|
|
30
|
+
varName: string | null;
|
|
31
|
+
createLine: number;
|
|
32
|
+
createCall: string;
|
|
33
|
+
scopeStart: number;
|
|
34
|
+
scopeEnd: number;
|
|
35
|
+
}
|
|
36
|
+
export declare const LANG_MAP: Record<string, SideEffectLang>;
|
|
37
|
+
export declare const FILE_GLOBS: string[];
|
|
38
|
+
export declare function stripStrings(line: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-Effect Analysis Helpers
|
|
3
|
+
*
|
|
4
|
+
* Context-aware utilities for smart side-effect detection.
|
|
5
|
+
* Follows the same architectural patterns as promise-safety-helpers.ts:
|
|
6
|
+
* - Scope-aware analysis (brace/indent tracking)
|
|
7
|
+
* - Variable binding tracking (pair resource creation with cleanup)
|
|
8
|
+
* - Framework detection (React useEffect, Go defer, Python with, etc.)
|
|
9
|
+
* - Path overlap analysis (circular file watcher detection)
|
|
10
|
+
*
|
|
11
|
+
* These helpers make side-effect detection SMART — instead of asking
|
|
12
|
+
* "does clearInterval exist anywhere in the file?", we ask
|
|
13
|
+
* "is the specific timer variable cleaned up in the right scope?"
|
|
14
|
+
*/
|
|
15
|
+
// ── Language detection ──
|
|
16
|
+
export const LANG_MAP = {
|
|
17
|
+
'.ts': 'ts', '.tsx': 'ts', '.mts': 'ts',
|
|
18
|
+
'.js': 'js', '.jsx': 'js', '.mjs': 'js', '.cjs': 'js',
|
|
19
|
+
'.py': 'py',
|
|
20
|
+
'.go': 'go',
|
|
21
|
+
'.rs': 'rs',
|
|
22
|
+
'.cs': 'cs',
|
|
23
|
+
'.java': 'java',
|
|
24
|
+
'.rb': 'rb',
|
|
25
|
+
};
|
|
26
|
+
export const FILE_GLOBS = [
|
|
27
|
+
'**/*.{ts,tsx,mts,js,jsx,mjs,cjs}',
|
|
28
|
+
'**/*.py',
|
|
29
|
+
'**/*.go',
|
|
30
|
+
'**/*.rs',
|
|
31
|
+
'**/*.cs',
|
|
32
|
+
'**/*.java',
|
|
33
|
+
'**/*.rb',
|
|
34
|
+
];
|
|
35
|
+
// ── Strip string contents to avoid false positives in regex matching ──
|
|
36
|
+
export function stripStrings(line) {
|
|
37
|
+
return line
|
|
38
|
+
.replace(/`[^`]*`/g, '""')
|
|
39
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, '""')
|
|
40
|
+
.replace(/'(?:[^'\\]|\\.)*'/g, '""');
|
|
41
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Drift Detection Gate — Naming Convention Rules and Regexes
|
|
3
|
+
*
|
|
4
|
+
* Contains per-language naming convention rules and naming pattern regexes.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Casing classification rules
|
|
8
|
+
*/
|
|
9
|
+
export declare function classifyCasing(name: string): 'camelCase' | 'snake_case' | 'PascalCase' | 'SCREAMING_SNAKE' | null;
|
|
10
|
+
/**
|
|
11
|
+
* Function name pattern for JavaScript
|
|
12
|
+
*/
|
|
13
|
+
export declare const JS_FUNCTION_PATTERN: RegExp;
|
|
14
|
+
/**
|
|
15
|
+
* Method definition pattern for all languages
|
|
16
|
+
*/
|
|
17
|
+
export declare const METHOD_PATTERN: RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* Arrow function assignment pattern
|
|
20
|
+
*/
|
|
21
|
+
export declare const ARROW_FUNCTION_PATTERN: RegExp;
|
|
22
|
+
/**
|
|
23
|
+
* Variable declaration pattern (non-function)
|
|
24
|
+
*/
|
|
25
|
+
export declare const VAR_DECLARATION_PATTERN: RegExp;
|
|
26
|
+
/**
|
|
27
|
+
* Python function pattern
|
|
28
|
+
*/
|
|
29
|
+
export declare const PYTHON_FUNCTION_PATTERN: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* Python variable pattern
|
|
32
|
+
*/
|
|
33
|
+
export declare const PYTHON_VAR_PATTERN: RegExp;
|
|
34
|
+
/**
|
|
35
|
+
* Go function pattern
|
|
36
|
+
*/
|
|
37
|
+
export declare const GO_FUNCTION_PATTERN: RegExp;
|
|
38
|
+
/**
|
|
39
|
+
* Go variable pattern
|
|
40
|
+
*/
|
|
41
|
+
export declare const GO_VAR_PATTERN: RegExp;
|
|
42
|
+
/**
|
|
43
|
+
* Rust function pattern
|
|
44
|
+
*/
|
|
45
|
+
export declare const RUST_FUNCTION_PATTERN: RegExp;
|
|
46
|
+
/**
|
|
47
|
+
* Rust variable pattern
|
|
48
|
+
*/
|
|
49
|
+
export declare const RUST_VAR_PATTERN: RegExp;
|
|
50
|
+
/**
|
|
51
|
+
* Ruby method pattern
|
|
52
|
+
*/
|
|
53
|
+
export declare const RUBY_METHOD_PATTERN: RegExp;
|
|
54
|
+
/**
|
|
55
|
+
* Ruby variable pattern
|
|
56
|
+
*/
|
|
57
|
+
export declare const RUBY_VAR_PATTERN: RegExp;
|
|
58
|
+
/**
|
|
59
|
+
* Java/Kotlin/C# method pattern
|
|
60
|
+
*/
|
|
61
|
+
export declare const JAVA_METHOD_PATTERN: RegExp;
|
|
62
|
+
/**
|
|
63
|
+
* Java/Kotlin/C# variable pattern
|
|
64
|
+
*/
|
|
65
|
+
export declare const JAVA_VAR_PATTERN: RegExp;
|
|
66
|
+
/**
|
|
67
|
+
* Error handling patterns
|
|
68
|
+
*/
|
|
69
|
+
export declare const TRY_CATCH_PATTERN: RegExp;
|
|
70
|
+
export declare const CATCH_PATTERN: RegExp;
|
|
71
|
+
export declare const RESULT_TYPE_PATTERN: RegExp;
|
|
72
|
+
/**
|
|
73
|
+
* Import style patterns
|
|
74
|
+
*/
|
|
75
|
+
export declare const NAMED_IMPORT_PATTERN: RegExp;
|
|
76
|
+
export declare const WILDCARD_IMPORT_PATTERN: RegExp;
|
|
77
|
+
export declare const SIDE_EFFECT_IMPORT_PATTERN: RegExp;
|
|
78
|
+
export declare const DEFAULT_IMPORT_PATTERN: RegExp;
|
|
79
|
+
/**
|
|
80
|
+
* Quote style detection
|
|
81
|
+
*/
|
|
82
|
+
export declare function countQuotes(line: string): {
|
|
83
|
+
single: number;
|
|
84
|
+
double: number;
|
|
85
|
+
backtick: number;
|
|
86
|
+
};
|