diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 6ddae7974d..3435abc781 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {InitialStylingFlags} from '@angular/compiler/src/core'; +import {AttributeMarker, InitialStylingFlags} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; @@ -200,7 +200,7 @@ describe('compiler compliance', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` - const $c1$ = ["class", "my-app", 0, "http://someuri/foo", "foo:bar", "baz", "title", "Hello", 0, "http://someuri/foo", "foo:qux", "quacks"]; + const $e0_attrs$ = ["class", "my-app", 0, "http://someuri/foo", "foo:bar", "baz", "title", "Hello", 0, "http://someuri/foo", "foo:qux", "quacks"]; … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -315,9 +315,11 @@ describe('compiler compliance', () => { const factory = 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "id"]; + … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "div"); + $r3$.ɵelement(0, "div", $e0_attrs$); } if (rf & 2) { $r3$.ɵelementProperty(0, "id", $r3$.ɵbind(ctx.id)); @@ -357,19 +359,20 @@ describe('compiler compliance', () => { } }; + const $e0_attrs$ = []; const factory = 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` template: function MyComponent_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "div"); + $r3$.ɵelement(0, "div", $e0_attrs$); $r3$.ɵpipe(1,"pipe"); } if (rf & 2) { - $r3$.ɵelementProperty(0, "ternary", $r3$.ɵbind((ctx.cond ? $r3$.ɵpureFunction1(8, _c0, ctx.a): _c1))); + $r3$.ɵelementProperty(0, "ternary", $r3$.ɵbind((ctx.cond ? $r3$.ɵpureFunction1(8, $c0$, ctx.a): $c1$))); $r3$.ɵelementProperty(0, "pipe", $r3$.ɵbind($r3$.ɵpipeBind3(1, 4, ctx.value, 1, 2))); - $r3$.ɵelementProperty(0, "and", $r3$.ɵbind((ctx.cond && $r3$.ɵpureFunction1(10, _c0, ctx.b)))); - $r3$.ɵelementProperty(0, "or", $r3$.ɵbind((ctx.cond || $r3$.ɵpureFunction1(12, _c0, ctx.c)))); + $r3$.ɵelementProperty(0, "and", $r3$.ɵbind((ctx.cond && $r3$.ɵpureFunction1(10, $c0$, ctx.b)))); + $r3$.ɵelementProperty(0, "or", $r3$.ɵbind((ctx.cond || $r3$.ɵpureFunction1(12, $c0$, ctx.c)))); } } `; @@ -705,6 +708,7 @@ describe('compiler compliance', () => { }; const MyAppDeclaration = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "names"]; const $e0_ff$ = function ($v$) { return ["Nancy", $v$]; }; … MyApp.ngComponentDef = $r3$.ɵdefineComponent({ @@ -716,7 +720,7 @@ describe('compiler compliance', () => { vars: 3, template: function MyApp_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "my-comp"); + $r3$.ɵelement(0, "my-comp", $e0_attrs$); } if (rf & 2) { $r3$.ɵelementProperty(0, "names", $r3$.ɵbind($r3$.ɵpureFunction1(1, $e0_ff$, ctx.customName))); @@ -784,6 +788,7 @@ describe('compiler compliance', () => { }; const MyAppDefinition = ` + const $e0_attr$ = [${AttributeMarker.SelectOnly}, "names"]; const $e0_ff$ = function ($v0$, $v1$, $v2$, $v3$, $v4$, $v5$, $v6$, $v7$, $v8$) { return ["start-", $v0$, $v1$, $v2$, $v3$, $v4$, "-middle-", $v5$, $v6$, $v7$, $v8$, "-end"]; } @@ -797,7 +802,7 @@ describe('compiler compliance', () => { vars: 11, template: function MyApp_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "my-comp"); + $r3$.ɵelement(0, "my-comp", $e0_attr$); } if (rf & 2) { $r3$.ɵelementProperty( @@ -849,6 +854,7 @@ describe('compiler compliance', () => { }; const MyAppDefinition = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "config"]; const $e0_ff$ = function ($v$) { return {"duration": 500, animation: $v$}; }; … MyApp.ngComponentDef = $r3$.ɵdefineComponent({ @@ -860,7 +866,7 @@ describe('compiler compliance', () => { vars: 3, template: function MyApp_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "object-comp"); + $r3$.ɵelement(0, "object-comp", $e0_attrs$); } if (rf & 2) { $r3$.ɵelementProperty(0, "config", $r3$.ɵbind($r3$.ɵpureFunction1(1, $e0_ff$, ctx.name))); @@ -913,6 +919,7 @@ describe('compiler compliance', () => { }; const MyAppDefinition = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "config"]; const $c0$ = {opacity: 0, duration: 0}; const $e0_ff$ = function ($v$) { return {opacity: 1, duration: $v$}; }; const $e0_ff_1$ = function ($v$) { return [$c0$, $v$]; }; @@ -927,7 +934,7 @@ describe('compiler compliance', () => { vars: 8, template: function MyApp_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "nested-comp"); + $r3$.ɵelement(0, "nested-comp", $e0_attrs$); } if (rf & 2) { $r3$.ɵelementProperty( @@ -1433,9 +1440,9 @@ describe('compiler compliance', () => { }; const template = ` - const $c0$ = ["ngFor","","ngForOf",""]; + const $c0$ = ["ngFor", "" , ${AttributeMarker.SelectOnly}, "ngForOf"]; const $c1$ = ["foo", ""]; - const $c2$ = ["ngIf",""]; + const $c2$ = [${AttributeMarker.SelectOnly}, "ngIf"]; function MyComponent_div_span_Template_3(rf, ctx) { if (rf & 1) { @@ -1545,8 +1552,8 @@ describe('compiler compliance', () => { vars: 2, template: function SimpleLayout_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelement(0, "lifecycle-comp"); - $r3$.ɵelement(1, "lifecycle-comp"); + $r3$.ɵelement(0, "lifecycle-comp", $e0_attrs$); + $r3$.ɵelement(1, "lifecycle-comp", $e1_attrs$); } if (rf & 2) { $r3$.ɵelementProperty(0, "name", $r3$.ɵbind(ctx.name1)); @@ -1656,7 +1663,7 @@ describe('compiler compliance', () => { `; const MyComponentDefinition = ` - const $_c0$ = ["for","","forOf",""]; + const $t1_attrs$ = ["for", "", ${AttributeMarker.SelectOnly}, "forOf"]; function MyComponent__svg_g_Template_1(rf, ctx) { if (rf & 1) { $r3$.ɵnamespaceSVG(); @@ -1677,7 +1684,7 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵnamespaceSVG(); $r3$.ɵelementStart(0,"svg"); - $r3$.ɵtemplate(1, MyComponent__svg_g_Template_1, 2, 0, null, $_c0$); + $r3$.ɵtemplate(1, MyComponent__svg_g_Template_1, 2, 0, null, $t1_attrs$); $r3$.ɵelementEnd(); } if (rf & 2) { $r3$.ɵelementProperty(1,"forOf",$r3$.ɵbind(ctx.items)); } @@ -1732,7 +1739,7 @@ describe('compiler compliance', () => { `; const MyComponentDefinition = ` - const $_c0$ = ["for","","forOf",""]; + const $t1_attrs$ = ["for", "", ${AttributeMarker.SelectOnly}, "forOf"]; function MyComponent_li_Template_1(rf, ctx) { if (rf & 1) { $r3$.ɵelementStart(0, "li"); @@ -1755,7 +1762,7 @@ describe('compiler compliance', () => { template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵelementStart(0, "ul"); - $r3$.ɵtemplate(1, MyComponent_li_Template_1, 2, 1, null, $_c0$); + $r3$.ɵtemplate(1, MyComponent_li_Template_1, 2, 1, null, $t1_attrs$); $r3$.ɵelementEnd(); } if (rf & 2) { @@ -1812,7 +1819,7 @@ describe('compiler compliance', () => { }; const MyComponentDefinition = ` - const $c1$ = ["for", "", "forOf", ""]; + const $t4_attrs$ = ["for", "", ${AttributeMarker.SelectOnly}, "forOf"]; function MyComponent_li_li_Template_4(rf, ctx) { if (rf & 1) { $r3$.ɵelementStart(0, "li"); @@ -1833,7 +1840,7 @@ describe('compiler compliance', () => { $r3$.ɵtext(2); $r3$.ɵelementEnd(); $r3$.ɵelementStart(3, "ul"); - $r3$.ɵtemplate(4, MyComponent_li_li_Template_4, 2, 2, null, $c1$); + $r3$.ɵtemplate(4, MyComponent_li_li_Template_4, 2, 2, null, $t4_attrs$); $r3$.ɵelementEnd(); $r3$.ɵelementEnd(); } diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index 43235bbc61..24923b575f 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - +import {AttributeMarker} from '@angular/compiler/src/core'; import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; @@ -73,9 +73,11 @@ describe('compiler compliance: bindings', () => { }; const template = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "title"]; + … template:function MyComponent_Template(rf, $ctx$){ if (rf & 1) { - $i0$.ɵelement(0, "a"); + $i0$.ɵelement(0, "a", $e0_attrs$); } if (rf & 2) { $i0$.ɵelementProperty(0, "title", $i0$.ɵbind($ctx$.title)); @@ -105,9 +107,11 @@ describe('compiler compliance: bindings', () => { }; const template = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "title"]; + … template:function MyComponent_Template(rf, $ctx$){ if (rf & 1) { - $i0$.ɵelement(0, "a"); + $i0$.ɵelement(0, "a", $e0_attrs$); } if (rf & 2) { $i0$.ɵelementProperty(0, "title", $i0$.ɵinterpolation1("Hello ", $ctx$.name, "")); diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts new file mode 100644 index 0000000000..26e650aa57 --- /dev/null +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts @@ -0,0 +1,299 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {AttributeMarker} from '@angular/compiler/src/core'; +import {setup} from '@angular/compiler/test/aot/test_util'; +import {compile, expectEmit} from './mock_compile'; + +describe('compiler compliance: directives', () => { + + const angularFiles = setup({ + compileAngular: false, + compileAnimations: false, + compileFakeCore: true, + }); + + describe('matching', () => { + + it('should not match directives on i18n attribute', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, Input, NgModule} from '@angular/core'; + + @Directive({selector: '[i18n]'}) + export class I18nDirective {} + + @Component({selector: 'my-component', template: '
'}) + export class MyComponent {} + + @NgModule({declarations: [I18nDirective, MyComponent]}) + export class MyModule{}` + } + }; + + // MyComponent definition should be: + const MyComponentDefinition = ` + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + selectors: [["my-component"]], + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, + features: [$r3$.ɵPublicFeature], + consts: 1, + vars: 0, + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵelement(0, "div"); + } + } + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); + }); + + it('should not match directives on i18n-prefixed attributes', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, Input, NgModule} from '@angular/core'; + + @Directive({selector: '[i18n]'}) + export class I18nDirective {} + + @Directive({selector: '[i18n-foo]'}) + export class I18nFooDirective {} + + @Directive({selector: '[foo]'}) + export class FooDirective {} + + @Component({selector: 'my-component', template: '
'}) + export class MyComponent {} + + @NgModule({declarations: [I18nDirective, I18nFooDirective, FooDirective, MyComponent]}) + export class MyModule{}` + } + }; + + // MyComponent definition should be: + const MyComponentDefinition = ` + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + selectors: [["my-component"]], + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, + features: [$r3$.ɵPublicFeature], + consts: 1, + vars: 0, + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵelement(0, "div"); + } + } + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); + }); + + it('should match directives on element bindings', () => { + + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, Input, NgModule} from '@angular/core'; + + @Directive({selector: '[someDirective]'}) + export class SomeDirective { + @Input() someDirective; + } + + @Component({selector: 'my-component', template: '
'}) + export class MyComponent {} + + @NgModule({declarations: [SomeDirective, MyComponent]}) + export class MyModule{} + ` + } + }; + + + // MyComponent definition should be: + const MyComponentDefinition = ` + … + const _c0 = [${AttributeMarker.SelectOnly}, "someDirective"]; + … + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + … + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵelement(0, "div", _c0); + } + if (rf & 2) { + $r3$.ɵelementProperty(0, "someDirective", $r3$.ɵbind(true)); + } + }, + … + directives: [SomeDirective] + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); + }); + + it('should match directives on ng-template bindings', () => { + + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, Input, NgModule} from '@angular/core'; + + @Directive({selector: '[someDirective]'}) + export class SomeDirective { + @Input() someDirective; + } + + @Component({selector: 'my-component', template: ''}) + export class MyComponent {} + + @NgModule({declarations: [SomeDirective, MyComponent]}) + export class MyModule{} + ` + } + }; + + + // MyComponent definition should be: + const MyComponentDefinition = ` + … + const $c0_a0$ = [${AttributeMarker.SelectOnly}, "someDirective"]; + … + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + … + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵtemplate(0, Template_0, 0, 0, null, $c0_a0$); + } + if (rf & 2) { + $r3$.ɵelementProperty(0, "someDirective", $r3$.ɵbind(true)); + } + }, + … + directives: [SomeDirective] + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); + }); + + it('should match structural directives', () => { + + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, Input, NgModule} from '@angular/core'; + + @Directive({selector: '[someDirective]'}) + export class SomeDirective { + @Input() someDirective; + } + + @Component({selector: 'my-component', template: '
'}) + export class MyComponent {} + + @NgModule({declarations: [SomeDirective, MyComponent]}) + export class MyModule{} + ` + } + }; + + // MyComponent definition should be: + const MyComponentDefinition = ` + … + const $c0_a0$ = ["someDirective", ""]; + … + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + … + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵtemplate(0, MyComponent_div_Template_0, 1, 0, null, $c0_a0$); + } + }, + … + directives: [SomeDirective] + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); + + }); + + it('should match directives on element outputs', () => { + + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, Output, EventEmitter, NgModule} from '@angular/core'; + + @Directive({selector: '[someDirective]'}) + export class SomeDirective { + @Output() someDirective = new EventEmitter(); + } + + @Component({selector: 'my-component', template: '
'}) + export class MyComponent { + noop() {} + } + + @NgModule({declarations: [SomeDirective, MyComponent]}) + export class MyModule{} + ` + } + }; + + + // MyComponent definition should be: + const MyComponentDefinition = ` + … + const $c0_a0$ = [${AttributeMarker.SelectOnly}, "someDirective"]; + … + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + … + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵelementStart(0, "div", $c0_a0$); + $r3$.ɵlistener("someDirective", function MyComponent_Template_div_someDirective_listener($event) { return ctx.noop(); }); + $r3$.ɵelementEnd(); + } + }, + … + directives: [SomeDirective] + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); + }); + + }); +}); \ No newline at end of file diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts index 582627cec7..2b4bb092ab 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts @@ -5,8 +5,8 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - -import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util'; +import {AttributeMarker} from '@angular/compiler/src/core'; +import {setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; /* These tests are codified version of the tests in compiler_canonical_spec.ts. Every @@ -41,9 +41,11 @@ describe('compiler compliance: listen()', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "click"]; + … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelementStart(0, "div"); + $r3$.ɵelementStart(0, "div", $e0_attrs$); $r3$.ɵlistener("click", function MyComponent_Template_div_click_listener($event) { ctx.onClick($event); return (1 == 2); @@ -86,20 +88,21 @@ describe('compiler compliance: listen()', () => { }; const template = ` - const $c0$ = ["ngIf",""]; + const $t0_attrs$ = [${AttributeMarker.SelectOnly}, "ngIf"]; + const $e_attrs$ = [${AttributeMarker.SelectOnly}, "click"]; function MyComponent_div_Template_0(rf, ctx) { if (rf & 1) { const $s$ = $r3$.ɵgetCurrentView(); $r3$.ɵelementStart(0, "div"); - $r3$.ɵelementStart(1, "div"); + $r3$.ɵelementStart(1, "div", $e_attrs$); $r3$.ɵlistener("click", function MyComponent_div_Template_0_div_click_listener($event) { $r3$.ɵrestoreView($s$); const $comp$ = $r3$.ɵnextContext(); return $comp$.onClick($comp$.foo); }); $r3$.ɵelementEnd(); - $r3$.ɵelementStart(2, "button"); + $r3$.ɵelementStart(2, "button", $e_attrs$); $r3$.ɵlistener("click", function MyComponent_div_Template_0_button_click_listener($event) { $r3$.ɵrestoreView($s$); const $comp2$ = $r3$.ɵnextContext(); @@ -147,7 +150,8 @@ describe('compiler compliance: listen()', () => { }; const MyComponentDefinition = ` - const $c0$ = ["user", ""]; + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "click"]; + const $e2_refs$ = ["user", ""]; … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -158,14 +162,14 @@ describe('compiler compliance: listen()', () => { vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵelementStart(0, "button"); + $r3$.ɵelementStart(0, "button", $e0_attrs$); $r3$.ɵlistener("click", function MyComponent_Template_button_click_listener($event) { const $user$ = $r3$.ɵreference(3); return ctx.onClick($user$.value); }); $r3$.ɵtext(1, "Save"); $r3$.ɵelementEnd(); - $r3$.ɵelement(2, "input", null, $c0$); + $r3$.ɵelement(2, "input", null, $e2_refs$); } } }); diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index 09726efddb..816c796dd2 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core'; -import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util'; - +import {AttributeMarker, InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core'; +import {setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; describe('compiler compliance: styling', () => { @@ -168,7 +167,8 @@ describe('compiler compliance: styling', () => { }; const template = ` - const _c0 = ["opacity","width","height",${InitialStylingFlags.VALUES_MODE},"opacity","1"]; + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "style"]; + const $e0_styling$ = ["opacity","width","height",${InitialStylingFlags.VALUES_MODE},"opacity","1"]; … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -181,8 +181,8 @@ describe('compiler compliance: styling', () => { vars: 1, template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { - $r3$.ɵelementStart(0, "div"); - $r3$.ɵelementStyling(null, _c0, $r3$.ɵzss); + $r3$.ɵelementStart(0, "div", $e0_attrs$); + $r3$.ɵelementStyling(null, $e0_styling$, $r3$.ɵzss); $r3$.ɵelementEnd(); } if (rf & 2) { @@ -324,7 +324,8 @@ describe('compiler compliance: styling', () => { }; const template = ` - const _c0 = ["grape","apple","orange",${InitialStylingFlags.VALUES_MODE},"grape",true]; + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class"]; + const $e0_cd$ = ["grape","apple","orange",${InitialStylingFlags.VALUES_MODE},"grape",true]; … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -337,8 +338,8 @@ describe('compiler compliance: styling', () => { vars: 1, template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { - $r3$.ɵelementStart(0, "div"); - $r3$.ɵelementStyling(_c0); + $r3$.ɵelementStart(0, "div", $e0_attrs$); + $r3$.ɵelementStyling($e0_cd$); $r3$.ɵelementEnd(); } if (rf & 2) { @@ -379,8 +380,9 @@ describe('compiler compliance: styling', () => { }; const template = ` - const _c0 = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true]; - const _c1 = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"]; + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class", "style"]; + const $e0_cd$ = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true]; + const $e0_sd$ = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"]; … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -393,8 +395,8 @@ describe('compiler compliance: styling', () => { vars: 2, template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { - $r3$.ɵelementStart(0, "div"); - $r3$.ɵelementStyling(_c0, _c1); + $r3$.ɵelementStart(0, "div", $e0_attrs$); + $r3$.ɵelementStyling($e0_cd$, $e0_sd$); $r3$.ɵelementEnd(); } if (rf & 2) { diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts index 45623362ab..6e7b5e795c 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - +import {AttributeMarker} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; @@ -50,12 +50,13 @@ describe('compiler compliance: template', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` - const $c0$ = ["ngFor","","ngForOf",""]; + const $c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"]; + const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "title", "click"]; function MyComponent_ul_li_div_Template_1(rf, ctx) { if (rf & 1) { const $s$ = $i0$.ɵgetCurrentView(); - $i0$.ɵelementStart(0, "div"); + $i0$.ɵelementStart(0, "div", $e0_attrs$); $i0$.ɵlistener("click", function MyComponent_ul_li_div_Template_1_div_click_listener($event){ $i0$.ɵrestoreView($s$); const $inner$ = ctx.$implicit; @@ -138,7 +139,7 @@ describe('compiler compliance: template', () => { }; const template = ` - const $c0$ = ["ngFor", "", "ngForOf", ""]; + const $c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"]; function MyComponent_span_Template_0(rf, ctx) { if (rf & 1) { @@ -191,8 +192,8 @@ describe('compiler compliance: template', () => { }; const template = ` - const $c0$ = ["ngFor", "", "ngForOf", ""]; - const $c1$ = ["ngIf", ""]; + const $c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"]; + const $c1$ = [${AttributeMarker.SelectOnly}, "ngIf"]; function MyComponent_div_span_Template_1(rf, ctx) { if (rf & 1) { @@ -262,7 +263,7 @@ describe('compiler compliance: template', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` - const $c0$ = ["ngFor", "", "ngForOf", ""]; + const $c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"]; function MyComponent_div_div_div_Template_1(rf, ctx) { if (rf & 1) { $i0$.ɵelementStart(0, "div"); @@ -336,7 +337,7 @@ describe('compiler compliance: template', () => { }; const template = ` - const $c0$ = ["attr", "", "boundAttr", ""]; + const $c0$ = ["attr", "l", ${AttributeMarker.SelectOnly}, "boundAttr"]; function Template_0(rf, ctx) { if (rf & 1) { @@ -380,7 +381,7 @@ describe('compiler compliance: template', () => { }; const template = ` - const _c0 = ["foo", ""]; + const $t0_refs$ = ["foo", ""]; function Template_0(rf, ctx) { if (rf & 1) { @@ -392,7 +393,7 @@ describe('compiler compliance: template', () => { template: function MyComponent_Template(rf, ctx) { if (rf & 1) { - $i0$.ɵtemplate(0, Template_0, 1, 0, null, null, _c0, i0.ɵtemplateRefExtractor); + $i0$.ɵtemplate(0, Template_0, 1, 0, null, null, $t0_refs$, $i0$.ɵtemplateRefExtractor); } }`; diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index e34aa4c1c0..58577c9039 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -383,3 +383,25 @@ export const enum RenderFlags { export const enum InitialStylingFlags { VALUES_MODE = 0b1, } + +// Pasted from render3/interfaces/node.ts +/** + * A set of marker values to be used in the attributes arrays. Those markers indicate that some + * items are not regular attributes and the processing should be adapted accordingly. + */ +export const enum AttributeMarker { + /** + * Marker indicates that the following 3 values in the attributes array are: + * namespaceUri, attributeName, attributeValue + * in that order. + */ + NamespaceURI = 0, + + /** + * This marker indicates that the following attribute names were extracted from bindings (ex.: + * [foo]="exp") and / or event handlers (ex. (bar)="doSth()"). + * Taking the above bindings and outputs as an example an attributes array could look as follows: + * ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar'] + */ + SelectOnly = 1 +} \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_ast.ts b/packages/compiler/src/render3/r3_ast.ts index 52767919ad..792f5a31e9 100644 --- a/packages/compiler/src/render3/r3_ast.ts +++ b/packages/compiler/src/render3/r3_ast.ts @@ -71,10 +71,10 @@ export class Element implements Node { export class Template implements Node { constructor( - public attributes: TextAttribute[], public inputs: BoundAttribute[], public children: Node[], - public references: Reference[], public variables: Variable[], - public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null, - public endSourceSpan: ParseSourceSpan|null) {} + public attributes: TextAttribute[], public inputs: BoundAttribute[], + public outputs: BoundEvent[], public children: Node[], public references: Reference[], + public variables: Variable[], public sourceSpan: ParseSourceSpan, + public startSourceSpan: ParseSourceSpan|null, public endSourceSpan: ParseSourceSpan|null) {} visit(visitor: Visitor): Result { return visitor.visitTemplate(this); } } @@ -167,6 +167,7 @@ export class TransformVisitor implements Visitor { visitTemplate(template: Template): Node { const newAttributes = transformAll(this, template.attributes); const newInputs = transformAll(this, template.inputs); + const newOutputs = transformAll(this, template.outputs); const newChildren = transformAll(this, template.children); const newReferences = transformAll(this, template.references); const newVariables = transformAll(this, template.variables); @@ -174,8 +175,8 @@ export class TransformVisitor implements Visitor { newChildren != template.children || newVariables != template.variables || newReferences != template.references) { return new Template( - newAttributes, newInputs, newChildren, newReferences, newVariables, template.sourceSpan, - template.startSourceSpan, template.endSourceSpan); + newAttributes, newInputs, newOutputs, newChildren, newReferences, newVariables, + template.sourceSpan, template.startSourceSpan, template.endSourceSpan); } return template; } diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index 22c31d8bcb..cabf6ae64a 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -181,7 +181,7 @@ class HtmlAstToIvyAst implements html.Visitor { const attrs = this.extractAttributes(element.name, parsedProperties); parsedElement = new t.Template( - attributes, attrs.bound, children, references, variables, element.sourceSpan, + attributes, attrs.bound, boundEvents, children, references, variables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan); } else { const attrs = this.extractAttributes(element.name, parsedProperties); @@ -193,9 +193,10 @@ class HtmlAstToIvyAst implements html.Visitor { if (elementHasInlineTemplate) { const attrs = this.extractAttributes('ng-template', templateParsedProperties); + // TODO(pk): test for this case parsedElement = new t.Template( - attrs.literal, attrs.bound, [parsedElement], [], templateVariables, element.sourceSpan, - element.startSourceSpan, element.endSourceSpan); + attrs.literal, attrs.bound, [], [parsedElement], [], templateVariables, + element.sourceSpan, element.startSourceSpan, element.endSourceSpan); } return parsedElement; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index ffe9fe3801..28968f4c88 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -30,7 +30,7 @@ import {htmlAstToRender3Ast} from '../r3_template_transform'; import {R3QueryMetadata} from './api'; import {parseStyle} from './styling'; -import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, invalid, mapToExpression, trimTrailingNulls, unsupported} from './util'; +import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, invalid, isI18NAttribute, mapToExpression, trimTrailingNulls, unsupported} from './util'; function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined { switch (type) { @@ -317,11 +317,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } // Match directives on non i18n attributes - if (this.directiveMatcher) { - const selector = createCssSelector(element.name, outputAttrs); - this.directiveMatcher.match( - selector, (sel: CssSelector, staticType: any) => { this.directives.add(staticType); }); - } + this.matchDirectives(element.name, element); // Regular element or ng-container creation mode const parameters: o.Expression[] = [o.literal(elementIndex)]; @@ -455,10 +451,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const hasStylingInstructions = initialStyleDeclarations.length || styleInputs.length || initialClassDeclarations.length || classInputs.length; - const attrArg: o.Expression = attributes.length > 0 ? - this.constantPool.getConstLiteral(o.literalArr(attributes), true) : - o.TYPED_NULL_EXPR; - parameters.push(attrArg); + // add attributes for directive matching purposes + attributes.push(...this.prepareSelectOnlyAttrs(allOtherInputs, element.outputs)); + parameters.push(this.toAttrsParam(attributes)); // local refs (ex.:
) parameters.push(this.prepareRefsParameter(element.references)); @@ -690,31 +685,15 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver o.TYPED_NULL_EXPR, ]; - // Match directives on both attributes and bound properties - const attributeNames: o.Expression[] = []; - const attributeMap: {[name: string]: string} = {}; + // find directives matching on a given node + this.matchDirectives('ng-template', template); - template.attributes.forEach(a => { - attributeNames.push(asLiteral(a.name), asLiteral('')); - attributeMap[a.name] = a.value; - }); - - template.inputs.forEach(i => { - attributeNames.push(asLiteral(i.name), asLiteral('')); - attributeMap[i.name] = ''; - }); - - if (this.directiveMatcher) { - const selector = createCssSelector('ng-template', attributeMap); - this.directiveMatcher.match( - selector, (cssSelector, staticType) => { this.directives.add(staticType); }); - } - - if (attributeNames.length) { - parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true)); - } else { - parameters.push(o.TYPED_NULL_EXPR); - } + // prepare attributes parameter (including attributes used for directive matching) + const attrsExprs: o.Expression[] = []; + template.attributes.forEach( + (a: t.TextAttribute) => { attrsExprs.push(asLiteral(a.name), asLiteral(a.value)); }); + attrsExprs.push(...this.prepareSelectOnlyAttrs(template.inputs, template.outputs)); + parameters.push(this.toAttrsParam(attrsExprs)); // local refs (ex.: ) if (template.references && template.references.length) { @@ -722,7 +701,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver parameters.push(o.importExpr(R3.templateRefExtractor)); } - // e.g. p(1, 'forOf', ɵbind(ctx.items)); + // handle property bindings e.g. p(1, 'forOf', ɵbind(ctx.items)); const context = o.variable(CONTEXT_NAME); template.inputs.forEach(input => { const value = input.value.visit(this._valueConverter); @@ -859,6 +838,47 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver o.importExpr(R3.bind).callFn([valExpr]); } + private matchDirectives(tagName: string, elOrTpl: t.Element|t.Template) { + if (this.directiveMatcher) { + const selector = createCssSelector(tagName, this.getAttrsForDirectiveMatching(elOrTpl)); + this.directiveMatcher.match( + selector, (cssSelector, staticType) => { this.directives.add(staticType); }); + } + } + + private getAttrsForDirectiveMatching(elOrTpl: t.Element|t.Template): {[name: string]: string} { + const attributesMap: {[name: string]: string} = {}; + + elOrTpl.attributes.forEach(a => { + if (!isI18NAttribute(a.name)) { + attributesMap[a.name] = a.value; + } + }); + elOrTpl.inputs.forEach(i => { attributesMap[i.name] = ''; }); + elOrTpl.outputs.forEach(o => { attributesMap[o.name] = ''; }); + + return attributesMap; + } + + private prepareSelectOnlyAttrs(inputs: t.BoundAttribute[], outputs: t.BoundEvent[]): + o.Expression[] { + const attrExprs: o.Expression[] = []; + + if (inputs.length || outputs.length) { + attrExprs.push(o.literal(core.AttributeMarker.SelectOnly)); + inputs.forEach((i: t.BoundAttribute) => { attrExprs.push(asLiteral(i.name)); }); + outputs.forEach((o: t.BoundEvent) => { attrExprs.push(asLiteral(o.name)); }); + } + + return attrExprs; + } + + private toAttrsParam(attrsExprs: o.Expression[]): o.Expression { + return attrsExprs.length > 0 ? + this.constantPool.getConstLiteral(o.literalArr(attrsExprs), true) : + o.TYPED_NULL_EXPR; + } + private prepareRefsParameter(references: t.Reference[]): o.Expression { if (!references || references.length === 0) { return o.TYPED_NULL_EXPR; diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts index 20e548515a..c5462e90c7 100644 --- a/packages/compiler/src/render3/view/util.ts +++ b/packages/compiler/src/render3/view/util.ts @@ -64,6 +64,10 @@ export function invalid(arg: o.Expression | o.Statement | t.Node): never { `Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`); } +export function isI18NAttribute(name: string): boolean { + return name === I18N_ATTR || name.startsWith(I18N_ATTR_PREFIX); +} + export function asLiteral(value: any): o.Expression { if (Array.isArray(value)) { return o.literalArr(value.map(asLiteral)); diff --git a/packages/core/src/render3/STATUS.md b/packages/core/src/render3/STATUS.md index f3d1550331..50cb4bffea 100644 --- a/packages/core/src/render3/STATUS.md +++ b/packages/core/src/render3/STATUS.md @@ -157,9 +157,9 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S | `
` | ❌ | ❌ | ❌ | | `
` | ❌ | ❌ | ❌ | | `
` | ❌ | ❌ | ❌ | -| [`
`][gh23560] | ✅ | ❌ | ❌ | +| [`
`][gh23560] | ✅ | ✅ | ✅ | | [``][gh23561] | ❌ | ❌ | ❌ | -| [``][gh24381] | ❌ | ❌ | ❌ | +| [``][gh24381] | ✅ | ✅ | ✅ | [gh23560]: https://github.com/angular/angular/issues/23560 [gh23561]: https://github.com/angular/angular/issues/23561 diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index e756c47002..cd7c5e35b5 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -85,8 +85,8 @@ export function compileComponent(type: Type, metadata: Component): void { // If component compilation is async, then the @NgModule annotation which declares the // component may execute and set an ngSelectorScope property on the component type. This - // allows the component to patch itself with directiveDefs from the module after it finishes - // compiling. + // allows the component to patch itself with directiveDefs from the module after it + // finishes compiling. if (hasSelectorScope(type)) { const scopes = transitiveScopesFor(type.ngSelectorScope); patchComponentDefWithScope(ngComponentDef, scopes); diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 6f3ddfd2f7..2f1cc03898 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -257,6 +257,15 @@ { "name": "_c2" }, + { + "name": "_c20" + }, + { + "name": "_c21" + }, + { + "name": "_c22" + }, { "name": "_c3" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 1a4e32d3f5..ceb4d8a9df 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -1019,6 +1019,15 @@ { "name": "_c2" }, + { + "name": "_c20" + }, + { + "name": "_c21" + }, + { + "name": "_c22" + }, { "name": "_c3" },