sicua
Version:
A tool for analyzing project structure and dependencies
548 lines (547 loc) • 20.7 kB
JavaScript
"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseJSXProps = parseJSXProps;
exports.extractExpressionString = extractExpressionString;
exports.getCodePosition = getCodePosition;
exports.nodeContainsJSX = nodeContainsJSX;
exports.extractComponentReferencesFromNode = extractComponentReferencesFromNode;
exports.extractHTMLElementReferencesFromNode = extractHTMLElementReferencesFromNode;
exports.parseHTMLElement = parseHTMLElement;
exports.extractTextContentFromJSX = extractTextContentFromJSX;
exports.shouldIncludeHTMLTag = shouldIncludeHTMLTag;
exports.parseJSXElement = parseJSXElement;
exports.extractComponentReferencesFromStatement = extractComponentReferencesFromStatement;
exports.extractHTMLElementReferencesFromStatement = extractHTMLElementReferencesFromStatement;
exports.isPageFile = isPageFile;
exports.isDynamicSegment = isDynamicSegment;
exports.isCatchAllSegment = isCatchAllSegment;
exports.isRouteGroup = isRouteGroup;
exports.parseRoutePath = parseRoutePath;
exports.matchesPattern = matchesPattern;
exports.parseFileToAST = parseFileToAST;
exports.extractDefaultExportName = extractDefaultExportName;
exports.checkMultipleReturns = checkMultipleReturns;
const t = __importStar(require("@babel/types"));
const reactSpecific_1 = require("../../../utils/ast/reactSpecific");
/**
* Utility functions for component flow analysis
*/
/**
* Parses JSX props into prop references
*/
function parseJSXProps(attributes, sourceCode) {
const props = [];
for (const attr of attributes) {
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
const name = attr.name.name;
let value = "";
let isDynamic = false;
if (attr.value) {
if (t.isStringLiteral(attr.value)) {
value = attr.value.value;
}
else if (t.isJSXExpressionContainer(attr.value)) {
if (!t.isJSXEmptyExpression(attr.value.expression)) {
value = extractExpressionString(attr.value.expression, sourceCode);
isDynamic = true;
}
}
}
props.push({ name, value, isDynamic });
}
}
return props;
}
/**
* Extracts string representation of expressions
*/
function extractExpressionString(expression, sourceCode) {
if (expression.start !== null && expression.end !== null) {
return sourceCode.slice(expression.start, expression.end);
}
if (t.isIdentifier(expression)) {
return expression.name;
}
else if (t.isBooleanLiteral(expression)) {
return String(expression.value);
}
else if (t.isNumericLiteral(expression)) {
return String(expression.value);
}
else if (t.isStringLiteral(expression)) {
return `"${expression.value}"`;
}
return "unknown";
}
/**
* Gets code position from node
*/
function getCodePosition(node) {
return {
line: node.loc?.start.line || 0,
column: node.loc?.start.column || 0,
startOffset: node.start || 0,
endOffset: node.end || 0,
};
}
/**
* Checks if a node contains JSX elements
*/
function nodeContainsJSX(node) {
if (t.isJSXElement(node) || t.isJSXFragment(node)) {
return true;
}
if (t.isConditionalExpression(node)) {
return nodeContainsJSX(node.consequent) || nodeContainsJSX(node.alternate);
}
if (t.isLogicalExpression(node)) {
return nodeContainsJSX(node.right) || nodeContainsJSX(node.left);
}
if (t.isArrayExpression(node)) {
return node.elements.some((element) => element && nodeContainsJSX(element));
}
if (t.isCallExpression(node)) {
// Function calls might return JSX - assume they do for now
return true;
}
return false;
}
/**
* Extracts component references from various node types - ENHANCED VERSION
*/
function extractComponentReferencesFromNode(node, sourceCode) {
const references = [];
if (t.isJSXElement(node)) {
// Only include React components (uppercase), not HTML elements
if ((0, reactSpecific_1.isReactComponentElement)(node)) {
references.push(parseJSXElement(node, sourceCode));
}
// Also analyze children for nested components
for (const child of node.children) {
if (t.isJSXElement(child)) {
references.push(...extractComponentReferencesFromNode(child, sourceCode));
}
else if (t.isJSXExpressionContainer(child) &&
!t.isJSXEmptyExpression(child.expression)) {
references.push(...extractComponentReferencesFromNode(child.expression, sourceCode));
}
}
}
else if (t.isJSXFragment(node)) {
for (const child of node.children) {
if (t.isJSXElement(child)) {
references.push(...extractComponentReferencesFromNode(child, sourceCode));
}
else if (t.isJSXExpressionContainer(child) &&
!t.isJSXEmptyExpression(child.expression)) {
references.push(...extractComponentReferencesFromNode(child.expression, sourceCode));
}
}
}
else if (t.isConditionalExpression(node)) {
// Handle ternary operators: condition ? ComponentA : ComponentB
references.push(...extractComponentReferencesFromNode(node.consequent, sourceCode));
references.push(...extractComponentReferencesFromNode(node.alternate, sourceCode));
}
else if (t.isLogicalExpression(node)) {
// Handle logical expressions: condition && Component or condition || Component
if (node.operator === "&&") {
references.push(...extractComponentReferencesFromNode(node.right, sourceCode));
}
else if (node.operator === "||") {
references.push(...extractComponentReferencesFromNode(node.left, sourceCode));
references.push(...extractComponentReferencesFromNode(node.right, sourceCode));
}
}
else if (t.isCallExpression(node)) {
// Handle function calls that might return JSX components
if (t.isIdentifier(node.callee)) {
references.push({
name: node.callee.name,
isJSXElement: false,
props: [],
position: getCodePosition(node),
});
}
else if (t.isMemberExpression(node.callee)) {
// Handle member expressions like React.createElement or obj.method()
const memberName = extractMemberExpressionName(node.callee);
if (memberName) {
references.push({
name: memberName,
isJSXElement: false,
props: [],
position: getCodePosition(node),
});
}
}
}
else if (t.isArrayExpression(node)) {
// Handle arrays of JSX elements (like map results)
for (const element of node.elements) {
if (element) {
references.push(...extractComponentReferencesFromNode(element, sourceCode));
}
}
}
else if (t.isIdentifier(node)) {
// Handle bare identifiers that might be component references
// Only include if it's capitalized (component convention)
if (node.name.charAt(0) === node.name.charAt(0).toUpperCase()) {
references.push({
name: node.name,
isJSXElement: false,
props: [],
position: getCodePosition(node),
});
}
}
else if (t.isMemberExpression(node)) {
// Handle member expressions like React.Component
const memberName = extractMemberExpressionName(node);
if (memberName &&
memberName.charAt(0) === memberName.charAt(0).toUpperCase()) {
references.push({
name: memberName,
isJSXElement: false,
props: [],
position: getCodePosition(node),
});
}
}
return references;
}
/**
* NEW: Extracts HTML element references from various node types
*/
function extractHTMLElementReferencesFromNode(node, sourceCode, includeTags = [], excludeTags = [], includeAll = false) {
const references = [];
if (t.isJSXElement(node)) {
const elementName = (0, reactSpecific_1.getJSXElementName)(node.openingElement.name);
// Only include HTML elements (lowercase), not React components
if (!(0, reactSpecific_1.isReactComponentElement)(node) &&
shouldIncludeHTMLTag(elementName, includeTags, excludeTags, includeAll)) {
references.push(parseHTMLElement(node, sourceCode));
}
// Also analyze children for nested HTML elements
for (const child of node.children) {
if (t.isJSXElement(child)) {
references.push(...extractHTMLElementReferencesFromNode(child, sourceCode, includeTags, excludeTags, includeAll));
}
else if (t.isJSXExpressionContainer(child) &&
!t.isJSXEmptyExpression(child.expression)) {
references.push(...extractHTMLElementReferencesFromNode(child.expression, sourceCode, includeTags, excludeTags, includeAll));
}
}
}
else if (t.isJSXFragment(node)) {
for (const child of node.children) {
if (t.isJSXElement(child)) {
references.push(...extractHTMLElementReferencesFromNode(child, sourceCode, includeTags, excludeTags, includeAll));
}
else if (t.isJSXExpressionContainer(child) &&
!t.isJSXEmptyExpression(child.expression)) {
references.push(...extractHTMLElementReferencesFromNode(child.expression, sourceCode, includeTags, excludeTags, includeAll));
}
}
}
else if (t.isConditionalExpression(node)) {
// Handle ternary operators: condition ? <div> : <span>
references.push(...extractHTMLElementReferencesFromNode(node.consequent, sourceCode, includeTags, excludeTags, includeAll));
references.push(...extractHTMLElementReferencesFromNode(node.alternate, sourceCode, includeTags, excludeTags, includeAll));
}
else if (t.isLogicalExpression(node)) {
// Handle logical expressions: condition && <div> or condition || <span>
if (node.operator === "&&") {
references.push(...extractHTMLElementReferencesFromNode(node.right, sourceCode, includeTags, excludeTags, includeAll));
}
else if (node.operator === "||") {
references.push(...extractHTMLElementReferencesFromNode(node.left, sourceCode, includeTags, excludeTags, includeAll));
references.push(...extractHTMLElementReferencesFromNode(node.right, sourceCode, includeTags, excludeTags, includeAll));
}
}
else if (t.isArrayExpression(node)) {
// Handle arrays of JSX elements (like map results)
for (const element of node.elements) {
if (element) {
references.push(...extractHTMLElementReferencesFromNode(element, sourceCode, includeTags, excludeTags, includeAll));
}
}
}
return references;
}
/**
* NEW: Parses HTML element into HTMLElementReference
*/
function parseHTMLElement(element, sourceCode, captureTextContent = true, maxTextLength = 100) {
const tagName = (0, reactSpecific_1.getJSXElementName)(element.openingElement.name);
const props = parseJSXProps(element.openingElement.attributes, sourceCode);
const hasChildren = element.children.length > 0;
// Extract text content if enabled and element has text children
let textContent;
if (captureTextContent && hasChildren) {
textContent = extractTextContentFromJSX(element, sourceCode);
// Truncate if too long
if (textContent && textContent.length > maxTextLength) {
textContent = textContent.substring(0, maxTextLength) + "...";
}
}
return {
tagName,
props,
hasChildren,
textContent,
position: getCodePosition(element),
};
}
/**
* NEW: Extracts text content from JSX element
*/
function extractTextContentFromJSX(element, sourceCode) {
const textParts = [];
for (const child of element.children) {
if (t.isJSXText(child)) {
// Direct text content
const trimmedText = child.value.trim();
if (trimmedText.length > 0) {
textParts.push(trimmedText);
}
}
else if (t.isJSXExpressionContainer(child)) {
if (t.isStringLiteral(child.expression)) {
// String literals in expressions
textParts.push(child.expression.value);
}
else if (t.isTemplateLiteral(child.expression)) {
// Template literals
textParts.push("[template]"); // Placeholder for template literals
}
else if (t.isIdentifier(child.expression)) {
// Variable references
textParts.push(`{${child.expression.name}}`);
}
else {
// Other expressions
textParts.push("{expr}");
}
}
// Could extend to handle more complex nested structures
}
const fullText = textParts.join(" ").trim();
return fullText.length > 0 ? fullText : undefined;
}
/**
* NEW: Determines if an HTML tag should be included based on filter criteria
*/
function shouldIncludeHTMLTag(tagName, includeTags, excludeTags, includeAll) {
// Check exclude list first
if (excludeTags.includes(tagName)) {
return false;
}
// If includeAll is true, include everything not excluded
if (includeAll) {
return true;
}
// Otherwise, only include if in the include list
return includeTags.includes(tagName);
}
/**
* Helper function to extract name from member expressions
*/
function extractMemberExpressionName(memberExpr) {
if (t.isIdentifier(memberExpr.object) &&
t.isIdentifier(memberExpr.property)) {
return `${memberExpr.object.name}.${memberExpr.property.name}`;
}
else if (t.isIdentifier(memberExpr.property)) {
return memberExpr.property.name;
}
return null;
}
/**
* Parses JSX element into component reference
*/
function parseJSXElement(element, sourceCode) {
const name = (0, reactSpecific_1.getJSXElementName)(element.openingElement.name);
const props = parseJSXProps(element.openingElement.attributes, sourceCode);
return {
name,
isJSXElement: true,
props,
position: getCodePosition(element),
};
}
/**
* Extracts component references from statements
*/
function extractComponentReferencesFromStatement(statement, sourceCode) {
if (t.isReturnStatement(statement) && statement.argument) {
return extractComponentReferencesFromNode(statement.argument, sourceCode);
}
else if (t.isExpressionStatement(statement)) {
return extractComponentReferencesFromNode(statement.expression, sourceCode);
}
else if (t.isBlockStatement(statement)) {
const references = [];
for (const stmt of statement.body) {
references.push(...extractComponentReferencesFromStatement(stmt, sourceCode));
}
return references;
}
return [];
}
/**
* NEW: Extracts HTML element references from statements
*/
function extractHTMLElementReferencesFromStatement(statement, sourceCode, includeTags = [], excludeTags = [], includeAll = false) {
if (t.isReturnStatement(statement) && statement.argument) {
return extractHTMLElementReferencesFromNode(statement.argument, sourceCode, includeTags, excludeTags, includeAll);
}
else if (t.isExpressionStatement(statement)) {
return extractHTMLElementReferencesFromNode(statement.expression, sourceCode, includeTags, excludeTags, includeAll);
}
else if (t.isBlockStatement(statement)) {
const references = [];
for (const stmt of statement.body) {
references.push(...extractHTMLElementReferencesFromStatement(stmt, sourceCode, includeTags, excludeTags, includeAll));
}
return references;
}
return [];
}
/**
* Checks if a filename is a page file
*/
function isPageFile(fileName) {
const nameWithoutExt = fileName.replace(/\.(js|jsx|ts|tsx)$/, "");
return nameWithoutExt === "page";
}
/**
* Checks if a segment is dynamic ([param])
*/
function isDynamicSegment(segment) {
return (segment.startsWith("[") &&
segment.endsWith("]") &&
!segment.startsWith("[..."));
}
/**
* Checks if a segment is catch-all ([...param])
*/
function isCatchAllSegment(segment) {
return segment.startsWith("[...") && segment.endsWith("]");
}
/**
* Checks if a segment is a route group ((group))
*/
function isRouteGroup(segment) {
return segment.startsWith("(") && segment.endsWith(")");
}
/**
* Parses route path into segments
*/
function parseRoutePath(routePath) {
const cleanPath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
if (!cleanPath) {
return [];
}
return cleanPath.split("/").filter((segment) => segment.length > 0);
}
/**
* Simple pattern matching for route filtering
*/
function matchesPattern(filePath, pattern) {
// Convert glob-like pattern to regex
const regexPattern = pattern
.replace(/\*\*/g, ".*")
.replace(/\*/g, "[^/]*")
.replace(/\./g, "\\.");
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(filePath);
}
/**
* Parses file content to AST
*/
function parseFileToAST(content) {
try {
const { parse } = require("@babel/parser");
return parse(content, {
sourceType: "module",
plugins: [
"jsx",
"typescript",
"decorators",
"classProperties",
"exportDefaultFrom",
"exportNamespaceFrom",
"dynamicImport",
],
errorRecovery: true,
});
}
catch (error) {
console.warn("Error parsing AST:", error);
return null;
}
}
/**
* Extracts default export name from AST
*/
function extractDefaultExportName(ast) {
let exportName = null;
const traverse = require("@babel/traverse").default;
traverse(ast, {
ExportDefaultDeclaration(path) {
const declaration = path.node.declaration;
if (t.isIdentifier(declaration)) {
exportName = declaration.name;
}
else if (t.isFunctionDeclaration(declaration) && declaration.id) {
exportName = declaration.id.name;
}
else if (t.isArrowFunctionExpression(declaration)) {
// For arrow functions, we'll use the variable name if it's assigned
const parent = path.parent;
if (t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)) {
exportName = parent.id.name;
}
}
},
});
return exportName;
}
/**
* Checks if a component has multiple return statements
*/
function checkMultipleReturns(ast) {
let returnCount = 0;
const traverse = require("@babel/traverse").default;
traverse(ast, {
ReturnStatement() {
returnCount++;
},
});
return returnCount > 1;
}