sicua
Version:
A tool for analyzing project structure and dependencies
325 lines (324 loc) • 12.8 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;
};
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;