UNPKG

@sun-asterisk/sunlint

Version:

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

188 lines (161 loc) 4.69 kB
/** * JavaScript AST Parser * Uses Tree-sitter for JavaScript parsing * Rule C005: Single responsibility - JavaScript AST parsing only */ const BaseASTParser = require('../base-parser'); class JavaScriptParser extends BaseASTParser { constructor() { super('javascript'); } async initialize() { try { const Parser = require('tree-sitter'); const JavaScript = require('tree-sitter-javascript'); this.parser = new Parser(); this.parser.setLanguage(JavaScript); return true; } catch (error) { console.warn('⚠️ Tree-sitter JavaScript parser not available:', error.message); return false; } } async parse(code, filePath) { if (!this.parser) { const initialized = await this.initialize(); if (!initialized) return null; } try { const tree = this.parser.parse(code); return tree.rootNode; } catch (error) { console.warn(`⚠️ JavaScript AST parsing failed for ${filePath}:`, error.message); return null; } } async analyzeRule(ruleId, code, filePath) { const rootNode = await this.parse(code, filePath); if (!rootNode) return null; switch (ruleId) { case 'C010': return this.analyzeC010(rootNode, code, filePath); case 'C012': return this.analyzeC012(rootNode, code, filePath); default: return null; } } /** * Analyze C010: Limit block nesting depth */ analyzeC010(rootNode, code, filePath) { const violations = []; const maxDepth = 4; // Default limit const blockTypes = [ 'if_statement', 'for_statement', 'for_in_statement', 'while_statement', 'do_statement', 'try_statement', 'switch_statement', 'with_statement' ]; this.traverseAST(rootNode, (node) => { if (blockTypes.includes(node.type)) { const depth = this.calculateNestingDepth(node, blockTypes); if (depth > maxDepth) { violations.push({ ruleId: 'C010', severity: 'warning', message: `Block nesting depth (${depth}) exceeds maximum allowed (${maxDepth})`, line: node.startPosition.row + 1, column: node.startPosition.column + 1, endLine: node.endPosition.row + 1, endColumn: node.endPosition.column + 1, source: this.getNodeText(node, code) }); } } }); return violations; } /** * Analyze C012: Limit function/method complexity */ analyzeC012(rootNode, code, filePath) { const violations = []; const maxComplexity = 10; // Default limit const functionTypes = [ 'function_declaration', 'method_definition', 'arrow_function', 'function_expression' ]; this.traverseAST(rootNode, (node) => { if (functionTypes.includes(node.type)) { const complexity = this.calculateCyclomaticComplexity(node); if (complexity > maxComplexity) { violations.push({ ruleId: 'C012', severity: 'warning', message: `Function complexity (${complexity}) exceeds maximum allowed (${maxComplexity})`, line: node.startPosition.row + 1, column: node.startPosition.column + 1, endLine: node.endPosition.row + 1, endColumn: node.endPosition.column + 1, source: this.getNodeText(node, code) }); } } }); return violations; } /** * Calculate nesting depth from a given node */ calculateNestingDepth(startNode, blockTypes) { let depth = 0; let current = startNode.parent; while (current) { if (blockTypes.includes(current.type)) { depth++; } current = current.parent; } return depth + 1; // Include the current node } /** * Calculate cyclomatic complexity */ calculateCyclomaticComplexity(functionNode) { let complexity = 1; // Base complexity const complexityNodes = [ 'if_statement', 'else_clause', 'for_statement', 'for_in_statement', 'while_statement', 'do_statement', 'switch_case', 'try_statement', 'catch_clause', 'conditional_expression' ]; this.traverseAST(functionNode, (node) => { if (complexityNodes.includes(node.type)) { complexity++; } }); return complexity; } /** * Get text content of a node */ getNodeText(node, code) { const startIndex = node.startIndex; const endIndex = node.endIndex; return code.slice(startIndex, endIndex); } } module.exports = JavaScriptParser;