sicua
Version:
A tool for analyzing project structure and dependencies
437 lines (436 loc) • 15.9 kB
JavaScript
;
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.determineFileContextType = determineFileContextType;
exports.calculateComplexityLevel = calculateComplexityLevel;
exports.getDependencyPurpose = getDependencyPurpose;
exports.identifyReactPatterns = identifyReactPatterns;
exports.getExportType = getExportType;
exports.estimateTokenCount = estimateTokenCount;
exports.sanitizeFilePath = sanitizeFilePath;
const path = __importStar(require("path"));
const typescript_1 = __importDefault(require("typescript"));
/**
* Determines the file context type based on file path, content, and AST
*/
function determineFileContextType(filePath, content, sourceFile) {
const fileName = path.basename(filePath);
const ext = path.extname(filePath);
// Test files
if (fileName.includes(".test.") ||
fileName.includes(".spec.") ||
filePath.includes("__tests__")) {
return "test";
}
// Style files
if ([".css", ".scss", ".less", ".module.css"].includes(ext)) {
return "style";
}
// Config files
if (fileName.includes("config") ||
fileName.includes("Config") ||
["next.config.js", "webpack.config.js", "tailwind.config.js"].includes(fileName)) {
return "config";
}
// API routes (Next.js pattern)
if (filePath.includes("/api/") || filePath.includes("\\api\\")) {
return "api-route";
}
// Middleware
if (fileName.includes("middleware") || fileName.includes("Middleware")) {
return "middleware";
}
// Constants
if (fileName.includes("constant") ||
fileName.includes("Constant") ||
fileName.toLowerCase().includes("enum")) {
return "constant";
}
// Type definitions
if (fileName.includes(".types.") ||
fileName.includes(".d.ts") ||
hasOnlyTypeDefinitions(sourceFile)) {
return "type-definition";
}
// **FIXED: Check React components BEFORE hooks**
// React components (check FIRST)
if (hasReactComponentPattern(content, sourceFile, fileName)) {
return "react-component";
}
// **FIXED: More specific React hook detection**
// React hooks (check AFTER components, with better detection)
if (isCustomHookFile(fileName, content, sourceFile)) {
return "react-hook";
}
// Services
if (fileName.includes("service") ||
fileName.includes("Service") ||
(fileName.includes("api") && !filePath.includes("/api/"))) {
return "service";
}
// Business logic (enhanced detection)
if (isBusinessLogicFile(fileName, content)) {
return "business-logic";
}
// Default to utility
return "utility";
}
/**
* **FIXED: Enhanced React component detection**
* Checks if file has React component patterns with better logic
*/
function hasReactComponentPattern(content, sourceFile, fileName) {
// Strong indicators of React component
const componentIndicators = [
// File name patterns
fileName.match(/^[A-Z][a-zA-Z]*\.(tsx|jsx)$/), // PascalCase .tsx/.jsx files
// JSX usage
content.includes("<") && content.includes("/>"),
content.includes("return (") && content.includes("<"),
content.includes("return <"),
// React imports with component usage
(content.includes("import React") || content.includes("from 'react'")) &&
(content.includes("export default") ||
content.includes("export const") ||
content.includes("export function")),
// Component-specific patterns
content.includes("props:") && content.includes("React.FC"),
content.includes("interface") && content.includes("Props"),
// JSX elements
/<[A-Z][a-zA-Z]*/.test(content), // JSX components like <MyComponent>
// React patterns
content.includes("children") && content.includes("React"),
];
// Must have at least 2 strong indicators
const indicatorCount = componentIndicators.filter(Boolean).length;
// Additional check: file exports something that looks like a component
const hasComponentExport = hasComponentExportPattern(content, fileName);
return indicatorCount >= 2 || hasComponentExport;
}
/**
* **NEW: Better custom hook detection**
* Only files that export custom hooks should be classified as react-hook
*/
function isCustomHookFile(fileName, content, sourceFile) {
// File name starts with "use" and is PascalCase
const hasHookFileName = fileName.match(/^use[A-Z][a-zA-Z]*\.(ts|tsx)$/);
// Exports a function that starts with "use"
const exportsHook = /export\s+(default\s+)?(?:function\s+|const\s+)use[A-Z][a-zA-Z]*/.test(content);
// Contains React hook calls but is NOT a component
const usesReactHooks = /use(State|Effect|Context|Reducer|Memo|Callback|Ref)/.test(content);
const isNotComponent = !hasReactComponentPattern(content, sourceFile, fileName);
// Must either have hook filename OR export a hook function
// AND use React hooks AND not be a component
return (hasHookFileName || exportsHook) && usesReactHooks && isNotComponent;
}
/**
* **NEW: Detect business logic files**
*/
function isBusinessLogicFile(fileName, content) {
const businessPatterns = [
fileName.toLowerCase().includes("logic"),
fileName.toLowerCase().includes("service"),
fileName.toLowerCase().includes("controller"),
fileName.toLowerCase().includes("handler"),
fileName.toLowerCase().includes("processor"),
fileName.toLowerCase().includes("manager"),
content.includes("business") && content.includes("logic"),
content.includes("calculate") && content.includes("process"),
content.includes("validate") && content.includes("rules"),
];
return businessPatterns.filter(Boolean).length >= 2;
}
/**
* **NEW: Check if file exports a component**
*/
function hasComponentExportPattern(content, fileName) {
const componentName = fileName.replace(/\.(tsx?|jsx?)$/, "");
// Check for default export of component
const defaultExportPatterns = [
`export default ${componentName}`,
`export default function ${componentName}`,
`export { ${componentName} }`,
`export { default as ${componentName} }`,
];
return defaultExportPatterns.some((pattern) => content.includes(pattern));
}
/**
* Calculates complexity level based on various metrics
*/
function calculateComplexityLevel(sourceFile, content) {
let complexityScore = 0;
// Line count factor
const lineCount = content.split("\n").length;
if (lineCount > 200)
complexityScore += 3;
else if (lineCount > 100)
complexityScore += 2;
else if (lineCount > 50)
complexityScore += 1;
// Function count
const functionCount = countFunctions(sourceFile);
if (functionCount > 10)
complexityScore += 2;
else if (functionCount > 5)
complexityScore += 1;
// Import count
const importCount = countImports(sourceFile);
if (importCount > 15)
complexityScore += 2;
else if (importCount > 8)
complexityScore += 1;
// Nested complexity patterns
if (content.includes("useEffect") && content.includes("useState"))
complexityScore += 1;
if (content.includes("useContext") || content.includes("useReducer"))
complexityScore += 1;
if (content.includes("async") && content.includes("await"))
complexityScore += 1;
if (content.includes("try") && content.includes("catch"))
complexityScore += 1;
// JSX complexity
const jsxElementCount = (content.match(/<[A-Z]/g) || []).length;
if (jsxElementCount > 20)
complexityScore += 2;
else if (jsxElementCount > 10)
complexityScore += 1;
// Return complexity level
if (complexityScore >= 8)
return "very-high";
if (complexityScore >= 5)
return "high";
if (complexityScore >= 2)
return "medium";
return "low";
}
/**
* Determines dependency purpose based on package name
*/
function getDependencyPurpose(packageName) {
const purposeMap = {
// UI Libraries
"@mui/material": "ui-library",
antd: "ui-library",
"react-bootstrap": "ui-library",
"chakra-ui": "ui-library",
// State Management
redux: "state-management",
zustand: "state-management",
recoil: "state-management",
mobx: "state-management",
// Routing
"react-router": "routing",
"next/router": "routing",
"@reach/router": "routing",
// Data Fetching
axios: "data-fetching",
swr: "data-fetching",
"react-query": "data-fetching",
"@tanstack/react-query": "data-fetching",
// Styling
"styled-components": "styling",
emotion: "styling",
tailwindcss: "styling",
// Utilities
lodash: "utility",
ramda: "utility",
uuid: "utility",
// Validation
yup: "validation",
joi: "validation",
zod: "validation",
// Date/Time
dayjs: "date-time",
moment: "date-time",
"date-fns": "date-time",
// Animation
"framer-motion": "animation",
"react-spring": "animation",
// Form Handling
"react-hook-form": "form-handling",
formik: "form-handling",
};
// Direct match
if (purposeMap[packageName]) {
return purposeMap[packageName];
}
// Pattern matching
if (packageName.includes("react") && packageName.includes("form"))
return "form-handling";
if (packageName.includes("test") || packageName.includes("jest"))
return "testing";
if (packageName.includes("webpack") || packageName.includes("babel"))
return "build-tool";
if (packageName.includes("style") || packageName.includes("css"))
return "styling";
return "utility";
}
/**
* Identifies React patterns in the source file
*/
function identifyReactPatterns(sourceFile, content) {
const patterns = [];
// Check for functional components
if ((content.includes("function ") && content.includes("return (")) ||
(content.includes("const ") && content.includes("=> ("))) {
patterns.push("functional-component");
}
// Check for class components
if (content.includes("class ") && content.includes("extends Component")) {
patterns.push("class-component");
}
// Check for custom hooks
if (content.includes("function use") || content.includes("const use")) {
patterns.push("custom-hook");
}
// Check for context patterns
if (content.includes("createContext") ||
content.includes("React.createContext")) {
patterns.push("context-provider");
}
if (content.includes("useContext")) {
patterns.push("context-consumer");
}
// Check for HOC pattern
if (content.includes("withComponent") ||
content.includes("compose(") ||
/return\s+function\s*\([^)]*\)\s*{/.test(content)) {
patterns.push("hoc");
}
// Check for render props
if (content.includes("children(") || content.includes("render(")) {
patterns.push("render-props");
}
// Check for controlled/uncontrolled patterns
if (content.includes("value=") && content.includes("onChange=")) {
patterns.push("controlled-component");
}
if (content.includes("defaultValue=") || content.includes("ref=")) {
patterns.push("uncontrolled-component");
}
return patterns;
}
/**
* Determines export type from AST node
*/
function getExportType(node) {
if (typescript_1.default.isExportAssignment(node)) {
if (typescript_1.default.isFunctionExpression(node.expression) ||
typescript_1.default.isArrowFunction(node.expression)) {
return "default-function";
}
if (typescript_1.default.isClassExpression(node.expression)) {
return "default-class";
}
return "default-object";
}
if (typescript_1.default.isExportDeclaration(node)) {
// Handle named exports
if (node.exportClause && typescript_1.default.isNamedExports(node.exportClause)) {
// This is a re-export, need more context
return "named-object";
}
}
if (typescript_1.default.isFunctionDeclaration(node)) {
return "named-function";
}
if (typescript_1.default.isClassDeclaration(node)) {
return "named-class";
}
if (typescript_1.default.isInterfaceDeclaration(node)) {
return "named-interface";
}
if (typescript_1.default.isTypeAliasDeclaration(node)) {
return "named-type";
}
if (typescript_1.default.isEnumDeclaration(node)) {
return "named-enum";
}
if (typescript_1.default.isVariableStatement(node)) {
return "named-constant";
}
return "named-object";
}
/**
* Estimates token count for a given text
*/
function estimateTokenCount(text) {
// Rough estimation: ~4 characters per token for code
return Math.ceil(text.length / 4);
}
/**
* Sanitizes file path for display
*/
function sanitizeFilePath(filePath, srcDir) {
return path.relative(srcDir, filePath).replace(/\\/g, "/");
}
/**
* Checks if file contains only type definitions
*/
function hasOnlyTypeDefinitions(sourceFile) {
let hasNonTypeDeclaration = false;
typescript_1.default.forEachChild(sourceFile, (node) => {
if (!typescript_1.default.isInterfaceDeclaration(node) &&
!typescript_1.default.isTypeAliasDeclaration(node) &&
!typescript_1.default.isEnumDeclaration(node) &&
!typescript_1.default.isImportDeclaration(node) &&
!typescript_1.default.isExportDeclaration(node)) {
hasNonTypeDeclaration = true;
}
});
return !hasNonTypeDeclaration;
}
/**
* Counts functions in source file
*/
function countFunctions(sourceFile) {
let count = 0;
function visit(node) {
if (typescript_1.default.isFunctionDeclaration(node) ||
typescript_1.default.isMethodDeclaration(node) ||
typescript_1.default.isArrowFunction(node) ||
typescript_1.default.isFunctionExpression(node)) {
count++;
}
typescript_1.default.forEachChild(node, visit);
}
visit(sourceFile);
return count;
}
/**
* Counts imports in source file
*/
function countImports(sourceFile) {
let count = 0;
typescript_1.default.forEachChild(sourceFile, (node) => {
if (typescript_1.default.isImportDeclaration(node)) {
count++;
}
});
return count;
}