agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
156 lines (138 loc) • 3.88 kB
JavaScript
/**
* @file AST Parser utility
* @description Common AST parsing functionality for performance analyzers
*/
const { parseAST } = require('../../utils/astHelpers');
const walk = require('acorn-walk');
/**
* Parse JavaScript content to AST
* @param {string} content - JavaScript content
* @param {string} filePath - File path for error reporting
* @returns {Object|null} AST object or null if parsing fails
*/
function parseToAST(content, filePath) {
try {
// Try parsing as ES2020 first
return parseAST(content, {
ecmaVersion: 2020,
allowHashBang: true,
locations: true
});
} catch (moduleError) {
try {
// Fallback to script mode for CommonJS
return parseAST(content, {
ecmaVersion: 2020,
sourceType: 'script',
allowHashBang: true,
locations: true
});
} catch (scriptError) {
try {
// Try with even more permissive settings
return parseAST(content, {
ecmaVersion: 'latest',
sourceType: 'module',
allowHashBang: true,
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
locations: true
});
} catch (finalError) {
console.error(`Failed to parse ${filePath}: ${finalError.message}`);
throw new Error(`Unable to parse ${filePath} as JavaScript: ${finalError.message}`);
}
}
}
}
/**
* Check if file can be parsed as JavaScript
* @param {string} filePath - File path
* @returns {boolean} True if file can be parsed
*/
function canParseAsJS(filePath) {
return /\.(js|jsx|ts|tsx|mjs|cjs)$/.test(filePath);
}
/**
* Get the line number from an AST node
* @param {Object} node - AST node
* @returns {number} Line number or 0 if not found
*/
function getNodeLine(node) {
if (!node) return 0;
// Check for loc property (most common)
if (node.loc && node.loc.start && typeof node.loc.start.line === 'number') {
return node.loc.start.line;
}
// Check for line property directly
if (typeof node.line === 'number') {
return node.line;
}
// Check for start property
if (typeof node.start === 'number') {
// For acorn, we might need to calculate line from position
// This is a fallback - ideally loc should be available
return 1; // Default to line 1 if we only have position
}
return 0;
}
/**
* Check if a node is inside a loop
* @param {Object} node - Current AST node
* @param {Array} ancestors - Array of ancestor nodes
* @returns {boolean} True if node is inside a loop
*/
function isInsideLoop(node, ancestors = []) {
if (!ancestors || !Array.isArray(ancestors)) {
return false;
}
const loopTypes = [
'ForStatement',
'ForInStatement',
'ForOfStatement',
'WhileStatement',
'DoWhileStatement'
];
// Check if any ancestor is a loop
return ancestors.some(ancestor =>
ancestor && loopTypes.includes(ancestor.type)
);
}
/**
* Get code snippet from a node
* @param {Object} node - AST node
* @param {string} source - Source code
* @returns {string} Code snippet
*/
function getNodeSource(node, source) {
if (!node || !source) return '';
if (typeof node.start === 'number' && typeof node.end === 'number') {
return source.substring(node.start, node.end);
}
return '';
}
/**
* Collect nodes of specific types
* @param {Object} ast - AST tree
* @param {Array} nodeTypes - Array of node types to collect
* @returns {Array} Array of collected nodes
*/
function collectNodes(ast, nodeTypes) {
const nodes = [];
walk.simple(ast, {
...nodeTypes.reduce((acc, type) => {
acc[type] = (node) => nodes.push(node);
return acc;
}, {})
});
return nodes;
}
module.exports = {
parseToAST,
walk,
canParseAsJS,
getNodeLine,
isInsideLoop,
getNodeSource,
collectNodes
};