UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

325 lines (324 loc) 12.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImportExtractor = void 0; const typescript_1 = __importDefault(require("typescript")); const path = __importStar(require("path")); const contextUtils_1 = require("../utils/contextUtils"); class ImportExtractor { constructor(srcDir) { this.srcDir = srcDir; } /** * Extracts all dependency context from a source file */ extractDependencies(sourceFile, filePath) { const external = []; const internal = []; const reactSpecific = { hooks: [], components: [], patterns: [], }; const utilityImports = []; typescript_1.default.forEachChild(sourceFile, (node) => { if (typescript_1.default.isImportDeclaration(node)) { this.processImportDeclaration(node, filePath, external, internal, reactSpecific, utilityImports); } }); // Also check for dynamic imports this.extractDynamicImports(sourceFile, external, internal); return { external, internal, reactSpecific: [reactSpecific], utilityImports, }; } /** * Processes a single import declaration */ processImportDeclaration(node, filePath, external, internal, reactSpecific, utilityImports) { const moduleSpecifier = node.moduleSpecifier; if (!typescript_1.default.isStringLiteral(moduleSpecifier)) return; const importPath = moduleSpecifier.text; const isRelativeImport = importPath.startsWith("./") || importPath.startsWith("../"); const isAbsoluteImport = importPath.startsWith("/") || importPath.startsWith("@/"); if (isRelativeImport || isAbsoluteImport) { this.processInternalImport(node, importPath, filePath, internal); } else { this.processExternalImport(node, importPath, external, reactSpecific, utilityImports); } } /** * Processes internal (relative/absolute) imports */ processInternalImport(node, importPath, currentFilePath, internal) { const resolvedPath = this.resolveInternalPath(importPath, currentFilePath); const relationship = this.determineRelationship(importPath, currentFilePath); const usageType = this.determineUsageType(node, resolvedPath); internal.push({ path: (0, contextUtils_1.sanitizeFilePath)(resolvedPath, this.srcDir), relationship, usageType, }); } /** * Processes external (npm package) imports */ processExternalImport(node, importPath, external, reactSpecific, utilityImports) { const packageName = this.extractPackageName(importPath); const purpose = (0, contextUtils_1.getDependencyPurpose)(packageName); const criticality = this.determineCriticality(packageName, purpose); // Handle React-specific imports if (packageName === "react" || packageName.startsWith("react-")) { this.processReactImport(node, packageName, reactSpecific); } // Add to external dependencies const existingDep = external.find((dep) => dep.name === packageName); if (!existingDep) { external.push({ name: packageName, purpose, criticality, }); } // Track utility imports if (purpose === "utility") { const importedNames = this.extractImportedNames(node); utilityImports.push(...importedNames); } } /** * Processes React-specific imports */ processReactImport(node, packageName, reactSpecific) { const importedNames = this.extractImportedNames(node); importedNames.forEach((name) => { // React hooks if (name.startsWith("use")) { reactSpecific.hooks.push(name); } // React components else if (name[0] && name[0] === name[0].toUpperCase()) { reactSpecific.components.push(name); } }); // Identify patterns based on package if (packageName === "react") { if (importedNames.includes("createContext")) { reactSpecific.patterns.push("context-provider"); } if (importedNames.includes("Component")) { reactSpecific.patterns.push("class-component"); } } } /** * Extracts dynamic imports (lazy loading, etc.) */ extractDynamicImports(sourceFile, external, internal) { const visit = (node) => { // Handle import() expressions if (typescript_1.default.isCallExpression(node) && node.expression.kind === typescript_1.default.SyntaxKind.ImportKeyword) { const arg = node.arguments[0]; if (typescript_1.default.isStringLiteral(arg)) { const importPath = arg.text; if (this.isExternalImport(importPath)) { const packageName = this.extractPackageName(importPath); const purpose = (0, contextUtils_1.getDependencyPurpose)(packageName); external.push({ name: packageName, purpose, criticality: "medium", // Dynamic imports are usually less critical }); } } } // Handle React.lazy if (typescript_1.default.isCallExpression(node)) { const expression = node.expression; if ((typescript_1.default.isIdentifier(expression) && expression.text === "lazy") || (typescript_1.default.isPropertyAccessExpression(expression) && typescript_1.default.isIdentifier(expression.expression) && expression.expression.text === "React" && typescript_1.default.isIdentifier(expression.name) && expression.name.text === "lazy")) { // This is a lazy-loaded component const arg = node.arguments[0]; if (typescript_1.default.isArrowFunction(arg) || typescript_1.default.isFunctionExpression(arg)) { // Look for dynamic import inside the function typescript_1.default.forEachChild(arg, visit); } } } typescript_1.default.forEachChild(node, visit); }; visit(sourceFile); } /** * Extracts imported names from import declaration */ extractImportedNames(node) { const names = []; if (node.importClause) { // Default import if (node.importClause.name) { names.push(node.importClause.name.text); } // Named imports if (node.importClause.namedBindings) { if (typescript_1.default.isNamedImports(node.importClause.namedBindings)) { node.importClause.namedBindings.elements.forEach((element) => { names.push(element.name.text); }); } // Namespace import else if (typescript_1.default.isNamespaceImport(node.importClause.namedBindings)) { names.push(node.importClause.namedBindings.name.text); } } } return names; } /** * Resolves internal import path to absolute path */ resolveInternalPath(importPath, currentFilePath) { if (importPath.startsWith("@/")) { // Absolute import with alias return path.join(this.srcDir, importPath.substring(2)); } // Relative import const currentDir = path.dirname(currentFilePath); return path.resolve(currentDir, importPath); } /** * Determines relationship type between files */ determineRelationship(importPath, currentFilePath) { const currentDir = path.dirname(currentFilePath); const importDir = path.dirname(path.resolve(currentDir, importPath)); // Same directory - likely siblings if (currentDir === importDir) { return "sibling"; } // Parent directory if (importDir === path.dirname(currentDir)) { return "parent-child"; } // Utility patterns if (importPath.includes("util") || importPath.includes("helper")) { return "utility-consumer"; } // Type patterns if (importPath.includes("type") || importPath.includes("interface")) { return "type-provider"; } // Service patterns if (importPath.includes("service") || importPath.includes("api")) { return "service-consumer"; } // Config patterns if (importPath.includes("config") || importPath.includes("constant")) { return "config-consumer"; } return "utility-consumer"; // Default } /** * Determines usage type based on import and file patterns */ determineUsageType(node, resolvedPath) { const fileName = path.basename(resolvedPath); // Check file name patterns if (fileName.includes("type") || fileName.includes(".d.ts")) { return "type"; } if (fileName.includes("constant") || fileName.includes("config")) { return "constant"; } if (fileName.startsWith("use") && fileName[3]?.toUpperCase() === fileName[3]) { return "hook"; } // Check import patterns const importedNames = this.extractImportedNames(node); const hasCapitalizedImports = importedNames.some((name) => name[0] && name[0] === name[0].toUpperCase()); if (hasCapitalizedImports) { return "component"; } return "utility"; } /** * Extracts package name from import path */ extractPackageName(importPath) { // Handle scoped packages if (importPath.startsWith("@")) { const parts = importPath.split("/"); return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0]; } // Handle regular packages return importPath.split("/")[0]; } /** * Determines if import is external (npm package) */ isExternalImport(importPath) { return (!importPath.startsWith("./") && !importPath.startsWith("../") && !importPath.startsWith("/") && !importPath.startsWith("@/")); } /** * Determines criticality of a dependency */ determineCriticality(packageName, purpose) { // Core dependencies if (["react", "react-dom", "next"].includes(packageName)) { return "high"; } // Important functional dependencies if (["state-management", "routing", "data-fetching"].includes(purpose)) { return "high"; } // UI and styling dependencies if (["ui-library", "styling", "form-handling"].includes(purpose)) { return "medium"; } // Utilities and development tools if (["utility", "testing", "build-tool"].includes(purpose)) { return "low"; } return "medium"; // Default } } exports.ImportExtractor = ImportExtractor;