UNPKG

woaru

Version:

Universal Project Setup Autopilot - Analyze and automatically configure development tools for ANY programming language

986 lines 39.7 kB
/** * WOARU Code Smell Analyzer Module * Production-Ready implementation with comprehensive security and error handling */ import fs from 'fs-extra'; import { APP_CONFIG } from '../config/constants.js'; import { t, initializeI18n } from '../config/i18n.js'; import { triggerHook, } from '../core/HookSystem.js'; /** * Sanitizes file path to prevent information leakage * @param filePath - The file path to sanitize * @returns Sanitized file path safe for display */ function sanitizeFilePath(filePath) { if (typeof filePath !== 'string') { return 'unknown-file'; } // Remove sensitive parts and limit length const baseName = filePath.split(/[/\\]/).pop() || 'unknown-file'; return (baseName.replace(/[^a-zA-Z0-9._-]/g, '').substring(0, 50) || 'unknown-file'); } /** * Sanitizes function names to prevent injection attacks * @param functionName - The function name to sanitize * @returns Sanitized function name safe for display */ function sanitizeFunctionName(functionName) { if (typeof functionName !== 'string') { return 'anonymous'; } // Remove dangerous characters and limit length return (functionName.replace(/[^a-zA-Z0-9_$]/g, '').substring(0, 100) || 'anonymous'); } /** * Validates code content for security * @param content - Code content to validate * @param filePath - File path for context * @returns True if content appears safe to analyze */ function isSecureContent(content, filePath) { // Check for excessive size if (content.length > 1000000) { // 1MB limit return false; } // Skip security checks for test files - they often contain malicious patterns for testing if (filePath && (filePath.includes('.test.') || filePath.includes('__tests__') || filePath.includes('/test/'))) { return true; } // Check for suspicious patterns - very specific to avoid false positives const suspiciousPatterns = [ /<script[^>]*>.*(?:fetch|XMLHttpRequest|location\.href|window\.open|document\.cookie).*<\/script>/i, // Only malicious script tags /eval\s*\(['"]\s*[^'"]*(?:document|window|location|fetch|XMLHttpRequest)[^'"]*\s*['"]\)/i, // Only malicious eval /new\s+Function\s*\(['"]\s*[^'"]*(?:document|window|location|fetch|XMLHttpRequest)[^'"]*\s*['"]\)/i, // Only malicious Function constructor /data:text\/html[^"']*<script.*(?:fetch|XMLHttpRequest|location)/i, // Data URLs with malicious scripts ]; return !suspiciousPatterns.some(pattern => pattern.test(content)); } /** * Type guard for language validation * @param language - Language to validate * @returns True if language is supported */ function isSupportedLanguage(language) { const supportedLanguages = ['javascript', 'typescript', 'js', 'ts']; return (typeof language === 'string' && supportedLanguages.includes(language.toLowerCase())); } /** * CodeSmellAnalyzer - Advanced static code analysis engine for detecting code quality issues * * The CodeSmellAnalyzer is a production-ready implementation that provides comprehensive * static code analysis for JavaScript and TypeScript codebases. It detects various types * of code smells, anti-patterns, and quality issues using sophisticated pattern matching, * cyclomatic complexity analysis, and security-first validation approaches. * * Key features: * - Production-ready with comprehensive security validation * - Hook integration for extensible analysis workflows * - Timeout protection and resource management * - Multi-metric analysis (complexity, length, parameters, nesting) * - Detailed pattern detection with context-aware rules * - Performance metrics and analysis tracking * * Supported analysis types: * - Variable declaration patterns (var keyword detection) * - Equality operator analysis (weak vs strict comparison) * - Debug statement detection (console.log, debugger) * - Magic number identification * - Cyclomatic complexity calculation * - Function length analysis * - Parameter count validation * - Nesting depth evaluation * * @example * ```typescript * const analyzer = new CodeSmellAnalyzer(); * * // Analyze a TypeScript file for code smells * const findings = await analyzer.analyzeFile('./src/utils/helpers.ts', 'typescript'); * * // Process findings by severity * const critical = findings.filter(f => f.severity === 'error'); * const warnings = findings.filter(f => f.severity === 'warning'); * * console.log(`Found ${critical.length} critical issues:`); * critical.forEach(finding => { * console.log(` Line ${finding.line}: ${finding.message}`); * console.log(` Suggestion: ${finding.suggestion}`); * }); * * // Get analysis performance metrics * const metrics = analyzer.getAnalysisMetrics(); * console.log(`Analyzed ${metrics.filesAnalyzed} files`); * console.log(`Average analysis time: ${metrics.averageAnalysisTime}ms`); * ``` * * @example * ```typescript * // Batch analysis of multiple files * const analyzer = new CodeSmellAnalyzer(); * const files = ['app.js', 'utils.js', 'config.js']; * * for (const file of files) { * const findings = await analyzer.analyzeFile(file, 'javascript'); * * // Group findings by type * const grouped = findings.reduce((acc, finding) => { * if (!acc[finding.type]) acc[finding.type] = []; * acc[finding.type].push(finding); * return acc; * }, {}); * * console.log(`${file}: ${Object.keys(grouped).length} issue types found`); * } * ``` * * @since 1.0.0 */ export class CodeSmellAnalyzer { complexityThreshold = APP_CONFIG.QUALITY.COMPLEXITY_THRESHOLD; functionLengthThreshold = APP_CONFIG.QUALITY.FUNCTION_LENGTH_THRESHOLD; parameterCountThreshold = APP_CONFIG.QUALITY.PARAMETER_COUNT_THRESHOLD; nestingDepthThreshold = APP_CONFIG.QUALITY.NESTING_DEPTH_THRESHOLD; // Security limits MAX_FILE_SIZE = 1000000; // 1MB MAX_ANALYSIS_TIME = 30000; // 30 seconds MAX_FINDINGS_PER_FILE = 1000; /** * Analysis performance metrics */ analysisMetrics = { filesAnalyzed: 0, totalFindings: 0, averageAnalysisTime: 0, securityIssues: 0, }; /** * Performs comprehensive code smell analysis on a single source file * * Analyzes the specified file for various code quality issues, anti-patterns, * and potential improvements. The analysis is performed with security-first * principles, including input validation, timeout protection, and resource limits. * Integration with the WOARU Hook System enables extensible analysis workflows. * * Analysis categories performed: * - **Pattern Detection**: var keywords, weak equality operators, debug statements * - **Code Complexity**: Cyclomatic complexity calculation with decision point analysis * - **Structure Analysis**: Function length, parameter count, nesting depth evaluation * - **Quality Metrics**: Magic number detection, maintainability indicators * - **Security Validation**: Content scanning, path validation, resource limits * * Security features: * - Path traversal prevention with normalized path validation * - File size limits to prevent memory exhaustion attacks * - Content validation to detect potentially malicious code patterns * - Timeout protection for analysis operations * - Input sanitization for all user-provided data * * @param filePath - Absolute path to the source file to analyze * @param language - Programming language identifier ('javascript', 'typescript', 'js', 'ts') * @returns Promise resolving to array of CodeSmellFinding objects with detailed analysis results * * @throws {Error} Throws errors for invalid file paths or security validation failures * * @example * ```typescript * const analyzer = new CodeSmellAnalyzer(); * * // Analyze a TypeScript file for code quality issues * const findings = await analyzer.analyzeFile('./src/components/UserForm.tsx', 'typescript'); * * // Filter findings by severity level * const errorFindings = findings.filter(f => f.severity === 'error'); * const warningFindings = findings.filter(f => f.severity === 'warning'); * * // Display critical issues requiring immediate attention * errorFindings.forEach(finding => { * console.error(`🔴 ${finding.type} at line ${finding.line}:${finding.column}`); * console.error(` ${finding.message}`); * console.error(` 💡 ${finding.suggestion}`); * }); * * // Group findings by issue type for better organization * const byType = findings.reduce((acc, finding) => { * if (!acc[finding.type]) acc[finding.type] = []; * acc[finding.type].push(finding); * return acc; * }, {}); * * console.log(`Analysis summary:`); * console.log(`- ${byType['complexity']?.length || 0} complexity issues`); * console.log(`- ${byType['var-keyword']?.length || 0} var declarations`); * console.log(`- ${byType['weak-equality']?.length || 0} weak equality operators`); * ``` * * @example * ```typescript * // Analyze with error handling and metrics tracking * const analyzer = new CodeSmellAnalyzer(); * * try { * const findings = await analyzer.analyzeFile('./legacy/oldCode.js', 'javascript'); * * // Check for specific patterns * const hasVarUsage = findings.some(f => f.type === 'var-keyword'); * const hasHighComplexity = findings.some(f => * f.type === 'complexity' && f.severity === 'error' * ); * * if (hasVarUsage) { * console.log('⚠️ Legacy var declarations found - consider refactoring to let/const'); * } * * if (hasHighComplexity) { * console.log('🔥 High complexity functions detected - consider breaking down'); * } * * // Get performance metrics * const metrics = analyzer.getAnalysisMetrics(); * console.log(`Analysis completed in ${metrics.averageAnalysisTime}ms`); * * } catch (error) { * console.error('Analysis failed:', error.message); * } * ``` * * @since 1.0.0 */ async analyzeFile(filePath, language) { const startTime = Date.now(); try { await initializeI18n(); // Validate inputs if (!this.validateInputs(filePath, language)) { return []; } const sanitizedPath = sanitizeFilePath(filePath); // Security check: validate file path if (!(await this.isSecureFilePath(filePath))) { console.warn(`Skipping potentially unsafe file: ${sanitizedPath}`); return []; } const content = await this.readFileSecurely(filePath); if (!content) { return []; } const context = { filePath: sanitizedPath, language: language.toLowerCase(), content, contentLength: content.length, isSecure: isSecureContent(content, sanitizedPath), }; if (!context.isSecure) { console.warn(`Skipping file with suspicious content: ${sanitizedPath}`); this.analysisMetrics.securityIssues++; return []; } // 🪝 HOOK: beforeFileAnalysis - KI-freundliche Regelwelt const beforeAnalysisData = { filePath: sanitizedPath, language: context.language, fileSize: context.contentLength, timestamp: new Date(), }; try { await triggerHook('beforeFileAnalysis', beforeAnalysisData); } catch (hookError) { console.debug(`Hook error (beforeFileAnalysis): ${this.sanitizeError(hookError)}`); } const findings = await this.performAnalysisWithTimeout(context); // 🪝 HOOK: afterFileAnalysis - KI-freundliche Regelwelt const afterAnalysisData = { filePath: sanitizedPath, language: context.language, results: findings, duration: Date.now() - startTime, timestamp: new Date(), success: true, }; try { await triggerHook('afterFileAnalysis', afterAnalysisData); } catch (hookError) { console.debug(`Hook error (afterFileAnalysis): ${this.sanitizeError(hookError)}`); } // Update metrics this.analysisMetrics.filesAnalyzed++; this.analysisMetrics.totalFindings += findings.length; this.analysisMetrics.averageAnalysisTime = (this.analysisMetrics.averageAnalysisTime + (Date.now() - startTime)) / 2; return findings.slice(0, this.MAX_FINDINGS_PER_FILE); } catch (error) { const sanitizedPath = sanitizeFilePath(filePath); const sanitizedError = this.sanitizeError(error); console.error(t('code_smell_analyzer.error_analyzing_file', { filePath: sanitizedPath, }), sanitizedError); return []; } } /** * Input validation for analyze file method */ validateInputs(filePath, language) { if (typeof filePath !== 'string' || filePath.length === 0) { console.debug('Invalid file path provided'); return false; } if (!isSupportedLanguage(language)) { console.debug(`Unsupported language: ${language}`); return false; } return true; } /** * Security validation for file paths */ async isSecureFilePath(filePath) { try { // Normalize path to prevent directory traversal const pathModule = await import('path'); const normalizedPath = pathModule.normalize(filePath); // Check for suspicious patterns if (normalizedPath.includes('..') || normalizedPath.includes('\x00') || normalizedPath.length > 500) { return false; } // Check if file exists and is readable const stats = await fs.stat(normalizedPath); if (!stats.isFile() || stats.size > this.MAX_FILE_SIZE) { return false; } return true; } catch { return false; } } /** * Secure file reading with size limits */ async readFileSecurely(filePath) { try { const content = await fs.readFile(filePath, 'utf-8'); if (content.length > this.MAX_FILE_SIZE) { console.warn(`File too large: ${sanitizeFilePath(filePath)}`); return null; } return content; } catch (error) { console.debug(`Failed to read file: ${this.sanitizeError(error)}`); return null; } } /** * Perform analysis with timeout protection */ async performAnalysisWithTimeout(context) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Analysis timeout')); }, this.MAX_ANALYSIS_TIME); try { const findings = this.analyzeContent(context); clearTimeout(timeout); resolve(findings); } catch (error) { clearTimeout(timeout); reject(error); } }); } /** * Main analysis method for JavaScript/TypeScript content */ analyzeContent(context) { const findings = []; const lines = context.content.split('\n'); if (lines.length > 10000) { console.warn(`File too large for detailed analysis: ${context.filePath}`); return []; } try { // Basic pattern-based analysis with error isolation findings.push(...this.checkVarKeyword(lines)); findings.push(...this.checkWeakEquality(lines)); findings.push(...this.checkConsoleLog(lines)); findings.push(...this.checkMagicNumbers(lines)); // More sophisticated analysis with error isolation findings.push(...this.analyzeComplexity(context.content)); findings.push(...this.analyzeFunctionLength(context.content)); findings.push(...this.analyzeParameterCount(context.content)); findings.push(...this.analyzeNestingDepth(context.content)); } catch (error) { console.debug(`Analysis error: ${this.sanitizeError(error)}`); } return findings; } /** * Sanitize error messages for security */ sanitizeError(error) { if (typeof error === 'string') { return error.replace(/\/[^\s]*\/[^\s]*/g, '[PATH]').substring(0, 200); } if (error instanceof Error) { return this.sanitizeError(error.message); } return 'Unknown analysis error'; } /** * Check for var keyword usage with security validation */ checkVarKeyword(lines) { const findings = []; try { lines.forEach((line, index) => { if (typeof line !== 'string') return; const trimmedLine = line.trim(); if (trimmedLine.length > 1000) return; // Skip excessively long lines // Match var declarations but avoid false positives in comments const varMatch = line.match(/(?:^|\s|;)(var\s+\w+)/); if (varMatch && !trimmedLine.startsWith('//') && !trimmedLine.startsWith('*') && !trimmedLine.includes('/*')) { const column = Math.max(1, line.indexOf(varMatch[1]) + 1); findings.push({ type: 'var-keyword', message: t('code_smell_analyzer.var_keyword_message'), severity: 'warning', line: index + 1, column, rule: APP_CONFIG.ESLINT_RULES.NO_VAR, suggestion: t('code_smell_analyzer.var_keyword_suggestion'), }); } }); } catch (error) { console.debug(`Error in checkVarKeyword: ${this.sanitizeError(error)}`); } return findings; } /** * Check for weak equality operators with security validation */ checkWeakEquality(lines) { const findings = []; try { lines.forEach((line, index) => { if (typeof line !== 'string') return; const trimmedLine = line.trim(); if (trimmedLine.length > 1000) return; if (trimmedLine.startsWith('//') || trimmedLine.startsWith('*') || trimmedLine.includes('/*')) return; // Check for == and != (but not === or !==) const weakEqualityMatch = line.match(/(^|[^!=])([!=]=)(?!=)/); if (weakEqualityMatch) { const column = Math.max(1, line.indexOf(weakEqualityMatch[2]) + 1); const operator = weakEqualityMatch[2]; const strictOperator = operator === '==' ? '===' : '!=='; findings.push({ type: 'weak-equality', message: t('code_smell_analyzer.weak_equality_message', { strictOperator, operator, }), severity: 'warning', line: index + 1, column, rule: APP_CONFIG.ESLINT_RULES.EQEQEQ, suggestion: t('code_smell_analyzer.weak_equality_suggestion', { operator, strictOperator, }), }); } }); } catch (error) { console.debug(`Error in checkWeakEquality: ${this.sanitizeError(error)}`); } return findings; } /** * Check for console statements with security validation */ checkConsoleLog(lines) { const findings = []; try { lines.forEach((line, index) => { if (typeof line !== 'string') return; const trimmedLine = line.trim(); if (trimmedLine.length > 1000) return; if (trimmedLine.startsWith('//') || trimmedLine.startsWith('*') || trimmedLine.includes('/*')) return; const consoleMatch = line.match(/console\.(log|warn|error|info|debug)/); if (consoleMatch) { const column = Math.max(1, line.indexOf(consoleMatch[0]) + 1); findings.push({ type: 'console-log', message: t('code_smell_analyzer.console_log_message'), severity: 'warning', line: index + 1, column, rule: 'no-console', suggestion: t('code_smell_analyzer.console_log_suggestion'), }); } }); } catch (error) { console.debug(`Error in checkConsoleLog: ${this.sanitizeError(error)}`); } return findings; } /** * Check for magic numbers with security validation */ checkMagicNumbers(lines) { const findings = []; try { lines.forEach((line, index) => { if (typeof line !== 'string') return; const trimmedLine = line.trim(); if (trimmedLine.length > 1000) return; if (trimmedLine.startsWith('//') || trimmedLine.startsWith('*') || trimmedLine.includes('/*')) return; // Look for magic numbers (not 0, 1, -1, or numbers in obvious contexts) const magicNumberMatch = line.match(/(?:[^.\w]|^)([2-9]\d+|\d{3,})(?![.\w])/); if (magicNumberMatch && !line.includes('line') && !line.includes('port') && !line.includes('timeout') && !line.includes('version') && !line.includes('http')) { const sanitizedNumber = magicNumberMatch[1].substring(0, 20); // Limit length const column = Math.max(1, line.indexOf(magicNumberMatch[1]) + 1); findings.push({ type: 'magic-number', message: t('code_smell_analyzer.magic_number_message', { number: sanitizedNumber, }), severity: 'info', line: index + 1, column, rule: 'no-magic-numbers', suggestion: t('code_smell_analyzer.magic_number_suggestion'), }); } }); } catch (error) { console.debug(`Error in checkMagicNumbers: ${this.sanitizeError(error)}`); } return findings; } /** * Analyze cyclomatic complexity with security validation */ analyzeComplexity(content) { const findings = []; try { const functions = this.extractFunctions(content); if (functions.length > 100) { console.warn('Too many functions detected, limiting complexity analysis'); return []; } functions.forEach(func => { try { const sanitizedFunctionName = sanitizeFunctionName(func.name); const complexity = this.calculateCyclomaticComplexity(func.body); if (complexity > this.complexityThreshold && complexity < 1000) { // Sanity check findings.push({ type: 'complexity', message: t('code_smell_analyzer.high_complexity_message', { functionName: sanitizedFunctionName, complexity: complexity.toString(), }), severity: complexity > APP_CONFIG.QUALITY_THRESHOLDS.COMPLEXITY_WARNING ? 'error' : 'warning', line: Math.max(1, func.line), column: Math.max(1, func.column), rule: 'complexity', suggestion: t('code_smell_analyzer.break_down_functions'), }); } } catch (funcError) { console.debug(`Error analyzing function complexity: ${this.sanitizeError(funcError)}`); } }); } catch (error) { console.debug(`Error in analyzeComplexity: ${this.sanitizeError(error)}`); } return findings; } /** * Analyze function length with security validation */ analyzeFunctionLength(content) { const findings = []; try { const functions = this.extractFunctions(content); if (functions.length > 100) { console.warn('Too many functions detected, limiting length analysis'); return []; } functions.forEach(func => { try { const sanitizedFunctionName = sanitizeFunctionName(func.name); const lines = func.body.split('\n'); const length = lines.length; if (length > this.functionLengthThreshold && length < 10000) { // Sanity check findings.push({ type: 'function-length', message: t('code_smell_analyzer.long_function_message', { functionName: sanitizedFunctionName, length: length.toString(), }), severity: 'warning', line: Math.max(1, func.line), column: Math.max(1, func.column), rule: 'max-lines-per-function', suggestion: t('code_smell_analyzer.break_down_functions'), }); } } catch (funcError) { console.debug(`Error analyzing function length: ${this.sanitizeError(funcError)}`); } }); } catch (error) { console.debug(`Error in analyzeFunctionLength: ${this.sanitizeError(error)}`); } return findings; } /** * Analyze parameter count with security validation */ analyzeParameterCount(content) { const findings = []; try { const functions = this.extractFunctions(content); if (functions.length > 100) { console.warn('Too many functions detected, limiting parameter analysis'); return []; } functions.forEach(func => { try { const sanitizedFunctionName = sanitizeFunctionName(func.name); const paramCount = Array.isArray(func.parameters) ? func.parameters.length : 0; if (paramCount > this.parameterCountThreshold && paramCount < 100) { // Sanity check findings.push({ type: 'parameter-count', message: t('code_smell_analyzer.too_many_parameters_message', { functionName: sanitizedFunctionName, paramCount: paramCount.toString(), }), severity: 'warning', line: Math.max(1, func.line), column: Math.max(1, func.column), rule: 'max-params', suggestion: t('code_smell_analyzer.use_options_object'), }); } } catch (funcError) { console.debug(`Error analyzing parameter count: ${this.sanitizeError(funcError)}`); } }); } catch (error) { console.debug(`Error in analyzeParameterCount: ${this.sanitizeError(error)}`); } return findings; } /** * Analyze nesting depth with security validation */ analyzeNestingDepth(content) { const findings = []; try { const lines = content.split('\n'); if (lines.length > 10000) { console.warn('File too large for nesting depth analysis'); return []; } let currentDepth = 0; let maxDepth = 0; let maxDepthLine = 0; lines.forEach((line, index) => { if (typeof line !== 'string' || line.length > 1000) return; const trimmedLine = line.trim(); if (trimmedLine.startsWith('//') || trimmedLine.startsWith('*') || trimmedLine.includes('/*')) return; try { // Count opening braces const openBraces = (line.match(/\{/g) || []).length; const closeBraces = (line.match(/\}/g) || []).length; currentDepth += openBraces - closeBraces; // Sanity check for depth if (currentDepth < 0) currentDepth = 0; if (currentDepth > 50) return; // Skip if unreasonably deep if (currentDepth > maxDepth) { maxDepth = currentDepth; maxDepthLine = index + 1; } } catch (lineError) { console.debug(`Error processing line ${index}: ${this.sanitizeError(lineError)}`); } }); if (maxDepth > this.nestingDepthThreshold && maxDepth < 50) { findings.push({ type: 'nested-depth', message: t('code_smell_analyzer.excessive_nesting_message', { maxDepth: maxDepth.toString(), }), severity: 'warning', line: Math.max(1, maxDepthLine), column: 1, rule: 'max-depth', suggestion: t('code_smell_analyzer.extract_nested_logic'), }); } } catch (error) { console.debug(`Error in analyzeNestingDepth: ${this.sanitizeError(error)}`); } return findings; } /** * Extract functions with security validation and enhanced parsing */ extractFunctions(content) { const functions = []; try { if (content.length > this.MAX_FILE_SIZE) { console.warn('Content too large for function extraction'); return []; } const lines = content.split('\n'); if (lines.length > 10000) { console.warn('Too many lines for function extraction'); return []; } // Enhanced regex for function detection with security considerations const functionRegex = /(?:function\s+(\w+)\s*\(([^)]*)\)|(\w+)\s*[:=]\s*(?:function\s*)?\(([^)]*)\)\s*=>?|(\w+)\s*\(([^)]*)\)\s*\{)/g; lines.forEach((line, index) => { if (typeof line !== 'string' || line.length > 1000) return; try { let match; const originalRegex = new RegExp(functionRegex); while ((match = originalRegex.exec(line)) !== null && functions.length < 100) { const rawFunctionName = match[1] || match[3] || match[5] || 'anonymous'; const functionName = sanitizeFunctionName(rawFunctionName); const rawParams = match[2] || match[4] || match[6] || ''; const params = this.parseParameters(rawParams); // Extract function body with security validation const functionBody = this.extractFunctionBody(content, index); if (!functionBody || functionBody.length > 50000) continue; functions.push({ name: functionName, body: functionBody, line: index + 1, column: Math.max(1, (match.index || 0) + 1), parameters: params, startIndex: index, endIndex: index + functionBody.split('\n').length, }); // Prevent infinite loops if (match.index === originalRegex.lastIndex) { originalRegex.lastIndex++; } } } catch (lineError) { console.debug(`Error extracting functions from line ${index}: ${this.sanitizeError(lineError)}`); } }); } catch (error) { console.debug(`Error in extractFunctions: ${this.sanitizeError(error)}`); } return functions; } /** * Parse function parameters with security validation */ parseParameters(rawParams) { try { if (typeof rawParams !== 'string' || rawParams.length > 500) { return []; } return rawParams .split(',') .map(p => p .trim() .replace(/[^a-zA-Z0-9_$]/g, '') .substring(0, 50)) .filter(p => p.length > 0) .slice(0, 20); // Limit to 20 parameters } catch (error) { console.debug(`Error parsing parameters: ${this.sanitizeError(error)}`); return []; } } /** * Extract function body with security validation and limits */ extractFunctionBody(content, startLine) { try { const lines = content.split('\n'); if (startLine < 0 || startLine >= lines.length) { return ''; } let braceCount = 0; const bodyLines = []; let foundStart = false; const maxLines = 1000; // Limit function body size for (let i = startLine; i < Math.min(lines.length, startLine + maxLines); i++) { const line = lines[i]; if (typeof line !== 'string') continue; if (!foundStart) { if (line.includes('{')) { foundStart = true; } } if (foundStart) { bodyLines.push(line); try { braceCount += (line.match(/\{/g) || []).length; braceCount -= (line.match(/\}/g) || []).length; // Safety check for malformed code if (braceCount < 0) braceCount = 0; if (braceCount > 50) break; // Unreasonably deep nesting if (braceCount === 0) { break; } } catch (braceError) { console.debug(`Error counting braces: ${this.sanitizeError(braceError)}`); break; } } // Prevent excessive memory usage if (bodyLines.length > maxLines) { break; } } return bodyLines.join('\n'); } catch (error) { console.debug(`Error extracting function body: ${this.sanitizeError(error)}`); return ''; } } /** * Calculate cyclomatic complexity with security validation */ calculateCyclomaticComplexity(functionBody) { try { if (typeof functionBody !== 'string' || functionBody.length > 50000) { return 1; // Return base complexity for invalid input } let complexity = APP_CONFIG.QUALITY_THRESHOLDS.BASE_COMPLEXITY; // Count decision points with safety limits const decisionPoints = [ /\bif\b/g, /\belse\b/g, /\bwhile\b/g, /\bfor\b/g, /\bswitch\b/g, /\bcase\b/g, /\bcatch\b/g, /\b&&\b/g, /\b\|\|\b/g, /\?\s*.*\s*:/g, // Ternary operator ]; decisionPoints.forEach(pattern => { try { const matches = functionBody.match(pattern); if (matches && matches.length < 1000) { // Sanity check complexity += matches.length; } } catch (patternError) { console.debug(`Error matching pattern: ${this.sanitizeError(patternError)}`); } }); // Cap complexity at reasonable limit return Math.min(complexity, 999); } catch (error) { console.debug(`Error calculating complexity: ${this.sanitizeError(error)}`); return 1; } } /** * Get analysis performance metrics */ getAnalysisMetrics() { return { ...this.analysisMetrics }; } /** * Reset analysis metrics */ resetMetrics() { this.analysisMetrics = { filesAnalyzed: 0, totalFindings: 0, averageAnalysisTime: 0, securityIssues: 0, }; } } //# sourceMappingURL=CodeSmellAnalyzer.js.map