UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

277 lines (276 loc) 10.9 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; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PathResolver = void 0; const path = __importStar(require("path")); /** * High-performance path resolution service * Centralizes and optimizes all path normalization and resolution logic */ class PathResolver { constructor(config, scanResult) { this.context = this.createParseContext(config); this.filePathSet = new Set(scanResult.filePaths); this.relativePathSet = this.buildRelativePathSet(scanResult.filePaths, config.projectPath); } /** * Normalize file path relative to appropriate base directory */ normalizeFilePath(filePath) { // Try to make path relative to source directory first if (filePath.startsWith(this.context.sourceDirectory)) { const relativePath = path.relative(this.context.sourceDirectory, filePath); return relativePath || "."; } // Fallback to project root const relativePath = path.relative(this.context.projectRoot, filePath); return relativePath || "."; } /** * Extract directory for component relation */ extractDirectory(filePath) { const normalizedPath = this.normalizeFilePath(filePath); const directory = path.dirname(normalizedPath); // Handle root directory cases if (directory === "." || directory === "") { return "/"; } return directory; } /** * Resolve import path considering project structure with O(1) lookup */ resolveImportPath(importPath, currentFile) { const cleanPath = importPath.replace(/['"]/g, "").trim(); // Check if it's external first (most common case) if (this.isExternalPackage(cleanPath)) { return { normalizedPath: cleanPath, isExternal: true, packageName: this.extractPackageName(cleanPath), }; } // Handle internal imports if (cleanPath.startsWith(".") || cleanPath.startsWith("/")) { try { const currentDir = path.dirname(currentFile); let resolvedPath; if (cleanPath.startsWith(".")) { // Relative import resolvedPath = path.resolve(currentDir, cleanPath); } else { // Absolute import from project root resolvedPath = path.resolve(this.context.projectRoot, cleanPath.substring(1)); } const normalizedPath = this.normalizeFilePath(resolvedPath); return { normalizedPath, isExternal: false, }; } catch (error) { console.warn(`Failed to resolve import path: ${importPath} from ${currentFile}`); return { normalizedPath: cleanPath, isExternal: false, }; } } // Default to treating as internal if not clearly external return { normalizedPath: cleanPath, isExternal: false, }; } /** * Check if import is external package with O(1) performance */ isExternalPackage(importPath) { const cleanPath = importPath.replace(/['"]/g, "").trim(); // Definitely internal patterns - return false immediately if (cleanPath.startsWith("./") || cleanPath.startsWith("../") || cleanPath.startsWith("@/") || cleanPath.startsWith("/") || cleanPath.includes("\\") || cleanPath.startsWith("app/") || cleanPath.startsWith("pages/") || cleanPath.startsWith("components/") || cleanPath.startsWith("lib/") || cleanPath.startsWith("utils/") || cleanPath.startsWith("hooks/") || cleanPath.startsWith("types/") || cleanPath.startsWith("src/") || cleanPath.startsWith("styles/") || cleanPath.endsWith(".css") || cleanPath.endsWith(".scss") || cleanPath.endsWith(".sass") || cleanPath.endsWith(".less") || cleanPath.endsWith(".module.css") || cleanPath.includes(".types") || cleanPath.includes(".schema")) { return false; } // Check against precomputed relative paths set - O(1) lookup const pathVariations = this.generatePathVariations(cleanPath); for (const variation of pathVariations) { if (this.relativePathSet.has(variation)) { return false; // It's internal } } // Check if it's a known npm package pattern return this.isValidPackagePattern(cleanPath); } /** * Extract package name from import path */ extractPackageName(importPath) { const cleanPath = importPath.replace(/['"]/g, "").trim(); // Scoped package (e.g., @react/types) if (cleanPath.startsWith("@")) { const parts = cleanPath.split("/"); if (parts.length >= 2) { return `${parts[0]}/${parts[1]}`; } return parts[0]; } // Regular package (e.g., react, lodash/get) const firstPart = cleanPath.split("/")[0]; return firstPart; } /** * Enhanced component detection based on project structure */ shouldTreatAsComponent(filePath) { const fileName = path.basename(filePath, path.extname(filePath)); const normalizedPath = this.normalizeFilePath(filePath); // Next.js specific component detection if (this.context.projectType === "nextjs") { // App router specific files if (this.context.routerType === "app") { const appRouterFiles = [ "layout", "page", "loading", "error", "not-found", "template", "default", ]; if (appRouterFiles.includes(fileName.toLowerCase())) { return true; } } // Pages router specific files if (this.context.routerType === "pages") { const pagesRouterFiles = ["_app", "_document", "_error", "404", "500"]; if (pagesRouterFiles.includes(fileName)) { return true; } } } // General component detection const isReactFile = filePath.endsWith(".tsx") || filePath.endsWith(".jsx"); const isComponentName = /^[A-Z]/.test(fileName); // Starts with capital letter const isInComponentsDir = normalizedPath.includes("component"); return isReactFile && (isComponentName || isInComponentsDir); } /** * Create parse context from config */ createParseContext(config) { const projectStructure = config.getProjectStructure(); return { projectType: projectStructure?.projectType || "react", routerType: projectStructure?.routerType, sourceDirectory: config.srcDir, projectRoot: config.projectPath, }; } /** * Build set of relative paths for O(1) lookup */ buildRelativePathSet(filePaths, projectRoot) { const relativePathSet = new Set(); for (const filePath of filePaths) { const relativePath = path .relative(projectRoot, filePath) .replace(/\\/g, "/"); // Add all variations that might be used in imports const variations = this.generatePathVariations(relativePath); for (const variation of variations) { relativePathSet.add(variation); } } return relativePathSet; } /** * Generate path variations for import matching */ generatePathVariations(basePath) { const variations = [basePath]; // Add with different extensions const withoutExt = basePath.replace(/\.(ts|tsx|js|jsx)$/, ""); variations.push(withoutExt); variations.push(`${withoutExt}.ts`); variations.push(`${withoutExt}.tsx`); variations.push(`${withoutExt}.js`); variations.push(`${withoutExt}.jsx`); // Add index variations variations.push(`${withoutExt}/index.ts`); variations.push(`${withoutExt}/index.tsx`); variations.push(`${withoutExt}/index.js`); variations.push(`${withoutExt}/index.jsx`); // Add variations that might match directory paths if (basePath.includes("/")) { const parts = basePath.split("/"); const fileName = parts[parts.length - 1]; const dirPath = parts.slice(0, -1).join("/"); variations.push(fileName); variations.push(dirPath); } return variations.filter((v) => v.length > 0); } /** * Check if string matches valid npm package pattern */ isValidPackagePattern(cleanPath) { // Scoped package (e.g., @org/package) if (cleanPath.startsWith("@") && cleanPath.includes("/")) { const parts = cleanPath.split("/"); return (parts.length >= 2 && parts[0].startsWith("@") && parts[1].length > 0); } // Regular package (e.g., react, lodash) if (!cleanPath.includes("/")) { return /^[a-z0-9][a-z0-9\-_]*$/i.test(cleanPath); } // Package subpath (e.g., lodash/get, react-dom/client) const firstPart = cleanPath.split("/")[0]; return firstPart.length > 0 && /^[a-z0-9][a-z0-9\-_]*$/i.test(firstPart); } } exports.PathResolver = PathResolver;