fix(pipe-parser): add support for more sophisticated expressions that worked in version 4.2.0 with the regex based parser (#185)
* fix(pipe-parser): add support for more sophisticated expressions
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							5e0da552b0
						
					
				
				
					commit
					619b3c56ea
				
			| @@ -1,4 +1,16 @@ | ||||
| import { TmplAstNode, parseTemplate, BindingPipe, LiteralPrimitive, Conditional, TmplAstTextAttribute } from '@angular/compiler'; | ||||
| import { | ||||
| 	AST, | ||||
| 	TmplAstNode, | ||||
| 	parseTemplate, | ||||
| 	BindingPipe, | ||||
| 	LiteralPrimitive, | ||||
| 	Conditional, | ||||
| 	TmplAstTextAttribute, | ||||
| 	Binary, | ||||
| 	LiteralMap, | ||||
| 	LiteralArray, | ||||
| 	Interpolation | ||||
| } from '@angular/compiler'; | ||||
|  | ||||
| import { ParserInterface } from './parser.interface'; | ||||
| import { TranslationCollection } from '../utils/translation.collection'; | ||||
| @@ -36,9 +48,8 @@ export class PipeParser implements ParserInterface { | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		if (node?.value?.ast?.expressions) { | ||||
| 			const translateables = node.value.ast.expressions.filter((exp: any) => this.expressionIsOrHasBindingPipe(exp)); | ||||
| 			ret.push(...translateables); | ||||
| 		if (node?.value?.ast) { | ||||
| 			ret.push(...this.getTranslatablesFromAst(node.value.ast)); | ||||
| 		} | ||||
|  | ||||
| 		if (node?.attributes) { | ||||
| @@ -51,17 +62,8 @@ export class PipeParser implements ParserInterface { | ||||
| 		if (node?.inputs) { | ||||
| 			node.inputs.forEach((input: any) => { | ||||
| 				// <element [attrib]="'identifier' | translate"> | ||||
| 				if (input?.value?.ast && this.expressionIsOrHasBindingPipe(input.value.ast)) { | ||||
| 					ret.push(input.value.ast); | ||||
| 				} | ||||
|  | ||||
| 				// <element attrib="{{'identifier' | translate}}>" | ||||
| 				if (input?.value?.ast?.expressions) { | ||||
| 					input.value.ast.expressions.forEach((exp: BindingPipe) => { | ||||
| 						if (this.expressionIsOrHasBindingPipe(exp)) { | ||||
| 							ret.push(exp); | ||||
| 						} | ||||
| 					}); | ||||
| 				if (input?.value?.ast) { | ||||
| 					ret.push(...this.getTranslatablesFromAst(input.value.ast)); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| @@ -84,7 +86,63 @@ export class PipeParser implements ParserInterface { | ||||
| 		return ret; | ||||
| 	} | ||||
|  | ||||
| 	protected expressionIsOrHasBindingPipe(exp: any): boolean { | ||||
| 	protected getTranslatablesFromAst(ast: AST): BindingPipe[] { | ||||
| 		// the entire expression is the translate pipe, e.g.: | ||||
| 		// - 'foo' | translate | ||||
| 		// - (condition ? 'foo' : 'bar') | translate | ||||
| 		if (this.expressionIsOrHasBindingPipe(ast)) { | ||||
| 			return [ast]; | ||||
| 		} | ||||
|  | ||||
| 		// angular double curly bracket interpolation, e.g.: | ||||
| 		// - {{ expressions }} | ||||
| 		if (ast instanceof Interpolation) { | ||||
| 			return this.getTranslatablesFromAsts(ast.expressions); | ||||
| 		} | ||||
|  | ||||
| 		// ternary operator, e.g.: | ||||
| 		// - condition ? null : ('foo' | translate) | ||||
| 		// - condition ? ('foo' | translate) : null | ||||
| 		if (ast instanceof Conditional) { | ||||
| 			return this.getTranslatablesFromAsts([ast.trueExp, ast.falseExp]); | ||||
| 		} | ||||
|  | ||||
| 		// string concatenation, e.g.: | ||||
| 		// - 'foo' + 'bar' + ('baz' | translate) | ||||
| 		if (ast instanceof Binary) { | ||||
| 			return this.getTranslatablesFromAsts([ast.left, ast.right]); | ||||
| 		} | ||||
|  | ||||
| 		// a pipe on the outer expression, but not the translate pipe - ignore the pipe, visit the expression, e.g.: | ||||
| 		// - { foo: 'Hello' | translate } | json | ||||
| 		if (ast instanceof BindingPipe) { | ||||
| 			return this.getTranslatablesFromAst(ast.exp); | ||||
| 		} | ||||
|  | ||||
| 		// object - ignore the keys, visit all values, e.g.: | ||||
| 		// - { key1: 'value1' | translate, key2: 'value2' | translate } | ||||
| 		if (ast instanceof LiteralMap) { | ||||
| 			return this.getTranslatablesFromAsts(ast.values); | ||||
| 		} | ||||
|  | ||||
| 		// array - visit all its values, e.g.: | ||||
| 		// - [ 'value1' | translate, 'value2' | translate ] | ||||
| 		if (ast instanceof LiteralArray) { | ||||
| 			return this.getTranslatablesFromAsts(ast.expressions); | ||||
| 		} | ||||
|  | ||||
| 		return []; | ||||
| 	} | ||||
|  | ||||
| 	protected getTranslatablesFromAsts(asts: AST[]): BindingPipe[] { | ||||
| 		return this.flatten(asts.map(ast => this.getTranslatablesFromAst(ast))); | ||||
| 	} | ||||
|  | ||||
| 	protected flatten<T extends AST>(array: T[][]): T[] { | ||||
| 		return [].concat(...array); | ||||
| 	} | ||||
|  | ||||
| 	protected expressionIsOrHasBindingPipe(exp: any): exp is BindingPipe { | ||||
| 		if (exp.name && exp.name === TRANSLATE_PIPE_NAME) { | ||||
| 			return true; | ||||
| 		} | ||||
|   | ||||
| @@ -47,12 +47,48 @@ describe('PipeParser', () => { | ||||
| 		expect(keys).to.deep.equal(['Hello', 'World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from ternary operators right expression', () => { | ||||
| 		const contents = `{{ condition ? null : ('World' | translate) }}`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from ternary operators inside attribute bindings', () => { | ||||
| 		const contents = `<span [attr]="condition ? null : ('World' | translate)"></span>`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from ternary operators left expression', () => { | ||||
| 		const contents = `{{ condition ? ('World' | translate) : null }}`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings inside string concatenation', () => { | ||||
| 		const contents = `{{ 'a' + ('Hello' | translate) + 'b' + 'c' + ('World' | translate) + 'd' }}`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['Hello', 'World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from object', () => { | ||||
| 		const contents = `{{ { foo: 'Hello' | translate, bar: ['World' | translate], deep: { nested: { baz: 'Yes' | translate } } } | json }}`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['Hello', 'World', 'Yes']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from ternary operators inside attribute bindings', () => { | ||||
| 		const contents = `<span [attr]="(condition ? 'Hello' : 'World') | translate"></span>`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['Hello', 'World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from nested expressions', () => { | ||||
| 		const contents = `<span [attr]="{ foo: ['a' + ((condition ? 'Hello' : 'World') | translate) + 'b'] }"></span>`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
| 		expect(keys).to.deep.equal(['Hello', 'World']); | ||||
| 	}); | ||||
|  | ||||
| 	it('should extract strings from nested ternary operators ', () => { | ||||
| 		const contents = `<h3>{{ (condition ? 'Hello' : anotherCondition ? 'Nested' : 'World' ) | translate }}</h3>`; | ||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user