fix(directive-parser) add support for bound attributes
This commit is contained in:
		| @@ -3,7 +3,18 @@ import { | |||||||
| 	TmplAstNode as Node, | 	TmplAstNode as Node, | ||||||
| 	TmplAstElement as Element, | 	TmplAstElement as Element, | ||||||
| 	TmplAstText as Text, | 	TmplAstText as Text, | ||||||
| 	TmplAstTemplate as Template | 	TmplAstTemplate as Template, | ||||||
|  | 	TmplAstTextAttribute as TextAttribute, | ||||||
|  | 	TmplAstBoundAttribute as BoundAttribute, | ||||||
|  | 	AST, | ||||||
|  | 	ASTWithSource, | ||||||
|  | 	LiteralPrimitive, | ||||||
|  | 	Conditional, | ||||||
|  | 	Binary, | ||||||
|  | 	BindingPipe, | ||||||
|  | 	Interpolation, | ||||||
|  | 	LiteralArray, | ||||||
|  | 	LiteralMap | ||||||
| } from '@angular/compiler'; | } from '@angular/compiler'; | ||||||
|  |  | ||||||
| import { ParserInterface } from './parser.interface'; | import { ParserInterface } from './parser.interface'; | ||||||
| @@ -24,13 +35,22 @@ export class DirectiveParser implements ParserInterface { | |||||||
| 		const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes); | 		const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes); | ||||||
|  |  | ||||||
| 		elements.forEach((element) => { | 		elements.forEach((element) => { | ||||||
| 			const attr = this.getAttribute(element, TRANSLATE_ATTR_NAME); | 			const attribute = this.getAttribute(element, TRANSLATE_ATTR_NAME); | ||||||
| 			if (attr) { | 			if (attribute?.value) { | ||||||
| 				collection = collection.add(attr); | 				collection = collection.add(attribute.value); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			const boundAttribute = this.getBoundAttribute(element, TRANSLATE_ATTR_NAME); | ||||||
|  | 			if (boundAttribute?.value) { | ||||||
|  | 				this.getLiteralPrimitives(boundAttribute.value).forEach((literalPrimitive) => { | ||||||
|  | 					collection = collection.add(literalPrimitive.value); | ||||||
|  | 				}); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			const textNodes = this.getTextNodes(element); | 			const textNodes = this.getTextNodes(element); | ||||||
| 			textNodes.forEach((textNode: Text) => { | 			textNodes.forEach((textNode) => { | ||||||
| 				collection = collection.add(textNode.value.trim()); | 				collection = collection.add(textNode.value.trim()); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
| @@ -43,11 +63,13 @@ export class DirectiveParser implements ParserInterface { | |||||||
| 	 */ | 	 */ | ||||||
| 	protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] { | 	protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] { | ||||||
| 		let elements: ElementLike[] = []; | 		let elements: ElementLike[] = []; | ||||||
| 		nodes.filter(this.isElementLike) | 		nodes.filter(this.isElementLike).forEach((element) => { | ||||||
| 			.forEach((element) => { |  | ||||||
| 			if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) { | 			if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) { | ||||||
| 				elements = [...elements, element]; | 				elements = [...elements, element]; | ||||||
| 			} | 			} | ||||||
|  | 			if (this.hasBoundAttribute(element, TRANSLATE_ATTR_NAME)) { | ||||||
|  | 				elements = [...elements, element]; | ||||||
|  | 			} | ||||||
| 			const childElements = this.getElementsWithTranslateAttribute(element.children); | 			const childElements = this.getElementsWithTranslateAttribute(element.children); | ||||||
| 			if (childElements.length) { | 			if (childElements.length) { | ||||||
| 				elements = [...elements, ...childElements]; | 				elements = [...elements, ...childElements]; | ||||||
| @@ -76,8 +98,59 @@ export class DirectiveParser implements ParserInterface { | |||||||
| 	 * Get attribute value if present on element | 	 * Get attribute value if present on element | ||||||
| 	 * @param element | 	 * @param element | ||||||
| 	 */ | 	 */ | ||||||
| 	protected getAttribute(element: ElementLike, name: string): string | undefined { | 	protected getAttribute(element: ElementLike, name: string): TextAttribute { | ||||||
| 		return element.attributes.find((attribute) => attribute.name === name)?.value; | 		return element.attributes.find((attribute) => attribute.name === name); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Check if bound attribute is present on element | ||||||
|  | 	 * @param element | ||||||
|  | 	 * @param name | ||||||
|  | 	 */ | ||||||
|  | 	protected hasBoundAttribute(element: ElementLike, name: string): boolean { | ||||||
|  | 		return this.getBoundAttribute(element, name) !== undefined; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Get bound attribute if present on element | ||||||
|  | 	 * @param element | ||||||
|  | 	 * @param name | ||||||
|  | 	 */ | ||||||
|  | 	protected getBoundAttribute(element: ElementLike, name: string): BoundAttribute { | ||||||
|  | 		return element.inputs.find((input) => input.name === name); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Get literal primitives from expression | ||||||
|  | 	 * @param exp | ||||||
|  | 	 */ | ||||||
|  | 	protected getLiteralPrimitives(exp: AST): LiteralPrimitive[] { | ||||||
|  | 		if (exp instanceof LiteralPrimitive) { | ||||||
|  | 			return [exp]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		let visit: AST[] = []; | ||||||
|  | 		if (exp instanceof Interpolation) { | ||||||
|  | 			visit = exp.expressions; | ||||||
|  | 		} else if (exp instanceof LiteralArray) { | ||||||
|  | 			visit = exp.expressions; | ||||||
|  | 		} else if (exp instanceof LiteralMap) { | ||||||
|  | 			visit = exp.values; | ||||||
|  | 		} else if (exp instanceof BindingPipe) { | ||||||
|  | 			visit = [exp.exp]; | ||||||
|  | 		} else if (exp instanceof Conditional) { | ||||||
|  | 			visit = [exp.trueExp, exp.falseExp]; | ||||||
|  | 		} else if (exp instanceof Binary) { | ||||||
|  | 			visit = [exp.left, exp.right]; | ||||||
|  | 		} else if (exp instanceof ASTWithSource) { | ||||||
|  | 			visit = [exp.ast]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		let results: LiteralPrimitive[] = []; | ||||||
|  | 		visit.forEach((child) => { | ||||||
|  | 			results = [...results, ...this.getLiteralPrimitives(child)]; | ||||||
|  | 		}); | ||||||
|  | 		return results; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -104,5 +177,4 @@ export class DirectiveParser implements ParserInterface { | |||||||
| 	protected parseTemplate(template: string, path: string): Node[] { | 	protected parseTemplate(template: string, path: string): Node[] { | ||||||
| 		return parseTemplate(template, path).nodes; | 		return parseTemplate(template, path).nodes; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,55 @@ describe('DirectiveParser', () => { | |||||||
| 		parser = new DirectiveParser(); | 		parser = new DirectiveParser(); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using literal map in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="{ key1: 'value1' | translate, key2: 'value2' | translate }"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['value1', 'value2']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using literal arrays in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="[ 'value1' | translate, 'value2' | translate ]"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['value1', 'value2']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using binding pipe in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="'KEY1' | withPipe"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['KEY1']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using binary expression in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="keyVar || 'KEY1'"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['KEY1']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using literal primitive in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="'KEY1'"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['KEY1']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using conditional in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="condition ? 'KEY1' : 'KEY2'"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['KEY1', 'KEY2']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using nested conditionals in bound attribute', () => { | ||||||
|  | 		const contents = `<div [translate]="isSunny ? (isWarm ? 'Sunny and warm' : 'Sunny but cold') : 'Not sunny'"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['Sunny and warm', 'Sunny but cold', 'Not sunny']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract keys when using interpolation', () => { | ||||||
|  | 		const contents = `<div translate="{{ 'KEY1' + key2 + 'KEY3' }}"></div>`; | ||||||
|  | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['KEY1', 'KEY3']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| 	it('should extract keys keeping proper whitespace', () => { | 	it('should extract keys keeping proper whitespace', () => { | ||||||
| 		const contents = ` | 		const contents = ` | ||||||
| 			<div translate> | 			<div translate> | ||||||
| @@ -120,4 +169,5 @@ describe('DirectiveParser', () => { | |||||||
| 		const keys = parser.extract(contents, templateFilename).keys(); | 		const keys = parser.extract(contents, templateFilename).keys(); | ||||||
| 		expect(keys).to.deep.equal(['this is an example']); | 		expect(keys).to.deep.equal(['this is an example']); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user