crapifyme
Version:
Ultra-fast developer productivity CLI tools - remove comments, logs, and more
187 lines • 7.24 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASTAnalyzer = void 0;
const parser_1 = require("@babel/parser");
const traverse_1 = __importDefault(require("@babel/traverse"));
class ASTAnalyzer {
constructor() {
this.usedIdentifiers = new Set();
this.importStatements = [];
this.scopeChain = [];
}
analyzeFile(content, filePath) {
this.reset();
try {
const ast = this.parseCode(content, filePath);
this.extractImports(ast, content);
this.analyzeUsage(ast);
return {
imports: this.importStatements,
unusedImports: this.findUnusedImports(),
duplicateGroups: this.findDuplicateImports(),
usedIdentifiers: this.usedIdentifiers,
scopeChain: this.scopeChain
};
}
catch (error) {
throw new Error(`AST analysis failed for ${filePath}: ${error.message}`);
}
}
reset() {
this.usedIdentifiers.clear();
this.importStatements = [];
this.scopeChain = [];
}
parseCode(content, filePath) {
const isTypeScript = /\.tsx?$/.test(filePath);
const isJSX = /\.(jsx|tsx)$/.test(filePath);
const plugins = ['objectRestSpread', 'functionBind', 'decorators-legacy'];
if (isTypeScript) {
plugins.push('typescript');
}
if (isJSX) {
plugins.push('jsx');
}
return (0, parser_1.parse)(content, {
sourceType: 'module',
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
plugins,
errorRecovery: true
});
}
extractImports(ast, content) {
const lines = content.split('\n');
(0, traverse_1.default)(ast, {
ImportDeclaration: (path) => {
const node = path.node;
const startLine = (node.loc?.start.line || 1) - 1;
const endLine = (node.loc?.end.line || 1) - 1;
const leadingComments = node.leadingComments?.map(c => c.value.trim()) || [];
const trailingComments = node.trailingComments?.map(c => c.value.trim()) || [];
const importKind = node.importKind;
const importStatement = {
source: node.source.value,
specifiers: this.extractSpecifiers(node.specifiers),
importKind: importKind === 'type' || importKind === 'typeof' ? importKind : 'value',
startPos: node.start || 0,
endPos: node.end || 0,
leadingComments,
trailingComments
};
this.importStatements.push(importStatement);
}
});
}
extractSpecifiers(specifiers) {
return specifiers.map(spec => {
if (spec.type === 'ImportDefaultSpecifier') {
return {
type: 'default',
local: spec.local.name
};
}
else if (spec.type === 'ImportNamespaceSpecifier') {
return {
type: 'namespace',
local: spec.local.name
};
}
else if (spec.type === 'ImportSpecifier') {
const importKind = spec.importKind;
return {
type: 'named',
imported: 'name' in spec.imported ? spec.imported.name : spec.imported.value,
local: spec.local.name,
importKind: importKind === 'typeof' || importKind === 'type' ? importKind : 'value'
};
}
throw new Error(`Unknown import specifier type: ${spec.type}`);
});
}
analyzeUsage(ast) {
const scopeStack = [[]];
(0, traverse_1.default)(ast, {
enter: path => {
if (path.isFunction() || path.isBlockStatement() || path.isProgram()) {
scopeStack.push([]);
}
},
exit: path => {
if (path.isFunction() || path.isBlockStatement() || path.isProgram()) {
this.scopeChain.push(scopeStack.pop() || []);
}
},
Identifier: (path) => {
if (path.isReferencedIdentifier()) {
this.usedIdentifiers.add(path.node.name);
const currentScope = scopeStack[scopeStack.length - 1];
if (currentScope && !currentScope.includes(path.node.name)) {
currentScope.push(path.node.name);
}
}
},
VariableDeclarator: path => {
if (path.node.id.type === 'Identifier') {
const currentScope = scopeStack[scopeStack.length - 1];
if (currentScope) {
currentScope.push(path.node.id.name);
}
}
}
});
}
findUnusedImports() {
return this.importStatements.filter(importStmt => {
return importStmt.specifiers.some(spec => {
return !this.usedIdentifiers.has(spec.local);
});
});
}
findDuplicateImports() {
const sourceGroups = new Map();
for (const importStmt of this.importStatements) {
const source = importStmt.source;
if (!sourceGroups.has(source)) {
sourceGroups.set(source, []);
}
sourceGroups.get(source).push(importStmt);
}
return Array.from(sourceGroups.values()).filter(group => group.length > 1);
}
canMergeImports(imports) {
if (imports.length <= 1)
return false;
const firstImport = imports[0];
return imports.every(imp => imp.importKind === firstImport.importKind && imp.source === firstImport.source);
}
mergeImports(imports) {
if (!this.canMergeImports(imports)) {
throw new Error('Cannot merge incompatible imports');
}
const mergedSpecifiers = [];
const seenLocals = new Set();
for (const importStmt of imports) {
for (const spec of importStmt.specifiers) {
if (!seenLocals.has(spec.local)) {
mergedSpecifiers.push(spec);
seenLocals.add(spec.local);
}
}
}
return {
source: imports[0].source,
specifiers: mergedSpecifiers,
importKind: imports[0].importKind,
startPos: Math.min(...imports.map(i => i.startPos)),
endPos: Math.max(...imports.map(i => i.endPos)),
leadingComments: imports[0].leadingComments,
trailingComments: imports[imports.length - 1].trailingComments
};
}
}
exports.ASTAnalyzer = ASTAnalyzer;
//# sourceMappingURL=ast-analyzer.js.map