intellify
Version:
Detect JavaScript & TypeScript errors and make codes more optimized.
224 lines (170 loc) • 5.27 kB
JavaScript
import * as acorn from 'acorn';
import { simple as walk } from 'acorn-walk';
import { debugLog, getLineFromLoc } from '../utils/helpers.js';
export function findUnusedVariables(code, filePath) {
const unusedVars = [];
try {
const ast = acorn.parse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true
});
const declarations = new Map();
const imports = new Map();
const references = new Set();
walk(ast, {
VariableDeclarator(node) {
if (node.id.type === 'Identifier') {
declarations.set(node.id.name, {
name: node.id.name,
file: filePath,
line: getLineFromLoc(node.loc),
type: 'variable'
});
}
},
ImportDeclaration(node) {
if (node.specifiers) {
node.specifiers.forEach(specifier => {
if (specifier.type === 'ImportDefaultSpecifier' ||
specifier.type === 'ImportSpecifier' ||
specifier.type === 'ImportNamespaceSpecifier') {
const importName = specifier.local.name;
imports.set(importName, {
name: importName,
source: node.source.value,
file: filePath,
line: getLineFromLoc(node.loc),
type: 'import'
});
}
});
}
},
CallExpression(node) {
if (node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments.length > 0 &&
node.arguments[0].type === 'Literal') {
if (node.parent &&
node.parent.type === 'VariableDeclarator' &&
node.parent.id.type === 'Identifier') {
const requireName = node.parent.id.name;
imports.set(requireName, {
name: requireName,
source: node.arguments[0].value,
file: filePath,
line: getLineFromLoc(node.loc),
type: 'require'
});
}
}
}
});
walk(ast, {
Identifier(node) {
if (node.parent &&
(node.parent.type === 'ImportSpecifier' ||
node.parent.type === 'ImportDefaultSpecifier' ||
node.parent.type === 'ImportNamespaceSpecifier')) {
return;
}
if (node.name === 'require' &&
node.parent &&
node.parent.type === 'CallExpression') {
return;
}
references.add(node.name);
}
});
for (const [name, info] of declarations.entries()) {
if (!references.has(name)) {
unusedVars.push(info);
}
}
for (const [name, info] of imports.entries()) {
if (!references.has(name)) {
unusedVars.push(info);
}
}
} catch (error) {
debugLog(`Error finding unused variables in ${filePath}: ${error.message}`);
}
return unusedVars;
}
export function findComments(code, filePath) {
const comments = [];
try {
acorn.parse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
onComment: (isBlock, text, start, end, startLoc, endLoc) => {
comments.push({
file: filePath,
line: startLoc.line,
text: isBlock ? `/*${text}*/` : `//${text}`,
isBlock: isBlock,
start: start,
end: end
});
}
});
} catch (error) {
debugLog(`Error finding comments in ${filePath}: ${error.message}`);
}
return comments;
}
export function fixComments(code, comments) {
let result = code;
let removed = 0;
let skipped = 0;
const sortedComments = [...comments].sort((a, b) => b.start - a.start);
for (const comment of sortedComments) {
try {
const before = result.substring(0, comment.start);
const after = result.substring(comment.end);
const testCode = before + after;
try {
acorn.parse(testCode, { ecmaVersion: 'latest', sourceType: 'module' });
result = testCode;
removed++;
debugLog(`Removed comment on line ${comment.line}`);
} catch (syntaxError) {
skipped++;
debugLog(`Skipped comment on line ${comment.line} (potential syntax issue)`);
}
} catch (error) {
skipped++;
debugLog(`Error processing comment on line ${comment.line}: ${error.message}`);
}
}
return {
code: result,
stats: { removed, skipped }
};
}
export function findSyntaxErrors(code, filePath) {
const errors = [];
try {
acorn.parse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true
});
} catch (error) {
errors.push({
file: filePath,
line: error.loc?.line || 'unknown',
message: error.message
});
}
return errors;
}
export default {
findUnusedVariables,
findComments,
fixComments,
findSyntaxErrors
};