sicua
Version:
A tool for analyzing project structure and dependencies
420 lines (419 loc) • 14.9 kB
JavaScript
"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.NodeModuleDetector = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
/**
* Helper class to distinguish between internal project components and external node_modules dependencies
*/
class NodeModuleDetector {
constructor(projectRoot, srcDirectory) {
this.projectRoot = projectRoot;
this.srcDirectory = srcDirectory;
this.packageJsonDependencies = new Set();
this.loadDependencies();
}
/**
* Determines if a component import is external (from node_modules) or internal - FIXED VERSION
*/
isExternalComponent(importPath, currentFilePath) {
// Relative imports are always internal
if (importPath.startsWith("./") || importPath.startsWith("../")) {
return false;
}
// Handle path aliases like @/components/ui/card - these are INTERNAL
if (importPath.startsWith("@/")) {
return false; // Path aliases are internal project components
}
// Check for Next.js built-in components (external)
if (this.isNextJSBuiltIn(importPath)) {
return true;
}
// Check for React built-in imports (external)
if (this.isReactBuiltInImport(importPath)) {
return true;
}
// Absolute imports starting with @ (but not @/) are likely scoped packages (external)
if (importPath.startsWith("@") && !importPath.startsWith("@/")) {
return this.isPackageDependency(importPath);
}
// If import doesn't contain any path separators, it's likely a bare package name (external)
if (!importPath.includes("/")) {
return this.isPackageDependency(importPath);
}
// Check if it's a known package dependency first
if (this.isPackageDependency(importPath)) {
return true;
}
// Check if the resolved path points to node_modules
const resolvedPath = this.resolveImportPath(importPath, currentFilePath);
if (resolvedPath && resolvedPath.includes("node_modules")) {
return true;
}
// If we can resolve it to a file within our project, it's internal
if (resolvedPath && this.isWithinProjectSource(resolvedPath)) {
return false;
}
// If it looks like an internal path pattern but we can't resolve it, assume internal
if (this.looksLikeInternalPath(importPath)) {
return false;
}
// Default to external if we can't determine
return true;
}
/**
* Checks if import path looks like an internal project path - UPDATED
*/
looksLikeInternalPath(importPath) {
// Common internal path patterns
const internalPatterns = [
/^components\//,
/^src\//,
/^app\//,
/^lib\//,
/^utils\//,
/^hooks\//,
/^pages\//,
/^styles\//,
/^types\//,
/^constants\//,
/^@\//, // Path aliases are internal
];
return internalPatterns.some((pattern) => pattern.test(importPath));
}
/**
* Checks if an import is a Next.js built-in component - NEW METHOD
*/
isNextJSBuiltIn(importPath) {
const nextJSBuiltIns = [
"next/link",
"next/image",
"next/head",
"next/script",
"next/router",
"next/navigation",
"next/app",
"next/document",
"next/error",
"next/font",
"next/headers",
"next/cookies",
"next/cache",
"next/server",
];
return nextJSBuiltIns.some((builtin) => importPath === builtin || importPath.startsWith(builtin + "/"));
}
/**
* Checks if an import is a React built-in import - NEW METHOD
*/
isReactBuiltInImport(importPath) {
const reactBuiltIns = [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"react-dom/client",
"react-dom/server",
];
return reactBuiltIns.some((builtin) => importPath === builtin || importPath.startsWith(builtin + "/"));
}
/**
* Extracts the package name from an import path
*/
getPackageName(importPath) {
// Handle scoped packages (@org/package)
if (importPath.startsWith("@") && !importPath.startsWith("@/")) {
const parts = importPath.split("/");
return parts.length > 1 ? `${parts[0]}/${parts[1]}` : parts[0];
}
// Handle regular packages
return importPath.split("/")[0];
}
/**
* Checks if a component reference is a native HTML element
*/
isNativeHTMLElement(componentName) {
const htmlElements = new Set([
"div",
"span",
"p",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"a",
"img",
"button",
"input",
"form",
"label",
"select",
"option",
"textarea",
"table",
"thead",
"tbody",
"tr",
"td",
"th",
"ul",
"ol",
"li",
"nav",
"header",
"footer",
"main",
"section",
"article",
"aside",
"figure",
"figcaption",
"video",
"audio",
"canvas",
"svg",
"path",
"circle",
"rect",
"line",
"polygon",
"iframe",
"embed",
"object",
"pre",
"code",
"blockquote",
"hr",
"br",
"strong",
"em",
"small",
"mark",
"del",
"ins",
"sub",
"sup",
]);
return htmlElements.has(componentName.toLowerCase());
}
/**
* Checks if a component reference is a React built-in (Fragment, Suspense, etc.) - UPDATED
*/
isReactBuiltIn(componentName) {
const reactBuiltIns = new Set([
"Fragment",
"Suspense",
"StrictMode",
"Profiler",
"React.Fragment",
"React.Suspense",
"React.StrictMode",
"React.Profiler",
"ErrorBoundary", // Note: This is not actually a React built-in, removing it
"Transition",
"SuspenseList",
"ConcurrentMode",
"unstable_ConcurrentMode",
]);
return reactBuiltIns.has(componentName);
}
/**
* Determines if an import should be considered internal to the project
*/
isInternalImport(importPath, currentFilePath) {
// Skip native HTML elements and React built-ins
if (this.isNativeHTMLElement(importPath) ||
this.isReactBuiltIn(importPath)) {
return false;
}
return !this.isExternalComponent(importPath, currentFilePath);
}
/**
* Resolves the full file path for an internal component
*/
resolveInternalComponentPath(importPath, currentFilePath) {
if (this.isExternalComponent(importPath, currentFilePath)) {
return null;
}
const resolvedPath = this.resolveImportPath(importPath, currentFilePath);
if (resolvedPath && this.isWithinProjectSource(resolvedPath)) {
return resolvedPath;
}
return null;
}
/**
* Gets real external dependencies from import paths - NEW METHOD
*/
getRealExternalDependencies(importPaths) {
const externalDeps = new Set();
for (const importPath of importPaths) {
if (this.isExternalComponent(importPath, "")) {
const packageName = this.getPackageName(importPath);
externalDeps.add(packageName);
}
}
return Array.from(externalDeps);
}
/**
* Private method to load dependencies from package.json
*/
loadDependencies() {
try {
const packageJsonPath = path.join(this.projectRoot, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
const dependencies = packageJson.dependencies || {};
const devDependencies = packageJson.devDependencies || {};
const peerDependencies = packageJson.peerDependencies || {};
Object.keys(dependencies).forEach((dep) => this.packageJsonDependencies.add(dep));
Object.keys(devDependencies).forEach((dep) => this.packageJsonDependencies.add(dep));
Object.keys(peerDependencies).forEach((dep) => this.packageJsonDependencies.add(dep));
// Add known external packages that might not be in package.json
const knownExternals = [
"react",
"react-dom",
"next",
"@types/react",
"@types/react-dom",
"@types/node",
"typescript",
];
knownExternals.forEach((dep) => this.packageJsonDependencies.add(dep));
}
catch (error) {
console.warn("Could not load package.json dependencies:", error);
}
}
/**
* Private method to check if a package is listed in dependencies
*/
isPackageDependency(importPath) {
const packageName = this.getPackageName(importPath);
return this.packageJsonDependencies.has(packageName);
}
/**
* Private method to resolve import paths - ENHANCED
*/
resolveImportPath(importPath, currentFilePath) {
try {
// Handle relative imports
if (importPath.startsWith("./") || importPath.startsWith("../")) {
const currentDir = path.dirname(currentFilePath);
const resolvedPath = path.resolve(currentDir, importPath);
// Try different extensions
const extensions = [
".tsx",
".ts",
".jsx",
".js",
"/index.tsx",
"/index.ts",
"/index.jsx",
"/index.js",
];
for (const ext of extensions) {
const fullPath = resolvedPath + ext;
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
}
// Handle path aliases (@/...) - these are INTERNAL
if (importPath.startsWith("@/")) {
const aliasPath = importPath.replace("@/", "");
// Try both src directory and project root
const possibleBases = [
this.srcDirectory,
path.join(this.projectRoot, "src"),
this.projectRoot,
];
for (const base of possibleBases) {
const srcBasedPath = path.join(base, aliasPath);
const extensions = [
".tsx",
".ts",
".jsx",
".js",
"/index.tsx",
"/index.ts",
"/index.jsx",
"/index.js",
];
for (const ext of extensions) {
const fullPath = srcBasedPath + ext;
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
}
}
// Handle absolute imports from src (without @/) - treat as internal
if (!importPath.startsWith("@") && !importPath.includes("node_modules")) {
const possibleBases = [
this.srcDirectory,
path.join(this.projectRoot, "src"),
this.projectRoot,
];
for (const base of possibleBases) {
const srcBasedPath = path.join(base, importPath);
const extensions = [
".tsx",
".ts",
".jsx",
".js",
"/index.tsx",
"/index.ts",
"/index.jsx",
"/index.js",
];
for (const ext of extensions) {
const fullPath = srcBasedPath + ext;
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
}
}
return null;
}
catch (error) {
return null;
}
}
/**
* Private method to check if a path is within the project source directory
*/
isWithinProjectSource(filePath) {
const normalizedPath = path.normalize(filePath);
const normalizedSrc = path.normalize(this.srcDirectory);
const normalizedProject = path.normalize(this.projectRoot);
return (normalizedPath.startsWith(normalizedSrc) ||
(normalizedPath.startsWith(normalizedProject) &&
!normalizedPath.includes("node_modules")));
}
}
exports.NodeModuleDetector = NodeModuleDetector;