UNPKG

@sun-asterisk/sunlint

Version:

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

187 lines (153 loc) 6.06 kB
const fs = require('fs'); const path = require('path'); /** * Rule C031 - Validation Logic Separation * Kiểm tra logic validation có bị trộn lẫn với business logic không */ class ValidationSeparationAnalyzer { constructor() { this.ruleId = 'C031'; this.ruleName = 'Validation Logic Separation'; this.category = 'architecture'; this.severity = 'warning'; this.description = 'Logic kiểm tra dữ liệu (validate) phải nằm riêng biệt'; } analyzeFile(filePath, options = {}) { const violations = []; try { if (!fs.existsSync(filePath)) { return violations; } const content = fs.readFileSync(filePath, 'utf8'); const lines = content.split('\n'); // Detect functions with mixed validation and business logic const functions = this.extractFunctions(content); for (const func of functions) { const validationCount = this.countValidationStatements(func.body); const businessLogicCount = this.countBusinessLogicStatements(func.body); // If both validation and business logic exist in same function if (validationCount > 0 && businessLogicCount > 0) { const maxValidationAllowed = options.maxValidationStatementsInFunction || 3; if (validationCount > maxValidationAllowed) { violations.push({ line: func.startLine, column: 1, message: `Function '${func.name}' has ${validationCount} validation statements mixed with business logic. Consider separating validation logic.`, ruleId: this.ruleId, severity: this.severity, source: lines[func.startLine - 1]?.trim() || '' }); } } } } catch (error) { console.error(`Error analyzing ${filePath}:`, error.message); } return violations; } extractFunctions(content) { const functions = []; const lines = content.split('\n'); // Simple function detection patterns const functionPatterns = [ /function\s+(\w+)\s*\(/g, /const\s+(\w+)\s*=\s*\(/g, /(\w+)\s*\(\s*[^)]*\s*\)\s*=>/g, /(\w+)\s*:\s*function\s*\(/g ]; for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const pattern of functionPatterns) { const matches = line.matchAll(pattern); for (const match of matches) { const functionName = match[1]; const startLine = i + 1; // Extract function body (simple approach) const body = this.extractFunctionBody(lines, i); functions.push({ name: functionName, startLine, body }); } } } return functions; } extractFunctionBody(lines, startIndex) { let body = ''; let braceCount = 0; let inFunction = false; for (let i = startIndex; i < lines.length; i++) { const line = lines[i]; if (line.includes('{')) { braceCount += (line.match(/\{/g) || []).length; inFunction = true; } if (inFunction) { body += line + '\n'; } if (line.includes('}')) { braceCount -= (line.match(/\}/g) || []).length; if (braceCount <= 0 && inFunction) { break; } } } return body; } countValidationStatements(code) { const validationPatterns = [ /if\s*\(\s*!.*\)\s*\{?\s*throw/g, /if\s*\(.*\.\s*length\s*[<>=]\s*\d+\)/g, /if\s*\(.*\s*==\s*null\s*\||\s*.*\s*==\s*undefined\)/g, /if\s*\(.*\s*!\s*=\s*null\s*&&\s*.*\s*!\s*=\s*undefined\)/g, /throw\s+new\s+Error\s*\(/g, /assert\s*\(/g, /validate\w*\s*\(/g, /check\w*\s*\(/g ]; let count = 0; for (const pattern of validationPatterns) { const matches = code.match(pattern); if (matches) { count += matches.length; } } return count; } countBusinessLogicStatements(code) { const businessLogicPatterns = [ /calculate\w*\s*\(/g, /process\w*\s*\(/g, /save\w*\s*\(/g, /update\w*\s*\(/g, /delete\w*\s*\(/g, /send\w*\s*\(/g, /return\s+\w+\s*\(/g, /await\s+\w+\s*\(/g ]; let count = 0; for (const pattern of businessLogicPatterns) { const matches = code.match(pattern); if (matches) { count += matches.length; } } return count; } // Main analyze method expected by CLI async analyze(files, language, config) { const violations = []; for (const filePath of files) { try { const fileViolations = this.analyzeFile(filePath, config); violations.push(...fileViolations); } catch (error) { console.error(`Error analyzing file ${filePath}:`, error.message); } } return violations; } } module.exports = new ValidationSeparationAnalyzer();