UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

437 lines (436 loc) 15.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; }; 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; }