UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

490 lines (487 loc) 19.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ASTUtils = void 0; const typescript_1 = __importDefault(require("typescript")); const reactSpecific_1 = require("./reactSpecific"); class ASTUtils { /** * Gets the name of a function-like declaration */ /* static getFunctionName(node: ts.Node | ts.FunctionDeclaration | ts.ArrowFunction): string {} */ static getFunctionName(node) { // Function declaration if (typescript_1.default.isFunctionDeclaration(node) && node.name) { return node.name.text; } // Variable declaration (includes arrow functions) else if (typescript_1.default.isVariableDeclaration(node) && typescript_1.default.isIdentifier(node.name)) { return node.name.text; } // Method declaration else if (typescript_1.default.isMethodDeclaration(node) && typescript_1.default.isIdentifier(node.name)) { return node.name.text; } // Arrow function cases if (typescript_1.default.isArrowFunction(node)) { let parent = node.parent; // Handle variable declarations if (typescript_1.default.isVariableDeclaration(parent) && typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } // Handle property assignments if (typescript_1.default.isPropertyAssignment(parent) && parent.name) { if (typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } if (typescript_1.default.isStringLiteral(parent.name)) { return parent.name.text; } } // Handle binary expressions (e.g., this.handler = () => {}) if (typescript_1.default.isBinaryExpression(parent) && parent.left) { if (typescript_1.default.isPropertyAccessExpression(parent.left)) { return parent.left.name.text; } if (typescript_1.default.isIdentifier(parent.left)) { return parent.left.text; } } // Handle export default arrow function if (typescript_1.default.isExportAssignment(parent) && parent.expression === node) { const sourceFile = node.getSourceFile(); const fileName = sourceFile.fileName; const baseName = fileName.split("/").pop()?.split(".")[0]; return baseName || "DefaultExport"; } // Handle object literal method shorthand if (typescript_1.default.isShorthandPropertyAssignment(parent)) { return parent.name.text; } } // Function expression else if (typescript_1.default.isFunctionExpression(node) && node.name) { return node.name.text; } // Property assignment (e.g., exports.Component = ...) else if (typescript_1.default.isPropertyAssignment(node) && typescript_1.default.isIdentifier(node.name)) { return node.name.text; } // Binary expression (e.g., module.exports = ...) else if (typescript_1.default.isBinaryExpression(node) && typescript_1.default.isPropertyAccessExpression(node.left)) { return node.left.name.text; } // Named exports (e.g., export const MyComponent = ...) else if (typescript_1.default.isExportDeclaration(node) && node.exportClause && typescript_1.default.isNamedExports(node.exportClause)) { const elements = node.exportClause.elements; if (elements.length === 1 && typescript_1.default.isExportSpecifier(elements[0])) { return elements[0].name.text; } } // Export assignment (e.g., export = MyComponent) else if (typescript_1.default.isExportAssignment(node) && typescript_1.default.isIdentifier(node.expression)) { return node.expression.text; } // Variable declaration within export declaration else if (typescript_1.default.isVariableStatement(node)) { const declarations = node.declarationList.declarations; if (declarations.length === 1 && typescript_1.default.isIdentifier(declarations[0].name)) { return declarations[0].name.text; } } // Handle nested function declarations in namespace/module else if (typescript_1.default.isModuleDeclaration(node)) { const body = node.body; if (body && typescript_1.default.isModuleBlock(body)) { for (const statement of body.statements) { const name = this.getFunctionName(statement); if (name) return name; } } } return "Anonymous Function"; } /** * Finds all nodes matching a specific predicate across all source files */ static findNodesInFiles(sourceFiles, predicate) { const results = new Map(); sourceFiles.forEach((sourceFile, filePath) => { const fileResults = []; function visit(node) { if (predicate(node)) { fileResults.push(node); } typescript_1.default.forEachChild(node, visit); } visit(sourceFile); if (fileResults.length > 0) { results.set(filePath, fileResults); } }); return results; } /** * Finds all nodes matching a predicate in a single file */ static findNodes(sourceFile, predicate) { const results = []; function visit(node) { if (predicate(node)) { results.push(node); } typescript_1.default.forEachChild(node, visit); } visit(sourceFile); return results; } /** * Gets the location info for a node with source file context */ static getNodeLocation(node, sourceFile) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); return { line, column: character }; } /** * Gets the source file for a node */ static getSourceFileForNode(node, sourceFiles) { const fileName = node.getSourceFile()?.fileName; return fileName ? sourceFiles.get(fileName) : undefined; } /** * Builds a path to a node from its ancestors */ static getNodePath(node) { const path = []; let current = node; while (current) { path.unshift(current); current = current.parent; } return path; } /** * Gets the nearest parent matching a predicate */ static findNearestParent(node, predicate) { let current = node.parent; while (current) { if (predicate(current)) { return current; } current = current.parent; } return undefined; } /** * Finds all references to an identifier across all files */ static findReferencesInFiles(sourceFiles, identifier) { const results = new Map(); sourceFiles.forEach((sourceFile, filePath) => { const fileResults = this.findReferences(sourceFile, identifier); if (fileResults.length > 0) { results.set(filePath, fileResults); } }); return results; } /** * Finds all references to an identifier in a single file */ static findReferences(sourceFile, identifier) { return this.findNodes(sourceFile, typescript_1.default.isIdentifier).filter((id) => id.text === identifier); } /** * Gets all containing conditions for a node */ /* static findContainingConditions(node: ts.Node): string[] { const conditions: string[] = []; let current: ts.Node | undefined = node; while (current) { if (ts.isIfStatement(current)) { conditions.push(current.expression.getText()); } else if (ts.isConditionalExpression(current)) { conditions.push(current.condition.getText()); } else if ( ts.isBinaryExpression(current) && (current.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || current.operatorToken.kind === ts.SyntaxKind.BarBarToken) ) { conditions.push(current.getText()); } current = current.parent; } return conditions; } */ static findContainingConditions(node) { const conditions = []; let current = node; while (current) { // If statement conditions if (typescript_1.default.isIfStatement(current)) { conditions.push(current.expression.getText()); } // Ternary expressions else if (typescript_1.default.isConditionalExpression(current)) { conditions.push(current.condition.getText()); } // Logical expressions (&& and ||) else if (typescript_1.default.isBinaryExpression(current)) { if (current.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken || current.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken || current.operatorToken.kind === typescript_1.default.SyntaxKind.QuestionQuestionToken) { conditions.push(current.getText()); } } // JSX conditional rendering else if (typescript_1.default.isJsxElement(current) || typescript_1.default.isJsxSelfClosingElement(current)) { const conditionalAttrs = this.findJsxConditionalAttributes(current); conditions.push(...conditionalAttrs); } // Handle switch statements else if (typescript_1.default.isCaseClause(current) || typescript_1.default.isDefaultClause(current)) { const switchStmt = current.parent.parent; if (typescript_1.default.isSwitchStatement(switchStmt)) { conditions.push(`${switchStmt.expression.getText()} === ${current.getText()}`); } } current = current.parent; } return [...new Set(conditions)]; // Remove duplicates } static findJsxConditionalAttributes(node) { const conditions = []; // Get attributes based on node type const attributes = typescript_1.default.isJsxElement(node) ? node.openingElement.attributes : node.attributes; // Process JSX attributes attributes.properties.forEach((prop) => { if (typescript_1.default.isJsxAttribute(prop)) { // Handle conditional rendering props if (prop.name.getText() === "hidden" || prop.name.getText() === "disabled" || prop.name.getText() === "show" || prop.name.getText() === "when" || prop.name.getText() === "if") { if (prop.initializer && typescript_1.default.isJsxExpression(prop.initializer) && prop.initializer.expression) { conditions.push(prop.initializer.expression.getText()); } } // Handle render props with conditions if (prop.name.getText().startsWith("render") && prop.initializer) { if (typescript_1.default.isJsxExpression(prop.initializer) && prop.initializer.expression) { const expression = prop.initializer.expression; if (typescript_1.default.isArrowFunction(expression) || typescript_1.default.isFunctionExpression(expression)) { // Look for conditions within the render prop function this.findConditionsInFunction(expression).forEach((cond) => conditions.push(cond)); } } } } }); return conditions; } static findConditionsInFunction(node) { const conditions = []; const visitor = (node) => { if (typescript_1.default.isIfStatement(node)) { conditions.push(node.expression.getText()); } else if (typescript_1.default.isConditionalExpression(node)) { conditions.push(node.condition.getText()); } else if (typescript_1.default.isBinaryExpression(node) && (node.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken || node.operatorToken.kind === typescript_1.default.SyntaxKind.QuestionQuestionToken)) { conditions.push(node.getText()); } typescript_1.default.forEachChild(node, visitor); }; typescript_1.default.forEachChild(node, visitor); return conditions; } /** * Gets all identifiers used in a node */ static findIdentifiersInFiles(sourceFiles) { const results = new Map(); sourceFiles.forEach((sourceFile, filePath) => { const fileResults = this.findNodes(sourceFile, typescript_1.default.isIdentifier); if (fileResults.length > 0) { results.set(filePath, fileResults); } }); return results; } /** * Gets the containing function-like declaration */ static getContainingFunction(node) { return this.findNearestParent(node, (n) => typescript_1.default.isFunctionLike(n)); } /** * Gets the containing block scope */ static getContainingBlock(node) { return this.findNearestParent(node, typescript_1.default.isBlock); } /** * Checks if a node represents a pure expression */ static isPure(node) { if (typescript_1.default.isLiteralExpression(node)) return true; if (typescript_1.default.isIdentifier(node)) return true; if (typescript_1.default.isArrayLiteralExpression(node)) { return node.elements.every((e) => typescript_1.default.isExpression(e) && this.isPure(e)); } if (typescript_1.default.isObjectLiteralExpression(node)) { return node.properties.every((p) => typescript_1.default.isPropertyAssignment(p) && this.isPure(p.initializer)); } return false; } /** * Finds nodes across multiple files with source file context */ static findNodesWithContext(sourceFiles, predicate) { const results = []; sourceFiles.forEach((sourceFile, filePath) => { function visit(node) { if (predicate(node)) { results.push({ node, sourceFile, filePath }); } typescript_1.default.forEachChild(node, visit); } visit(sourceFile); }); return results; } static isErrorCreation(node) { if (typescript_1.default.isNewExpression(node)) { const className = node.expression.getText(); return className.includes("Error"); } return false; } static isPromiseRejection(node) { if (typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression)) { const name = node.expression.text; return name === "reject" || name === "Promise.reject"; } return false; } static isInsideCatchClause(node) { let current = node.parent; while (current) { if (typescript_1.default.isCatchClause(current)) { return true; } current = current.parent; } return false; } static isCustomErrorClass(node) { if (typescript_1.default.isClassDeclaration(node)) { const heritage = node.heritageClauses?.some((clause) => clause.types.some((type) => type.expression.getText().includes("Error"))); return !!heritage; } return false; } static getCustomErrorClassName(node) { return node.name?.text; } static getFunctionNameFromNode(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)) { const parent = node.parent; if (typescript_1.default.isVariableDeclaration(parent) && typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } } return "anonymous"; } static safeGetNodeText(node) { try { return node?.getText() ?? ""; } catch { return ""; } } static safeIsArrayBindingPattern(node) { try { return !!node && typescript_1.default.isArrayBindingPattern(node); } catch { return false; } } static isTestFile(node) { const sourceFile = node.getSourceFile(); const fileName = sourceFile.fileName.toLowerCase(); return (fileName.includes(".test.") || fileName.includes(".spec.") || fileName.includes("__tests__")); } static isHook(node) { return (typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression) && node.expression.text.startsWith("use") && node.expression.text !== "useState" && node.expression.text !== "useEffect"); } static getHookName(node) { if (typescript_1.default.isIdentifier(node.expression)) { return node.expression.text; } return "anonymous-hook"; } static isAnalyzableFunction(node, typeChecker // Add typeChecker parameter ) { return ((typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isArrowFunction(node)) && !(0, reactSpecific_1.isReactComponent)(node, typeChecker) && // Pass typeChecker !this.isTestFile(node)); } static isEventHandler(node) { return ((typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isPropertyDeclaration(node)) && node.name && typescript_1.default.isIdentifier(node.name) && (node.name.text.startsWith("handle") || node.name.text.startsWith("on"))); } static isPromiseRelated(node) { if (typescript_1.default.isCallExpression(node)) { const text = node.expression.getText(); return (text.includes("Promise") || text.includes("fetch") || text.includes("axios") || text.includes("request") || text.includes("query")); } return false; } } exports.ASTUtils = ASTUtils;