@rigour-labs/core 4.3.0 → 4.3.1

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.
@@ -0,0 +1,260 @@
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
+ * @since v4.3.0
16
+ */
17
+ export type SideEffectLang = 'js' | 'ts' | 'py' | 'go' | 'rs' | 'cs' | 'java' | 'rb';
18
+ export interface SideEffectViolation {
19
+ rule: string;
20
+ severity: 'critical' | 'high' | 'medium' | 'low';
21
+ file: string;
22
+ line: number;
23
+ match: string;
24
+ description: string;
25
+ hint: string;
26
+ }
27
+ /**
28
+ * Tracks a resource creation and its expected cleanup.
29
+ * E.g.: { varName: 'timer', createLine: 5, createCall: 'setInterval' }
30
+ */
31
+ export interface ResourceBinding {
32
+ varName: string | null;
33
+ createLine: number;
34
+ createCall: string;
35
+ scopeStart: number;
36
+ scopeEnd: number;
37
+ }
38
+ export declare const LANG_MAP: Record<string, SideEffectLang>;
39
+ export declare const FILE_GLOBS: string[];
40
+ export declare function stripStrings(line: string): string;
41
+ /**
42
+ * Find the enclosing function scope for a given line.
43
+ * Returns { start, end } of the function body.
44
+ * For module-level code, returns { start: 0, end: lines.length }.
45
+ *
46
+ * Follows promise-safety's approach of backward scanning with brace tracking.
47
+ */
48
+ export declare function findEnclosingFunction(lines: string[], lineIdx: number, lang: SideEffectLang): {
49
+ start: number;
50
+ end: number;
51
+ };
52
+ /**
53
+ * Find the end of a brace-delimited block starting at `start`.
54
+ */
55
+ export declare function findBlockEndBrace(lines: string[], start: number): number;
56
+ /**
57
+ * Find the end of an indentation-delimited block (Python).
58
+ */
59
+ export declare function findBlockEndIndent(lines: string[], start: number): number;
60
+ /**
61
+ * Extract the variable name from an assignment.
62
+ * "const timer = setInterval(...)" → "timer"
63
+ * "let fd = fs.open(...)" → "fd"
64
+ * "self.watcher = chokidar.watch()" → "self.watcher"
65
+ * "timer := time.NewTicker(...)" → "timer" (Go)
66
+ *
67
+ * Returns null if the call result is NOT stored in a variable.
68
+ */
69
+ export declare function extractVariableBinding(line: string, lang: SideEffectLang): string | null;
70
+ /**
71
+ * Check if a specific variable is used in a cleanup call within a scope.
72
+ *
73
+ * Unlike the naive "does clearInterval exist in the file?", this checks:
74
+ * 1. The cleanup function references the specific variable
75
+ * 2. The cleanup is within the correct scope (same function or cleanup callback)
76
+ *
77
+ * Example: for variable "timer" and cleanup patterns [/clearInterval/],
78
+ * matches: `clearInterval(timer)`, `clearInterval(this.timer)`, `timer.close()`
79
+ */
80
+ export declare function hasCleanupForVariable(lines: string[], varName: string, scopeStart: number, scopeEnd: number, cleanupPatterns: RegExp[], lang: SideEffectLang): boolean;
81
+ /**
82
+ * Check if a line is inside a cleanup/teardown context.
83
+ *
84
+ * Cleanup contexts where resource cleanup is expected:
85
+ * - JS/TS: useEffect return function, componentWillUnmount, beforeDestroy, ngOnDestroy, dispose()
86
+ * - Python: __del__, __exit__, close(), cleanup(), teardown
87
+ * - Go: defer statement
88
+ * - Java: finally block, close() method, @PreDestroy
89
+ * - C#: Dispose(), using block, finalizer
90
+ * - Ruby: ensure block, at_exit
91
+ */
92
+ export declare function isInsideCleanupContext(lines: string[], lineIdx: number, lang: SideEffectLang): boolean;
93
+ /**
94
+ * Check if a timer/resource creation is inside a React useEffect
95
+ * that returns a cleanup function.
96
+ *
97
+ * Pattern:
98
+ * useEffect(() => {
99
+ * const timer = setInterval(...) ← creation
100
+ * return () => clearInterval(timer) ← cleanup
101
+ * }, [deps])
102
+ */
103
+ export declare function isInUseEffectWithCleanup(lines: string[], lineIdx: number): boolean;
104
+ /**
105
+ * Check if a Go resource open is immediately followed by defer close.
106
+ *
107
+ * Idiomatic Go:
108
+ * f, err := os.Open(path)
109
+ * if err != nil { return err }
110
+ * defer f.Close()
111
+ */
112
+ export declare function hasGoDefer(lines: string[], openLine: number, varName: string): boolean;
113
+ /**
114
+ * Check if a Python open() is inside a `with` statement (context manager).
115
+ */
116
+ export declare function isPythonWithStatement(line: string): boolean;
117
+ /**
118
+ * Check if a Java resource open is inside try-with-resources.
119
+ * Pattern: try (var x = new FileStream(...)) { ... }
120
+ */
121
+ export declare function isJavaTryWithResources(lines: string[], lineIdx: number): boolean;
122
+ /**
123
+ * Check if a C# resource is inside a using statement/declaration.
124
+ * Patterns: `using (var x = ...)` or `using var x = ...` (C# 8+)
125
+ */
126
+ export declare function isCSharpUsing(line: string): boolean;
127
+ /**
128
+ * Check if a Ruby File.open uses block form (auto-closes).
129
+ * Pattern: File.open(path) do |f| ... end
130
+ * File.open(path) { |f| ... }
131
+ */
132
+ export declare function isRubyBlockForm(line: string): boolean;
133
+ /**
134
+ * Check if a Rust resource is automatically dropped (RAII).
135
+ * In Rust, all resources are dropped when they go out of scope,
136
+ * so we only flag resources in unsafe blocks or static/global context.
137
+ */
138
+ export declare function isRustAutoDropped(lines: string[], lineIdx: number): boolean;
139
+ /**
140
+ * Extract the path being watched from a file watcher call.
141
+ * Returns null if path cannot be determined.
142
+ *
143
+ * Handles:
144
+ * - fs.watch('./src', ...)
145
+ * - chokidar.watch(['./src', './lib'], ...)
146
+ * - Observer(path=...)
147
+ * - fsnotify.NewWatcher() + watcher.Add(path)
148
+ */
149
+ export declare function extractWatchedPath(line: string): string | null;
150
+ /**
151
+ * Extract write target path from a file write call.
152
+ * Returns null if path cannot be determined.
153
+ */
154
+ export declare function extractWritePath(line: string): string | null;
155
+ /**
156
+ * Check if a write path could trigger a file watcher.
157
+ *
158
+ * Smart matching:
159
+ * - "./src/output.js" is inside watched "./src"
160
+ * - "./dist/bundle.js" is NOT inside "./src"
161
+ * - If either path is a variable reference ($var), consider it suspicious
162
+ * - Exact matches always overlap
163
+ */
164
+ export declare function pathsOverlap(watchPath: string | null, writePath: string | null): boolean;
165
+ /**
166
+ * Extract loop body with correct scope tracking.
167
+ * Uses brace/indent matching (not just "next N lines").
168
+ */
169
+ export declare function extractLoopBody(lines: string[], loopLine: number, lang: SideEffectLang): {
170
+ body: string;
171
+ start: number;
172
+ end: number;
173
+ };
174
+ /**
175
+ * Extract all function definitions with their bodies.
176
+ * Used for recursion detection — need to check if function calls itself
177
+ * within its own extracted body (not just anywhere in the file).
178
+ */
179
+ export declare function extractFunctionDefs(lines: string[], lang: SideEffectLang): {
180
+ name: string;
181
+ start: number;
182
+ end: number;
183
+ params: string;
184
+ }[];
185
+ /**
186
+ * Check if a function has a base case (return/break before recursive call).
187
+ * Smart: actually checks that the base case comes BEFORE the recursive call,
188
+ * not just that both exist somewhere in the body.
189
+ */
190
+ export declare function hasBaseCase(bodyLines: string[], funcName: string): boolean;
191
+ /**
192
+ * Check if a function has a depth/limit parameter (implies bounded recursion).
193
+ * Smarter than just checking for the word "depth" anywhere — checks the
194
+ * function signature specifically.
195
+ */
196
+ export declare function hasDepthParameter(funcLine: string): boolean;
197
+ /**
198
+ * Check if a code block contains I/O operations.
199
+ * Language-aware: knows which stdlib calls are I/O.
200
+ */
201
+ export declare function containsIO(body: string, lang: SideEffectLang): boolean;
202
+ /**
203
+ * Check if a loop body or its preamble contains a retry limit.
204
+ *
205
+ * Smart: checks variable declarations before the loop AND inside the loop.
206
+ * Recognizes both explicit counters and library patterns.
207
+ */
208
+ export declare function hasRetryLimit(lines: string[], loopLine: number, bodyEnd: number): boolean;
209
+ /**
210
+ * Check if error handling inside a loop constitutes a retry pattern.
211
+ * Not just "catch exists" but "catch is followed by continue or the loop wraps the try".
212
+ */
213
+ export declare function hasCatchWithContinue(body: string, lang: SideEffectLang): boolean;
214
+ /**
215
+ * Check if a line contains a process spawn call.
216
+ */
217
+ export declare function isProcessSpawn(line: string, lang: SideEffectLang): RegExpMatchArray | null;
218
+ /**
219
+ * Check if a line contains a timer creation call.
220
+ * Returns the timer function name if matched.
221
+ */
222
+ export declare function isTimerCreation(line: string, lang: SideEffectLang): string | null;
223
+ /**
224
+ * Get cleanup patterns for timers (language-specific).
225
+ */
226
+ export declare function getTimerCleanupPatterns(lang: SideEffectLang): RegExp[];
227
+ /**
228
+ * Get cleanup patterns for spawned processes.
229
+ */
230
+ export declare function getProcessCleanupPatterns(lang: SideEffectLang): RegExp[];
231
+ /**
232
+ * Check if a line contains an unbounded loop construct.
233
+ */
234
+ export declare function isUnboundedLoop(line: string, lang: SideEffectLang): boolean;
235
+ /**
236
+ * Check if a line creates a file watcher.
237
+ * Returns the watcher function name if matched.
238
+ */
239
+ export declare function isFileWatcher(line: string, lang: SideEffectLang): string | null;
240
+ /**
241
+ * Check if a code body contains file write operations.
242
+ * Returns the first write call found, or null.
243
+ */
244
+ export declare function findWriteInBody(body: string, lang: SideEffectLang): string | null;
245
+ /**
246
+ * Check if a file watcher callback has debounce/throttle protection.
247
+ */
248
+ export declare function hasDebounceProtection(body: string): boolean;
249
+ /**
250
+ * Get resource open patterns for lifecycle checking.
251
+ */
252
+ export declare function isResourceOpen(line: string, lang: SideEffectLang): string | null;
253
+ /**
254
+ * Get resource close patterns.
255
+ */
256
+ export declare function getResourceClosePatterns(lang: SideEffectLang): RegExp[];
257
+ /**
258
+ * Check if an exit/signal handler respawns the process (auto-restart pattern).
259
+ */
260
+ export declare function isExitHandler(line: string, lang: SideEffectLang): boolean;