UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

172 lines (145 loc) 5.04 kB
/** * @file AST-based repeated DOM query detector * @description Detects repeated DOM queries using AST analysis */ const { parseToAST, getNodeLine, getNodeSource, walk } = require('./astParser'); const { PERFORMANCE_PATTERNS } = require('../performancePatterns'); /** * Detect repeated DOM queries using AST * @param {string} content - File content * @param {string} filePath - File path * @returns {Array} Array of repeated DOM query issues */ function detectRepeatedDomQueriesAST(content, filePath) { const issues = []; const ast = parseToAST(content, filePath); if (!ast) return []; const pattern = PERFORMANCE_PATTERNS['repeated_dom_query']; const queryMap = new Map(); // Find all DOM query calls walk.simple(ast, { CallExpression(node) { const queryInfo = extractDomQuery(node); if (queryInfo) { const key = `${queryInfo.method}:${queryInfo.selector}`; if (!queryMap.has(key)) { queryMap.set(key, []); } queryMap.get(key).push({ node, line: getNodeLine(node), code: getNodeSource(content, node).trim(), method: queryInfo.method, selector: queryInfo.selector }); } } }); // Find repeated queries in same scope queryMap.forEach((occurrences, key) => { if (occurrences.length > 1) { // Group by function scope const scopeGroups = groupByScope(occurrences, ast); scopeGroups.forEach(group => { if (group.length > 1) { const [method, selector] = key.split(':'); issues.push({ type: 'repeated_dom_query', severity: pattern.severity, category: pattern.category, location: `${filePath}:${group[0].line},${group[group.length - 1].line}`, line: group[0].line, selector: selector, queryType: method, queryCount: group.length, code: group[0].code, description: `DOM query for "${selector}" repeated ${group.length} times in same scope`, summary: `Repeated DOM query for "${selector}"`, recommendation: 'Cache DOM query result in a variable to avoid repeated lookups', effort: pattern.effort, impact: pattern.impact, estimatedSavings: '30-60% DOM performance improvement' }); } }); } }); return issues; } function extractDomQuery(node) { // document.getElementById('...') if (node.callee.type === 'MemberExpression' && node.callee.object.name === 'document') { const method = node.callee.property.name; const domMethods = ['getElementById', 'getElementsByClassName', 'getElementsByTagName', 'querySelector', 'querySelectorAll']; if (domMethods.includes(method) && node.arguments[0]) { const arg = node.arguments[0]; if (arg.type === 'Literal' && typeof arg.value === 'string') { return { method, selector: arg.value }; } } } // jQuery: $('...') or jQuery('...') if ((node.callee.name === '$' || node.callee.name === 'jQuery') && node.arguments[0] && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') { return { method: 'jQuery', selector: node.arguments[0].value }; } return null; } function groupByScope(occurrences, ast) { const groups = []; const processed = new Set(); occurrences.forEach(occurrence => { if (processed.has(occurrence)) return; const group = [occurrence]; processed.add(occurrence); // Find other occurrences in same function scope const scope = findFunctionScope(occurrence.node, ast); occurrences.forEach(other => { if (other !== occurrence && !processed.has(other)) { const otherScope = findFunctionScope(other.node, ast); // Same scope and within reasonable distance if (scope === otherScope && Math.abs(other.line - occurrence.line) <= 50) { group.push(other); processed.add(other); } } }); if (group.length > 1) { groups.push(group.sort((a, b) => a.line - b.line)); } }); return groups; } function findFunctionScope(node, ast) { let functionId = null; walk.ancestor(ast, { [node.type]: (currentNode, ancestors) => { if (currentNode === node) { // Find the nearest function ancestor for (let i = ancestors.length - 1; i >= 0; i--) { const ancestor = ancestors[i]; if (ancestor.type === 'FunctionDeclaration' || ancestor.type === 'FunctionExpression' || ancestor.type === 'ArrowFunctionExpression') { functionId = ancestor.start; // Use start position as unique ID break; } } } } }); return functionId; } module.exports = { detectRepeatedDomQueriesAST };