From d867d5c588ac5058de4905ce1567e8b34f21dfa0 Mon Sep 17 00:00:00 2001 From: shaharkazaz Date: Sat, 3 Aug 2024 22:38:25 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20support=20pipe=20params?= =?UTF-8?q?=20extraction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template-extraction/pipe/pipe-spec.ts | 11 +++ .../pipe/with-params/1.html | 9 ++ src/keys-builder/template/pipe.extractor.ts | 90 +++++++++++++------ src/utils/validators.utils.ts | 4 + 4 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 __tests__/buildTranslationFiles/template-extraction/pipe/with-params/1.html diff --git a/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts b/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts index 5d46580..eb9c70a 100644 --- a/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts @@ -9,6 +9,8 @@ import { defaultValue, generateKeys, mockResolveProjectBasePath, + resolveValueWithParams, + setParamsInput, } from '../../../spec-utils'; import { Config } from '../../../../src/types'; @@ -50,5 +52,14 @@ export function testPipeExtraction(fileFormat: Config['fileFormat']) { buildTranslationFiles(config); assertTranslation({ type, expected, fileFormat }); }); + + it('should extract params', () => { + const expected = { + ...generateKeys({ end: 2, withParams: true }), + 'admin.1': resolveValueWithParams(['someKey']), + }; + buildTranslationFiles(setParamsInput(config)); + assertTranslation({ type, expected, fileFormat }); + }); }); } diff --git a/__tests__/buildTranslationFiles/template-extraction/pipe/with-params/1.html b/__tests__/buildTranslationFiles/template-extraction/pipe/with-params/1.html new file mode 100644 index 0000000..e310ddb --- /dev/null +++ b/__tests__/buildTranslationFiles/template-extraction/pipe/with-params/1.html @@ -0,0 +1,9 @@ +
+ {{ '1' | transloco:{'1': ''} }} +

+
+ + + diff --git a/src/keys-builder/template/pipe.extractor.ts b/src/keys-builder/template/pipe.extractor.ts index 7ce9a3e..91b312a 100644 --- a/src/keys-builder/template/pipe.extractor.ts +++ b/src/keys-builder/template/pipe.extractor.ts @@ -1,4 +1,11 @@ -import { AST, ASTWithSource, TmplAstNode } from '@angular/compiler'; +import { + AST, + ASTWithSource, + BindingPipe, + LiteralMap, + LiteralPrimitive, + TmplAstNode, +} from '@angular/compiler'; import { ExtractorConfig } from '../../types'; import { addKey } from '../add-key'; @@ -21,6 +28,7 @@ import { isBlockNode, resolveBlockChildNodes, } from './utils'; +import { notNil } from '../../utils/validators.utils'; export function pipeExtractor(config: TemplateExtractorConfig) { const ast = parseTemplate(config); @@ -44,7 +52,12 @@ function traverse(nodes: TmplAstNode[], config: ExtractorConfig) { } for (const ast of astTrees) { - addKeysFromAst(getPipeValuesFromAst(ast), config); + const pipes = getTranslocoPipeAst(ast) as BindingPipe[]; + const keysWithParams = pipes + .map((p) => resolveKeyAndParam(p)) + .flat() + .filter(notNil); + addKeysFromAst(keysWithParams, config); } } } @@ -60,21 +73,10 @@ function isTranslocoPipe(ast: any): boolean { return isTransloco || (isPipeChaining && isTranslocoPipe(ast.exp)); } -function getPipeValuesFromAst(ast: AST): AST[] { +function getTranslocoPipeAst(ast: AST): AST[] { let exp = []; if (isBindingPipe(ast) && isTranslocoPipe(ast)) { - if (isLiteralExpression(ast.exp)) { - return [ast.exp]; - } else if (isConditionalExpression(ast.exp)) { - return [ast.exp.trueExp, ast.exp.falseExp]; - } else { - let pipe = ast; - while (isBindingPipe(pipe.exp)) { - pipe = pipe.exp; - } - - return [pipe.exp]; - } + return [ast]; } else if (isBindingPipe(ast)) { exp = [...ast.args, ast.exp]; } else if (isLiteralMap(ast)) { @@ -91,20 +93,56 @@ function getPipeValuesFromAst(ast: AST): AST[] { exp = [ast.receiver]; } - return exp.map(getPipeValuesFromAst).flat(); + return exp.map(getTranslocoPipeAst).flat(); } -function addKeysFromAst(expressions: AST[], config: ExtractorConfig): void { - for (const exp of expressions) { - if (isConditionalExpression(exp)) { - addKeysFromAst([exp.trueExp, exp.falseExp], config); - } else if (isLiteralExpression(exp)) { - const [key, scopeAlias] = resolveAliasAndKey(exp.value, config.scopes); - addKey({ - ...config, - keyWithoutScope: key, - scopeAlias, +interface KeyWithParam { + keyNode: LiteralPrimitive; + paramsNode: AST; +} + +function resolveKeyAndParam( + pipe: BindingPipe, + paramsNode?: AST, +): KeyWithParam | KeyWithParam[] | null { + const resolvedParams: AST = paramsNode ?? pipe.args[0]; + if (isConditionalExpression(pipe.exp)) { + return [pipe.exp.trueExp, pipe.exp.falseExp] + .filter(isLiteralExpression) + .map((keyNode) => { + return { + keyNode, + paramsNode: resolvedParams, + }; }); + } else if (isLiteralExpression(pipe.exp)) { + return { + keyNode: pipe.exp, + paramsNode: resolvedParams, + }; + } else if (isBindingPipe(pipe.exp)) { + let nestedPipe = pipe; + while (isBindingPipe(nestedPipe.exp)) { + nestedPipe = nestedPipe.exp; } + + return resolveKeyAndParam(nestedPipe, resolvedParams); + } + + return null; +} + +function addKeysFromAst(keys: KeyWithParam[], config: ExtractorConfig): void { + for (const { keyNode, paramsNode } of keys) { + const [key, scopeAlias] = resolveAliasAndKey(keyNode.value, config.scopes); + const params = isLiteralMap(paramsNode) + ? paramsNode.keys.map((k) => k.key) + : []; + addKey({ + ...config, + keyWithoutScope: key, + scopeAlias, + params, + }); } } diff --git a/src/utils/validators.utils.ts b/src/utils/validators.utils.ts index 50aefd4..8f2451a 100644 --- a/src/utils/validators.utils.ts +++ b/src/utils/validators.utils.ts @@ -12,6 +12,10 @@ export function isNil(value: unknown): value is undefined | null { return isUndefined(value) || value === null; } +export function notNil(value: T | undefined | null): value is T { + return !isNil(value); +} + export function isDirectory(path: string): boolean { return existsSync(path) && lstatSync(path).isDirectory(); }