UNPKG

@sun-asterisk/sunlint

Version:

☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards

346 lines (282 loc) 11.3 kB
/** * Regex-based analyzer for C013 - No Dead Code * Purpose: Simple fallback detection for commented code and basic dead code patterns */ const { CommentDetector } = require('../../utils/rule-helpers'); class C013RegexBasedAnalyzer { constructor() { this.ruleId = 'C013'; this.ruleName = 'No Dead Code (Regex-Based)'; this.verbose = false; } initialize(options = {}) { this.verbose = options.verbose || false; if (this.verbose) { console.log(`[DEBUG] 🔧 C013 Regex-Based: Analyzer initialized`); } } async analyze(files, language, options = {}) { const violations = []; for (const filePath of files) { if (options.verbose) { console.log(`🔍 Running C013 regex analysis on ${require('path').basename(filePath)}`); } try { const content = require('fs').readFileSync(filePath, 'utf8'); const fileViolations = this.analyzeFile(content, filePath); violations.push(...fileViolations); } catch (error) { console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`); } } return violations; } analyzeFile(content, filePath) { const violations = []; const lines = content.split('\n'); // 1. Detect commented out code const commentedCodeViolations = this.detectCommentedCode(lines, filePath); violations.push(...commentedCodeViolations); // 2. Detect simple unreachable code patterns const unreachableCodeViolations = this.detectUnreachableCode(lines, filePath); violations.push(...unreachableCodeViolations); return violations; } detectCommentedCode(lines, filePath) { const violations = []; // Use CommentDetector to filter out comment lines const filteredLines = CommentDetector.filterCommentLines(lines); // High-confidence patterns for commented out code (reduced false positives) const highConfidencePatterns = [ /^(function\s+\w+\s*\()/, // function declarations /^(const\s+\w+\s*=\s*)/, // const assignments /^(let\s+\w+\s*=\s*)/, // let assignments /^(if\s*\([^)]+\)\s*{)/, // if statements with braces /^(for\s*\([^)]*\)\s*{)/, // for loops with braces /^(while\s*\([^)]+\)\s*{)/, // while loops with braces /^(return\s+[^;]+;?)/, // return statements /^(import\s+.*from)/, // import statements /^(export\s+(default\s+)?)/, // export statements /^(class\s+\w+)/, // class declarations /^(\w+\s*\([^)]*\)\s*{)/ // method calls with braces ]; for (let i = 0; i < filteredLines.length; i++) { const { line, lineNumber, isComment } = filteredLines[i]; // Only process comment lines if (!isComment) continue; const trimmedLine = line.trim(); // Skip obvious documentation comments if (this.isDocumentationComment(trimmedLine)) { continue; } // Extract comment content let commentContent = ''; if (trimmedLine.startsWith('//')) { commentContent = trimmedLine.substring(2).trim(); } else if (trimmedLine.startsWith('/*') && trimmedLine.endsWith('*/')) { commentContent = trimmedLine.substring(2, trimmedLine.length - 2).trim(); } else { continue; // Skip other comment types } // Skip very short comments or obvious explanations if (commentContent.length < 10 || this.isExplanatoryComment(commentContent)) { continue; } // Check for high-confidence code patterns const isLikelyCode = highConfidencePatterns.some(pattern => pattern.test(commentContent)); if (isLikelyCode && this.looksLikeRealCode(commentContent)) { violations.push({ file: filePath, line: lineNumber, column: line.indexOf(trimmedLine.charAt(0)) + 1, message: `Commented out code detected: "${commentContent.substring(0, 50)}...". Remove dead code or use Git for version history.`, severity: 'warning', ruleId: this.ruleId, type: 'commented-code' }); } } return violations; } isExplanatoryComment(commentContent) { const explanatoryStarters = [ 'this', 'the', 'we', 'it', 'you', 'note:', 'warning:', 'todo:', 'fixme:', 'explanation', 'reason', 'because', 'when', 'if you', 'make sure', 'see', 'check', 'ensure', 'verify', 'remember', 'important' ]; const lowerText = commentContent.toLowerCase(); return explanatoryStarters.some(starter => lowerText.startsWith(starter)); } isDocumentationComment(line) { // Skip JSDoc and obvious documentation if (line.startsWith('/**') || line.startsWith('*') || line.includes('TODO') || line.includes('FIXME')) { return true; } // Skip comments that are clearly explanatory const explanatoryWords = ['note', 'todo', 'fixme', 'hack', 'bug', 'issue', 'warning', 'caution']; const commentText = line.substring(2).toLowerCase().trim(); return explanatoryWords.some(word => commentText.startsWith(word)); } looksLikeRealCode(commentContent) { // Must have code-like characteristics const hasCodeCharacteristics = ( commentContent.includes('(') || commentContent.includes('{') || commentContent.includes('=') || commentContent.includes(';') || commentContent.includes('.') || /\w+\.\w+/.test(commentContent) || /const\s+\w+/.test(commentContent) || /let\s+\w+/.test(commentContent) || /return\s+/.test(commentContent) || /console\./.test(commentContent) || /\w+\s*=\s*/.test(commentContent) ); // And be substantial enough (lowered threshold) const isLongEnough = commentContent.length >= 10; // And not be obvious explanatory text const isNotExplanatory = !this.isExplanatoryComment(commentContent); return hasCodeCharacteristics && isLongEnough && isNotExplanatory; } isExplanatoryComment(text) { const explanatoryStarters = [ 'this', 'the', 'we', 'it', 'you', 'note:', 'warning:', 'todo:', 'fixme:', 'explanation', 'reason', 'because', 'when', 'if you', 'make sure', 'describes', 'explanation:', 'note that', 'important:', 'remember' ]; const lowerText = text.toLowerCase().trim(); return explanatoryStarters.some(starter => lowerText.startsWith(starter)); } checkCommentBlock(lines, startIndex, filePath) { let commentBlock = ''; let endIndex = startIndex; // Collect the full comment block for (let j = startIndex; j < lines.length; j++) { commentBlock += lines[j] + '\n'; if (lines[j].includes('*/')) { endIndex = j; break; } } // Clean the comment block const cleanedComment = commentBlock .replace(/\/\*|\*\/|\*\s*/g, '') .trim(); // Check if it looks like commented code if (cleanedComment.length >= 50 && this.blockLooksLikeCode(cleanedComment)) { return { file: filePath, line: startIndex + 1, column: lines[startIndex].indexOf('/*') + 1, message: `Commented out code block detected. Remove dead code or use Git for version history.`, severity: 'warning', ruleId: this.ruleId, type: 'commented-code-block' }; } return null; } blockLooksLikeCode(text) { const codeIndicators = [ /function\s+\w+/g, /const\s+\w+\s*=/g, /let\s+\w+\s*=/g, /if\s*\(/g, /for\s*\(/g, /return\s+/g, /\w+\s*=\s*\w+/g, /\w+\.\w+/g ]; let indicatorCount = 0; for (const indicator of codeIndicators) { const matches = text.match(indicator); if (matches) { indicatorCount += matches.length; } } // If we find multiple code indicators, it's likely commented code return indicatorCount >= 3; } detectUnreachableCode(lines, filePath) { const violations = []; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Skip empty lines and comments if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) { continue; } // Look for simple return statements if (this.isSimpleReturn(line)) { // Check if there's code after this return within the same block const unreachableLines = this.findUnreachableCodeAfterReturn(lines, i); for (const unreachableLine of unreachableLines) { violations.push({ file: filePath, line: unreachableLine + 1, column: 1, message: `Unreachable code detected after return statement. Remove dead code.`, severity: 'warning', ruleId: this.ruleId, type: 'unreachable-code' }); } } } return violations; } isSimpleReturn(line) { // Match simple return statements const cleanLine = line.replace(/;?\s*$/, ''); return ( /^return\s*;?\s*$/.test(cleanLine) || /^return\s+[^{}\[\(]+;?\s*$/.test(cleanLine) || /^return\s+(true|false|null|undefined|\d+|"[^"]*"|'[^']*')\s*;?\s*$/.test(cleanLine) ); } findUnreachableCodeAfterReturn(lines, returnLineIndex) { const unreachableLines = []; // Look for code after the return statement until we hit a closing brace for (let i = returnLineIndex + 1; i < lines.length; i++) { const line = lines[i].trim(); // Skip empty lines and comments if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) { continue; } // Stop if we hit a closing brace (end of function/block) if (line === '}' || line === '};' || line.startsWith('} ')) { break; } // Stop if we hit catch/finally blocks (these are reachable) if (line.includes('catch') || line.includes('finally') || line.startsWith('} catch') || line.startsWith('} finally') || line === '} catch (e) {' || line.match(/}\s*catch\s*\(/)) { break; } // This looks like unreachable code if (this.isExecutableCode(line)) { unreachableLines.push(i); break; // Only report the first unreachable line to avoid spam } } return unreachableLines; } isExecutableCode(line) { // Exclude lines that are just structural if (line === '}' || line === '};' || line === '},' || line.match(/^\s*}\s*$/)) { return false; } // Exclude catch/finally blocks and their variations if (line.includes('catch') || line.includes('finally') || line.startsWith('} catch') || line.startsWith('} finally') || line.match(/}\s*catch\s*\(/) || line.match(/}\s*finally\s*\{/)) { return false; } // Exclude plain closing braces with catch/finally if (line.match(/^\s*}\s*(catch|finally)/)) { return false; } // This looks like executable code return true; } } module.exports = C013RegexBasedAnalyzer;