fix(compiler): support dotted property binding

fixes angular/flex-layout#34
This commit is contained in:
Victor Berchet 2016-12-09 14:42:13 -08:00
parent 95f48292b1
commit 3bee521aa4
2 changed files with 1499 additions and 1527 deletions

View File

@ -245,18 +245,12 @@ export class BindingParser {
let unit: string = null; let unit: string = null;
let bindingType: PropertyBindingType; let bindingType: PropertyBindingType;
let boundPropertyName: string; let boundPropertyName: string = null;
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR); const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
let securityContexts: SecurityContext[]; let securityContexts: SecurityContext[];
if (parts.length === 1) { // Check check for special cases (prefix style, attr, class)
const partValue = parts[0]; if (parts.length > 1) {
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
securityContexts = calcPossibleSecurityContexts(
this._schemaRegistry, elementSelector, boundPropertyName, false);
bindingType = PropertyBindingType.Property;
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
} else {
if (parts[0] == ATTRIBUTE_PREFIX) { if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts[1]; boundPropertyName = parts[1];
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true); this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
@ -280,12 +274,18 @@ export class BindingParser {
boundPropertyName = parts[1]; boundPropertyName = parts[1];
bindingType = PropertyBindingType.Style; bindingType = PropertyBindingType.Style;
securityContexts = [SecurityContext.STYLE]; securityContexts = [SecurityContext.STYLE];
} else {
this._reportError(`Invalid property name '${boundProp.name}'`, boundProp.sourceSpan);
bindingType = null;
securityContexts = [];
} }
} }
// If not a special case, use the full property name
if (boundPropertyName === null) {
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
securityContexts = calcPossibleSecurityContexts(
this._schemaRegistry, elementSelector, boundPropertyName, false);
bindingType = PropertyBindingType.Property;
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
}
return new BoundElementPropertyAst( return new BoundElementPropertyAst(
boundPropertyName, bindingType, securityContexts.length === 1 ? securityContexts[0] : null, boundPropertyName, bindingType, securityContexts.length === 1 ? securityContexts[0] : null,
securityContexts.length > 1, boundProp.expression, unit, boundProp.sourceSpan); securityContexts.length > 1, boundProp.expression, unit, boundProp.sourceSpan);

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata'; import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata';
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry'; import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast';
import {TEMPLATE_TRANSFORMS, TemplateParser, splitClasses} from '@angular/compiler/src/template_parser/template_parser'; import {TEMPLATE_TRANSFORMS, TemplateParser, splitClasses} from '@angular/compiler/src/template_parser/template_parser';
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings'; import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
import {SchemaMetadata, SecurityContext, Type} from '@angular/core'; import {SchemaMetadata, SecurityContext} from '@angular/core';
import {Console} from '@angular/core/src/console'; import {Console} from '@angular/core/src/console';
import {TestBed, inject} from '@angular/core/testing'; import {TestBed, inject} from '@angular/core/testing';
@ -257,8 +257,7 @@ export function main() {
}); });
}); });
describe( describe('TemplateParser', () => {
'TemplateParser', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureCompiler({providers: [TEST_COMPILER_PROVIDERS, MOCK_SCHEMA_REGISTRY]}); TestBed.configureCompiler({providers: [TEST_COMPILER_PROVIDERS, MOCK_SCHEMA_REGISTRY]});
}); });
@ -330,6 +329,13 @@ export function main() {
]); ]);
}); });
it('should parse dotted name bound properties', () => {
expect(humanizeTplAst(parse('<div [dot.name]="v">', []))).toEqual([
[ElementAst, 'div'],
[BoundElementPropertyAst, PropertyBindingType.Property, 'dot.name', 'v', null]
]);
});
it('should normalize property names via the element schema', () => { it('should normalize property names via the element schema', () => {
expect(humanizeTplAst(parse('<div [mappedAttr]="v">', []))).toEqual([ expect(humanizeTplAst(parse('<div [mappedAttr]="v">', []))).toEqual([
[ElementAst, 'div'], [ElementAst, 'div'],
@ -365,21 +371,6 @@ export function main() {
]); ]);
}); });
it('should report invalid prefixes', () => {
expect(() => parse('<p [atTr.foo]>', []))
.toThrowError(
`Template parse errors:\nInvalid property name 'atTr.foo' ("<p [ERROR ->][atTr.foo]>"): TestComp@0:3`);
expect(() => parse('<p [sTyle.foo]>', []))
.toThrowError(
`Template parse errors:\nInvalid property name 'sTyle.foo' ("<p [ERROR ->][sTyle.foo]>"): TestComp@0:3`);
expect(() => parse('<p [Class.foo]>', []))
.toThrowError(
`Template parse errors:\nInvalid property name 'Class.foo' ("<p [ERROR ->][Class.foo]>"): TestComp@0:3`);
expect(() => parse('<p [bar.foo]>', []))
.toThrowError(
`Template parse errors:\nInvalid property name 'bar.foo' ("<p [ERROR ->][bar.foo]>"): TestComp@0:3`);
});
describe('errors', () => { describe('errors', () => {
it('should throw error when binding to an unknown property', () => { it('should throw error when binding to an unknown property', () => {
expect(() => parse('<my-component [invalidProp]="bar"></my-component>', [])) expect(() => parse('<my-component [invalidProp]="bar"></my-component>', []))
@ -397,10 +388,8 @@ Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
2. If 'unknown' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<unknown></unknown>"): TestComp@0:0`); 2. If 'unknown' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<unknown></unknown>"): TestComp@0:0`);
}); });
it('should throw error when binding to an unknown custom element w/o bindings', it('should throw error when binding to an unknown custom element w/o bindings', () => {
() => { expect(() => parse('<un-known></un-known>', [])).toThrowError(`Template parse errors:
expect(() => parse('<un-known></un-known>', []))
.toThrowError(`Template parse errors:
'un-known' is not a known element: 'un-known' is not a known element:
1. If 'un-known' is an Angular component, then verify that it is part of this module. 1. If 'un-known' is an Angular component, then verify that it is part of this module.
2. If 'un-known' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<un-known></un-known>"): TestComp@0:0`); 2. If 'un-known' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<un-known></un-known>"): TestComp@0:0`);
@ -433,20 +422,16 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
]); ]);
}); });
it('should parse bound properties via {{...}} and not report them as attributes', it('should parse bound properties via {{...}} and not report them as attributes', () => {
() => {
expect(humanizeTplAst(parse('<div prop="{{v}}">', []))).toEqual([ expect(humanizeTplAst(parse('<div prop="{{v}}">', []))).toEqual([
[ElementAst, 'div'], [ElementAst, 'div'],
[ [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null]
BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null
]
]); ]);
}); });
it('should parse bound properties via bind-animate- and not report them as attributes', it('should parse bound properties via bind-animate- and not report them as attributes',
() => { () => {
expect( expect(humanizeTplAst(parse('<div bind-animate-someAnimation="value2">', [], [], [])))
humanizeTplAst(parse('<div bind-animate-someAnimation="value2">', [], [], [])))
.toEqual([ .toEqual([
[ElementAst, 'div'], [ElementAst, 'div'],
[ [
@ -565,12 +550,12 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
it('should allow events on explicit embedded templates that are emitted by a directive', it('should allow events on explicit embedded templates that are emitted by a directive',
() => { () => {
const dirA = CompileDirectiveMetadata const dirA =
CompileDirectiveMetadata
.create({ .create({
selector: 'template', selector: 'template',
outputs: ['e'], outputs: ['e'],
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
{reference: {filePath: someModuleUrl, name: 'DirA'}})
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([
@ -605,31 +590,31 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
describe('directives', () => { describe('directives', () => {
it('should order directives by the directives array in the View and match them only once', it('should order directives by the directives array in the View and match them only once',
() => { () => {
const dirA = CompileDirectiveMetadata const dirA =
CompileDirectiveMetadata
.create({ .create({
selector: '[a]', selector: '[a]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
{reference: {filePath: someModuleUrl, name: 'DirA'}})
}) })
.toSummary(); .toSummary();
const dirB = CompileDirectiveMetadata const dirB =
CompileDirectiveMetadata
.create({ .create({
selector: '[b]', selector: '[b]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirB'}})
{reference: {filePath: someModuleUrl, name: 'DirB'}})
}) })
.toSummary(); .toSummary();
const dirC = CompileDirectiveMetadata const dirC =
CompileDirectiveMetadata
.create({ .create({
selector: '[c]', selector: '[c]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirC'}})
{reference: {filePath: someModuleUrl, name: 'DirC'}})
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<div a c b a b>', [dirA, dirB, dirC]))).toEqual([ expect(humanizeTplAst(parse('<div a c b a b>', [dirA, dirB, dirC]))).toEqual([
[ElementAst, 'div'], [AttrAst, 'a', ''], [AttrAst, 'c', ''], [AttrAst, 'b', ''], [ElementAst, 'div'], [AttrAst, 'a', ''], [AttrAst, 'c', ''], [AttrAst, 'b', ''],
[AttrAst, 'a', ''], [AttrAst, 'b', ''], [DirectiveAst, dirA], [AttrAst, 'a', ''], [AttrAst, 'b', ''], [DirectiveAst, dirA], [DirectiveAst, dirB],
[DirectiveAst, dirB], [DirectiveAst, dirC] [DirectiveAst, dirC]
]); ]);
}); });
@ -723,8 +708,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<div [a]="expr"></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div [a]="expr"></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [DirectiveAst, dirA], [ElementAst, 'div'], [DirectiveAst, dirA], [BoundDirectivePropertyAst, 'b', 'expr']
[BoundDirectivePropertyAst, 'b', 'expr']
]); ]);
}); });
@ -830,8 +814,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
} }
function createDir( function createDir(
selector: string, selector: string, {providers = null, viewProviders = null, deps = [], queries = []}: {
{providers = null, viewProviders = null, deps = [], queries = []}: {
providers?: CompileProviderMetadata[], providers?: CompileProviderMetadata[],
viewProviders?: CompileProviderMetadata[], viewProviders?: CompileProviderMetadata[],
deps?: string[], deps?: string[],
@ -991,8 +974,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
it('should mark directives and dependencies of directives as eager', () => { it('should mark directives and dependencies of directives as eager', () => {
const provider0 = createProvider('service0'); const provider0 = createProvider('service0');
const provider1 = createProvider('service1'); const provider1 = createProvider('service1');
const dirA = const dirA = createDir('[dirA]', {providers: [provider0, provider1], deps: ['service0']});
createDir('[dirA]', {providers: [provider0, provider1], deps: ['service0']});
const elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0]; const elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0];
expect(elAst.providers.length).toBe(3); expect(elAst.providers.length).toBe(3);
expect(elAst.providers[0].providers).toEqual([provider0]); expect(elAst.providers[0].providers).toEqual([provider0]);
@ -1258,34 +1240,33 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
describe('directives', () => { describe('directives', () => {
it('should locate directives in property bindings', () => { it('should locate directives in property bindings', () => {
const dirA = CompileDirectiveMetadata const dirA =
CompileDirectiveMetadata
.create({ .create({
selector: '[a=b]', selector: '[a=b]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
{reference: {filePath: someModuleUrl, name: 'DirA'}}),
inputs: ['a'] inputs: ['a']
}) })
.toSummary(); .toSummary();
const dirB = CompileDirectiveMetadata const dirB =
CompileDirectiveMetadata
.create({ .create({
selector: '[b]', selector: '[b]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirB'}})
{reference: {filePath: someModuleUrl, name: 'DirB'}})
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<div template="a b" b>', [dirA, dirB]))).toEqual([ expect(humanizeTplAst(parse('<div template="a b" b>', [dirA, dirB]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, dirA], [EmbeddedTemplateAst], [DirectiveAst, dirA], [BoundDirectivePropertyAst, 'a', 'b'],
[BoundDirectivePropertyAst, 'a', 'b'], [ElementAst, 'div'], [AttrAst, 'b', ''], [ElementAst, 'div'], [AttrAst, 'b', ''], [DirectiveAst, dirB]
[DirectiveAst, dirB]
]); ]);
}); });
it('should not locate directives in variables', () => { it('should not locate directives in variables', () => {
const dirA = CompileDirectiveMetadata const dirA =
CompileDirectiveMetadata
.create({ .create({
selector: '[a]', selector: '[a]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
{reference: {filePath: someModuleUrl, name: 'DirA'}})
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<div template="let a=b">', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div template="let a=b">', [dirA]))).toEqual([
@ -1294,11 +1275,11 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should not locate directives in references', () => { it('should not locate directives in references', () => {
const dirA = CompileDirectiveMetadata const dirA =
CompileDirectiveMetadata
.create({ .create({
selector: '[a]', selector: '[a]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
{reference: {filePath: someModuleUrl, name: 'DirA'}})
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<div ref-a>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div ref-a>', [dirA]))).toEqual([
@ -1328,8 +1309,7 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
let compCounter: number; let compCounter: number;
beforeEach(() => { compCounter = 0; }); beforeEach(() => { compCounter = 0; });
function createComp( function createComp(selector: string, ngContentSelectors: string[]): CompileDirectiveSummary {
selector: string, ngContentSelectors: string[]): CompileDirectiveSummary {
return CompileDirectiveMetadata return CompileDirectiveMetadata
.create({ .create({
selector: selector, selector: selector,
@ -1353,9 +1333,8 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
describe('project text nodes', () => { describe('project text nodes', () => {
it('should project text nodes with wildcard selector', () => { it('should project text nodes with wildcard selector', () => {
expect(humanizeContentProjection(parse('<div>hello</div>', [ expect(humanizeContentProjection(parse('<div>hello</div>', [createComp('div', ['*'])])))
createComp('div', ['*']) .toEqual([['div', null], ['#text(hello)', 0]]);
]))).toEqual([['div', null], ['#text(hello)', 0]]);
}); });
}); });
@ -1428,10 +1407,9 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should project children of components with ngNonBindable', () => { it('should project children of components with ngNonBindable', () => {
expect( expect(humanizeContentProjection(parse('<div ngNonBindable>{{hello}}<span></span></div>', [
humanizeContentProjection(parse( createComp('div', ['*'])
'<div ngNonBindable>{{hello}}<span></span></div>', [createComp('div', ['*'])]))) ]))).toEqual([['div', null], ['#text({{hello}})', 0], ['span', 0]]);
.toEqual([['div', null], ['#text({{hello}})', 0], ['span', 0]]);
}); });
it('should match the element when there is an inline template', () => { it('should match the element when there is an inline template', () => {
@ -1510,8 +1488,7 @@ Can't have multiple template bindings on one element. Use only one attribute nam
}); });
it('should report invalid property names', () => { it('should report invalid property names', () => {
expect(() => parse('<div [invalidProp]></div>', [])) expect(() => parse('<div [invalidProp]></div>', [])).toThrowError(`Template parse errors:
.toThrowError(`Template parse errors:
Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ERROR ->][invalidProp]></div>"): TestComp@0:5`); Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ERROR ->][invalidProp]></div>"): TestComp@0:5`);
}); });
@ -1655,8 +1632,7 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should keep nested children of elements with ngNonBindable', () => { it('should keep nested children of elements with ngNonBindable', () => {
expect(humanizeTplAst(parse('<div ngNonBindable><span>{{b}}</span></div>', []))) expect(humanizeTplAst(parse('<div ngNonBindable><span>{{b}}</span></div>', []))).toEqual([
.toEqual([
[ElementAst, 'div'], [AttrAst, 'ngNonBindable', ''], [ElementAst, 'span'], [ElementAst, 'div'], [AttrAst, 'ngNonBindable', ''], [ElementAst, 'span'],
[TextAst, '{{b}}'] [TextAst, '{{b}}']
]); ]);
@ -1680,11 +1656,10 @@ Property binding a not used by any directive on an embedded template. Make sure
it('should convert <ng-content> elements into regular elements inside of elements with ngNonBindable', it('should convert <ng-content> elements into regular elements inside of elements with ngNonBindable',
() => { () => {
expect( expect(humanizeTplAst(parse('<div ngNonBindable><ng-content></ng-content>a</div>', [])))
humanizeTplAst(parse('<div ngNonBindable><ng-content></ng-content>a</div>', [])))
.toEqual([ .toEqual([
[ElementAst, 'div'], [AttrAst, 'ngNonBindable', ''], [ElementAst, 'div'], [AttrAst, 'ngNonBindable', ''], [ElementAst, 'ng-content'],
[ElementAst, 'ng-content'], [TextAst, 'a'] [TextAst, 'a']
]); ]);
}); });
@ -1717,10 +1692,8 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support variables', () => { it('should support variables', () => {
expect(humanizeTplAstSourceSpans(parse('<template let-a="b"></template>', []))) expect(humanizeTplAstSourceSpans(parse('<template let-a="b"></template>', []))).toEqual([
.toEqual([ [EmbeddedTemplateAst, '<template let-a="b">'], [VariableAst, 'a', 'b', 'let-a="b"']
[EmbeddedTemplateAst, '<template let-a="b">'],
[VariableAst, 'a', 'b', 'let-a="b"']
]); ]);
}); });
@ -1769,8 +1742,8 @@ Property binding a not used by any directive on an embedded template. Make sure
}) })
.toSummary(); .toSummary();
expect(humanizeTplAstSourceSpans(parse('<div a>', [dirA, comp]))).toEqual([ expect(humanizeTplAstSourceSpans(parse('<div a>', [dirA, comp]))).toEqual([
[ElementAst, 'div', '<div a>'], [AttrAst, 'a', '', 'a'], [ElementAst, 'div', '<div a>'], [AttrAst, 'a', '', 'a'], [DirectiveAst, dirA, '<div a>'],
[DirectiveAst, dirA, '<div a>'], [DirectiveAst, comp, '<div a>'] [DirectiveAst, comp, '<div a>']
]); ]);
}); });
@ -1782,11 +1755,11 @@ Property binding a not used by any directive on an embedded template. Make sure
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'elDir'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'elDir'}})
}) })
.toSummary(); .toSummary();
const attrSel = CompileDirectiveMetadata const attrSel =
CompileDirectiveMetadata
.create({ .create({
selector: '[href]', selector: '[href]',
type: createTypeMeta( type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'attrDir'}})
{reference: {filePath: someModuleUrl, name: 'attrDir'}})
}) })
.toSummary(); .toSummary();
@ -1812,8 +1785,7 @@ Property binding a not used by any directive on an embedded template. Make sure
}) })
.toSummary(); .toSummary();
expect(humanizeTplAstSourceSpans(parse('<div [aProp]="foo"></div>', [dirA]))).toEqual([ expect(humanizeTplAstSourceSpans(parse('<div [aProp]="foo"></div>', [dirA]))).toEqual([
[ElementAst, 'div', '<div [aProp]="foo">'], [ElementAst, 'div', '<div [aProp]="foo">'], [DirectiveAst, dirA, '<div [aProp]="foo">'],
[DirectiveAst, dirA, '<div [aProp]="foo">'],
[BoundDirectivePropertyAst, 'aProp', 'foo', '[aProp]="foo"'] [BoundDirectivePropertyAst, 'aProp', 'foo', '[aProp]="foo"']
]); ]);
}); });
@ -1880,8 +1852,8 @@ The pipe 'test' could not be found ("[ERROR ->]{{a | test}}"): TestComp@0:0`);
'<template ngPluralCase="many">big</template>' + '<template ngPluralCase="many">big</template>' +
'</ng-container>'; '</ng-container>';
expect(humanizeTplAst(parse(shortForm, [ expect(humanizeTplAst(parse(shortForm, []))).toEqual(humanizeTplAst(parse(expandedForm, [
]))).toEqual(humanizeTplAst(parse(expandedForm, []))); ])));
}); });
it('should expand select messages', () => { it('should expand select messages', () => {
@ -1891,8 +1863,8 @@ The pipe 'test' could not be found ("[ERROR ->]{{a | test}}"): TestComp@0:0`);
'<template ngSwitchDefault>bar</template>' + '<template ngSwitchDefault>bar</template>' +
'</ng-container>'; '</ng-container>';
expect(humanizeTplAst(parse(shortForm, [ expect(humanizeTplAst(parse(shortForm, []))).toEqual(humanizeTplAst(parse(expandedForm, [
]))).toEqual(humanizeTplAst(parse(expandedForm, []))); ])));
}); });
it('should be possible to escape ICU messages', () => { it('should be possible to escape ICU messages', () => {