@behavioral-contracts/verify-cli 1.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/LICENSE +119 -0
- package/README.md +694 -0
- package/dist/analyze-results.js +253 -0
- package/dist/analyzer.d.ts +366 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +2592 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/analyzers/async-error-analyzer.d.ts +72 -0
- package/dist/analyzers/async-error-analyzer.d.ts.map +1 -0
- package/dist/analyzers/async-error-analyzer.js +243 -0
- package/dist/analyzers/async-error-analyzer.js.map +1 -0
- package/dist/analyzers/event-listener-analyzer.d.ts +102 -0
- package/dist/analyzers/event-listener-analyzer.d.ts.map +1 -0
- package/dist/analyzers/event-listener-analyzer.js +253 -0
- package/dist/analyzers/event-listener-analyzer.js.map +1 -0
- package/dist/analyzers/react-query-analyzer.d.ts +66 -0
- package/dist/analyzers/react-query-analyzer.d.ts.map +1 -0
- package/dist/analyzers/react-query-analyzer.js +341 -0
- package/dist/analyzers/react-query-analyzer.js.map +1 -0
- package/dist/analyzers/return-value-analyzer.d.ts +61 -0
- package/dist/analyzers/return-value-analyzer.d.ts.map +1 -0
- package/dist/analyzers/return-value-analyzer.js +225 -0
- package/dist/analyzers/return-value-analyzer.js.map +1 -0
- package/dist/code-snippet.d.ts +48 -0
- package/dist/code-snippet.d.ts.map +1 -0
- package/dist/code-snippet.js +84 -0
- package/dist/code-snippet.js.map +1 -0
- package/dist/corpus-loader.d.ts +33 -0
- package/dist/corpus-loader.d.ts.map +1 -0
- package/dist/corpus-loader.js +155 -0
- package/dist/corpus-loader.js.map +1 -0
- package/dist/fixture-tester.d.ts +28 -0
- package/dist/fixture-tester.d.ts.map +1 -0
- package/dist/fixture-tester.js +176 -0
- package/dist/fixture-tester.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +375 -0
- package/dist/index.js.map +1 -0
- package/dist/package-discovery.d.ts +62 -0
- package/dist/package-discovery.d.ts.map +1 -0
- package/dist/package-discovery.js +299 -0
- package/dist/package-discovery.js.map +1 -0
- package/dist/reporter.d.ts +43 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +347 -0
- package/dist/reporter.js.map +1 -0
- package/dist/reporters/benchmarking.d.ts +70 -0
- package/dist/reporters/benchmarking.d.ts.map +1 -0
- package/dist/reporters/benchmarking.js +191 -0
- package/dist/reporters/benchmarking.js.map +1 -0
- package/dist/reporters/d3-visualizer.d.ts +40 -0
- package/dist/reporters/d3-visualizer.d.ts.map +1 -0
- package/dist/reporters/d3-visualizer.js +803 -0
- package/dist/reporters/d3-visualizer.js.map +1 -0
- package/dist/reporters/health-score.d.ts +33 -0
- package/dist/reporters/health-score.d.ts.map +1 -0
- package/dist/reporters/health-score.js +149 -0
- package/dist/reporters/health-score.js.map +1 -0
- package/dist/reporters/index.d.ts +11 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +11 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/package-breakdown.d.ts +48 -0
- package/dist/reporters/package-breakdown.d.ts.map +1 -0
- package/dist/reporters/package-breakdown.js +185 -0
- package/dist/reporters/package-breakdown.js.map +1 -0
- package/dist/reporters/positive-evidence.d.ts +42 -0
- package/dist/reporters/positive-evidence.d.ts.map +1 -0
- package/dist/reporters/positive-evidence.js +436 -0
- package/dist/reporters/positive-evidence.js.map +1 -0
- package/dist/tsconfig-generator.d.ts +17 -0
- package/dist/tsconfig-generator.d.ts.map +1 -0
- package/dist/tsconfig-generator.js +107 -0
- package/dist/tsconfig-generator.js.map +1 -0
- package/dist/types.d.ts +298 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Phase 6.1: Analyze baseline test results
|
|
4
|
+
* Parses audit JSON files and generates summary report
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const OUTPUT_DATE = process.argv[2] || new Date().toISOString().split('T')[0].replace(/-/g, '');
|
|
12
|
+
const OUTPUT_DIR = path.join(path.dirname(__dirname), 'output', OUTPUT_DATE);
|
|
13
|
+
function parseAuditFile(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
16
|
+
return JSON.parse(content);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error(`Failed to parse ${filePath}:`, err);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function analyzePackage(packageName) {
|
|
24
|
+
const safeName = packageName.replace(/\//g, '-');
|
|
25
|
+
const auditPath = path.join(OUTPUT_DIR, `${safeName}-audit.json`);
|
|
26
|
+
const result = {
|
|
27
|
+
name: packageName,
|
|
28
|
+
status: 'ERROR',
|
|
29
|
+
totalViolations: 0,
|
|
30
|
+
properViolations: 0,
|
|
31
|
+
missingViolations: 0,
|
|
32
|
+
instanceViolations: 0,
|
|
33
|
+
violations: [],
|
|
34
|
+
};
|
|
35
|
+
if (!fs.existsSync(auditPath)) {
|
|
36
|
+
result.error = 'Audit file not found';
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
const audit = parseAuditFile(auditPath);
|
|
40
|
+
if (!audit) {
|
|
41
|
+
result.error = 'Failed to parse audit file';
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
result.totalViolations = audit.violations.length;
|
|
45
|
+
result.violations = audit.violations;
|
|
46
|
+
// Categorize violations by fixture file
|
|
47
|
+
for (const violation of audit.violations) {
|
|
48
|
+
const filename = path.basename(violation.file);
|
|
49
|
+
if (filename === 'proper-error-handling.ts') {
|
|
50
|
+
result.properViolations++;
|
|
51
|
+
}
|
|
52
|
+
else if (filename === 'missing-error-handling.ts') {
|
|
53
|
+
result.missingViolations++;
|
|
54
|
+
}
|
|
55
|
+
else if (filename === 'instance-usage.ts') {
|
|
56
|
+
result.instanceViolations++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// PASS if no violations in proper-error-handling.ts
|
|
60
|
+
result.status = result.properViolations === 0 ? 'PASS' : 'FAIL';
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function generateReport(results) {
|
|
64
|
+
const passed = results.filter(r => r.status === 'PASS');
|
|
65
|
+
const failed = results.filter(r => r.status === 'FAIL');
|
|
66
|
+
const errors = results.filter(r => r.status === 'ERROR');
|
|
67
|
+
let report = `# Phase 6.1: Baseline Fixture Testing - Complete Results
|
|
68
|
+
|
|
69
|
+
**Date:** ${new Date().toISOString().split('T')[0]}
|
|
70
|
+
**Output Directory:** \`verify-cli/output/${OUTPUT_DATE}/\`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Summary
|
|
75
|
+
|
|
76
|
+
- **Total Packages:** ${results.length}
|
|
77
|
+
- **Passed:** ${passed.length} (${((passed.length / results.length) * 100).toFixed(1)}%)
|
|
78
|
+
- **Failed:** ${failed.length} (${((failed.length / results.length) * 100).toFixed(1)}%)
|
|
79
|
+
- **Errors:** ${errors.length}
|
|
80
|
+
|
|
81
|
+
**Pass Criteria:** 0 violations in \`proper-error-handling.ts\`
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Overall Results
|
|
86
|
+
|
|
87
|
+
### ✅ Passed Packages (${passed.length})
|
|
88
|
+
|
|
89
|
+
${passed.length > 0 ? passed.map(r => `- **${r.name}** - ${r.totalViolations} total violations (${r.missingViolations} missing, ${r.instanceViolations} instance)`).join('\n') : '_None_'}
|
|
90
|
+
|
|
91
|
+
### ❌ Failed Packages (${failed.length})
|
|
92
|
+
|
|
93
|
+
${failed.length > 0 ? failed.map(r => `- **${r.name}** - ${r.properViolations} violations in proper-error-handling.ts`).join('\n') : '_None_'}
|
|
94
|
+
|
|
95
|
+
### ⚠️ Error Packages (${errors.length})
|
|
96
|
+
|
|
97
|
+
${errors.length > 0 ? errors.map(r => `- **${r.name}** - ${r.error}`).join('\n') : '_None_'}
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Detailed Results by Package
|
|
102
|
+
|
|
103
|
+
`;
|
|
104
|
+
// Sort by status then name
|
|
105
|
+
const sortedResults = results.sort((a, b) => {
|
|
106
|
+
if (a.status !== b.status) {
|
|
107
|
+
const order = { PASS: 0, FAIL: 1, ERROR: 2 };
|
|
108
|
+
return order[a.status] - order[b.status];
|
|
109
|
+
}
|
|
110
|
+
return a.name.localeCompare(b.name);
|
|
111
|
+
});
|
|
112
|
+
for (const result of sortedResults) {
|
|
113
|
+
const emoji = result.status === 'PASS' ? '✅' : result.status === 'FAIL' ? '❌' : '⚠️';
|
|
114
|
+
report += `### ${emoji} ${result.name}\n\n`;
|
|
115
|
+
report += `**Status:** ${result.status}\n\n`;
|
|
116
|
+
if (result.status === 'ERROR') {
|
|
117
|
+
report += `**Error:** ${result.error}\n\n`;
|
|
118
|
+
report += `---\n\n`;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
report += `**Violations:**\n`;
|
|
122
|
+
report += `- Total: ${result.totalViolations}\n`;
|
|
123
|
+
report += `- In proper-error-handling.ts: ${result.properViolations}\n`;
|
|
124
|
+
report += `- In missing-error-handling.ts: ${result.missingViolations}\n`;
|
|
125
|
+
report += `- In instance-usage.ts: ${result.instanceViolations}\n\n`;
|
|
126
|
+
if (result.properViolations > 0) {
|
|
127
|
+
report += `**Issues in proper-error-handling.ts:**\n\n`;
|
|
128
|
+
const properViolations = result.violations.filter(v => path.basename(v.file) === 'proper-error-handling.ts');
|
|
129
|
+
for (const v of properViolations) {
|
|
130
|
+
report += `- Line ${v.line}: ${v.message}\n`;
|
|
131
|
+
report += ` - Package: \`${v.package}\`, Method: \`${v.method}\`\n`;
|
|
132
|
+
report += ` - Severity: ${v.severity}\n\n`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
report += `---\n\n`;
|
|
136
|
+
}
|
|
137
|
+
// Pattern analysis
|
|
138
|
+
report += `## Pattern Analysis\n\n`;
|
|
139
|
+
const failurePatterns = {};
|
|
140
|
+
for (const result of failed) {
|
|
141
|
+
for (const v of result.violations) {
|
|
142
|
+
if (path.basename(v.file) === 'proper-error-handling.ts') {
|
|
143
|
+
const key = `${v.package}:${v.method}`;
|
|
144
|
+
failurePatterns[key] = (failurePatterns[key] || 0) + 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (Object.keys(failurePatterns).length > 0) {
|
|
149
|
+
report += `### Common False Positives\n\n`;
|
|
150
|
+
const sorted = Object.entries(failurePatterns).sort((a, b) => b[1] - a[1]);
|
|
151
|
+
for (const [pattern, count] of sorted) {
|
|
152
|
+
report += `- \`${pattern}\` - ${count} occurrence(s)\n`;
|
|
153
|
+
}
|
|
154
|
+
report += `\n`;
|
|
155
|
+
}
|
|
156
|
+
// Recommendations
|
|
157
|
+
report += `## Recommendations\n\n`;
|
|
158
|
+
if (failed.length > 0) {
|
|
159
|
+
report += `### High Priority Fixes\n\n`;
|
|
160
|
+
report += `The following packages have violations in proper-error-handling.ts and need investigation:\n\n`;
|
|
161
|
+
for (const result of failed.slice(0, 10)) {
|
|
162
|
+
report += `1. **${result.name}**\n`;
|
|
163
|
+
report += ` - Review proper-error-handling.ts fixture\n`;
|
|
164
|
+
report += ` - Verify error handling patterns are actually proper\n`;
|
|
165
|
+
report += ` - Update contract or analyzer if needed\n\n`;
|
|
166
|
+
}
|
|
167
|
+
if (failed.length > 10) {
|
|
168
|
+
report += `_...and ${failed.length - 10} more packages_\n\n`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
report += `### Next Steps\n\n`;
|
|
172
|
+
report += `1. **Investigate Failed Packages**: Review each package with proper-error-handling.ts violations\n`;
|
|
173
|
+
report += `2. **Fix Contracts**: Update contracts to match actual proper patterns\n`;
|
|
174
|
+
report += `3. **Fix Analyzer**: Improve analyzer detection logic if needed\n`;
|
|
175
|
+
report += `4. **Fix Fixtures**: Correct fixture code if patterns are actually improper\n`;
|
|
176
|
+
report += `5. **Re-test**: Run Phase 6.1 again after fixes\n\n`;
|
|
177
|
+
report += `---\n\n`;
|
|
178
|
+
report += `**Generated:** ${new Date().toISOString()}\n`;
|
|
179
|
+
return report;
|
|
180
|
+
}
|
|
181
|
+
async function main() {
|
|
182
|
+
console.log(`Analyzing results in: ${OUTPUT_DIR}`);
|
|
183
|
+
const packages = [
|
|
184
|
+
"axios",
|
|
185
|
+
"cloudinary",
|
|
186
|
+
"discord.js",
|
|
187
|
+
"express",
|
|
188
|
+
"firebase-admin",
|
|
189
|
+
"ioredis",
|
|
190
|
+
"mongodb",
|
|
191
|
+
"mongoose",
|
|
192
|
+
"openai",
|
|
193
|
+
"pg",
|
|
194
|
+
"react-hook-form",
|
|
195
|
+
"redis",
|
|
196
|
+
"square",
|
|
197
|
+
"stripe",
|
|
198
|
+
"twilio",
|
|
199
|
+
"typescript",
|
|
200
|
+
"zod",
|
|
201
|
+
"@anthropic-ai/sdk",
|
|
202
|
+
"@aws-sdk/client-s3",
|
|
203
|
+
"@clerk/nextjs",
|
|
204
|
+
"@octokit/rest",
|
|
205
|
+
"@prisma/client",
|
|
206
|
+
"@sendgrid/mail",
|
|
207
|
+
"@slack/web-api",
|
|
208
|
+
"@supabase/supabase-js",
|
|
209
|
+
"@tanstack/react-query",
|
|
210
|
+
"bullmq",
|
|
211
|
+
"@vercel/postgres",
|
|
212
|
+
"drizzle-orm",
|
|
213
|
+
"socket.io",
|
|
214
|
+
"joi",
|
|
215
|
+
"ethers",
|
|
216
|
+
"fastify",
|
|
217
|
+
"next",
|
|
218
|
+
"dotenv",
|
|
219
|
+
"jsonwebtoken",
|
|
220
|
+
"bcrypt",
|
|
221
|
+
"multer",
|
|
222
|
+
"helmet",
|
|
223
|
+
"cors",
|
|
224
|
+
"winston",
|
|
225
|
+
"passport",
|
|
226
|
+
"knex",
|
|
227
|
+
"typeorm",
|
|
228
|
+
"graphql",
|
|
229
|
+
"uuid",
|
|
230
|
+
"date-fns",
|
|
231
|
+
"@nestjs/common",
|
|
232
|
+
"@hapi/hapi",
|
|
233
|
+
];
|
|
234
|
+
const results = [];
|
|
235
|
+
for (const pkg of packages) {
|
|
236
|
+
const result = analyzePackage(pkg);
|
|
237
|
+
results.push(result);
|
|
238
|
+
const emoji = result.status === 'PASS' ? '✅' : result.status === 'FAIL' ? '❌' : '⚠️';
|
|
239
|
+
console.log(`${emoji} ${pkg}: ${result.status} (${result.properViolations} proper violations)`);
|
|
240
|
+
}
|
|
241
|
+
console.log(`\nGenerating report...`);
|
|
242
|
+
const report = generateReport(results);
|
|
243
|
+
const reportPath = path.join(__dirname, '../dev-notes/findings/phase6-baseline-complete-results.md');
|
|
244
|
+
// Ensure directory exists
|
|
245
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
246
|
+
fs.writeFileSync(reportPath, report, 'utf-8');
|
|
247
|
+
console.log(`\nReport saved to: ${reportPath}`);
|
|
248
|
+
console.log(`\nSummary:`);
|
|
249
|
+
console.log(` Passed: ${results.filter(r => r.status === 'PASS').length}`);
|
|
250
|
+
console.log(` Failed: ${results.filter(r => r.status === 'FAIL').length}`);
|
|
251
|
+
console.log(` Errors: ${results.filter(r => r.status === 'ERROR').length}`);
|
|
252
|
+
}
|
|
253
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Analyzer - uses TypeScript Compiler API to detect behavioral contract violations
|
|
3
|
+
*/
|
|
4
|
+
import type { PackageContract, Violation, AnalyzerConfig } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Main analyzer that coordinates the verification process
|
|
7
|
+
*/
|
|
8
|
+
export declare class Analyzer {
|
|
9
|
+
private program;
|
|
10
|
+
private typeChecker;
|
|
11
|
+
private contracts;
|
|
12
|
+
private violations;
|
|
13
|
+
private projectRoot;
|
|
14
|
+
private includeTests;
|
|
15
|
+
private typeToPackage;
|
|
16
|
+
private classToPackage;
|
|
17
|
+
private factoryToPackage;
|
|
18
|
+
private awaitPatternToPackage;
|
|
19
|
+
constructor(config: AnalyzerConfig, contracts: Map<string, PackageContract>);
|
|
20
|
+
/**
|
|
21
|
+
* Builds detection maps from contract definitions
|
|
22
|
+
* This replaces hardcoded mappings with data-driven approach
|
|
23
|
+
*/
|
|
24
|
+
private buildDetectionMaps;
|
|
25
|
+
/**
|
|
26
|
+
* Analyzes all files in the program and returns violations
|
|
27
|
+
*/
|
|
28
|
+
analyze(): Violation[];
|
|
29
|
+
/**
|
|
30
|
+
* Extracts all package imports from a source file
|
|
31
|
+
*/
|
|
32
|
+
private extractImports;
|
|
33
|
+
/**
|
|
34
|
+
* Determines if a file is a test file based on common patterns
|
|
35
|
+
* Test files are excluded by default because:
|
|
36
|
+
* - Tests intentionally expect errors to be thrown
|
|
37
|
+
* - Test frameworks (Jest, Vitest) handle errors automatically
|
|
38
|
+
* - 90%+ of test violations are false positives
|
|
39
|
+
*/
|
|
40
|
+
private isTestFile;
|
|
41
|
+
/**
|
|
42
|
+
* Analyzes a single source file
|
|
43
|
+
*/
|
|
44
|
+
private analyzeFile;
|
|
45
|
+
/**
|
|
46
|
+
* Detects async functions with unprotected await expressions
|
|
47
|
+
*/
|
|
48
|
+
private detectAsyncErrors;
|
|
49
|
+
/**
|
|
50
|
+
* Detects functions with unprotected return value error checks
|
|
51
|
+
*/
|
|
52
|
+
private detectReturnValueErrors;
|
|
53
|
+
/**
|
|
54
|
+
* Creates a violation for unprotected return value error checks
|
|
55
|
+
*/
|
|
56
|
+
private createReturnValueViolation;
|
|
57
|
+
/**
|
|
58
|
+
* Detects instances missing required event listeners
|
|
59
|
+
*/
|
|
60
|
+
private detectEventListenerErrors;
|
|
61
|
+
/**
|
|
62
|
+
* Creates a violation for missing required event listeners
|
|
63
|
+
*/
|
|
64
|
+
private createEventListenerViolation;
|
|
65
|
+
/**
|
|
66
|
+
* Creates a violation for unprotected async calls
|
|
67
|
+
*/
|
|
68
|
+
private createAsyncErrorViolation;
|
|
69
|
+
/**
|
|
70
|
+
* Detects which package is being called from the await expression text
|
|
71
|
+
* Uses dynamic pattern matching from contract detection rules
|
|
72
|
+
*/
|
|
73
|
+
private detectPackageFromAwaitText;
|
|
74
|
+
/**
|
|
75
|
+
* Detects package using TypeScript's type system (MOST ACCURATE METHOD)
|
|
76
|
+
*
|
|
77
|
+
* Uses TypeScript's type checker to determine which package a variable belongs to
|
|
78
|
+
* based on its type, eliminating false positives from pattern matching.
|
|
79
|
+
*
|
|
80
|
+
* Steps:
|
|
81
|
+
* 1. Get the type of the object the method is called on
|
|
82
|
+
* 2. Extract the type name (e.g., "TextChannel", "Model")
|
|
83
|
+
* 3. Look up which package defines this type
|
|
84
|
+
*
|
|
85
|
+
* Example:
|
|
86
|
+
* const channel: TextChannel = getChannel();
|
|
87
|
+
* await channel.createInvite();
|
|
88
|
+
*
|
|
89
|
+
* → channel has type TextChannel
|
|
90
|
+
* → TextChannel is from discord.js (per contract detection.type_names)
|
|
91
|
+
* → Return "discord.js"
|
|
92
|
+
*
|
|
93
|
+
* This eliminates false positives from pattern overlap:
|
|
94
|
+
* - mongoose ".create" won't match discord.js ".createInvite()"
|
|
95
|
+
* - Each type uniquely identifies its package
|
|
96
|
+
*
|
|
97
|
+
* @param node The call expression node from the AST
|
|
98
|
+
* @returns Package name if type is recognized, null otherwise
|
|
99
|
+
*/
|
|
100
|
+
private detectPackageFromType;
|
|
101
|
+
/**
|
|
102
|
+
* Extracts package name from a TypeScript type
|
|
103
|
+
* Handles edge cases: generics, aliases, unions, intersections
|
|
104
|
+
*
|
|
105
|
+
* Edge cases:
|
|
106
|
+
* 1. Type aliases: import { Client as Bot } from 'discord.js' → resolve "Bot" to "Client"
|
|
107
|
+
* 2. Generic types: Model<User> → extract base type "Model"
|
|
108
|
+
* 3. Union types: TextChannel | VoiceChannel → try all types in union
|
|
109
|
+
* 4. Intersection types: A & B → try all types in intersection
|
|
110
|
+
*
|
|
111
|
+
* @param type The TypeScript type to analyze
|
|
112
|
+
* @returns Package name if recognized, null otherwise
|
|
113
|
+
*/
|
|
114
|
+
private extractPackageFromType;
|
|
115
|
+
/**
|
|
116
|
+
* Detects package from tracked instance (most accurate method)
|
|
117
|
+
* Extracts instance name from await expression and checks if it's tracked
|
|
118
|
+
*
|
|
119
|
+
* Examples:
|
|
120
|
+
* "await this.catModel.find()" → extracts "catModel" → checks if tracked
|
|
121
|
+
* "await user.save()" → extracts "user" → checks if tracked
|
|
122
|
+
* "await Model.create()" → extracts "Model" → checks if tracked
|
|
123
|
+
*/
|
|
124
|
+
private detectPackageFromTrackedInstance;
|
|
125
|
+
/**
|
|
126
|
+
* Creates a violation for a detected package
|
|
127
|
+
* Extracted from createAsyncErrorViolation to reduce duplication
|
|
128
|
+
*/
|
|
129
|
+
private createViolationForPackage;
|
|
130
|
+
/**
|
|
131
|
+
* Creates a violation for empty or ineffective catch blocks
|
|
132
|
+
*/
|
|
133
|
+
private createEmptyCatchViolation;
|
|
134
|
+
/**
|
|
135
|
+
* Analyzes a call expression to see if it violates any contracts
|
|
136
|
+
*/
|
|
137
|
+
private analyzeCallExpression;
|
|
138
|
+
/**
|
|
139
|
+
* Analyzes React Query hooks for error handling
|
|
140
|
+
*/
|
|
141
|
+
private analyzeReactQueryHook;
|
|
142
|
+
/**
|
|
143
|
+
* Checks if a mutation variable is used with mutateAsync in a try-catch block
|
|
144
|
+
*/
|
|
145
|
+
private checkMutateAsyncInTryCatch;
|
|
146
|
+
/**
|
|
147
|
+
* Checks a React Query postcondition and returns a violation if not met
|
|
148
|
+
*/
|
|
149
|
+
private checkReactQueryPostcondition;
|
|
150
|
+
/**
|
|
151
|
+
* Walks up a property access chain and returns components
|
|
152
|
+
* Example: prisma.user.create → { root: 'prisma', chain: ['user'], method: 'create' }
|
|
153
|
+
* Example: axios.get → { root: 'axios', chain: [], method: 'get' }
|
|
154
|
+
* Example: openai.chat.completions.create → { root: 'openai', chain: ['chat', 'completions'], method: 'create' }
|
|
155
|
+
*/
|
|
156
|
+
private walkPropertyAccessChain;
|
|
157
|
+
/**
|
|
158
|
+
* Extracts call site information from a call expression
|
|
159
|
+
*/
|
|
160
|
+
private extractCallSite;
|
|
161
|
+
/**
|
|
162
|
+
* Extracts the instance variable name from a call expression
|
|
163
|
+
* e.g., for "axiosInstance.get(...)" returns "axiosInstance"
|
|
164
|
+
*/
|
|
165
|
+
private extractInstanceVariable;
|
|
166
|
+
/**
|
|
167
|
+
* Resolves which package a function comes from by looking at imports
|
|
168
|
+
*/
|
|
169
|
+
private resolvePackageFromImports;
|
|
170
|
+
/**
|
|
171
|
+
* Extracts package name from new expressions
|
|
172
|
+
* Examples: new PrismaClient() → "@prisma/client"
|
|
173
|
+
* new Stripe(key) → "stripe"
|
|
174
|
+
* new OpenAI(config) → "openai"
|
|
175
|
+
*/
|
|
176
|
+
private extractPackageFromNewExpression;
|
|
177
|
+
/**
|
|
178
|
+
* Extracts package name from axios.create() call
|
|
179
|
+
* Returns the package name if this is an axios.create() or similar factory call
|
|
180
|
+
*/
|
|
181
|
+
private extractPackageFromAxiosCreate;
|
|
182
|
+
/**
|
|
183
|
+
* Extracts package name from generic factory methods defined in detection rules
|
|
184
|
+
* Examples:
|
|
185
|
+
* mongoose.model('User', schema) → "mongoose" (if factory_methods includes "model")
|
|
186
|
+
* prisma.client() → "prisma" (if factory_methods includes "client")
|
|
187
|
+
*/
|
|
188
|
+
private extractPackageFromGenericFactory;
|
|
189
|
+
/**
|
|
190
|
+
* Extracts package name from schema factory methods (z.object(), z.string(), etc.)
|
|
191
|
+
* Returns the package name if this is a schema creation call
|
|
192
|
+
*/
|
|
193
|
+
private extractPackageFromSchemaFactory;
|
|
194
|
+
/**
|
|
195
|
+
* Analyzes what error handling exists around a call site
|
|
196
|
+
*/
|
|
197
|
+
/**
|
|
198
|
+
* Checks if a call is within an Express route that has error middleware
|
|
199
|
+
* Pattern: app.post('/route', middleware, errorHandler)
|
|
200
|
+
* where errorHandler has 4 parameters (err, req, res, next)
|
|
201
|
+
*/
|
|
202
|
+
private isWithinExpressErrorMiddleware;
|
|
203
|
+
/**
|
|
204
|
+
* Checks if a call is within a NestJS controller method
|
|
205
|
+
* NestJS controllers use exception filters to handle errors globally
|
|
206
|
+
*/
|
|
207
|
+
private isWithinNestJSController;
|
|
208
|
+
/**
|
|
209
|
+
* Checks if a node has a specific decorator
|
|
210
|
+
*/
|
|
211
|
+
private hasDecorator;
|
|
212
|
+
/**
|
|
213
|
+
* Finds a function definition by name in the source file
|
|
214
|
+
*/
|
|
215
|
+
private findFunctionDefinition;
|
|
216
|
+
private analyzeErrorHandling;
|
|
217
|
+
/**
|
|
218
|
+
* Checks if a node is inside a try-catch block
|
|
219
|
+
*/
|
|
220
|
+
private isInTryCatch;
|
|
221
|
+
/**
|
|
222
|
+
* Checks if a node is inside a callback/arrow function that contains a try-catch block
|
|
223
|
+
* Handles fastify routes, socket.io event handlers, etc.
|
|
224
|
+
*/
|
|
225
|
+
private isInsideCallbackWithTryCatch;
|
|
226
|
+
/**
|
|
227
|
+
* Checks if a node is inside a try block within a function
|
|
228
|
+
*/
|
|
229
|
+
private isNodeInsideTryBlock;
|
|
230
|
+
/**
|
|
231
|
+
* Checks if a function contains a try-catch block in its body
|
|
232
|
+
*/
|
|
233
|
+
private functionContainsTryCatch;
|
|
234
|
+
/**
|
|
235
|
+
* Checks if this is a route/event handler registration with try-catch in the handler
|
|
236
|
+
* Handles: fastify routes (app.get, app.post), socket.io events (io.on, socket.on)
|
|
237
|
+
* Pattern: app.get('/route', async (req, res) => { try { ... } catch { ... } })
|
|
238
|
+
*/
|
|
239
|
+
private isRouteHandlerWithTryCatch;
|
|
240
|
+
/**
|
|
241
|
+
* Checks if a function contains await expressions
|
|
242
|
+
*/
|
|
243
|
+
private functionContainsAwait;
|
|
244
|
+
/**
|
|
245
|
+
* Checks if there's a finally block that cleans up resources
|
|
246
|
+
* Pattern: const client = await pool.connect(); ... finally { client.release(); }
|
|
247
|
+
*/
|
|
248
|
+
private hasFinallyCleanup;
|
|
249
|
+
/**
|
|
250
|
+
* Checks if a finally block calls a cleanup method on a variable
|
|
251
|
+
*/
|
|
252
|
+
private finallyBlockCallsCleanup;
|
|
253
|
+
/**
|
|
254
|
+
* Checks if a call uses callback-based error handling
|
|
255
|
+
* Pattern: callback((error, result) => { if (error) return reject(error); })
|
|
256
|
+
*/
|
|
257
|
+
private hasCallbackErrorHandling;
|
|
258
|
+
/**
|
|
259
|
+
* Checks if a callback function body checks the error parameter
|
|
260
|
+
* Looks for patterns like: if (error) or if (err)
|
|
261
|
+
*/
|
|
262
|
+
private callbackChecksErrorParam;
|
|
263
|
+
/**
|
|
264
|
+
* Finds the enclosing catch clause for a node
|
|
265
|
+
*/
|
|
266
|
+
private findEnclosingCatchClause;
|
|
267
|
+
/**
|
|
268
|
+
* Checks if a catch block checks error.response exists
|
|
269
|
+
*/
|
|
270
|
+
private catchChecksResponseExists;
|
|
271
|
+
/**
|
|
272
|
+
* Checks if an expression checks for response property
|
|
273
|
+
*/
|
|
274
|
+
private expressionChecksResponse;
|
|
275
|
+
/**
|
|
276
|
+
* Checks if a catch block checks status codes
|
|
277
|
+
*/
|
|
278
|
+
private catchChecksStatusCode;
|
|
279
|
+
/**
|
|
280
|
+
* Extracts which status codes are explicitly handled
|
|
281
|
+
*/
|
|
282
|
+
private extractHandledStatusCodes;
|
|
283
|
+
/**
|
|
284
|
+
* Checks if catch block has retry logic
|
|
285
|
+
*/
|
|
286
|
+
private catchHasRetryLogic;
|
|
287
|
+
/**
|
|
288
|
+
* Checks if a postcondition is violated at a call site
|
|
289
|
+
*/
|
|
290
|
+
private checkPostcondition;
|
|
291
|
+
/**
|
|
292
|
+
* Checks if a function call result has proper null handling
|
|
293
|
+
* Used for Clerk functions that return null when not authenticated
|
|
294
|
+
*/
|
|
295
|
+
private checkNullHandling;
|
|
296
|
+
/**
|
|
297
|
+
* Checks if a condition is a null check for the given variables
|
|
298
|
+
*/
|
|
299
|
+
private isNullCheckCondition;
|
|
300
|
+
/**
|
|
301
|
+
* Finds the containing function/method for a node
|
|
302
|
+
*/
|
|
303
|
+
private findContainingFunction;
|
|
304
|
+
/**
|
|
305
|
+
* Checks if a function call has hardcoded credentials (string literals)
|
|
306
|
+
* vs environment variables (process.env.*)
|
|
307
|
+
*
|
|
308
|
+
* Returns true if hardcoded credentials are detected (violation)
|
|
309
|
+
* Returns false if credentials come from environment variables (valid)
|
|
310
|
+
*/
|
|
311
|
+
private checkHardcodedCredentials;
|
|
312
|
+
/**
|
|
313
|
+
* Finds a variable declaration in the scope of the given node
|
|
314
|
+
*/
|
|
315
|
+
private findVariableDeclaration;
|
|
316
|
+
/**
|
|
317
|
+
* Checks if a specific file exists in the project
|
|
318
|
+
* Tries multiple possible locations (root, src/, etc.)
|
|
319
|
+
*
|
|
320
|
+
* @param fileName - The file name to search for (e.g., 'middleware.ts')
|
|
321
|
+
* @param variations - Optional variations of the file name (e.g., ['middleware.ts', 'middleware.js'])
|
|
322
|
+
* @returns The full path if found, null otherwise
|
|
323
|
+
*/
|
|
324
|
+
private checkFileExists;
|
|
325
|
+
/**
|
|
326
|
+
* Checks if a file imports and exports specific patterns
|
|
327
|
+
*
|
|
328
|
+
* @param filePath - Absolute path to the file to check
|
|
329
|
+
* @param importPattern - Object specifying what to look for in imports
|
|
330
|
+
* @param exportPattern - Object specifying what to look for in exports
|
|
331
|
+
* @returns Object with hasImport and hasExport booleans
|
|
332
|
+
*/
|
|
333
|
+
private checkFileImportsAndExports;
|
|
334
|
+
/**
|
|
335
|
+
* Checks if middleware.ts exists and properly exports clerkMiddleware
|
|
336
|
+
* This is specific to @clerk/nextjs middleware setup
|
|
337
|
+
*
|
|
338
|
+
* @returns true if middleware is properly configured, false otherwise
|
|
339
|
+
*/
|
|
340
|
+
private checkClerkMiddlewareExists;
|
|
341
|
+
/**
|
|
342
|
+
* Creates a violation object
|
|
343
|
+
*/
|
|
344
|
+
private createViolation;
|
|
345
|
+
/**
|
|
346
|
+
* Gets statistics about the analysis run
|
|
347
|
+
*/
|
|
348
|
+
getStats(): {
|
|
349
|
+
filesAnalyzed: number;
|
|
350
|
+
contractsApplied: number;
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Analyzes S3 send() calls to detect command type
|
|
354
|
+
* Pattern: s3Client.send(new GetObjectCommand(...))
|
|
355
|
+
*/
|
|
356
|
+
private analyzeS3SendCall;
|
|
357
|
+
/**
|
|
358
|
+
* Analyzes an S3 command call and creates violations if needed
|
|
359
|
+
*/
|
|
360
|
+
private analyzeS3Command;
|
|
361
|
+
/**
|
|
362
|
+
* Maps S3 command types to their corresponding postcondition IDs
|
|
363
|
+
*/
|
|
364
|
+
private mapS3CommandToPostcondition;
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
|