UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

370 lines (369 loc) 14.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FunctionClassifier = void 0; exports.classifyFunction = classifyFunction; exports.shouldIncludeBasedOnClassification = shouldIncludeBasedOnClassification; const typescript_1 = __importDefault(require("typescript")); const analysisUtils_1 = require("../../../utils/common/analysisUtils"); const errorHandler_1 = require("./errorHandler"); const reactSpecific_1 = require("../../../utils/ast/reactSpecific"); /** * Enhanced classifies functions based on their characteristics and usage patterns */ class FunctionClassifier { constructor(errorHandler, typeChecker) { this.errorHandler = errorHandler; this.typeChecker = typeChecker; } /** * Classifies a function node with enhanced error handling * @param node The function node to classify * @param filePath Optional file path for error context * @param functionName Optional function name for error context * @returns Function classification result */ classifyFunction(node, filePath, functionName) { const defaultClassification = { isReactComponent: false, usesReactHooks: false, hasReactSpecificOperations: false, usesFrontendAPIs: false, usesThisKeyword: false, isReducerOrStateManagement: false, }; return { isReactComponent: this.errorHandler.safeClassification(node, (n) => this.classifyReactComponent(n), false, filePath, functionName), usesReactHooks: this.errorHandler.safeClassification(node, (n) => (0, analysisUtils_1.usesReactHooks)(n), false, filePath, functionName), hasReactSpecificOperations: this.errorHandler.safeClassification(node, (n) => (0, analysisUtils_1.hasReactSpecificOperations)(n), false, filePath, functionName), usesFrontendAPIs: this.errorHandler.safeClassification(node, (n) => (0, analysisUtils_1.usesFrontendAPIs)(n), false, filePath, functionName), usesThisKeyword: this.errorHandler.safeClassification(node, (n) => (0, analysisUtils_1.usesThisKeyword)(n), false, filePath, functionName), isReducerOrStateManagement: this.errorHandler.safeClassification(node, (n) => this.classifyReducerOrStateManagement(n), false, filePath, functionName), }; } /** * Enhanced React component classification with better type checking */ classifyReactComponent(node) { // Use enhanced type-aware classification if type checker available if (this.typeChecker && this.isFunctionLikeNode(node)) { return this.classifyReactComponentWithTypes(node); } // Fallback to existing implementation return (0, reactSpecific_1.isReactComponent)(node); } /** * Type-aware React component classification */ classifyReactComponentWithTypes(node) { try { // Check return type const signature = this.typeChecker.getSignatureFromDeclaration(node); if (signature) { const returnType = this.typeChecker.getReturnTypeOfSignature(signature); const returnTypeString = this.typeChecker.typeToString(returnType); if (this.isReactReturnType(returnTypeString)) { return true; } } // Check function name patterns if (this.hasReactComponentNamePattern(node)) { return true; } // Check function body for JSX if (this.hasJSXInBody(node)) { return true; } return false; } catch (error) { // Fallback to basic implementation on error return (0, reactSpecific_1.isReactComponent)(node); } } /** * Enhanced reducer/state management classification */ classifyReducerOrStateManagement(node) { if (!this.isFunctionLikeNode(node)) { return false; } try { // Check existing patterns const hasReducer = (0, analysisUtils_1.hasReducerPattern)(node); const hasStateSpread = (0, analysisUtils_1.hasStateSpreadPattern)(node); if (hasReducer || hasStateSpread) { return true; } // Additional state management patterns return this.hasAdditionalStatePatterns(node); } catch (error) { return false; } } /** * Checks for additional state management patterns */ hasAdditionalStatePatterns(node) { if (!node) return false; let hasStatePatterns = false; const visit = (node) => { // Redux-style action creators if (this.isActionCreatorPattern(node)) { hasStatePatterns = true; return; } // Zustand store patterns if (this.isZustandStorePattern(node)) { hasStatePatterns = true; return; } // Context provider patterns if (this.isContextProviderPattern(node)) { hasStatePatterns = true; return; } // State update function patterns if (this.isStateUpdatePattern(node)) { hasStatePatterns = true; return; } if (!hasStatePatterns) { typescript_1.default.forEachChild(node, visit); } }; visit(node); return hasStatePatterns; } /** * Checks if node represents an action creator pattern */ isActionCreatorPattern(node) { if (typescript_1.default.isCallExpression(node)) { const callText = node.expression.getText().toLowerCase(); return (callText.includes("createaction") || callText.includes("actioncreator") || callText.includes("dispatch")); } if (typescript_1.default.isReturnStatement(node) && node.expression) { if (typescript_1.default.isObjectLiteralExpression(node.expression)) { const hasTypeProperty = node.expression.properties.some((prop) => typescript_1.default.isPropertyAssignment(prop) && typescript_1.default.isIdentifier(prop.name) && prop.name.text === "type"); return hasTypeProperty; } } return false; } /** * Checks if node represents a Zustand store pattern */ isZustandStorePattern(node) { if (typescript_1.default.isCallExpression(node)) { const callText = node.expression.getText().toLowerCase(); return (callText.includes("create") && (callText.includes("store") || callText.includes("zustand"))); } return false; } /** * Checks if node represents a Context provider pattern */ isContextProviderPattern(node) { if (typescript_1.default.isCallExpression(node)) { const callText = node.expression.getText().toLowerCase(); return (callText.includes("createcontext") || callText.includes("provider") || callText.includes("usecontext")); } return false; } /** * Checks if node represents a state update pattern */ isStateUpdatePattern(node) { if (typescript_1.default.isCallExpression(node)) { const callText = node.expression.getText().toLowerCase(); return (callText.includes("setstate") || callText.includes("updatestate") || callText.includes("mergestate")); } // Check for useState setter patterns if (typescript_1.default.isIdentifier(node)) { const name = node.text.toLowerCase(); return name.startsWith("set") && name.length > 3; } return false; } /** * Checks if return type is React-related */ isReactReturnType(returnTypeString) { const reactTypes = [ "jsx.element", "reactelement", "react.reactelement", "react.functioncomponent", "react.fc", "react.component", "jsxelement", ]; const lowerType = returnTypeString.toLowerCase(); return reactTypes.some((type) => lowerType.includes(type)); } /** * Checks if function has React component naming pattern */ hasReactComponentNamePattern(node) { try { let functionName; if (typescript_1.default.isFunctionDeclaration(node) && node.name) { functionName = node.name.text; } else if (typescript_1.default.isMethodDeclaration(node) && typescript_1.default.isIdentifier(node.name)) { functionName = node.name.text; } else if (typescript_1.default.isArrowFunction(node)) { const parent = node.parent; if (typescript_1.default.isVariableDeclaration(parent) && typescript_1.default.isIdentifier(parent.name)) { functionName = parent.name.text; } } if (functionName) { // React components typically start with capital letter return (/^[A-Z][a-zA-Z0-9]*$/.test(functionName) && !functionName.includes("_") && functionName.length > 1); } return false; } catch (error) { return false; } } /** * Checks if function body contains JSX */ hasJSXInBody(node) { if (!node.body) return false; let hasJSX = false; const visit = (node) => { if (typescript_1.default.isJsxElement(node) || typescript_1.default.isJsxSelfClosingElement(node) || typescript_1.default.isJsxFragment(node)) { hasJSX = true; return; } if (!hasJSX) { typescript_1.default.forEachChild(node, visit); } }; visit(node.body); return hasJSX; } /** * Type guard for function-like nodes */ isFunctionLikeNode(node) { return (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isArrowFunction(node)); } /** * Determines if a function should be included in the analysis * based on its classification (enhanced version) */ shouldIncludeBasedOnClassification(classification, filePath, functionName) { return this.errorHandler.safeExecute(() => !(classification.isReactComponent || classification.usesReactHooks || classification.hasReactSpecificOperations || classification.usesFrontendAPIs || classification.usesThisKeyword || classification.isReducerOrStateManagement), false, // Conservative fallback - exclude on error "classification-based inclusion check", errorHandler_1.ErrorCategory.CLASSIFICATION, errorHandler_1.ErrorSeverity.MEDIUM, filePath, functionName); } /** * Gets classification confidence score */ getClassificationConfidence(node, classification) { let confidence = 1.0; // Reduce confidence if type checker unavailable for React components if (classification.isReactComponent && !this.typeChecker) { confidence *= 0.8; } // Reduce confidence if function has ambiguous patterns if (this.hasAmbiguousPatterns(node)) { confidence *= 0.7; } // Increase confidence if multiple patterns agree const positiveClassifications = Object.values(classification).filter(Boolean).length; if (positiveClassifications > 2) { confidence *= 1.1; } return Math.min(confidence, 1.0); } /** * Checks for patterns that might be ambiguous */ hasAmbiguousPatterns(node) { // Functions that use 'this' but might not be class methods // Functions with generic names that could be anything // Functions with mixed patterns (e.g., React hooks in non-React functions) if (!node) return false; // Simple heuristic - functions with very generic names if (this.isFunctionLikeNode(node)) { const name = this.getFunctionName(node); const genericNames = [ "handler", "callback", "fn", "func", "method", "action", ]; return genericNames.some((generic) => name.toLowerCase().includes(generic)); } return false; } /** * Helper to get function name from any function-like node */ getFunctionName(node) { if (typescript_1.default.isFunctionDeclaration(node) && node.name) { return node.name.text; } if (typescript_1.default.isMethodDeclaration(node) && typescript_1.default.isIdentifier(node.name)) { return node.name.text; } if (typescript_1.default.isArrowFunction(node) && node.parent) { if (typescript_1.default.isVariableDeclaration(node.parent) && typescript_1.default.isIdentifier(node.parent.name)) { return node.parent.name.text; } } return "anonymous"; } } exports.FunctionClassifier = FunctionClassifier; /** * Backward compatibility function - creates a classifier and uses it */ function classifyFunction(node) { const errorHandler = new errorHandler_1.ErrorHandler({ logErrors: false }); const classifier = new FunctionClassifier(errorHandler); return classifier.classifyFunction(node); } /** * Backward compatibility function for inclusion checking */ function shouldIncludeBasedOnClassification(classification) { const errorHandler = new errorHandler_1.ErrorHandler({ logErrors: false }); const classifier = new FunctionClassifier(errorHandler); return classifier.shouldIncludeBasedOnClassification(classification); }