fix(directive-parser) add support for bound attributes

This commit is contained in:
Kim Biesbjerg 2020-05-28 19:35:26 +02:00 committed by GitHub
parent 8afbb2f3a9
commit 85cd1e4a46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 18 deletions

View File

@ -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,16 +63,18 @@ 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)) {
const childElements = this.getElementsWithTranslateAttribute(element.children); elements = [...elements, element];
if (childElements.length) { }
elements = [...elements, ...childElements]; const childElements = this.getElementsWithTranslateAttribute(element.children);
} if (childElements.length) {
}); elements = [...elements, ...childElements];
}
});
return elements; return elements;
} }
@ -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;
} }
} }

View File

@ -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']);
}); });
}); });