@jsverse/transloco-keys-manager
Version:
Extract translatable keys from projects that uses Transloco
149 lines • 5.54 kB
JavaScript
import { RecursiveAstVisitor, } from '@angular/compiler';
import { addKey } from '../add-key.js';
import { resolveAliasAndKey } from '../utils/resolvers.utils.js';
import { isBoundAttribute, isBoundText, isElement, isInterpolation, isCall, isNgTemplateTag, isSupportedNode, isTemplate, parseTemplate, isBlockNode, resolveBlockChildNodes, resolveKeysFromLiteralMap, } from './utils.js';
import { isConditionalExpression, isLiteralExpression, isLiteralMap } from '@jsverse/angular-utils';
export function structuralDirectiveExtractor(config) {
const ast = parseTemplate(config);
traverse(ast.nodes, [], config);
}
export function traverse(nodes, containers, config) {
for (const node of nodes) {
if (isBlockNode(node)) {
traverse(resolveBlockChildNodes(node), containers, config);
continue;
}
let methodUsages = [];
if (isBoundText(node)) {
const { expressions } = node.value
.ast;
methodUsages = getMethodUsages(expressions, containers);
}
else if (isSupportedNode(node, [isTemplate, isElement])) {
if (isTranslocoTemplate(node)) {
for (const metadata of resolveMetadata(node)) {
containers.push(metadata);
}
}
let attrsSource = node.inputs;
if (isTemplate(node)) {
attrsSource = node.inputs.concat(node.templateAttrs.filter(isBoundAttribute));
}
const boundAttrs = attrsSource
.map((input) => {
const { ast } = input.value;
return isInterpolation(ast) ? ast.expressions : ast;
})
.flat();
methodUsages = getMethodUsages(boundAttrs, containers);
traverse(node.children, containers, config);
}
addKeysFromAst(methodUsages, config);
}
}
class MethodCallUnwrapper extends RecursiveAstVisitor {
expressions = [];
visitCall(method, context) {
this.expressions.push(method);
super.visitCall(method, context);
}
}
/**
* Extract method calls from an AST.
*/
function unwrapMethodCalls(exp) {
const unwrapper = new MethodCallUnwrapper();
unwrapper.visit(exp);
return unwrapper.expressions;
}
function getMethodUsages(expressions, containers) {
return expressions
.flatMap(unwrapMethodCalls)
.filter((exp) => isTranslocoMethod(exp, containers))
.map((exp) => {
const [keyNode, paramsNode] = exp.args;
return {
keyNode,
params: isLiteralMap(paramsNode)
? resolveKeysFromLiteralMap(paramsNode)
: [],
...containers.find(({ name, spanOffset: { start, end } }) => {
const inRange = exp.sourceSpan.end < end && exp.sourceSpan.start > start;
return exp.receiver.name === name && inRange;
}),
};
});
}
function isTranslocoAttr(attr) {
return attr.name === 'transloco';
}
function isPrefixAttr(attr) {
return attr.name === 'translocoPrefix' || attr.name === 'translocoRead';
}
function isTranslocoTemplate(node) {
return (isTemplate(node) &&
(node.templateAttrs.some(isTranslocoAttr) ||
(isNgTemplateTag(node) && node.attributes.some(isTranslocoAttr))));
}
function isTranslocoMethod(exp, containers) {
return (isCall(exp) &&
containers.some(({ name }) => name === exp.receiver.name));
}
function resolveMetadata(node) {
/*
* An ngTemplate element might have more than once implicit variables, we need to capture all of them.
* */
let metadata;
if (isNgTemplateTag(node)) {
const implicitVars = node.variables.filter((attr) => !attr.value);
let read = node.attributes.find(isPrefixAttr)?.value;
if (!read) {
const ast = node.inputs.find(isPrefixAttr)?.value?.ast;
if (isLiteralExpression(ast)) {
read = ast.value;
}
}
metadata = implicitVars.map(({ name }) => ({ name, read }));
}
else {
const { name } = node.variables.find((variable) => variable.value === '$implicit');
const read = node.templateAttrs.find(isPrefixAttr)?.value;
metadata = isLiteralExpression(read?.ast)
? [{ name, read: read.ast.value }]
: [{ name }];
}
return metadata.map((metadata) => {
const sourceSpan = node.sourceSpan;
return {
...metadata,
spanOffset: {
start: sourceSpan.start.offset,
end: sourceSpan.end.offset,
},
};
});
}
function addKeysFromAst(expressions, config) {
for (const { keyNode, read, params } of expressions) {
if (isConditionalExpression(keyNode)) {
addKeysFromAst([keyNode.trueExp, keyNode.falseExp].map((kn) => {
return {
keyNode: kn,
read,
params,
};
}), config);
}
else if (isLiteralExpression(keyNode) && keyNode.value) {
let value = read ? `${read}.${keyNode.value}` : keyNode.value;
const [key, scopeAlias] = resolveAliasAndKey(value, config.scopes);
addKey({
...config,
params,
keyWithoutScope: key,
scopeAlias,
});
}
}
}
//# sourceMappingURL=structural-directive.extractor.js.map