sicua
Version:
A tool for analyzing project structure and dependencies
309 lines (308 loc) • 11 kB
JavaScript
;
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.replacer = void 0;
exports.isNodeCommented = isNodeCommented;
exports.isLikelyFrontendFunction = isLikelyFrontendFunction;
exports.isLikelyEventHandler = isLikelyEventHandler;
exports.extractDependencies = extractDependencies;
exports.extractCalledFunctions = extractCalledFunctions;
exports.hasJsxReturn = hasJsxReturn;
exports.safeNodeText = safeNodeText;
exports.serializeJsxElement = serializeJsxElement;
exports.isSimpleSetter = isSimpleSetter;
exports.usesReactHooks = usesReactHooks;
exports.usesFrontendAPIs = usesFrontendAPIs;
exports.usesThisKeyword = usesThisKeyword;
exports.hasReactSpecificOperations = hasReactSpecificOperations;
exports.hasReducerPattern = hasReducerPattern;
exports.hasStateSpreadPattern = hasStateSpreadPattern;
exports.generateComponentId = generateComponentId;
const typescript_1 = __importDefault(require("typescript"));
const path = __importStar(require("path"));
const reactOperations_1 = require("../../constants/reactOperations");
function isNodeCommented(sourceFile, node) {
const nodeStart = node.getFullStart();
const nodeEnd = node.getEnd();
const sourceText = sourceFile.getFullText();
const textBeforeNode = sourceText.substring(0, nodeStart);
const lastNewLineIndex = textBeforeNode.lastIndexOf("\n");
const lineStart = lastNewLineIndex === -1 ? 0 : lastNewLineIndex + 1;
if (textBeforeNode.substring(lineStart).trim().startsWith("//")) {
return true;
}
const textAroundNode = sourceText.substring(Math.max(0, nodeStart - 2), nodeEnd + 2);
if (textAroundNode.startsWith("/*") && textAroundNode.endsWith("*/")) {
return true;
}
return false;
}
function isLikelyFrontendFunction(fileName, functionName) {
const frontendIndicators = [
"component",
"page",
"view",
"screen",
"reducer",
"action",
"store",
"context",
"hook",
"provider",
"render",
"styles",
];
const lowerFileName = fileName.toLowerCase();
const lowerFunctionName = functionName.toLowerCase();
return frontendIndicators.some((indicator) => lowerFileName.includes(indicator) || lowerFunctionName.includes(indicator));
}
function isLikelyEventHandler(functionName) {
const eventHandlerPrefixes = ["handle", "on"];
const lowerFunctionName = functionName.toLowerCase();
return eventHandlerPrefixes.some((prefix) => lowerFunctionName.startsWith(prefix));
}
function extractDependencies(node) {
const dependencies = [];
function visit(n) {
if (typescript_1.default.isIdentifier(n) && !typescript_1.default.isPropertyAccessExpression(n.parent)) {
dependencies.push(n.getText());
}
typescript_1.default.forEachChild(n, visit);
}
visit(node);
return Array.from(new Set(dependencies));
}
function extractCalledFunctions(node) {
const calledFunctions = [];
function visit(n) {
if (typescript_1.default.isCallExpression(n) && typescript_1.default.isIdentifier(n.expression)) {
calledFunctions.push(n.expression.getText());
}
typescript_1.default.forEachChild(n, visit);
}
visit(node);
return Array.from(new Set(calledFunctions));
}
function hasJsxReturn(node) {
if (typescript_1.default.isJsxElement(node) ||
typescript_1.default.isJsxFragment(node) ||
typescript_1.default.isJsxSelfClosingElement(node)) {
return true;
}
if (typescript_1.default.isReturnStatement(node) && node.expression) {
return hasJsxReturn(node.expression);
}
return node.getChildren().some((child) => hasJsxReturn(child));
}
function safeNodeText(node) {
if (!node)
return "";
try {
return node.getText();
}
catch {
return "";
}
}
// Helper function to safely serialize JSX elements
function serializeJsxElement(node) {
try {
return node.getText();
}
catch {
return "";
}
}
const replacer = (key, value) => {
if (value instanceof Set) {
return Array.from(value);
}
// Handle TypeScript nodes
if (key === "element" && value && typeof value.getText === "function") {
return value.getText();
}
if (key === "condition" && value && typeof value.getText === "function") {
return value.getText();
}
return value;
};
exports.replacer = replacer;
function isSimpleSetter(node) {
if (!typescript_1.default.isArrowFunction(node)) {
return false;
}
try {
// Check if it's a simple setter pattern: (value) => set({ prop: value })
const body = node.body;
if (typescript_1.default.isCallExpression(body)) {
const callee = body.expression;
if (typescript_1.default.isIdentifier(callee) && callee.text === "set") {
return true;
}
}
// Check if it's a block with a single set statement
if (typescript_1.default.isBlock(body) && body.statements.length === 1) {
const statement = body.statements[0];
if (typescript_1.default.isExpressionStatement(statement)) {
const expr = statement.expression;
if (typescript_1.default.isCallExpression(expr)) {
const callee = expr.expression;
if (typescript_1.default.isIdentifier(callee) && callee.text === "set") {
return true;
}
}
}
}
return false;
}
catch (error) {
console.warn("Error in isSimpleSetter:", error);
return false;
}
}
function usesReactHooks(node) {
if (typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression)) {
const hookNames = [
"useState",
"useEffect",
"useContext",
"useReducer",
"useCallback",
"useMemo",
"useRef",
];
if (hookNames.includes(node.expression.text)) {
return true;
}
}
return node.getChildren().some((child) => usesReactHooks(child));
}
function usesFrontendAPIs(node) {
if (typescript_1.default.isPropertyAccessExpression(node)) {
const frontendAPIs = [
"localStorage",
"sessionStorage",
"document",
"window",
"navigator",
"history",
"location",
];
if (frontendAPIs.includes(node.expression.getText())) {
return true;
}
}
if (typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression)) {
const frontendFunctions = [
"alert",
"confirm",
"prompt",
"setTimeout",
"setInterval",
"requestAnimationFrame",
];
if (frontendFunctions.includes(node.expression.text)) {
return true;
}
}
return node.getChildren().some((child) => usesFrontendAPIs(child));
}
function usesThisKeyword(node) {
if (node.kind === typescript_1.default.SyntaxKind.ThisKeyword) {
return true;
}
return node.getChildren().some((child) => usesThisKeyword(child));
}
function hasReactSpecificOperations(node) {
try {
const nodeText = node.getText().toLowerCase();
// Early return for simple setters
if (isSimpleSetter(node)) {
return true;
}
// Get all identifiers in the node
const identifiers = [];
const collectIdentifiers = (n) => {
if (typescript_1.default.isIdentifier(n)) {
identifiers.push(n.text.toLowerCase());
}
typescript_1.default.forEachChild(n, collectIdentifiers);
};
collectIdentifiers(node);
return reactOperations_1.REACT_OPERATIONS.some((op) => identifiers.includes(op) || nodeText.includes(op));
}
catch (error) {
console.warn("Error in hasReactSpecificOperations:", error);
// Default to false if we can't determine
return false;
}
}
function hasReducerPattern(node) {
let hasSwitchOrIfOnActionType = false;
const checkActionType = (expr) => {
if (typescript_1.default.isPropertyAccessExpression(expr)) {
return (expr.name.text === "type" && expr.expression.getText() === "action");
}
return false;
};
typescript_1.default.forEachChild(node, (child) => {
if (typescript_1.default.isSwitchStatement(child) && checkActionType(child.expression)) {
hasSwitchOrIfOnActionType = true;
}
if (typescript_1.default.isIfStatement(child) &&
child.expression &&
checkActionType(child.expression)) {
hasSwitchOrIfOnActionType = true;
}
});
return hasSwitchOrIfOnActionType;
}
function hasStateSpreadPattern(node) {
let hasSpreadOperator = false;
let spreadsState = false;
const visitor = (n) => {
if (typescript_1.default.isSpreadElement(n)) {
hasSpreadOperator = true;
if (n.expression.getText() === "state") {
spreadsState = true;
}
}
if (!spreadsState) {
typescript_1.default.forEachChild(n, visitor);
}
};
typescript_1.default.forEachChild(node, visitor);
return hasSpreadOperator && spreadsState;
}
/**
* Generate unique component ID combining file path and component name
*/
function generateComponentId(component) {
const fileName = path.basename(component.fullPath, path.extname(component.fullPath));
return `${fileName}#${component.name}`;
}