sicua
Version:
A tool for analyzing project structure and dependencies
332 lines (331 loc) • 9.68 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.traverseAST = traverseAST;
exports.visitEachChild = visitEachChild;
exports.walkDescendants = walkDescendants;
exports.traverseWithContext = traverseWithContext;
exports.visitNodesOfKind = visitNodesOfKind;
exports.traverseDepthFirst = traverseDepthFirst;
exports.traverseBreadthFirst = traverseBreadthFirst;
exports.visitWithCallback = visitWithCallback;
exports.getNodePath = getNodePath;
exports.getContainingFunction = getContainingFunction;
exports.getContainingBlock = getContainingBlock;
exports.getSourceFileForNode = getSourceFileForNode;
exports.getParentOfKind = getParentOfKind;
exports.getAncestor = getAncestor;
exports.navigateToRoot = navigateToRoot;
exports.findSiblings = findSiblings;
exports.findNodes = findNodes;
exports.findNodesInFiles = findNodesInFiles;
exports.findNearestParent = findNearestParent;
exports.findReferences = findReferences;
exports.findReferencesInFiles = findReferencesInFiles;
exports.findNodesWithContext = findNodesWithContext;
exports.findComponentNode = findComponentNode;
exports.findIdentifiersInFiles = findIdentifiersInFiles;
const typescript_1 = __importDefault(require("typescript"));
const reactSpecific_1 = require("./reactSpecific");
/**
* Traverses AST nodes depth-first
*/
function traverseAST(node, callback) {
callback(node);
typescript_1.default.forEachChild(node, (child) => traverseAST(child, callback));
}
/**
* Visits each child of a node
*/
function visitEachChild(node, visitor) {
typescript_1.default.forEachChild(node, visitor);
}
/**
* Walks all descendant nodes
*/
function walkDescendants(node, predicate) {
const results = [];
const walk = (current) => {
if (!predicate || predicate(current)) {
results.push(current);
}
typescript_1.default.forEachChild(current, walk);
};
typescript_1.default.forEachChild(node, walk);
return results;
}
/**
* Traverses with context information
*/
function traverseWithContext(node, visitor, context) {
visitor(node, context);
typescript_1.default.forEachChild(node, (child) => traverseWithContext(child, visitor, context));
}
/**
* Visits nodes of specific kind
*/
function visitNodesOfKind(node, kind, visitor) {
if (node.kind === kind) {
visitor(node);
}
typescript_1.default.forEachChild(node, (child) => visitNodesOfKind(child, kind, visitor));
}
/**
* Traverses depth-first with early exit
*/
function traverseDepthFirst(node, visitor) {
const shouldContinue = visitor(node);
if (shouldContinue === false) {
return false;
}
let continueTraversal = true;
typescript_1.default.forEachChild(node, (child) => {
if (continueTraversal) {
continueTraversal = traverseDepthFirst(child, visitor) !== false;
}
});
return continueTraversal;
}
/**
* Traverses breadth-first
*/
function traverseBreadthFirst(node, visitor) {
const queue = [node];
while (queue.length > 0) {
const current = queue.shift();
visitor(current);
typescript_1.default.forEachChild(current, (child) => queue.push(child));
}
}
/**
* Visits with callback and collects results
*/
function visitWithCallback(node, callback) {
const results = [];
const visit = (current) => {
const result = callback(current);
if (result !== undefined) {
results.push(result);
}
typescript_1.default.forEachChild(current, visit);
};
visit(node);
return results;
}
/**
* Builds a path to a node from its ancestors
*/
function getNodePath(node) {
const path = [];
let current = node;
while (current) {
path.unshift(current);
current = current.parent;
}
return path;
}
/**
* Gets the containing function-like declaration
*/
function getContainingFunction(node) {
return findNearestParent(node, (n) => typescript_1.default.isFunctionLike(n));
}
/**
* Gets the containing block scope
*/
function getContainingBlock(node) {
return findNearestParent(node, typescript_1.default.isBlock);
}
/**
* Gets the source file for a node
*/
function getSourceFileForNode(node, sourceFiles) {
const fileName = node.getSourceFile()?.fileName;
return fileName ? sourceFiles.get(fileName) : undefined;
}
/**
* Gets the parent of a specific kind
*/
function getParentOfKind(node, kind) {
let current = node.parent;
while (current) {
if (current.kind === kind) {
return current;
}
current = current.parent;
}
return undefined;
}
/**
* Gets ancestor at a specific level
*/
function getAncestor(node, level) {
let current = node;
let currentLevel = 0;
while (current && currentLevel < level) {
current = current.parent;
currentLevel++;
}
return current;
}
/**
* Navigates to the root node (SourceFile)
*/
function navigateToRoot(node) {
let current = node;
while (current.parent) {
current = current.parent;
}
return current;
}
/**
* Finds sibling nodes
*/
function findSiblings(node) {
const parent = node.parent;
if (!parent)
return [];
const siblings = [];
typescript_1.default.forEachChild(parent, (child) => {
if (child !== node) {
siblings.push(child);
}
});
return siblings;
}
/**
* Finds all nodes matching a predicate in a single file
*/
function findNodes(sourceFile, predicate) {
const results = [];
function visit(node) {
if (predicate(node)) {
results.push(node);
}
typescript_1.default.forEachChild(node, visit);
}
visit(sourceFile);
return results;
}
/**
* Finds all nodes matching a specific predicate across all source files
*/
function 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;
}
/**
* Gets the nearest parent matching a predicate
*/
function 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 in a single file
*/
function findReferences(sourceFile, identifier) {
return findNodes(sourceFile, typescript_1.default.isIdentifier).filter((id) => id.text === identifier);
}
/**
* Finds all references to an identifier across all files
*/
function findReferencesInFiles(sourceFiles, identifier) {
const results = new Map();
sourceFiles.forEach((sourceFile, filePath) => {
const fileResults = findReferences(sourceFile, identifier);
if (fileResults.length > 0) {
results.set(filePath, fileResults);
}
});
return results;
}
/**
* Finds nodes across multiple files with source file context
*/
function 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;
}
/**
* Finds a component node in the source file by name
*/
function findComponentNode(node, componentName, typeChecker) {
if (typescript_1.default.isFunctionDeclaration(node)) {
// Function declaration component
if (node.name &&
node.name.text === componentName &&
(0, reactSpecific_1.isReactComponent)(node, typeChecker)) {
return node;
}
}
else if (typescript_1.default.isVariableDeclaration(node)) {
// Variable declaration (including arrow functions)
if (typescript_1.default.isIdentifier(node.name) && node.name.text === componentName) {
if (node.initializer) {
if (typescript_1.default.isArrowFunction(node.initializer) ||
typescript_1.default.isFunctionExpression(node.initializer)) {
return node.initializer;
}
}
return node;
}
}
else if (typescript_1.default.isExportAssignment(node)) {
// Export default
if (typescript_1.default.isIdentifier(node.expression) &&
node.expression.text === componentName) {
return node.expression;
}
}
let result;
typescript_1.default.forEachChild(node, (child) => {
if (!result) {
result = findComponentNode(child, componentName, typeChecker);
}
});
return result;
}
/**
* Gets all identifiers used in a node
*/
function findIdentifiersInFiles(sourceFiles) {
const results = new Map();
sourceFiles.forEach((sourceFile, filePath) => {
const fileResults = findNodes(sourceFile, typescript_1.default.isIdentifier);
if (fileResults.length > 0) {
results.set(filePath, fileResults);
}
});
return results;
}