UNPKG

bugnitor-security-scanner

Version:

AI-Era Security Scanner: Intelligent automated security review agent specializing in AI-generated vulnerability patterns

330 lines 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CodeQualityAnalyzer = exports.codeQualityMetrics = void 0; exports.codeQualityMetrics = [ { id: 'long-functions', name: 'Overly Long Functions', category: 'Maintainability', description: 'Functions with too many lines of code are hard to maintain', patterns: [ /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]{1000,}\}/gi, /=>\s*\{[\s\S]{800,}\}/gi, /def\s+\w+\s*\([^)]*\):[\s\S]{1200,}(?=\n\S|\n*$)/gi ], severity: 'medium', impact: 'Reduced maintainability and testability', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go'], recommendation: 'Break down large functions into smaller, focused functions', weight: 3 }, { id: 'deep-nesting', name: 'Deep Nesting', category: 'Complexity', description: 'Deeply nested code blocks increase cognitive complexity', patterns: [ /(\s{8,}|\t{4,})(if|for|while|try|with)/gi, /\{\s*\n\s*\{\s*\n\s*\{\s*\n\s*\{/gi ], severity: 'medium', impact: 'Increased cognitive load and bug risk', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go', 'php'], recommendation: 'Reduce nesting using early returns, guard clauses, or extracted functions', weight: 2 }, { id: 'magic-numbers', name: 'Magic Numbers', category: 'Readability', description: 'Unexplained numeric literals reduce code readability', patterns: [ /(?<!const\s+\w+\s*=\s*)\b(?!0|1)\d{2,}\b(?!\s*[;,\)\]\}])/gi, /\b\d+\.\d{2,}\b(?!\s*[;,\)\]\}])/gi ], severity: 'low', impact: 'Reduced code readability and maintainability', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go', 'php'], recommendation: 'Replace magic numbers with named constants', weight: 1 }, { id: 'long-parameter-lists', name: 'Long Parameter Lists', category: 'Maintainability', description: 'Functions with too many parameters are hard to use and maintain', patterns: [ /function\s+\w+\s*\(([^)]*,){5,}[^)]*\)/gi, /def\s+\w+\s*\(([^)]*,){5,}[^)]*\)/gi, /\w+\s*\(([^)]*,){6,}[^)]*\)\s*=>/gi ], severity: 'medium', impact: 'Difficult to use and maintain functions', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go'], recommendation: 'Use parameter objects or break down functionality', weight: 2 }, { id: 'duplicate-code', name: 'Code Duplication', category: 'Maintainability', description: 'Repeated code blocks that should be refactored', patterns: [ // This is simplified - real duplicate detection would need AST analysis /(\w+\s*=\s*[^;]{20,};[\s\n]*){3,}/gi ], severity: 'medium', impact: 'Maintenance burden and inconsistency risk', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go', 'php'], recommendation: 'Extract common code into reusable functions or modules', weight: 3 }, { id: 'missing-error-handling', name: 'Missing Error Handling', category: 'Reliability', description: 'Operations that can fail without proper error handling', patterns: [ /(?:JSON\.parse|fs\.readFile|fetch|axios\.get|requests\.get)\s*\([^)]*\)(?!\s*\.catch)(?!\s*,\s*function)(?![\s\S]{0,100}(try|catch|except))/gi, /await\s+(?!.*try)(?!.*catch)/gi ], severity: 'high', impact: 'Application crashes and poor user experience', fileTypes: ['js', 'ts', 'py'], recommendation: 'Add proper error handling with try-catch blocks or error callbacks', weight: 4 }, { id: 'console-logs', name: 'Debug Code Left in Production', category: 'Code Hygiene', description: 'Debug statements that should be removed', patterns: [ /console\.(log|debug|info|warn|error)\s*\(/gi, /print\s*\(/gi, /debugger\s*;/gi, /var_dump\s*\(/gi ], severity: 'low', impact: 'Performance impact and information leakage', fileTypes: ['js', 'ts', 'py', 'php'], recommendation: 'Remove debug statements or use proper logging frameworks', weight: 1 }, { id: 'large-files', name: 'Overly Large Files', category: 'Maintainability', description: 'Files with too many lines are hard to navigate and maintain', patterns: [], // This will be checked based on line count severity: 'medium', impact: 'Difficult navigation and maintenance', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go', 'php'], recommendation: 'Split large files into smaller, focused modules', weight: 2 }, { id: 'missing-documentation', name: 'Missing Documentation', category: 'Maintainability', description: 'Public functions without documentation comments', patterns: [ /(?:export\s+)?(?:public\s+)?(?:function\s+\w+|class\s+\w+|def\s+\w+)(?![\s\S]{0,50}\/\*\*|[\s\S]{0,50}"""|[\s\S]{0,50}#)/gi ], severity: 'low', impact: 'Poor maintainability and developer experience', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go'], recommendation: 'Add JSDoc, docstrings, or equivalent documentation', weight: 1 }, { id: 'unused-variables', name: 'Unused Variables', category: 'Code Hygiene', description: 'Variables declared but never used', patterns: [ /(?:var|let|const)\s+(\w+)(?![\s\S]*\1(?!\s*[=:]))/gi, /function\s+\w+\s*\([^)]*(\w+)[^)]*\)[\s\S]*?\{(?![\s\S]*\1)[\s\S]*?\}/gi ], severity: 'low', impact: 'Code bloat and confusion', fileTypes: ['js', 'ts'], recommendation: 'Remove unused variables or prefix with underscore if intentionally unused', weight: 1 }, { id: 'hardcoded-values', name: 'Hardcoded Configuration Values', category: 'Maintainability', description: 'Configuration values that should be externalized', patterns: [ /(?:url|endpoint|server|host)\s*[:=]\s*["`'][^"`']*localhost[^"`']*["`']/gi, /(?:url|endpoint|server|host)\s*[:=]\s*["`'][^"`']*:\d{4,5}[^"`']*["`']/gi, /timeout\s*[:=]\s*\d{4,}/gi ], severity: 'medium', impact: 'Difficult environment-specific configuration', fileTypes: ['js', 'ts', 'py', 'java', 'cs', 'go', 'php'], recommendation: 'Move configuration to environment variables or config files', weight: 2 } ]; class CodeQualityAnalyzer { analyzeCodeQuality(content, filename, lineCount) { const fileExtension = filename.split('.').pop()?.toLowerCase(); if (!fileExtension) return { findings: [], qualityScore: this.getDefaultScore() }; const findings = []; const metricScores = {}; for (const metric of exports.codeQualityMetrics) { if (!metric.fileTypes.includes(fileExtension)) continue; const issues = this.findMetricIssues(content, filename, lineCount, metric); findings.push(...issues); // Calculate score for this metric (0-100, where 100 is perfect) const issueCount = issues.length; const penalty = Math.min(issueCount * 10, 80); // Max 80% penalty const score = Math.max(100 - penalty, 20); // Min 20% score metricScores[metric.category] = metricScores[metric.category] || { score: 0, issues: 0, description: '' }; metricScores[metric.category].score = Math.min(metricScores[metric.category].score + score * metric.weight, 100); metricScores[metric.category].issues += issueCount; metricScores[metric.category].description = this.getCategoryDescription(metric.category); } // Normalize scores by weight for (const category in metricScores) { const totalWeight = exports.codeQualityMetrics .filter(m => m.category === category && m.fileTypes.includes(fileExtension)) .reduce((sum, m) => sum + m.weight, 0); if (totalWeight > 0) { metricScores[category].score = metricScores[category].score / totalWeight; } } const qualityScore = this.calculateOverallScore(metricScores); return { findings, qualityScore }; } findMetricIssues(content, filename, lineCount, metric) { const findings = []; // Special handling for large files if (metric.id === 'large-files' && lineCount > 500) { findings.push({ type: 'config', category: metric.category, severity: lineCount > 1000 ? 'high' : 'medium', title: metric.name, description: `File has ${lineCount} lines (recommended: <500)`, file: filename, line: 1, column: 1, code: `File length: ${lineCount} lines`, codeContext: { before: '', after: '' }, recommendation: metric.recommendation, confidence: 1.0, impact: metric.impact, effort: 'medium' }); return findings; } // Pattern-based detection for (const pattern of metric.patterns) { let match; while ((match = pattern.exec(content)) !== null) { const line = this.getLineNumber(content, match.index || 0); const column = this.getColumnNumber(content, match.index || 0); const codeContext = this.getCodeContext(content, match.index || 0); findings.push({ type: 'config', category: metric.category, severity: metric.severity, title: metric.name, description: metric.description, file: filename, line, column, code: codeContext.matchedCode, codeContext: { before: codeContext.contextBefore, after: codeContext.contextAfter }, recommendation: metric.recommendation, confidence: 0.8, impact: metric.impact, effort: 'low' }); if (!pattern.global) break; } pattern.lastIndex = 0; } return findings; } getLineNumber(content, index) { return content.substring(0, index).split('\n').length; } getColumnNumber(content, index) { const lines = content.substring(0, index).split('\n'); return lines[lines.length - 1].length + 1; } getCodeContext(content, index) { const lines = content.split('\n'); const currentLineIndex = this.getLineNumber(content, index) - 1; const contextBefore = currentLineIndex > 0 ? lines[currentLineIndex - 1] : ''; const currentLine = lines[currentLineIndex] || ''; const contextAfter = currentLineIndex < lines.length - 1 ? lines[currentLineIndex + 1] : ''; return { matchedCode: currentLine.trim(), contextBefore: contextBefore.trim(), contextAfter: contextAfter.trim() }; } calculateOverallScore(metricScores) { const categories = Object.keys(metricScores); if (categories.length === 0) { return this.getDefaultScore(); } const maintainability = metricScores['Maintainability']?.score || 100; const readability = metricScores['Readability']?.score || 100; const complexity = metricScores['Complexity']?.score || 100; const reliability = metricScores['Reliability']?.score || 100; const codeHygiene = metricScores['Code Hygiene']?.score || 100; // Weighted overall score const overall = Math.round((maintainability * 0.3 + readability * 0.2 + complexity * 0.2 + reliability * 0.2 + codeHygiene * 0.1)); return { overall, maintainability: Math.round(maintainability), readability: Math.round(readability), complexity: Math.round(complexity), security: Math.round(reliability), // Using reliability as security proxy breakdown: metricScores }; } getDefaultScore() { return { overall: 100, maintainability: 100, readability: 100, complexity: 100, security: 100, breakdown: {} }; } getCategoryDescription(category) { const descriptions = { 'Maintainability': 'How easy the code is to modify and extend', 'Readability': 'How easy the code is to read and understand', 'Complexity': 'How complex the code structure is', 'Reliability': 'How robust the code is against errors', 'Code Hygiene': 'How clean and well-organized the code is' }; return descriptions[category] || 'Code quality metric'; } } exports.CodeQualityAnalyzer = CodeQualityAnalyzer; //# sourceMappingURL=code-quality-analyzer.js.map