sicua
Version:
A tool for analyzing project structure and dependencies
198 lines (197 loc) • 8.13 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextExtractor = void 0;
const typescript_1 = __importDefault(require("typescript"));
const astUtils_1 = require("../utils/astUtils");
/**
* Extracts enhanced context information for translation keys
*/
class ContextExtractor {
/**
* Extracts code context for a call expression
* @param call The call expression node
* @param sourceFile The source file
* @returns Code context object
*/
extractContextForCall(call, sourceFile) {
return (0, astUtils_1.getContextCode)(call, sourceFile);
}
/**
* Enhances a translation key with additional context information
* @param key Translation key to enhance
* @param sourceFile Source file containing the key
* @returns Enhanced translation key with usage context
*/
enhanceKeyWithContext(key, sourceFile) {
// Make sure we have context code
const contextCode = key.contextCode ||
this.extractCodeLinesAround(sourceFile, key.location.line);
// Extract additional usage context
const usageContext = this.extractUsageContext(key, sourceFile);
// Return the enhanced key
return {
...key,
contextCode,
usageContext,
};
}
/**
* Extracts code lines around a specific line
* @param sourceFile Source file
* @param lineNumber Line number (1-based)
* @returns Object with before, current, and after lines
*/
extractCodeLinesAround(sourceFile, lineNumber) {
const fileLines = sourceFile.text.split("\n");
// Transform from 0-based (TypeScript API) to 1-based (our line numbers)
// Since we're already receiving a 1-based line number, we don't need to adjust it
const lineIndex = lineNumber - 1; // Convert to 0-based index for array access
const beforeLine = lineIndex > 0 ? fileLines[lineIndex - 1].trim() : "";
const currentLine = fileLines[lineIndex].trim();
const afterLine = lineIndex < fileLines.length - 1 ? fileLines[lineIndex + 1].trim() : "";
return {
before: beforeLine,
line: currentLine,
after: afterLine,
};
}
/**
* Extracts detailed usage context for a translation key
* @param translationKey Translation key
* @param sourceFile Source file
* @returns Enhanced usage context information
*/
extractUsageContext(translationKey, sourceFile) {
let isInJSX = false;
let isInConditional = false;
let isInEventHandler = false;
let parentComponent;
let renderCount = 0;
// Find the call expression for this key
const callNode = this.findCallNode(translationKey, sourceFile);
if (callNode) {
// Analyze the call node's context
const context = this.analyzeNodeContext(callNode, sourceFile);
isInJSX = context.isInJSX;
isInConditional = context.isInConditional;
isInEventHandler = context.isInEventHandler;
parentComponent = context.parentComponent;
renderCount = context.renderCount;
}
return {
isInJSX,
isInConditional,
parentComponent,
isInEventHandler,
renderCount,
};
}
/**
* Finds the call expression node for a translation key
* @param translationKey Translation key
* @param sourceFile Source file
* @returns Call expression node if found
*/
findCallNode(translationKey, sourceFile) {
const findNode = (node) => {
if (typescript_1.default.isCallExpression(node)) {
const { line, character } = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, node.getStart());
// Check if this is the node for our translation key
// Compare both line and column position for accuracy
if (line + 1 === translationKey.location.line &&
character + 1 === translationKey.location.column &&
typescript_1.default.isIdentifier(node.expression) &&
node.arguments.length > 0 &&
typescript_1.default.isStringLiteral(node.arguments[0]) &&
node.arguments[0].text === translationKey.key) {
return node;
}
}
for (const child of node.getChildren(sourceFile)) {
const result = findNode(child);
if (result)
return result;
}
return undefined;
};
return findNode(sourceFile);
}
/**
* Analyzes the context of a node
* @param node Node to analyze
* @param sourceFile Source file
* @returns Context analysis
*/
analyzeNodeContext(node, sourceFile) {
let isInJSX = false;
let isInConditional = false;
let isInEventHandler = false;
let parentComponent;
let renderCount = 0;
// Check if the node is inside JSX
let parent = node.parent;
while (parent) {
// Check for JSX
if (typescript_1.default.isJsxExpression(parent)) {
isInJSX = true;
}
// Check for conditional rendering
if (typescript_1.default.isConditionalExpression(parent) ||
typescript_1.default.isIfStatement(parent) ||
(typescript_1.default.isBinaryExpression(parent) &&
(parent.operatorToken.kind ===
typescript_1.default.SyntaxKind.AmpersandAmpersandToken ||
parent.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken))) {
isInConditional = true;
}
// Check for event handlers
if (typescript_1.default.isJsxAttribute(parent) && parent.name.getText().startsWith("on")) {
isInEventHandler = true;
}
// Count render function occurrences - Fixed version
if (typescript_1.default.isMethodDeclaration(parent) &&
typescript_1.default.isIdentifier(parent.name) &&
parent.name.text === "render") {
renderCount++;
}
else if (typescript_1.default.isFunctionDeclaration(parent) &&
parent.name &&
parent.name.text === "render") {
renderCount++;
}
else if (typescript_1.default.isVariableDeclaration(parent) &&
typescript_1.default.isIdentifier(parent.name) &&
parent.name.text === "render") {
renderCount++;
}
// Find parent component function
if ((typescript_1.default.isFunctionDeclaration(parent) ||
typescript_1.default.isArrowFunction(parent) ||
typescript_1.default.isFunctionExpression(parent)) &&
!parentComponent) {
// For function declaration, get the name
if (typescript_1.default.isFunctionDeclaration(parent) && parent.name) {
parentComponent = parent.name.text;
}
// For variable declaration with arrow function, get variable name
else if (parent.parent && typescript_1.default.isVariableDeclaration(parent.parent)) {
if (typescript_1.default.isIdentifier(parent.parent.name)) {
parentComponent = parent.parent.name.text;
}
}
}
parent = parent.parent;
}
return {
isInJSX,
isInConditional,
parentComponent,
isInEventHandler,
renderCount,
};
}
}
exports.ContextExtractor = ContextExtractor;