UNPKG

@vendure/ngx-translate-extract

Version:
127 lines (126 loc) 7.24 kB
import path from 'node:path'; import fs from 'node:fs'; import { findConfigFile, parseConfigFileTextToJson } from 'typescript'; import { TranslationCollection } from '../utils/translation.collection.js'; import { findClassDeclarations, findClassPropertiesByType, findPropertyCallExpressions, findMethodCallExpressions, getStringsFromExpression, findMethodParameterByType, findConstructorDeclaration, getSuperClassName, getImportPath, findFunctionExpressions, findVariableNameByInjectType, findInlineInjectCallExpressions, getAST, getNamedImport } from '../utils/ast-helpers.js'; const TRANSLATE_SERVICE_TYPE_REFERENCE = 'TranslateService'; const TRANSLATE_SERVICE_METHOD_NAMES = ['get', 'instant', 'stream']; export class ServiceParser { static propertyMap = new Map(); extract(source, filePath) { const sourceFile = getAST(source, filePath); const classDeclarations = findClassDeclarations(sourceFile); const functionDeclarations = findFunctionExpressions(sourceFile); if (!classDeclarations && !functionDeclarations) { return null; } let collection = new TranslationCollection(); const translateServiceCallExpressions = []; functionDeclarations.forEach((fnDeclaration) => { const translateServiceVariableName = findVariableNameByInjectType(fnDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); const callExpressions = findMethodCallExpressions(sourceFile, translateServiceVariableName, TRANSLATE_SERVICE_METHOD_NAMES); const inlineInjectCallExpressions = findInlineInjectCallExpressions(sourceFile, TRANSLATE_SERVICE_TYPE_REFERENCE, TRANSLATE_SERVICE_METHOD_NAMES); translateServiceCallExpressions.push(...callExpressions, ...inlineInjectCallExpressions); }); classDeclarations.forEach((classDeclaration) => { const callExpressions = [ ...this.findConstructorParamCallExpressions(classDeclaration), ...this.findPropertyCallExpressions(classDeclaration, sourceFile) ]; translateServiceCallExpressions.push(...callExpressions); }); translateServiceCallExpressions .filter((callExpression) => !!callExpression.arguments?.[0]) .forEach((callExpression) => { const [firstArg] = callExpression.arguments; const strings = getStringsFromExpression(firstArg); collection = collection.addKeys(strings, filePath); }); return collection; } findConstructorParamCallExpressions(classDeclaration) { const constructorDeclaration = findConstructorDeclaration(classDeclaration); if (!constructorDeclaration) { return []; } const paramName = findMethodParameterByType(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); const methodCallExpressions = findMethodCallExpressions(constructorDeclaration, paramName, TRANSLATE_SERVICE_METHOD_NAMES); const inlineInjectCallExpressions = findInlineInjectCallExpressions(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE, TRANSLATE_SERVICE_METHOD_NAMES); const translateServiceLocalVariableName = findVariableNameByInjectType(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); const localVariableCallExpressions = translateServiceLocalVariableName ? findMethodCallExpressions(constructorDeclaration, translateServiceLocalVariableName, TRANSLATE_SERVICE_METHOD_NAMES) : []; return [...methodCallExpressions, ...localVariableCallExpressions, ...inlineInjectCallExpressions]; } findPropertyCallExpressions(classDeclaration, sourceFile) { const propNames = findClassPropertiesByType(classDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); if (propNames.length === 0) { propNames.push(...this.findParentClassProperties(classDeclaration, sourceFile)); } return propNames.flatMap((name) => findPropertyCallExpressions(classDeclaration, name, TRANSLATE_SERVICE_METHOD_NAMES)); } findParentClassProperties(classDeclaration, ast) { const superClassNameOrAlias = getSuperClassName(classDeclaration); if (!superClassNameOrAlias) { return []; } const importPath = getImportPath(ast, superClassNameOrAlias); if (!importPath) { return []; } const superClassName = getNamedImport(ast, superClassNameOrAlias, importPath); const currDir = path.join(path.dirname(ast.fileName), '/'); const cacheKey = `${currDir}|${importPath}`; if (ServiceParser.propertyMap.has(cacheKey)) { return ServiceParser.propertyMap.get(cacheKey); } let superClassPath; if (importPath.startsWith('.')) { superClassPath = path.resolve(currDir, importPath); } else if (importPath.startsWith('/')) { superClassPath = importPath; } else { let baseUrl = currDir; const tsconfigFilePath = findConfigFile(currDir, fs.existsSync); if (tsconfigFilePath) { const tsConfigFile = fs.readFileSync(tsconfigFilePath, { encoding: 'utf8' }); const config = parseConfigFileTextToJson(tsconfigFilePath, tsConfigFile).config; const compilerOptionsBaseUrl = config.compilerOptions?.baseUrl ?? ''; baseUrl = path.resolve(path.dirname(tsconfigFilePath), compilerOptionsBaseUrl); } superClassPath = path.resolve(baseUrl, importPath); } const superClassFile = superClassPath + '.ts'; let potentialSuperFiles; if (fs.existsSync(superClassFile) && fs.lstatSync(superClassFile).isFile()) { potentialSuperFiles = [superClassFile]; } else if (fs.existsSync(superClassPath) && fs.lstatSync(superClassPath).isDirectory()) { potentialSuperFiles = fs .readdirSync(superClassPath) .filter((file) => file.endsWith('.ts')) .map((file) => path.join(superClassPath, file)); } else { return []; } const allSuperClassPropertyNames = []; potentialSuperFiles.forEach((file) => { const superClassFileContent = fs.readFileSync(file, 'utf8'); const superClassAst = getAST(superClassFileContent, file); const superClassDeclarations = findClassDeclarations(superClassAst, superClassName); const superClassPropertyNames = superClassDeclarations .flatMap((superClassDeclaration) => findClassPropertiesByType(superClassDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE)); if (superClassPropertyNames.length > 0) { ServiceParser.propertyMap.set(cacheKey, superClassPropertyNames); allSuperClassPropertyNames.push(...superClassPropertyNames); } else { superClassDeclarations.forEach((declaration) => allSuperClassPropertyNames.push(...this.findParentClassProperties(declaration, superClassAst))); } }); return allSuperClassPropertyNames; } }