UNPKG

smart-renamer

Version:

🚀 Intelligent file and code naming suggestions based on project-specific naming conventions. Interactive CLI tool with AST-based code analysis for variables, functions, components, and more.

331 lines • 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ASTAnalyzer = void 0; const parser_1 = require("@babel/parser"); const fs_1 = require("fs"); class ASTAnalyzer { detectNamingConvention(name) { if (/^[A-Z][A-Z0-9_]*$/.test(name)) return 'CONSTANT_CASE'; if (/^[A-Z][a-zA-Z0-9]*$/.test(name)) return 'PascalCase'; if (/^[a-z][a-zA-Z0-9]*$/.test(name)) return 'camelCase'; if (/^[a-z][a-z0-9_]*$/.test(name)) return 'snake_case'; if (/^[a-z][a-z0-9-]*$/.test(name)) return 'kebab-case'; return 'mixed'; } isReactComponent(name, node) { if (!/^[A-Z]/.test(name)) return false; if (!node) return false; // Check if it's a function that returns JSX if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') { return true; // Simple heuristic - any capitalized function could be a component } return false; } isConstant(name) { return /^[A-Z][A-Z0-9_]*$/.test(name); } extractIdentifiersFromNode(node, identifiers, filePath) { if (!node || typeof node !== 'object') return; // Handle variable declarations if (node.type === 'VariableDeclaration') { for (const declarator of node.declarations || []) { if (declarator.id && declarator.id.name) { const name = declarator.id.name; const isConst = this.isConstant(name); identifiers.push({ name, type: isConst ? 'constant' : 'variable', line: declarator.loc?.start?.line || 0, column: declarator.loc?.start?.column || 0, filePath, scope: 'global', isExported: this.checkIfExported(node), isReactComponent: !isConst ? this.isReactComponent(name, declarator.init) : false }); } } } // Handle function declarations if (node.type === 'FunctionDeclaration' && node.id && node.id.name) { const name = node.id.name; identifiers.push({ name, type: 'function', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported: this.checkIfExported(node), isReactComponent: this.isReactComponent(name, node) }); } // Handle class declarations if (node.type === 'ClassDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'class', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported: this.checkIfExported(node) }); } // Handle TypeScript interface declarations if (node.type === 'TSInterfaceDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'interface', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported: this.checkIfExported(node) }); } // Handle TypeScript type alias declarations if (node.type === 'TSTypeAliasDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'type', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported: this.checkIfExported(node) }); } // Handle TypeScript enum declarations if (node.type === 'TSEnumDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'enum', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported: this.checkIfExported(node) }); } // Handle export declarations if (node.type === 'ExportNamedDeclaration') { if (node.declaration) { // Mark declarations as exported this.extractExportedIdentifiers(node.declaration, identifiers, filePath, true); return; } // Handle export { name } syntax if (node.specifiers) { for (const specifier of node.specifiers) { if (specifier.exported && specifier.exported.name) { // This is for re-exports, we'll skip for now } } } } if (node.type === 'ExportDefaultDeclaration' && node.declaration) { this.extractExportedIdentifiers(node.declaration, identifiers, filePath, true); return; } // Recursively traverse child nodes for (const key in node) { if (key !== 'parent' && key !== 'loc' && key !== 'range' && key !== '_parent') { const child = node[key]; if (Array.isArray(child)) { child.forEach(c => { if (c && typeof c === 'object') { this.extractIdentifiersFromNode(c, identifiers, filePath); } }); } else if (child && typeof child === 'object') { this.extractIdentifiersFromNode(child, identifiers, filePath); } } } } checkIfExported(_node) { // Check if this node is inside an export declaration return false; // Will be properly set for exported nodes in the extraction logic } extractExportedIdentifiers(node, identifiers, filePath, isExported) { if (!node || typeof node !== 'object') return; // Handle variable declarations if (node.type === 'VariableDeclaration') { for (const declarator of node.declarations || []) { if (declarator.id && declarator.id.name) { const name = declarator.id.name; const isConst = this.isConstant(name); identifiers.push({ name, type: isConst ? 'constant' : 'variable', line: declarator.loc?.start?.line || 0, column: declarator.loc?.start?.column || 0, filePath, scope: 'global', isExported, isReactComponent: !isConst ? this.isReactComponent(name, declarator.init) : false }); } } return; } // Handle function declarations if (node.type === 'FunctionDeclaration' && node.id && node.id.name) { const name = node.id.name; identifiers.push({ name, type: 'function', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported, isReactComponent: this.isReactComponent(name, node) }); return; } // Handle class declarations if (node.type === 'ClassDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'class', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported }); return; } // Handle other TypeScript declarations... if (node.type === 'TSInterfaceDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'interface', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported }); return; } if (node.type === 'TSTypeAliasDeclaration' && node.id && node.id.name) { identifiers.push({ name: node.id.name, type: 'type', line: node.loc?.start?.line || 0, column: node.loc?.start?.column || 0, filePath, scope: 'global', isExported }); return; } } analyzeFile(filePath) { try { const content = (0, fs_1.readFileSync)(filePath, 'utf-8'); const ast = (0, parser_1.parse)(content, { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, plugins: [ 'jsx', 'typescript', 'decorators-legacy', 'classProperties', 'objectRestSpread', 'asyncGenerators', 'functionBind', 'exportDefaultFrom', 'exportNamespaceFrom', 'dynamicImport', 'nullishCoalescingOperator', 'optionalChaining' ] }); const identifiers = []; this.extractIdentifiersFromNode(ast, identifiers, filePath); return identifiers; } catch (error) { console.warn(`Warning: Could not analyze ${filePath}: ${error}`); return []; } } analyzeProject(rootPath, patterns = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx']) { const { glob } = require('glob'); const { join } = require('path'); const allIdentifiers = []; const conventions = { variables: {}, functions: {}, classes: {}, constants: {} }; // Find all matching files const files = []; for (const pattern of patterns) { const matches = glob.sync(pattern, { cwd: rootPath, ignore: ['node_modules/**', 'dist/**', 'build/**', '**/*.d.ts'] }); files.push(...matches.map((f) => join(rootPath, f))); } // Analyze each file for (const filePath of files) { const identifiers = this.analyzeFile(filePath); allIdentifiers.push(...identifiers); // Count conventions for (const identifier of identifiers) { const convention = this.detectNamingConvention(identifier.name); const category = identifier.type === 'constant' ? 'constants' : identifier.type === 'class' ? 'classes' : identifier.type === 'function' ? 'functions' : 'variables'; if (!conventions[category][convention]) { conventions[category][convention] = 0; } conventions[category][convention]++; } } // Calculate consistency score const totalByCategory = { variables: Object.values(conventions.variables).reduce((a, b) => a + b, 0), functions: Object.values(conventions.functions).reduce((a, b) => a + b, 0), classes: Object.values(conventions.classes).reduce((a, b) => a + b, 0), constants: Object.values(conventions.constants).reduce((a, b) => a + b, 0) }; let consistencyScore = 0; let totalCategories = 0; for (const [category, total] of Object.entries(totalByCategory)) { if (total > 0) { const categoryConventions = conventions[category]; const maxCount = Math.max(...Object.values(categoryConventions)); consistencyScore += maxCount / total; totalCategories++; } } consistencyScore = totalCategories > 0 ? consistencyScore / totalCategories : 0; return { identifiers: allIdentifiers, conventions, totalIdentifiers: allIdentifiers.length, consistencyScore }; } } exports.ASTAnalyzer = ASTAnalyzer; //# sourceMappingURL=ast-analyzer.js.map