diff --git a/src/parsers/directive.parser.ts b/src/parsers/directive.parser.ts index f0f8a75..d910b72 100644 --- a/src/parsers/directive.parser.ts +++ b/src/parsers/directive.parser.ts @@ -3,7 +3,18 @@ import { TmplAstNode as Node, TmplAstElement as Element, 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'; import { ParserInterface } from './parser.interface'; @@ -24,13 +35,22 @@ export class DirectiveParser implements ParserInterface { const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes); elements.forEach((element) => { - const attr = this.getAttribute(element, TRANSLATE_ATTR_NAME); - if (attr) { - collection = collection.add(attr); + const attribute = this.getAttribute(element, TRANSLATE_ATTR_NAME); + if (attribute?.value) { + collection = collection.add(attribute.value); 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); - textNodes.forEach((textNode: Text) => { + textNodes.forEach((textNode) => { collection = collection.add(textNode.value.trim()); }); }); @@ -43,16 +63,18 @@ export class DirectiveParser implements ParserInterface { */ protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] { let elements: ElementLike[] = []; - nodes.filter(this.isElementLike) - .forEach((element) => { - if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) { - elements = [...elements, element]; - } - const childElements = this.getElementsWithTranslateAttribute(element.children); - if (childElements.length) { - elements = [...elements, ...childElements]; - } - }); + nodes.filter(this.isElementLike).forEach((element) => { + if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) { + elements = [...elements, element]; + } + if (this.hasBoundAttribute(element, TRANSLATE_ATTR_NAME)) { + elements = [...elements, element]; + } + const childElements = this.getElementsWithTranslateAttribute(element.children); + if (childElements.length) { + elements = [...elements, ...childElements]; + } + }); return elements; } @@ -76,8 +98,59 @@ export class DirectiveParser implements ParserInterface { * Get attribute value if present on element * @param element */ - protected getAttribute(element: ElementLike, name: string): string | undefined { - return element.attributes.find((attribute) => attribute.name === name)?.value; + protected getAttribute(element: ElementLike, name: string): TextAttribute { + 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[] { return parseTemplate(template, path).nodes; } - } diff --git a/tests/parsers/directive.parser.spec.ts b/tests/parsers/directive.parser.spec.ts index 2c6cc6d..7d7b11d 100644 --- a/tests/parsers/directive.parser.spec.ts +++ b/tests/parsers/directive.parser.spec.ts @@ -12,6 +12,55 @@ describe('DirectiveParser', () => { parser = new DirectiveParser(); }); + + it('should extract keys when using literal map in bound attribute', () => { + const contents = `
`; + 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 = ``; + 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 = ``; + 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 = ``; + 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 = ``; + 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 = ``; + 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 = ``; + 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 = ``; + const keys = parser.extract(contents, templateFilename).keys(); + expect(keys).to.deep.equal(['KEY1', 'KEY3']); + }); + it('should extract keys keeping proper whitespace', () => { const contents = `