@vendure/ngx-translate-extract
Version:
Extract strings from projects using ngx-translate
177 lines (176 loc) • 7.71 kB
JavaScript
import { extname } from 'node:path';
import { ScriptKind, tsquery } from '@phenomnomnominal/tsquery';
import pkg from 'typescript';
const { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isConditionalExpression, isPropertyAccessExpression, isStringLiteralLike, SyntaxKind } = pkg;
export function getAST(source, fileName = '') {
const supportedScriptTypes = {
'.js': ScriptKind.JS,
'.jsx': ScriptKind.JSX,
'.ts': ScriptKind.TS,
'.tsx': ScriptKind.TSX
};
const scriptKind = supportedScriptTypes[extname(fileName)] ?? ScriptKind.TS;
return tsquery.ast(source, fileName, scriptKind);
}
export function getNamedImportIdentifiers(node, moduleName, importPath) {
const importStringLiteralValue = importPath instanceof RegExp ? `value=${importPath.toString()}` : `value="${importPath}"`;
const query = `ImportDeclaration:has(StringLiteral[${importStringLiteralValue}]) ImportSpecifier:has(Identifier[name="${moduleName}"]) > Identifier`;
return tsquery(node, query);
}
export function getNamedImport(node, importName, importPath) {
const identifiers = getNamedImportIdentifiers(node, importName, importPath);
return identifiers.at(0)?.text ?? null;
}
export function getNamedImportAlias(node, importName, importPath) {
const identifiers = getNamedImportIdentifiers(node, importName, importPath);
return identifiers.at(-1)?.text ?? null;
}
export function findClassDeclarations(node, name = null) {
let query = 'ClassDeclaration';
if (name) {
query += `:has(Identifier[name="${name}"])`;
}
return tsquery(node, query);
}
export function findFunctionExpressions(node) {
return tsquery(node, 'VariableDeclaration ArrowFunction, VariableDeclaration FunctionExpression');
}
export function getSuperClassName(node) {
const query = 'ClassDeclaration > HeritageClause Identifier';
const [result] = tsquery(node, query);
return result?.text;
}
export function getImportPath(node, className) {
const query = `ImportDeclaration:has(Identifier[name="${className}"]) StringLiteral`;
const [result] = tsquery(node, query);
return result?.text;
}
export function findClassPropertiesByType(node, type) {
return [
...findClassPropertiesConstructorParameterByType(node, type),
...findClassPropertiesDeclarationByType(node, type),
...findClassPropertiesDeclarationByInject(node, type),
...findClassPropertiesGetterByType(node, type)
];
}
export function findConstructorDeclaration(node) {
const query = 'Constructor';
const [result] = tsquery(node, query);
return result;
}
export function findMethodParameterByType(node, type) {
const query = `Parameter:has(TypeReference > Identifier[name="${type}"]) > Identifier`;
const [result] = tsquery(node, query);
if (result) {
return result.text;
}
return null;
}
export function findVariableNameByInjectType(node, type) {
const query = `VariableDeclaration:has(Identifier[name="inject"]):has(CallExpression > Identifier[name="${type}"]) > Identifier`;
const [result] = tsquery(node, query);
return result?.text ?? null;
}
export function findMethodCallExpressions(node, propName, fnName) {
const functionNames = typeof fnName === 'string' ? [fnName] : fnName;
const fnNameRegex = functionNames.join('|');
const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnNameRegex})$/]):has(PropertyAccessExpression:has(Identifier[name="${propName}"]):not(:has(ThisKeyword)), CallExpression:has(Identifier[name="inject"]))`;
return tsquery(node, query)
.filter((n) => functionNames.includes(n.getLastToken().getText()))
.map((n) => n.parent);
}
export function findClassPropertiesConstructorParameterByType(node, type) {
const query = `Constructor Parameter:has(TypeReference > Identifier[name="${type}"]):has(PublicKeyword,ProtectedKeyword,PrivateKeyword,ReadonlyKeyword) > Identifier`;
const result = tsquery(node, query);
return result.map((n) => n.text);
}
export function findClassPropertiesDeclarationByType(node, type) {
const query = `PropertyDeclaration:has(TypeReference > Identifier[name="${type}"])`;
const result = tsquery(node, query);
return result.map((n) => n.name.getText());
}
export function findClassPropertiesDeclarationByInject(node, type) {
const query = `PropertyDeclaration:has(CallExpression > Identifier[name="inject"]):has(CallExpression > Identifier[name="${type}"])`;
const result = tsquery(node, query);
return result.map((n) => n.name.getText());
}
export function findClassPropertiesGetterByType(node, type) {
const query = `GetAccessor:has(TypeReference > Identifier[name="${type}"]) > Identifier`;
const result = tsquery(node, query);
return result.map((n) => n.text);
}
export function findFunctionCallExpressions(node, fnName) {
if (Array.isArray(fnName)) {
fnName = fnName.join('|');
}
const query = `CallExpression:has(Identifier[name="${fnName}"]):not(:has(PropertyAccessExpression))`;
return tsquery(node, query);
}
export function findSimpleCallExpressions(node, fnName) {
if (Array.isArray(fnName)) {
fnName = fnName.join('|');
}
const query = `CallExpression:has(Identifier[name="${fnName}"])`;
return tsquery(node, query);
}
export function findPropertyCallExpressions(node, prop, fnName) {
if (Array.isArray(fnName)) {
fnName = fnName.join('|');
}
const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(ThisKeyword))`;
const result = tsquery(node, query);
const nodes = [];
result.forEach((n) => {
const identifier = isPropertyAccessExpression(n.expression) ? n.expression.name : null;
const property = identifier?.parent;
const method = property?.parent;
const callExpression = method?.parent;
if (identifier?.getText() === prop && isCallExpression(callExpression)) {
nodes.push(callExpression);
}
});
return nodes;
}
export function getStringsFromExpression(expression) {
if (isStringLiteralLike(expression) && expression.text !== '') {
return [expression.text];
}
if (isArrayLiteralExpression(expression)) {
return expression.elements.reduce((result, element) => {
const strings = getStringsFromExpression(element);
return [...result, ...strings];
}, []);
}
if (isBinaryExpression(expression)) {
const [left] = getStringsFromExpression(expression.left);
const [right] = getStringsFromExpression(expression.right);
if (expression.operatorToken.kind === SyntaxKind.PlusToken) {
if (typeof left === 'string' && typeof right === 'string') {
return [left + right];
}
}
if (expression.operatorToken.kind === SyntaxKind.BarBarToken) {
const result = [];
if (typeof left === 'string') {
result.push(left);
}
if (typeof right === 'string') {
result.push(right);
}
return result;
}
}
if (isConditionalExpression(expression)) {
const [whenTrue] = getStringsFromExpression(expression.whenTrue);
const [whenFalse] = getStringsFromExpression(expression.whenFalse);
const result = [];
if (typeof whenTrue === 'string') {
result.push(whenTrue);
}
if (typeof whenFalse === 'string') {
result.push(whenFalse);
}
return result;
}
return [];
}