@rigour-labs/core 3.0.5 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/deep/fact-extractor.d.ts +80 -0
- package/dist/deep/fact-extractor.js +626 -0
- package/dist/deep/index.d.ts +14 -0
- package/dist/deep/index.js +12 -0
- package/dist/deep/prompts.d.ts +22 -0
- package/dist/deep/prompts.js +374 -0
- package/dist/deep/verifier.d.ts +16 -0
- package/dist/deep/verifier.js +388 -0
- package/dist/gates/deep-analysis.d.ts +28 -0
- package/dist/gates/deep-analysis.js +302 -0
- package/dist/gates/deprecated-apis-rules-lang.d.ts +21 -0
- package/dist/gates/deprecated-apis-rules-lang.js +311 -0
- package/dist/gates/deprecated-apis-rules-node.d.ts +19 -0
- package/dist/gates/deprecated-apis-rules-node.js +199 -0
- package/dist/gates/deprecated-apis-rules.d.ts +6 -0
- package/dist/gates/deprecated-apis-rules.js +6 -0
- package/dist/gates/deprecated-apis.js +1 -502
- package/dist/gates/hallucinated-imports-lang.d.ts +16 -0
- package/dist/gates/hallucinated-imports-lang.js +374 -0
- package/dist/gates/hallucinated-imports-stdlib.d.ts +12 -0
- package/dist/gates/hallucinated-imports-stdlib.js +228 -0
- package/dist/gates/hallucinated-imports.d.ts +0 -98
- package/dist/gates/hallucinated-imports.js +10 -678
- package/dist/gates/phantom-apis-data.d.ts +33 -0
- package/dist/gates/phantom-apis-data.js +398 -0
- package/dist/gates/phantom-apis.js +1 -393
- package/dist/gates/phantom-apis.test.js +52 -0
- package/dist/gates/promise-safety-helpers.d.ts +19 -0
- package/dist/gates/promise-safety-helpers.js +101 -0
- package/dist/gates/promise-safety-rules.d.ts +7 -0
- package/dist/gates/promise-safety-rules.js +19 -0
- package/dist/gates/promise-safety.d.ts +1 -21
- package/dist/gates/promise-safety.js +51 -257
- package/dist/gates/runner.d.ts +4 -2
- package/dist/gates/runner.js +46 -1
- package/dist/gates/test-quality-lang.d.ts +30 -0
- package/dist/gates/test-quality-lang.js +188 -0
- package/dist/gates/test-quality.d.ts +0 -14
- package/dist/gates/test-quality.js +13 -186
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -2
- package/dist/inference/cloud-provider.d.ts +34 -0
- package/dist/inference/cloud-provider.js +126 -0
- package/dist/inference/index.d.ts +17 -0
- package/dist/inference/index.js +23 -0
- package/dist/inference/model-manager.d.ts +26 -0
- package/dist/inference/model-manager.js +106 -0
- package/dist/inference/sidecar-provider.d.ts +15 -0
- package/dist/inference/sidecar-provider.js +153 -0
- package/dist/inference/types.d.ts +77 -0
- package/dist/inference/types.js +19 -0
- package/dist/pattern-index/indexer-helpers.d.ts +38 -0
- package/dist/pattern-index/indexer-helpers.js +111 -0
- package/dist/pattern-index/indexer-lang.d.ts +13 -0
- package/dist/pattern-index/indexer-lang.js +244 -0
- package/dist/pattern-index/indexer-ts.d.ts +22 -0
- package/dist/pattern-index/indexer-ts.js +258 -0
- package/dist/pattern-index/indexer.d.ts +4 -106
- package/dist/pattern-index/indexer.js +58 -707
- package/dist/pattern-index/staleness-data.d.ts +6 -0
- package/dist/pattern-index/staleness-data.js +262 -0
- package/dist/pattern-index/staleness.js +1 -258
- package/dist/settings.d.ts +104 -0
- package/dist/settings.js +186 -0
- package/dist/storage/db.d.ts +16 -0
- package/dist/storage/db.js +132 -0
- package/dist/storage/findings.d.ts +14 -0
- package/dist/storage/findings.js +38 -0
- package/dist/storage/index.d.ts +9 -0
- package/dist/storage/index.js +8 -0
- package/dist/storage/patterns.d.ts +35 -0
- package/dist/storage/patterns.js +62 -0
- package/dist/storage/scans.d.ts +42 -0
- package/dist/storage/scans.js +55 -0
- package/dist/templates/index.d.ts +12 -16
- package/dist/templates/index.js +11 -527
- package/dist/templates/paradigms.d.ts +2 -0
- package/dist/templates/paradigms.js +46 -0
- package/dist/templates/presets.d.ts +14 -0
- package/dist/templates/presets.js +227 -0
- package/dist/templates/universal-config.d.ts +2 -0
- package/dist/templates/universal-config.js +190 -0
- package/dist/types/index.d.ts +438 -15
- package/dist/types/index.js +41 -1
- package/package.json +6 -2
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Analysis Gate — LLM-powered code quality analysis.
|
|
3
|
+
*
|
|
4
|
+
* Three-step pipeline:
|
|
5
|
+
* 1. AST extracts facts → "UserService has 8 public methods touching 4 domains"
|
|
6
|
+
* 2. LLM interprets facts → "UserService violates Single Responsibility"
|
|
7
|
+
* 3. AST verifies LLM → Does UserService actually have those methods? ✓
|
|
8
|
+
*
|
|
9
|
+
* AST grounds LLM. LLM interprets AST. Neither works alone.
|
|
10
|
+
*/
|
|
11
|
+
import { Gate } from './base.js';
|
|
12
|
+
import { createProvider } from '../inference/index.js';
|
|
13
|
+
import { extractFacts, factsToPromptString, chunkFacts, buildAnalysisPrompt, buildCrossFilePrompt, verifyFindings } from '../deep/index.js';
|
|
14
|
+
import { Logger } from '../utils/logger.js';
|
|
15
|
+
/** Max files to analyze before truncating (prevents OOM on huge repos) */
|
|
16
|
+
const MAX_ANALYZABLE_FILES = 500;
|
|
17
|
+
/** Setup timeout: 120s for model download, 30s for API connection */
|
|
18
|
+
const SETUP_TIMEOUT_MS = 120_000;
|
|
19
|
+
export class DeepAnalysisGate extends Gate {
|
|
20
|
+
config;
|
|
21
|
+
provider = null;
|
|
22
|
+
constructor(config) {
|
|
23
|
+
super('deep-analysis', 'Deep Code Quality Analysis');
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
get provenance() {
|
|
27
|
+
return 'deep-analysis';
|
|
28
|
+
}
|
|
29
|
+
async run(context) {
|
|
30
|
+
const { onProgress } = this.config;
|
|
31
|
+
const failures = [];
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
try {
|
|
34
|
+
// Step 0: Initialize inference provider (with timeout)
|
|
35
|
+
onProgress?.('\n Setting up Rigour Brain...\n');
|
|
36
|
+
this.provider = createProvider(this.config.options);
|
|
37
|
+
await Promise.race([
|
|
38
|
+
this.provider.setup(onProgress),
|
|
39
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Setup timed out. Check network or model availability.')), SETUP_TIMEOUT_MS)),
|
|
40
|
+
]);
|
|
41
|
+
const isLocal = !this.config.options.apiKey || this.config.options.provider === 'local';
|
|
42
|
+
if (isLocal) {
|
|
43
|
+
onProgress?.('\n 🔒 100% local analysis. Your code never leaves this machine.\n');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
onProgress?.(`\n ☁️ Using ${this.config.options.provider} API. Code is sent to cloud.\n`);
|
|
47
|
+
}
|
|
48
|
+
// Step 1: AST extracts facts
|
|
49
|
+
onProgress?.(' Extracting code facts...');
|
|
50
|
+
let allFacts = await extractFacts(context.cwd, context.ignore);
|
|
51
|
+
if (allFacts.length === 0) {
|
|
52
|
+
onProgress?.(' No analyzable files found. Check ignore patterns and file extensions.');
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
// Cap file count to prevent OOM on huge repos
|
|
56
|
+
if (allFacts.length > MAX_ANALYZABLE_FILES) {
|
|
57
|
+
onProgress?.(` ⚠ Found ${allFacts.length} files, capping at ${MAX_ANALYZABLE_FILES} (largest files prioritized).`);
|
|
58
|
+
// Sort by line count descending — analyze the biggest files first
|
|
59
|
+
allFacts.sort((a, b) => b.lineCount - a.lineCount);
|
|
60
|
+
allFacts = allFacts.slice(0, MAX_ANALYZABLE_FILES);
|
|
61
|
+
}
|
|
62
|
+
const agentCount = this.config.options.agents || 1;
|
|
63
|
+
const isCloud = !!this.config.options.apiKey;
|
|
64
|
+
onProgress?.(` Found ${allFacts.length} files to analyze${agentCount > 1 ? ` with ${agentCount} parallel agents` : ''}.`);
|
|
65
|
+
// Step 2: LLM interprets facts (in chunks)
|
|
66
|
+
const chunks = chunkFacts(allFacts);
|
|
67
|
+
const allFindings = [];
|
|
68
|
+
let failedChunks = 0;
|
|
69
|
+
if (agentCount > 1 && isCloud) {
|
|
70
|
+
// ── Multi-agent mode: partition chunks across N agents, analyze in parallel ──
|
|
71
|
+
// Each agent gets its own provider instance for true parallelism.
|
|
72
|
+
// Local mode stays sequential (single sidecar process).
|
|
73
|
+
onProgress?.(` Spawning ${agentCount} parallel agents...`);
|
|
74
|
+
const agentBuckets = Array.from({ length: agentCount }, () => []);
|
|
75
|
+
chunks.forEach((chunk, i) => agentBuckets[i % agentCount].push(chunk));
|
|
76
|
+
// Create N independent provider instances
|
|
77
|
+
const agentProviders = [];
|
|
78
|
+
for (let a = 0; a < agentCount; a++) {
|
|
79
|
+
if (agentBuckets[a].length === 0)
|
|
80
|
+
continue;
|
|
81
|
+
const p = createProvider(this.config.options);
|
|
82
|
+
await p.setup(); // Already connected — cloud setup is instant after first
|
|
83
|
+
agentProviders.push(p);
|
|
84
|
+
}
|
|
85
|
+
// Run all agents in parallel
|
|
86
|
+
const agentResults = await Promise.allSettled(agentProviders.map(async (provider, agentIdx) => {
|
|
87
|
+
const bucket = agentBuckets[agentIdx];
|
|
88
|
+
const findings = [];
|
|
89
|
+
let failed = 0;
|
|
90
|
+
for (let ci = 0; ci < bucket.length; ci++) {
|
|
91
|
+
const globalIdx = agentIdx + ci * agentCount + 1;
|
|
92
|
+
onProgress?.(` Agent ${agentIdx + 1}: batch ${ci + 1}/${bucket.length} (global ${globalIdx}/${chunks.length})`);
|
|
93
|
+
const factsStr = factsToPromptString(bucket[ci]);
|
|
94
|
+
const prompt = buildAnalysisPrompt(factsStr, this.config.checks);
|
|
95
|
+
try {
|
|
96
|
+
const response = await provider.analyze(prompt, {
|
|
97
|
+
maxTokens: this.config.maxTokens || 8192,
|
|
98
|
+
temperature: this.config.temperature || 0.1,
|
|
99
|
+
timeout: this.config.timeoutMs || 120000,
|
|
100
|
+
jsonMode: true,
|
|
101
|
+
});
|
|
102
|
+
findings.push(...parseFindings(response));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
failed++;
|
|
106
|
+
Logger.warn(`Agent ${agentIdx + 1} chunk ${ci + 1} failed: ${error.message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { findings, failed };
|
|
110
|
+
}));
|
|
111
|
+
// Merge results and dispose extra providers
|
|
112
|
+
for (let i = 0; i < agentResults.length; i++) {
|
|
113
|
+
const result = agentResults[i];
|
|
114
|
+
if (result.status === 'fulfilled') {
|
|
115
|
+
allFindings.push(...result.value.findings);
|
|
116
|
+
failedChunks += result.value.failed;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
failedChunks += agentBuckets[i].length;
|
|
120
|
+
Logger.warn(`Agent ${i + 1} failed entirely: ${result.reason?.message || 'unknown'}`);
|
|
121
|
+
}
|
|
122
|
+
agentProviders[i]?.dispose();
|
|
123
|
+
}
|
|
124
|
+
onProgress?.(` All ${agentCount} agents completed.`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// ── Single-agent mode: sequential chunk processing ──
|
|
128
|
+
let chunkIndex = 0;
|
|
129
|
+
for (const chunk of chunks) {
|
|
130
|
+
chunkIndex++;
|
|
131
|
+
onProgress?.(` Analyzing batch ${chunkIndex}/${chunks.length}...`);
|
|
132
|
+
const factsStr = factsToPromptString(chunk);
|
|
133
|
+
const prompt = buildAnalysisPrompt(factsStr, this.config.checks);
|
|
134
|
+
try {
|
|
135
|
+
const response = await this.provider.analyze(prompt, {
|
|
136
|
+
maxTokens: this.config.maxTokens || (isCloud ? 4096 : 512),
|
|
137
|
+
temperature: this.config.temperature || 0.1,
|
|
138
|
+
timeout: this.config.timeoutMs || (isCloud ? 120000 : 60000),
|
|
139
|
+
jsonMode: true,
|
|
140
|
+
});
|
|
141
|
+
const findings = parseFindings(response);
|
|
142
|
+
allFindings.push(...findings);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
failedChunks++;
|
|
146
|
+
Logger.warn(`Chunk ${chunkIndex} inference failed: ${error.message}`);
|
|
147
|
+
onProgress?.(` ⚠ Batch ${chunkIndex} failed: ${error.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Cross-file analysis (if we have enough files and at least some chunks succeeded)
|
|
152
|
+
if (allFacts.length >= 3 && failedChunks < chunks.length) {
|
|
153
|
+
onProgress?.(' Running cross-file analysis...');
|
|
154
|
+
try {
|
|
155
|
+
const crossPrompt = buildCrossFilePrompt(allFacts);
|
|
156
|
+
const crossResponse = await this.provider.analyze(crossPrompt, {
|
|
157
|
+
maxTokens: this.config.maxTokens || (isCloud ? 4096 : 512),
|
|
158
|
+
temperature: this.config.temperature || 0.1,
|
|
159
|
+
timeout: this.config.timeoutMs || (isCloud ? 120000 : 60000),
|
|
160
|
+
jsonMode: true,
|
|
161
|
+
});
|
|
162
|
+
const crossFindings = parseFindings(crossResponse);
|
|
163
|
+
allFindings.push(...crossFindings);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
Logger.warn(`Cross-file analysis failed: ${error.message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Step 3: AST verifies LLM
|
|
170
|
+
onProgress?.(' Verifying findings...');
|
|
171
|
+
const verified = verifyFindings(allFindings, allFacts);
|
|
172
|
+
const durationMs = Date.now() - startTime;
|
|
173
|
+
onProgress?.(` ✓ ${verified.length} verified findings (${allFindings.length - verified.length} dropped) in ${(durationMs / 1000).toFixed(1)}s`);
|
|
174
|
+
if (failedChunks > 0) {
|
|
175
|
+
onProgress?.(` ⚠ ${failedChunks}/${chunks.length} batches failed — results may be incomplete.`);
|
|
176
|
+
}
|
|
177
|
+
// Convert to Failure format
|
|
178
|
+
for (const finding of verified) {
|
|
179
|
+
const failure = this.createFailure(finding.description, [finding.file], finding.suggestion, `[${finding.category}] ${finding.description.substring(0, 80)}`, finding.line, undefined, finding.severity);
|
|
180
|
+
// Tag with deep analysis metadata
|
|
181
|
+
failure.confidence = finding.confidence;
|
|
182
|
+
failure.source = 'llm';
|
|
183
|
+
failure.category = finding.category;
|
|
184
|
+
failure.verified = finding.verified;
|
|
185
|
+
failures.push(failure);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
Logger.error(`Deep analysis failed: ${error.message}`);
|
|
190
|
+
onProgress?.(` ⚠ Deep analysis error: ${error.message}`);
|
|
191
|
+
// Don't fail the whole check — deep is advisory
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
this.provider?.dispose();
|
|
195
|
+
}
|
|
196
|
+
return failures;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Parse LLM response into structured findings.
|
|
201
|
+
* Handles various response formats (raw JSON, markdown-wrapped JSON, etc.)
|
|
202
|
+
*/
|
|
203
|
+
function parseFindings(response) {
|
|
204
|
+
if (!response || response.trim().length === 0) {
|
|
205
|
+
Logger.warn('Empty LLM response received');
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
// Try direct JSON parse first
|
|
210
|
+
const parsed = JSON.parse(response);
|
|
211
|
+
if (Array.isArray(parsed.findings))
|
|
212
|
+
return validateFindings(parsed.findings);
|
|
213
|
+
if (Array.isArray(parsed))
|
|
214
|
+
return validateFindings(parsed);
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Try extracting JSON from markdown code blocks
|
|
219
|
+
const jsonMatch = response.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
220
|
+
if (jsonMatch) {
|
|
221
|
+
try {
|
|
222
|
+
const parsed = JSON.parse(jsonMatch[1]);
|
|
223
|
+
if (Array.isArray(parsed.findings))
|
|
224
|
+
return validateFindings(parsed.findings);
|
|
225
|
+
if (Array.isArray(parsed))
|
|
226
|
+
return validateFindings(parsed);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Fall through
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Try finding JSON object in response
|
|
233
|
+
const objectMatch = response.match(/\{[\s\S]*"findings"[\s\S]*\}/);
|
|
234
|
+
if (objectMatch) {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(objectMatch[0]);
|
|
237
|
+
if (Array.isArray(parsed.findings))
|
|
238
|
+
return validateFindings(parsed.findings);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Give up
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Last resort: try to recover truncated JSON arrays
|
|
245
|
+
// LLMs sometimes exceed token limits, truncating the response mid-JSON
|
|
246
|
+
const recovered = recoverTruncatedFindings(response);
|
|
247
|
+
if (recovered.length > 0) {
|
|
248
|
+
Logger.info(`Recovered ${recovered.length} findings from truncated response`);
|
|
249
|
+
return recovered;
|
|
250
|
+
}
|
|
251
|
+
Logger.warn(`Could not parse LLM response as findings JSON. First 200 chars: ${response.substring(0, 200)}`);
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Attempt to recover individual finding objects from a truncated JSON response.
|
|
257
|
+
* Extracts complete JSON objects from partial arrays.
|
|
258
|
+
*/
|
|
259
|
+
function recoverTruncatedFindings(response) {
|
|
260
|
+
const findings = [];
|
|
261
|
+
// Match individual complete objects within the response
|
|
262
|
+
const objectRegex = /\{\s*"category"\s*:\s*"[^"]+"\s*,[\s\S]*?"description"\s*:\s*"[^"]*"[^}]*\}/g;
|
|
263
|
+
let match;
|
|
264
|
+
while ((match = objectRegex.exec(response)) !== null) {
|
|
265
|
+
try {
|
|
266
|
+
const obj = JSON.parse(match[0]);
|
|
267
|
+
if (obj.category && obj.file && obj.description) {
|
|
268
|
+
findings.push(obj);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Individual object was itself truncated — skip
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return validateFindings(findings);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Validate and sanitize findings from LLM response.
|
|
279
|
+
* Drops malformed entries that lack required fields.
|
|
280
|
+
*/
|
|
281
|
+
function validateFindings(raw) {
|
|
282
|
+
return raw.filter(f => {
|
|
283
|
+
if (!f || typeof f !== 'object')
|
|
284
|
+
return false;
|
|
285
|
+
if (!f.category || typeof f.category !== 'string')
|
|
286
|
+
return false;
|
|
287
|
+
if (!f.file || typeof f.file !== 'string')
|
|
288
|
+
return false;
|
|
289
|
+
if (!f.description || typeof f.description !== 'string')
|
|
290
|
+
return false;
|
|
291
|
+
// Normalize confidence
|
|
292
|
+
if (typeof f.confidence !== 'number' || f.confidence < 0 || f.confidence > 1) {
|
|
293
|
+
f.confidence = 0.5;
|
|
294
|
+
}
|
|
295
|
+
// Normalize severity
|
|
296
|
+
const validSeverities = ['critical', 'high', 'medium', 'low', 'info'];
|
|
297
|
+
if (!validSeverities.includes(f.severity)) {
|
|
298
|
+
f.severity = 'medium';
|
|
299
|
+
}
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-specific deprecated APIs: Python, Go, C#, Java
|
|
3
|
+
* Extracted to keep deprecated-apis.ts under 500 lines.
|
|
4
|
+
*/
|
|
5
|
+
import { DeprecatedRule } from './deprecated-apis-rules-node.js';
|
|
6
|
+
/**
|
|
7
|
+
* Python deprecated APIs — sourced from Python 3.12+ deprecation notices
|
|
8
|
+
*/
|
|
9
|
+
export declare const PYTHON_DEPRECATED_RULES: DeprecatedRule[];
|
|
10
|
+
/**
|
|
11
|
+
* Go deprecated APIs — sourced from Go official deprecation notices
|
|
12
|
+
*/
|
|
13
|
+
export declare const GO_DEPRECATED_RULES: DeprecatedRule[];
|
|
14
|
+
/**
|
|
15
|
+
* C# deprecated APIs — sourced from .NET deprecation notices
|
|
16
|
+
*/
|
|
17
|
+
export declare const CSHARP_DEPRECATED_RULES: DeprecatedRule[];
|
|
18
|
+
/**
|
|
19
|
+
* Java deprecated APIs — sourced from JDK deprecation notices
|
|
20
|
+
*/
|
|
21
|
+
export declare const JAVA_DEPRECATED_RULES: DeprecatedRule[];
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-specific deprecated APIs: Python, Go, C#, Java
|
|
3
|
+
* Extracted to keep deprecated-apis.ts under 500 lines.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Python deprecated APIs — sourced from Python 3.12+ deprecation notices
|
|
7
|
+
*/
|
|
8
|
+
export const PYTHON_DEPRECATED_RULES = [
|
|
9
|
+
// Security-deprecated
|
|
10
|
+
{
|
|
11
|
+
pattern: /\bmd5\s*\(|\.md5\s*\(/,
|
|
12
|
+
api: 'hashlib.md5() for passwords',
|
|
13
|
+
reason: 'MD5 is cryptographically broken — collision attacks proven since 2004',
|
|
14
|
+
replacement: 'hashlib.sha256(), hashlib.blake2b(), or bcrypt/argon2 for passwords',
|
|
15
|
+
category: 'security',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
pattern: /\bsha1\s*\(|\.sha1\s*\(/,
|
|
19
|
+
api: 'hashlib.sha1() for security',
|
|
20
|
+
reason: 'SHA-1 is cryptographically broken — SHAttered attack (2017)',
|
|
21
|
+
replacement: 'hashlib.sha256() or hashlib.sha3_256()',
|
|
22
|
+
category: 'security',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /\bpickle\.loads?\s*\(/,
|
|
26
|
+
api: 'pickle.load()/loads()',
|
|
27
|
+
reason: 'Arbitrary code execution vulnerability when loading untrusted data',
|
|
28
|
+
replacement: 'json.loads() for data, or use restricted_loads with allowlists',
|
|
29
|
+
category: 'security',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pattern: /\byaml\.load\s*\([^)]*(?!\bLoader\b)[^)]*\)/,
|
|
33
|
+
api: 'yaml.load() without Loader',
|
|
34
|
+
reason: 'Arbitrary code execution when loading untrusted YAML',
|
|
35
|
+
replacement: 'yaml.safe_load() or yaml.load(data, Loader=yaml.SafeLoader)',
|
|
36
|
+
category: 'security',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
pattern: /\bexec\s*\(\s*(?:input|request|f['"])/,
|
|
40
|
+
api: 'exec() with user input',
|
|
41
|
+
reason: 'Code injection vulnerability',
|
|
42
|
+
replacement: 'ast.literal_eval() for data, structured parsing for expressions',
|
|
43
|
+
category: 'security',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pattern: /\bos\.system\s*\(/,
|
|
47
|
+
api: 'os.system()',
|
|
48
|
+
reason: 'Shell injection vulnerability, no output capture',
|
|
49
|
+
replacement: 'subprocess.run() with shell=False',
|
|
50
|
+
category: 'security',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pattern: /subprocess\.(?:call|run|Popen)\s*\([^)]*shell\s*=\s*True/,
|
|
54
|
+
api: 'subprocess with shell=True',
|
|
55
|
+
reason: 'Shell injection vulnerability when args contain user input',
|
|
56
|
+
replacement: 'subprocess.run() with shell=False and list args',
|
|
57
|
+
category: 'security',
|
|
58
|
+
},
|
|
59
|
+
// Removed modules (Python 3.12+)
|
|
60
|
+
{
|
|
61
|
+
pattern: /\bimport\s+imp\b/,
|
|
62
|
+
api: 'import imp',
|
|
63
|
+
reason: 'Removed in Python 3.12 (PEP 594)',
|
|
64
|
+
replacement: 'importlib',
|
|
65
|
+
category: 'removed',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
pattern: /\bimport\s+(?:aifc|audioop|cgi|cgitb|chunk|crypt|imghdr|mailcap|msilib|nis|nntplib|ossaudiodev|pipes|sndhdr|spwd|sunau|telnetlib|uu|xdrlib)\b/,
|
|
69
|
+
api: 'Dead batteries module',
|
|
70
|
+
reason: 'Removed in Python 3.13 (PEP 594 — dead batteries)',
|
|
71
|
+
replacement: 'PyPI equivalents (see PEP 594 for specific replacements)',
|
|
72
|
+
category: 'removed',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
pattern: /\bfrom\s+distutils\b/,
|
|
76
|
+
api: 'distutils',
|
|
77
|
+
reason: 'Removed in Python 3.12 (PEP 632)',
|
|
78
|
+
replacement: 'setuptools or build',
|
|
79
|
+
category: 'removed',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
pattern: /\bimport\s+formatter\b/,
|
|
83
|
+
api: 'import formatter',
|
|
84
|
+
reason: 'Removed in Python 3.10',
|
|
85
|
+
replacement: 'No direct replacement — use string formatting',
|
|
86
|
+
category: 'removed',
|
|
87
|
+
},
|
|
88
|
+
// Superseded
|
|
89
|
+
{
|
|
90
|
+
pattern: /\bfrom\s+collections\s+import\s+(?:Mapping|MutableMapping|Sequence|MutableSequence|Set|MutableSet|Callable|Iterable|Iterator|Generator|Coroutine|Awaitable|AsyncIterable|AsyncIterator|AsyncGenerator|Hashable|Sized|Container|Collection|Reversible|MappingView|KeysView|ItemsView|ValuesView|ByteString)\b/,
|
|
91
|
+
api: 'collections ABCs',
|
|
92
|
+
reason: 'Removed in Python 3.10 — moved to collections.abc',
|
|
93
|
+
replacement: 'from collections.abc import ...',
|
|
94
|
+
category: 'removed',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
pattern: /\boptparse\b/,
|
|
98
|
+
api: 'optparse',
|
|
99
|
+
reason: 'Superseded since Python 3.2',
|
|
100
|
+
replacement: 'argparse',
|
|
101
|
+
category: 'superseded',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
pattern: /\bfrom\s+typing\s+import\s+(?:Dict|List|Set|Tuple|FrozenSet|Type|Deque|DefaultDict|OrderedDict|Counter|ChainMap|Awaitable|Coroutine|AsyncIterable|AsyncIterator|AsyncGenerator|Iterable|Iterator|Generator|Reversible|Container|Collection|Callable|AbstractSet|MutableSet|Mapping|MutableMapping|Sequence|MutableSequence|ByteString|MappingView|KeysView|ItemsView|ValuesView|ContextManager|AsyncContextManager|Pattern|Match)\b/,
|
|
105
|
+
api: 'typing generics (Dict, List, etc.)',
|
|
106
|
+
reason: 'Deprecated since Python 3.9 — use built-in generics (PEP 585)',
|
|
107
|
+
replacement: 'dict[], list[], set[], tuple[] (lowercase built-in types)',
|
|
108
|
+
category: 'superseded',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
pattern: /\bfrom\s+typing\s+import\s+(?:Optional|Union)\b/,
|
|
112
|
+
api: 'typing.Optional / typing.Union',
|
|
113
|
+
reason: 'Superseded in Python 3.10 — use X | Y syntax (PEP 604)',
|
|
114
|
+
replacement: 'X | None instead of Optional[X], X | Y instead of Union[X, Y]',
|
|
115
|
+
category: 'superseded',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
pattern: /\basyncio\.get_event_loop\s*\(\s*\)/,
|
|
119
|
+
api: 'asyncio.get_event_loop()',
|
|
120
|
+
reason: 'Deprecated in Python 3.10 — may create new loop unexpectedly',
|
|
121
|
+
replacement: 'asyncio.get_running_loop() or asyncio.run()',
|
|
122
|
+
category: 'superseded',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
pattern: /\bsetup\s*\(\s*[^)]*\buse_2to3\s*=/,
|
|
126
|
+
api: 'setup(use_2to3=True)',
|
|
127
|
+
reason: 'Removed in setuptools 58+ — Python 2 support dropped',
|
|
128
|
+
replacement: 'Write Python 3 only code',
|
|
129
|
+
category: 'removed',
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
/**
|
|
133
|
+
* Go deprecated APIs — sourced from Go official deprecation notices
|
|
134
|
+
*/
|
|
135
|
+
export const GO_DEPRECATED_RULES = [
|
|
136
|
+
{
|
|
137
|
+
pattern: /\bioutil\.ReadFile\s*\(/,
|
|
138
|
+
api: 'ioutil.ReadFile()', reason: 'Deprecated since Go 1.16 — io/ioutil package deprecated',
|
|
139
|
+
replacement: 'os.ReadFile()', category: 'superseded',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
pattern: /\bioutil\.WriteFile\s*\(/,
|
|
143
|
+
api: 'ioutil.WriteFile()', reason: 'Deprecated since Go 1.16',
|
|
144
|
+
replacement: 'os.WriteFile()', category: 'superseded',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
pattern: /\bioutil\.ReadAll\s*\(/,
|
|
148
|
+
api: 'ioutil.ReadAll()', reason: 'Deprecated since Go 1.16',
|
|
149
|
+
replacement: 'io.ReadAll()', category: 'superseded',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
pattern: /\bioutil\.ReadDir\s*\(/,
|
|
153
|
+
api: 'ioutil.ReadDir()', reason: 'Deprecated since Go 1.16',
|
|
154
|
+
replacement: 'os.ReadDir()', category: 'superseded',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
pattern: /\bioutil\.TempDir\s*\(/,
|
|
158
|
+
api: 'ioutil.TempDir()', reason: 'Deprecated since Go 1.17',
|
|
159
|
+
replacement: 'os.MkdirTemp()', category: 'superseded',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
pattern: /\bioutil\.TempFile\s*\(/,
|
|
163
|
+
api: 'ioutil.TempFile()', reason: 'Deprecated since Go 1.17',
|
|
164
|
+
replacement: 'os.CreateTemp()', category: 'superseded',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
pattern: /\bioutil\.NopCloser\s*\(/,
|
|
168
|
+
api: 'ioutil.NopCloser()', reason: 'Deprecated since Go 1.16',
|
|
169
|
+
replacement: 'io.NopCloser()', category: 'superseded',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
pattern: /\b"io\/ioutil"/,
|
|
173
|
+
api: 'import "io/ioutil"', reason: 'Entire io/ioutil package deprecated since Go 1.16',
|
|
174
|
+
replacement: 'Use os and io packages instead', category: 'superseded',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
pattern: /\bsort\.IntSlice\b|sort\.Float64Slice\b|sort\.StringSlice\b/,
|
|
178
|
+
api: 'sort.*Slice types', reason: 'Superseded since Go 1.21',
|
|
179
|
+
replacement: 'slices.Sort() or sort.Slice()', category: 'superseded',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
pattern: /\bmath\/rand"[\s\S]*?rand\.(Seed|Read)\s*\(/,
|
|
183
|
+
api: 'rand.Seed() / rand.Read()', reason: 'Deprecated in Go 1.20+',
|
|
184
|
+
replacement: 'Auto-seeded in Go 1.20+; use crypto/rand.Read()', category: 'superseded',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
pattern: /\bstrings\.Title\s*\(/,
|
|
188
|
+
api: 'strings.Title()', reason: 'Deprecated since Go 1.18 — broken for Unicode',
|
|
189
|
+
replacement: 'golang.org/x/text/cases.Title()', category: 'superseded',
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
/**
|
|
193
|
+
* C# deprecated APIs — sourced from .NET deprecation notices
|
|
194
|
+
*/
|
|
195
|
+
export const CSHARP_DEPRECATED_RULES = [
|
|
196
|
+
{
|
|
197
|
+
pattern: /\bnew\s+WebClient\s*\(/,
|
|
198
|
+
api: 'WebClient', reason: 'Deprecated in .NET 6+ — poor async support',
|
|
199
|
+
replacement: 'HttpClient', category: 'superseded',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
pattern: /\bBinaryFormatter\b/,
|
|
203
|
+
api: 'BinaryFormatter', reason: 'Security vulnerability — arbitrary code execution on deserialization',
|
|
204
|
+
replacement: 'System.Text.Json or JsonSerializer', category: 'security',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
pattern: /\bJavaScriptSerializer\b/,
|
|
208
|
+
api: 'JavaScriptSerializer', reason: 'Deprecated — poor performance and limited features',
|
|
209
|
+
replacement: 'System.Text.Json.JsonSerializer', category: 'superseded',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
pattern: /\bThread\.Abort\s*\(/,
|
|
213
|
+
api: 'Thread.Abort()', reason: 'Throws PlatformNotSupportedException in .NET 5+',
|
|
214
|
+
replacement: 'CancellationToken for cooperative cancellation', category: 'removed',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
pattern: /\bThread\.Suspend\s*\(|Thread\.Resume\s*\(/,
|
|
218
|
+
api: 'Thread.Suspend/Resume()', reason: 'Deprecated — causes deadlocks',
|
|
219
|
+
replacement: 'ManualResetEvent or SemaphoreSlim', category: 'removed',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
pattern: /\bAppDomain\.CreateDomain\s*\(/,
|
|
223
|
+
api: 'AppDomain.CreateDomain()', reason: 'Not supported in .NET Core/5+',
|
|
224
|
+
replacement: 'AssemblyLoadContext', category: 'removed',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
pattern: /\bRemoting\b.*\bChannel\b/,
|
|
228
|
+
api: '.NET Remoting', reason: 'Removed in .NET Core/5+',
|
|
229
|
+
replacement: 'gRPC, REST APIs, or SignalR', category: 'removed',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
pattern: /\bnew\s+SHA1(?:Managed|CryptoServiceProvider)\s*\(/,
|
|
233
|
+
api: 'SHA1Managed/CryptoServiceProvider', reason: 'SHA-1 cryptographically broken',
|
|
234
|
+
replacement: 'SHA256.Create() or SHA512.Create()', category: 'security',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
pattern: /\bnew\s+MD5CryptoServiceProvider\s*\(/,
|
|
238
|
+
api: 'MD5CryptoServiceProvider', reason: 'MD5 cryptographically broken',
|
|
239
|
+
replacement: 'SHA256.Create() or SHA512.Create()', category: 'security',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
pattern: /\bnew\s+(?:RijndaelManaged|DESCryptoServiceProvider|RC2CryptoServiceProvider|TripleDESCryptoServiceProvider)\s*\(/,
|
|
243
|
+
api: 'Legacy crypto providers', reason: 'Weak encryption algorithms',
|
|
244
|
+
replacement: 'Aes.Create()', category: 'security',
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
/**
|
|
248
|
+
* Java deprecated APIs — sourced from JDK deprecation notices
|
|
249
|
+
*/
|
|
250
|
+
export const JAVA_DEPRECATED_RULES = [
|
|
251
|
+
{
|
|
252
|
+
pattern: /\bnew\s+Date\s*\(\s*\d/,
|
|
253
|
+
api: 'new Date(year, month, ...)', reason: 'Deprecated since JDK 1.1',
|
|
254
|
+
replacement: 'java.time.LocalDate, LocalDateTime, ZonedDateTime', category: 'superseded',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
pattern: /\bnew\s+Vector\s*[<(]/,
|
|
258
|
+
api: 'Vector', reason: 'Legacy synchronized collection — poor performance',
|
|
259
|
+
replacement: 'ArrayList (or Collections.synchronizedList())', category: 'superseded',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
pattern: /\bnew\s+Hashtable\s*[<(]/,
|
|
263
|
+
api: 'Hashtable', reason: 'Legacy synchronized map — poor performance',
|
|
264
|
+
replacement: 'HashMap (or ConcurrentHashMap)', category: 'superseded',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
pattern: /\bnew\s+Stack\s*[<(]/,
|
|
268
|
+
api: 'Stack', reason: 'Legacy class — extends Vector unnecessarily',
|
|
269
|
+
replacement: 'Deque<> (ArrayDeque) with push/pop', category: 'superseded',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
pattern: /\bnew\s+StringBuffer\s*\(/,
|
|
273
|
+
api: 'StringBuffer', reason: 'Unnecessarily synchronized — slower than StringBuilder',
|
|
274
|
+
replacement: 'StringBuilder (unless thread safety needed)', category: 'superseded',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
pattern: /\.getYear\s*\(\s*\)(?!.*java\.time)/,
|
|
278
|
+
api: 'Date.getYear()', reason: 'Deprecated since JDK 1.1 — returns year - 1900',
|
|
279
|
+
replacement: 'LocalDate.now().getYear()', category: 'superseded',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
pattern: /Thread\.stop\s*\(/i,
|
|
283
|
+
api: 'Thread.stop()', reason: 'Deprecated — unsafe, can corrupt objects',
|
|
284
|
+
replacement: 'Thread.interrupt() with cooperative checking', category: 'security',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
pattern: /Thread\.destroy\s*\(|Thread\.suspend\s*\(|Thread\.resume\s*\(/,
|
|
288
|
+
api: 'Thread.destroy/suspend/resume()', reason: 'Deprecated — deadlock-prone',
|
|
289
|
+
replacement: 'Thread.interrupt() and wait/notify', category: 'removed',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
pattern: /Runtime\.runFinalizersOnExit\s*\(/,
|
|
293
|
+
api: 'Runtime.runFinalizersOnExit()', reason: 'Deprecated — inherently unsafe',
|
|
294
|
+
replacement: 'Runtime shutdown hooks or try-with-resources', category: 'removed',
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
pattern: /\bfinalize\s*\(\s*\)\s*(?:throws|\{)/,
|
|
298
|
+
api: 'finalize()', reason: 'Deprecated since Java 9 (JEP 421) — for removal',
|
|
299
|
+
replacement: 'Cleaner or try-with-resources (AutoCloseable)', category: 'superseded',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
pattern: /\bnew\s+Integer\s*\(|new\s+Long\s*\(|new\s+Double\s*\(|new\s+Boolean\s*\(|new\s+Float\s*\(/,
|
|
303
|
+
api: 'new Integer/Long/Double/Boolean/Float()', reason: 'Deprecated since Java 9 — valueOf preferred',
|
|
304
|
+
replacement: 'Integer.valueOf(), autoboxing, or parse methods', category: 'superseded',
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
pattern: /\bSecurityManager\b/,
|
|
308
|
+
api: 'SecurityManager', reason: 'Deprecated for removal since Java 17 (JEP 411)',
|
|
309
|
+
replacement: 'No direct replacement — use OS-level security', category: 'removed',
|
|
310
|
+
},
|
|
311
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule data for deprecated-apis gate.
|
|
3
|
+
* Node.js and Web API deprecation rules extracted to keep deprecated-apis.ts under 500 lines.
|
|
4
|
+
*/
|
|
5
|
+
export interface DeprecatedRule {
|
|
6
|
+
pattern: RegExp;
|
|
7
|
+
api: string;
|
|
8
|
+
reason: string;
|
|
9
|
+
replacement: string;
|
|
10
|
+
category: 'security' | 'removed' | 'superseded';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Node.js deprecated APIs — sourced from official Node.js deprecation list
|
|
14
|
+
*/
|
|
15
|
+
export declare const NODE_DEPRECATED_RULES: DeprecatedRule[];
|
|
16
|
+
/**
|
|
17
|
+
* Web API deprecated patterns
|
|
18
|
+
*/
|
|
19
|
+
export declare const WEB_DEPRECATED_RULES: DeprecatedRule[];
|