PRININIT / src /static-analyzer.ts
Rachit-Tw's picture
Upload 50 files
7d9309d verified
Raw
History Blame Contribute Delete
5.43 kB
/**
* Static Analysis Module
* Runs deterministic static analysis before LLM review
* Uses enhanced regex-based checks for syntax errors
* Injects findings into LLM prompt for context
*/
interface StaticAnalysisFinding {
line?: number
severity: 'error' | 'warning'
message: string
rule?: string
}
interface StaticAnalysisResult {
filename: string
findings: StaticAnalysisFinding[]
}
/**
* Analyze Python for basic syntax issues
*/
function analyzePythonBasic(content: string): StaticAnalysisFinding[] {
const findings: StaticAnalysisFinding[] = []
const lines = content.split('\n')
// Check for common Python issues
lines.forEach((line, idx) => {
const lineNum = idx + 1
// Check for unmatched parentheses
const openBrackets = (line.match(/\(/g) || []).length
const closeBrackets = (line.match(/\)/g) || []).length
if (openBrackets !== closeBrackets) {
findings.push({
line: lineNum,
severity: 'error',
message: 'Unmatched parentheses detected',
rule: 'syntax-balance'
})
}
// Check for unmatched braces
const openBraces = (line.match(/\{/g) || []).length
const closeBraces = (line.match(/\}/g) || []).length
if (openBraces !== closeBraces) {
findings.push({
line: lineNum,
severity: 'error',
message: 'Unmatched braces detected',
rule: 'syntax-balance'
})
}
// Check for unmatched square brackets
const openSquare = (line.match(/\[/g) || []).length
const closeSquare = (line.match(/\]/g) || []).length
if (openSquare !== closeSquare) {
findings.push({
line: lineNum,
severity: 'error',
message: 'Unmatched square brackets detected',
rule: 'syntax-balance'
})
}
})
return findings
}
/**
* Analyze JavaScript/TypeScript for basic syntax issues
*/
function analyzeJavaScriptBasic(_content: string): StaticAnalysisFinding[] {
return []
}
function analyzeTypeScriptEnhanced(content: string): StaticAnalysisFinding[] {
const findings: StaticAnalysisFinding[] = []
const lines = content.split('\n')
lines.forEach((line, idx) => {
const lineNum = idx + 1
const trimmed = line.trim()
// Check for TypeScript syntax issues
if (trimmed.includes('interface') && !trimmed.includes('{')) {
findings.push({
line: lineNum,
severity: 'error',
message: 'Interface declaration missing opening brace',
rule: 'ts-syntax'
})
}
// Check for type assertion issues
if (trimmed.match(/<[^>]+>\s*[^=]/) && !trimmed.includes('extends')) {
findings.push({
line: lineNum,
severity: 'warning',
message: 'Potential type assertion - consider using as keyword',
rule: 'ts-style'
})
}
// Check for async/await without error handling
if (
trimmed.includes('await') &&
!trimmed.includes('try') &&
!trimmed.includes('catch') &&
!line.includes('.catch(') &&
!line.includes('await Promise.')
) {
findings.push({
line: lineNum,
severity: 'warning',
message: 'Await without error handling',
rule: 'async-error'
})
}
// Check for console.log in production code
if (
trimmed.includes('console.log') ||
trimmed.includes('console.warn') ||
trimmed.includes('console.error')
) {
findings.push({
line: lineNum,
severity: 'warning',
message: 'Console statement detected',
rule: 'no-console'
})
}
// Check for any type usage (warning)
if (
trimmed.includes(': any') ||
trimmed.includes('any[]') ||
trimmed.includes('any>')
) {
findings.push({
line: lineNum,
severity: 'warning',
message: 'Using any type reduces type safety',
rule: 'no-any'
})
}
})
return findings
}
/**
* Main static analysis function
* Analyzes file content and returns findings
*/
export async function analyzeStatic(
content: string,
filename: string
): Promise<StaticAnalysisResult> {
const ext = filename.split('.').pop()?.toLowerCase()
let findings: StaticAnalysisFinding[] = []
switch (ext) {
case 'py':
findings = analyzePythonBasic(content)
break
case 'js':
case 'jsx':
case 'ts':
case 'tsx':
findings = analyzeTypeScriptEnhanced(content)
break
default:
// No static analysis for other file types
break
}
return {
filename,
findings
}
}
/**
* Format static analysis findings for injection into LLM prompt
* Bug Fix #2: Always return content so LLM knows analysis was performed
*/
export function formatStaticAnalysisFindings(
findings: StaticAnalysisFinding[]
): string {
if (findings.length === 0) {
return 'Static analysis: No syntax errors or common issues detected.'
}
const formatted = findings
.map(f => {
const line = f.line ? ` line ${f.line}` : ''
const rule = f.rule ? ` [${f.rule}]` : ''
return `- ${f.severity.toUpperCase()}${line}${rule}: ${f.message}`
})
.join('\n')
return `Static analysis detected:\n${formatted}`
}