sicua
Version:
A tool for analyzing project structure and dependencies
370 lines (369 loc) • 14.6 kB
JavaScript
"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);
}