Files
metabuilder/scripts/check-code-complexity.ts
2025-12-25 13:01:34 +00:00

176 lines
4.7 KiB
TypeScript

#!/usr/bin/env tsx
import { execSync } from 'child_process'
import { readdirSync, readFileSync, statSync } from 'fs'
import { join, extname } from 'path'
interface ComplexityMetrics {
file: string
functions: Array<{
name: string
complexity: number
lines: number
}>
averageComplexity: number
maxComplexity: number
violations: string[]
}
const MAX_CYCLOMATIC_COMPLEXITY = 10
const MAX_COGNITIVE_COMPLEXITY = 15
const MAX_NESTING = 4
function analyzeComplexity(): ComplexityMetrics[] {
const results: ComplexityMetrics[] = []
const srcDir = 'src'
function walkDir(dir: string) {
const files = readdirSync(dir)
for (const file of files) {
const fullPath = join(dir, file)
const stat = statSync(fullPath)
if (stat.isDirectory() && !['node_modules', '.next', 'dist', 'build'].includes(file)) {
walkDir(fullPath)
} else if (['.ts', '.tsx'].includes(extname(file))) {
analyzeFile(fullPath, results)
}
}
}
walkDir(srcDir)
return results
}
function analyzeFile(filePath: string, results: ComplexityMetrics[]) {
try {
const content = readFileSync(filePath, 'utf8')
const lines = content.split('\n')
const metrics: ComplexityMetrics = {
file: filePath,
functions: [],
averageComplexity: 0,
maxComplexity: 0,
violations: []
}
// Simple regex-based analysis (production would use AST parser)
const functionPattern = /(?:function|const|async\s+(?:function)?|export\s+(?:async\s+)?function)\s+(\w+)/g
let match
while ((match = functionPattern.exec(content)) !== null) {
const startIndex = match.index
const functionName = match[1]
// Calculate complexity by counting conditionals
const functionContent = extractFunctionBody(content, startIndex)
const complexity = calculateComplexity(functionContent)
const nestingLevel = calculateNestingLevel(functionContent)
metrics.functions.push({
name: functionName,
complexity,
lines: functionContent.split('\n').length
})
metrics.maxComplexity = Math.max(metrics.maxComplexity, complexity)
if (complexity > MAX_CYCLOMATIC_COMPLEXITY) {
metrics.violations.push(
`Function "${functionName}" has complexity ${complexity} (max: ${MAX_CYCLOMATIC_COMPLEXITY})`
)
}
if (nestingLevel > MAX_NESTING) {
metrics.violations.push(
`Function "${functionName}" has nesting level ${nestingLevel} (max: ${MAX_NESTING})`
)
}
}
if (metrics.functions.length > 0) {
metrics.averageComplexity =
metrics.functions.reduce((sum, f) => sum + f.complexity, 0) / metrics.functions.length
}
if (metrics.violations.length > 0) {
results.push(metrics)
}
} catch (e) {
// Skip files that can't be analyzed
}
}
function extractFunctionBody(content: string, startIndex: number): string {
let braceCount = 0
let inFunction = false
let result = ''
for (let i = startIndex; i < content.length; i++) {
const char = content[i]
if (char === '{') {
inFunction = true
braceCount++
}
if (inFunction) {
result += char
if (char === '}') {
braceCount--
if (braceCount === 0) break
}
}
}
return result
}
function calculateComplexity(code: string): number {
let complexity = 1
complexity += (code.match(/if\s*\(/g) || []).length
complexity += (code.match(/\?.*:/g) || []).length
complexity += (code.match(/case\s+/g) || []).length
complexity += (code.match(/catch\s*\(/g) || []).length
complexity += (code.match(/for\s*\(/g) || []).length
complexity += (code.match(/while\s*\(/g) || []).length
complexity += (code.match(/&&/g) || []).length * 0.1
complexity += (code.match(/\|\|/g) || []).length * 0.1
return Math.round(complexity * 10) / 10
}
function calculateNestingLevel(code: string): number {
let maxNesting = 0
let currentNesting = 0
for (const char of code) {
if (char === '{') {
currentNesting++
maxNesting = Math.max(maxNesting, currentNesting)
} else if (char === '}') {
currentNesting--
}
}
return maxNesting
}
// Main execution
const results = analyzeComplexity()
const summary = {
totalFilesAnalyzed: results.length,
violatingFiles: results.length,
totalViolations: results.reduce((sum, r) => sum + r.violations.length, 0),
avgMaxComplexity: results.length > 0
? results.reduce((sum, r) => sum + r.maxComplexity, 0) / results.length
: 0,
details: results,
timestamp: new Date().toISOString()
}
console.log(JSON.stringify(summary, null, 2))