From dd0815095f1e4ee0f4b73883470a31fcafea632c Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Tue, 23 Apr 2019 20:40:05 -0700 Subject: [PATCH] =?UTF-8?q?feat(ivy):=20add=20=C9=B5=C9=B5textInterpolateX?= =?UTF-8?q?=20instructions=20(#30011)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `ɵɵtextBinding(..., ɵɵinterpolationX())` instructions will now just be `ɵɵtextInterpolate(...)` instructions PR Close #30011 --- .../ngcc/test/rendering/renderer_spec.ts | 2 +- .../compliance/r3_compiler_compliance_spec.ts | 28 +- .../r3_view_compiler_binding_spec.ts | 4 +- .../test/compliance/r3_view_compiler_spec.ts | 19 +- .../r3_view_compiler_styling_spec.ts | 2 +- .../r3_view_compiler_template_spec.ts | 8 +- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 2 +- .../test/ngtsc/template_mapping_spec.ts | 21 +- packages/compiler/src/output/output_jit.ts | 2 +- .../compiler/src/render3/r3_identifiers.ts | 11 + .../compiler/src/render3/view/template.ts | 43 ++- .../core/src/core_render3_private_export.ts | 10 + packages/core/src/render3/index.ts | 13 +- packages/core/src/render3/instructions/all.ts | 2 + .../instructions/attribute_interpolation.ts | 2 +- .../src/render3/instructions/interpolation.ts | 289 +++++++++++++++++ .../instructions/property_interpolation.ts | 285 +---------------- .../instructions/text_interpolation.ts | 298 ++++++++++++++++++ packages/core/src/render3/jit/environment.ts | 10 + packages/core/test/acceptance/text_spec.ts | 115 +++++++ .../bundling/todo/bundle.golden_symbols.json | 6 + tools/public_api_guard/core/core.d.ts | 20 ++ 22 files changed, 864 insertions(+), 328 deletions(-) create mode 100644 packages/core/src/render3/instructions/interpolation.ts create mode 100644 packages/core/src/render3/instructions/text_interpolation.ts create mode 100644 packages/core/test/acceptance/text_spec.ts diff --git a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts index 58ee02eecd..a20e13174b 100644 --- a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts @@ -168,7 +168,7 @@ describe('Renderer', () => { ɵngcc0.ɵɵtext(0); } if (rf & 2) { ɵngcc0.ɵɵselect(0); - ɵngcc0.ɵɵtextBinding(0, ɵngcc0.ɵɵinterpolation1("", ctx.person.name, "")); + ɵngcc0.ɵɵtextInterpolate(ctx.person.name); } }, encapsulation: 2 }); /*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ type: Component, 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 998a42e2b1..705b619340 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -810,7 +810,7 @@ describe('compiler compliance', () => { const $myComp$ = $r3$.ɵɵnextContext(); const $foo$ = $r3$.ɵɵreference(1); $r3$.ɵɵselect(1); - $r3$.ɵɵtextBinding(1, $r3$.ɵɵinterpolation2("", $myComp$.salutation, " ", $foo$, "")); + $r3$.ɵɵtextInterpolate2("", $myComp$.salutation, " ", $foo$, ""); } } … @@ -2074,9 +2074,9 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵtextBinding(0, $r3$.ɵɵinterpolation1("", $r3$.ɵɵpipeBind2(1, 3, $r3$.ɵɵpipeBind2(2, 6, ctx.name, ctx.size), ctx.size), "")); + $r3$.ɵɵtextInterpolate($r3$.ɵɵpipeBind2(1, 3, $r3$.ɵɵpipeBind2(2, 6, ctx.name, ctx.size), ctx.size)); $r3$.ɵɵselect(4); - $r3$.ɵɵtextBinding(4, $r3$.ɵɵinterpolation2("", $r3$.ɵɵpipeBindV(5, 9, $r3$.ɵɵpureFunction1(18, $c0$, ctx.name)), " ", ctx.name ? 1 : $r3$.ɵɵpipeBind1(6, 16, 2), "")); + $r3$.ɵɵtextInterpolate2("", $r3$.ɵɵpipeBindV(5, 9, $r3$.ɵɵpureFunction1(18, $c0$, ctx.name)), " ", ctx.name ? 1 : $r3$.ɵɵpipeBind1(6, 16, 2), ""); } }, pipes: [MyPurePipe, MyPipe], @@ -2139,14 +2139,14 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵtextBinding(0, $r3$.ɵɵinterpolation5( + $r3$.ɵɵtextInterpolate5( "0:", i0.ɵɵpipeBind1(1, 5, ctx.name), "1:", i0.ɵɵpipeBind2(2, 7, ctx.name, 1), "2:", i0.ɵɵpipeBind3(3, 10, ctx.name, 1, 2), "3:", i0.ɵɵpipeBind4(4, 14, ctx.name, 1, 2, 3), "4:", i0.ɵɵpipeBindV(5, 19, $r3$.ɵɵpureFunction1(25, $c0$, ctx.name)), "" - )); + ); } }, pipes: [MyPipe], @@ -2192,7 +2192,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $user$ = $r3$.ɵɵreference(1); $r3$.ɵɵselect(2); - $r3$.ɵɵtextBinding(2, $r3$.ɵɵinterpolation1("Hello ", $user$.value, "!")); + $r3$.ɵɵtextInterpolate1("Hello ", $user$.value, "!"); } }, encapsulation: 2 @@ -2255,7 +2255,7 @@ describe('compiler compliance', () => { const $foo$ = $r3$.ɵɵreference(1); const $baz$ = $r3$.ɵɵreference(5); $r3$.ɵɵselect(1); - $r3$.ɵɵtextBinding(1, $r3$.ɵɵinterpolation3("", $foo$, "-", $bar$, "-", $baz$, "")); + $r3$.ɵɵtextInterpolate3("", $foo$, "-", $bar$, "-", $baz$, ""); } } function MyComponent_div_3_Template(rf, ctx) { @@ -2271,7 +2271,7 @@ describe('compiler compliance', () => { $r3$.ɵɵnextContext(); const $foo$ = $r3$.ɵɵreference(1); $r3$.ɵɵselect(1); - $r3$.ɵɵtextBinding(1, $r3$.ɵɵinterpolation2(" ", $foo$, "-", $bar$, " ")); + $r3$.ɵɵtextInterpolate2(" ", $foo$, "-", $bar$, " "); } } … @@ -2291,7 +2291,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $foo$ = $r3$.ɵɵreference(1); $r3$.ɵɵselect(2); - $r3$.ɵɵtextBinding(2, $r3$.ɵɵinterpolation1(" ", $foo$, " ")); + $r3$.ɵɵtextInterpolate1(" ", $foo$, " "); } }, directives:[IfDirective], @@ -2300,9 +2300,7 @@ describe('compiler compliance', () => { const result = compile(files, angularFiles); const source = result.source; - expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef'); - }); it('should support local refs mixed with context assignments', () => { @@ -2344,7 +2342,7 @@ describe('compiler compliance', () => { const $item$ = $i0$.ɵɵnextContext().$implicit; const $foo$ = $i0$.ɵɵreference(2); $r3$.ɵɵselect(1); - $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation2(" ", $foo$, " - ", $item$, " ")); + $i0$.ɵɵtextInterpolate2(" ", $foo$, " - ", $item$, " "); } } @@ -2648,7 +2646,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $item$ = ctx.$implicit; $r3$.ɵɵselect(1); - $r3$.ɵɵtextBinding(1, $r3$.ɵɵinterpolation1("", $item$.name, "")); + $r3$.ɵɵtextInterpolate($item$.name); } } … @@ -2731,7 +2729,7 @@ describe('compiler compliance', () => { const $info$ = ctx.$implicit; const $item$ = $r3$.ɵɵnextContext().$implicit; $r3$.ɵɵselect(1); - $r3$.ɵɵtextBinding(1, $r3$.ɵɵinterpolation2(" ", $item$.name, ": ", $info$.description, " ")); + $r3$.ɵɵtextInterpolate2(" ", $item$.name, ": ", $info$.description, " "); } } @@ -2749,7 +2747,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $item$ = ctx.$implicit; $r3$.ɵɵselect(2); - $r3$.ɵɵtextBinding(2, $r3$.ɵɵinterpolation1("", IDENT.name, "")); + $r3$.ɵɵtextInterpolate(IDENT.name); $r3$.ɵɵselect(4); $r3$.ɵɵproperty("forOf", IDENT.infos); } 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 93a4a36e0c..025eaada28 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 @@ -45,7 +45,7 @@ describe('compiler compliance: bindings', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation1("Hello ", $ctx$.name, "")); + $i0$.ɵɵtextInterpolate1("Hello ", $ctx$.name, ""); } }`; const result = compile(files, angularFiles); @@ -567,7 +567,7 @@ describe('compiler compliance: bindings', () => { if (rf & 2) { const $_r0$ = $i0$.ɵɵreference(1); $r3$.ɵɵselect(4); - $i0$.ɵɵtextBinding(4, $i0$.ɵɵinterpolation1(" ", $_r0$.id, " ")); + $i0$.ɵɵtextInterpolate1(" ", $_r0$.id, " "); } } `; diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts index 10dbc48055..57b0e0b71e 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts @@ -93,7 +93,7 @@ describe('r3_view_compiler', () => { describe('interpolations', () => { // Regression #21927 - it('should generate a correct call to bV with more than 8 interpolations', () => { + it('should generate a correct call to textInterpolateV with more than 8 interpolations', () => { const files: MockDirectory = { app: { 'example.ts': ` @@ -112,10 +112,19 @@ describe('r3_view_compiler', () => { } }; - const bV_call = - `$r3$.ɵɵinterpolationV([" ",ctx.list[0]," ",ctx.list[1]," ",ctx.list[2]," ",ctx.list[3], - " ",ctx.list[4]," ",ctx.list[5]," ",ctx.list[6]," ",ctx.list[7]," ",ctx.list[8], - " "])`; + const bV_call = ` + … + function MyApp_Template(rf, ctx) { + if (rf & 1) { + $i0$.ɵɵtext(0); + } + if (rf & 2) { + $i0$.ɵɵselect(0); + $i0$.ɵɵtextInterpolateV([" ", ctx.list[0], " ", ctx.list[1], " ", ctx.list[2], " ", ctx.list[3], " ", ctx.list[4], " ", ctx.list[5], " ", ctx.list[6], " ", ctx.list[7], " ", ctx.list[8], " "]); + } + } + … + `; const result = compile(files, angularFiles); expectEmit(result.source, bV_call, 'Incorrect bV call'); }); 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 29691ad60f..4a9580256c 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 @@ -999,7 +999,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵclassProp(0, $r3$.ɵɵpipeBind2(4, 10, $ctx$.fooExp, 2000)); $r3$.ɵɵstylingApply(); $r3$.ɵɵselect(5); - $r3$.ɵɵtextBinding(5, $r3$.ɵɵinterpolation1(" ", $ctx$.item, "")); + $r3$.ɵɵtextInterpolate1(" ", $ctx$.item, ""); } } `; 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 0ab46c841b..0f54733b7f 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 @@ -78,7 +78,7 @@ describe('compiler compliance: template', () => { $i0$.ɵɵselect(0); $i0$.ɵɵproperty("title", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component)); $r3$.ɵɵselect(1); - $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " ")); + $i0$.ɵɵtextInterpolate1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " "); } } @@ -215,7 +215,7 @@ describe('compiler compliance: template', () => { const $item$ = ctx.$implicit; const $i$ = ctx.index; $r3$.ɵɵselect(1); - $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation2(" ", $i$, " - ", $item$, " ")); + $i0$.ɵɵtextInterpolate2(" ", $i$, " - ", $item$, " "); } } // ... @@ -272,7 +272,7 @@ describe('compiler compliance: template', () => { const $i$ = $div$.index; const $item$ = $div$.$implicit; $r3$.ɵɵselect(1); - $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation2(" ", $i$, " - ", $item$, " ")); + $i0$.ɵɵtextInterpolate2(" ", $i$, " - ", $item$, " "); } } @@ -343,7 +343,7 @@ describe('compiler compliance: template', () => { const $middle$ = $i0$.ɵɵnextContext().$implicit; const $myComp$ = $i0$.ɵɵnextContext(2); $r3$.ɵɵselect(1); - $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation2(" ", $middle$.value, " - ", $myComp$.name, " ")); + $i0$.ɵɵtextInterpolate2(" ", $middle$.value, " - ", $myComp$.name, " "); } } diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 1bbcc200f6..a8046c9539 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -1961,7 +1961,7 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain('interpolation1("", ctx.text, "")'); + expect(jsContents).toContain('ɵɵtextInterpolate(ctx.text)'); }); it('should handle `encapsulation` field', () => { diff --git a/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts b/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts index 135311857d..f8899c36da 100644 --- a/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts @@ -44,7 +44,7 @@ describe('template source-mapping', () => { {source: '

', generated: 'i0.ɵɵelementStart(0, "h3")', sourceUrl: '../test.ts'}); expect(mappings).toContain({ source: 'Hello {{ name }}', - generated: 'i0.ɵɵtextBinding(1, i0.ɵɵinterpolation1("Hello ", ctx.name, ""))', + generated: 'i0.ɵɵtextInterpolate1("Hello ", ctx.name, "")', sourceUrl: '../test.ts' }); expect(mappings).toContain( @@ -57,8 +57,7 @@ describe('template source-mapping', () => { {source: '

', generated: 'i0.ɵɵelementStart(0, "h2")', sourceUrl: '../test.ts'}); expect(mappings).toContain({ source: '{{ greeting + " " + name }}', - generated: - 'i0.ɵɵtextBinding(1, i0.ɵɵinterpolation1("", ctx.greeting + " " + ctx.name, ""))', + generated: 'i0.ɵɵtextInterpolate(ctx.greeting + " " + ctx.name)', sourceUrl: '../test.ts' }); expect(mappings).toContain( @@ -85,8 +84,7 @@ describe('template source-mapping', () => { {source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts'}); expect(mappings).toContain({ source: '{{200.3 | percent : 2 }}', - generated: - 'i0.ɵɵtextBinding(1, i0.ɵɵinterpolation1("", i0.ɵɵpipeBind2(2, 1, 200.3, 2), ""))', + generated: 'i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(2, 1, 200.3, 2))', sourceUrl: '../test.ts' }); expect(mappings).toContain( @@ -270,7 +268,7 @@ describe('template source-mapping', () => { // expect(mappings).toContain({ // source: '{{ name }}', - // generated: 'i0.ɵɵtextBinding(1, i0.ɵɵinterpolation1("", ctx_r0.name, ""))', + // generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)', // sourceUrl: '../test.ts' // }); }); @@ -294,7 +292,7 @@ describe('template source-mapping', () => { // expect(mappings).toContain({ // source: '{{ name }}', - // generated: 'i0.ɵɵtextBinding(1, i0.ɵɵinterpolation1("", ctx_r0.name, ""))', + // generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)', // sourceUrl: '../test.ts' // }); }); @@ -370,7 +368,7 @@ describe('template source-mapping', () => { // Update mode expect(mappings).toContain({ - generated: 'i0.ɵɵtextBinding(3, i0.ɵɵinterpolation1("", 1 + 2, ""))', + generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts' }); @@ -396,9 +394,10 @@ describe('template source-mapping', () => { expect(mappings).toContain( {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../test.ts'}); + // TODO(benlesh): We need to circle back and prevent the extra parens from being generated. // Update mode expect(mappings).toContain({ - generated: 'i0.ɵɵtextBinding(3, i0.ɵɵinterpolation1("", 1 + 2, ""))', + generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts' }); @@ -452,7 +451,7 @@ describe('template source-mapping', () => { // Update mode expect(mappings).toContain({ - generated: 'i0.ɵɵtextBinding(3, i0.ɵɵinterpolation1("", 1 + 2, ""))', + generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../dir/test.html' }); @@ -496,7 +495,7 @@ describe('template source-mapping', () => { // Update mode expect(mappings).toContain({ - generated: 'i0.ɵɵtextBinding(3, i0.ɵɵinterpolation1("", 1 + 2, ""))', + generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../extraRootDir/test.html' }); diff --git a/packages/compiler/src/output/output_jit.ts b/packages/compiler/src/output/output_jit.ts index 1d3d2670b0..e446a1351b 100644 --- a/packages/compiler/src/output/output_jit.ts +++ b/packages/compiler/src/output/output_jit.ts @@ -56,7 +56,7 @@ export class JitEvaluator { evaluateCode( sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}, createSourceMap: boolean): any { - let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`; + let fnBody = `"use strict";${ctx.toSource()}\n//# sourceURL=${sourceUrl}`; const fnArgNames: string[] = []; const fnArgValues: any[] = []; for (const argName in vars) { diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 2c8e1547f3..7c8b0a061d 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -102,6 +102,17 @@ export class Identifiers { static getCurrentView: o.ExternalReference = {name: 'ɵɵgetCurrentView', moduleName: CORE}; + static textInterpolate: o.ExternalReference = {name: 'ɵɵtextInterpolate', moduleName: CORE}; + static textInterpolate1: o.ExternalReference = {name: 'ɵɵtextInterpolate1', moduleName: CORE}; + static textInterpolate2: o.ExternalReference = {name: 'ɵɵtextInterpolate2', moduleName: CORE}; + static textInterpolate3: o.ExternalReference = {name: 'ɵɵtextInterpolate3', moduleName: CORE}; + static textInterpolate4: o.ExternalReference = {name: 'ɵɵtextInterpolate4', moduleName: CORE}; + static textInterpolate5: o.ExternalReference = {name: 'ɵɵtextInterpolate5', moduleName: CORE}; + static textInterpolate6: o.ExternalReference = {name: 'ɵɵtextInterpolate6', moduleName: CORE}; + static textInterpolate7: o.ExternalReference = {name: 'ɵɵtextInterpolate7', moduleName: CORE}; + static textInterpolate8: o.ExternalReference = {name: 'ɵɵtextInterpolate8', moduleName: CORE}; + static textInterpolateV: o.ExternalReference = {name: 'ɵɵtextInterpolateV', moduleName: CORE}; + static restoreView: o.ExternalReference = {name: 'ɵɵrestoreView', moduleName: CORE}; static interpolation1: o.ExternalReference = {name: 'ɵɵinterpolation1', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 5714c819fa..783ac4ab65 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -941,9 +941,17 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const value = text.value.visit(this._valueConverter); this.allocateBindingSlots(value); - this.updateInstruction( - nodeIndex, text.sourceSpan, R3.textBinding, - () => [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]); + + if (value instanceof Interpolation) { + this.updateInstruction( + nodeIndex, text.sourceSpan, getTextInterpolationExpression(value), + () => this.getUpdateInstructionArguments(o.variable(CONTEXT_NAME), value)); + } else { + this.updateInstruction( + nodeIndex, text.sourceSpan, R3.textBinding, + () => + [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]); + } } visitText(text: t.Text) { @@ -1736,6 +1744,35 @@ function getAttributeInterpolationExpression(interpolation: Interpolation) { } } +/** + * Gets the instruction to generate for interpolated text. + * @param interpolation An Interpolation AST + */ +function getTextInterpolationExpression(interpolation: Interpolation): o.ExternalReference { + switch (getInterpolationArgsLength(interpolation)) { + case 1: + return R3.textInterpolate; + case 3: + return R3.textInterpolate1; + case 5: + return R3.textInterpolate2; + case 7: + return R3.textInterpolate3; + case 9: + return R3.textInterpolate4; + case 11: + return R3.textInterpolate5; + case 13: + return R3.textInterpolate6; + case 15: + return R3.textInterpolate7; + case 17: + return R3.textInterpolate8; + default: + return R3.textInterpolateV; + } +} + /** * Gets the number of arguments expected to be passed to a generated instruction in the case of * interpolation instructions. diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 324ea277be..6a9e57fb18 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -57,6 +57,16 @@ export { ɵɵelement, ɵɵlistener, ɵɵtext, + ɵɵtextInterpolate, + ɵɵtextInterpolate1, + ɵɵtextInterpolate2, + ɵɵtextInterpolate3, + ɵɵtextInterpolate4, + ɵɵtextInterpolate5, + ɵɵtextInterpolate6, + ɵɵtextInterpolate7, + ɵɵtextInterpolate8, + ɵɵtextInterpolateV, ɵɵembeddedViewStart, ɵɵprojection, ɵɵbind, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index cc6f99edfb..61c5c0daf7 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -107,7 +107,18 @@ export { ɵɵtemplate, ɵɵtext, - ɵɵtextBinding} from './instructions/all'; + ɵɵtextBinding, + ɵɵtextInterpolate, + ɵɵtextInterpolate1, + ɵɵtextInterpolate2, + ɵɵtextInterpolate3, + ɵɵtextInterpolate4, + ɵɵtextInterpolate5, + ɵɵtextInterpolate6, + ɵɵtextInterpolate7, + ɵɵtextInterpolate8, + ɵɵtextInterpolateV, +} from './instructions/all'; export {RenderFlags} from './interfaces/definition'; export {CssSelectorList} from './interfaces/projection'; diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index 4ae084f172..b876f4980c 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -36,6 +36,7 @@ export * from './element'; export * from './element_container'; export * from './embedded_view'; export * from './get_current_view'; +export * from './interpolation'; export * from './listener'; export * from './namespace'; export * from './next_context'; @@ -45,3 +46,4 @@ export * from './property_interpolation'; export * from './select'; export * from './styling'; export * from './text'; +export * from './text_interpolation'; diff --git a/packages/core/src/render3/instructions/attribute_interpolation.ts b/packages/core/src/render3/instructions/attribute_interpolation.ts index 5214208994..c0505532e9 100644 --- a/packages/core/src/render3/instructions/attribute_interpolation.ts +++ b/packages/core/src/render3/instructions/attribute_interpolation.ts @@ -8,7 +8,7 @@ import {SanitizerFn} from '../interfaces/sanitization'; import {getSelectedIndex} from '../state'; import {ɵɵelementAttribute} from './element'; -import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './property_interpolation'; +import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './interpolation'; import {TsickleIssue1009} from './shared'; diff --git a/packages/core/src/render3/instructions/interpolation.ts b/packages/core/src/render3/instructions/interpolation.ts new file mode 100644 index 0000000000..284bb5c143 --- /dev/null +++ b/packages/core/src/render3/instructions/interpolation.ts @@ -0,0 +1,289 @@ +/** + * @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 {assertEqual, assertLessThan} from '../../util/assert'; +import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings'; +import {BINDING_INDEX, TVIEW} from '../interfaces/view'; +import {getLView} from '../state'; +import {NO_CHANGE} from '../tokens'; +import {renderStringify} from '../util/misc_utils'; + +import {storeBindingMetadata} from './shared'; + + + +/** + * Create interpolation bindings with a variable number of expressions. + * + * If there are 1 to 8 expressions `interpolation1()` to `interpolation8()` should be used instead. + * Those are faster because there is no need to create an array of expressions and iterate over it. + * + * `values`: + * - has static text at even indexes, + * - has evaluated expressions at odd indexes. + * + * Returns the concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + * + * @codeGenApi + */ +export function ɵɵinterpolationV(values: any[]): string|NO_CHANGE { + ngDevMode && assertLessThan(2, values.length, 'should have at least 3 values'); + ngDevMode && assertEqual(values.length % 2, 1, 'should have an odd number of values'); + let isBindingUpdated = false; + const lView = getLView(); + const tData = lView[TVIEW].data; + let bindingIndex = lView[BINDING_INDEX]; + + if (tData[bindingIndex] == null) { + // 2 is the index of the first static interstitial value (ie. not prefix) + for (let i = 2; i < values.length; i += 2) { + tData[bindingIndex++] = values[i]; + } + bindingIndex = lView[BINDING_INDEX]; + } + + for (let i = 1; i < values.length; i += 2) { + // Check if bindings (odd indexes) have changed + isBindingUpdated = bindingUpdated(lView, bindingIndex++, values[i]) || isBindingUpdated; + } + lView[BINDING_INDEX] = bindingIndex; + storeBindingMetadata(lView, values[0], values[values.length - 1]); + + if (!isBindingUpdated) { + return NO_CHANGE; + } + + // Build the updated content + let content = values[0]; + for (let i = 1; i < values.length; i += 2) { + content += renderStringify(values[i]) + values[i + 1]; + } + + return content; +} + +/** + * Creates an interpolation binding with 1 expression. + * + * @param prefix static value used for concatenation only. + * @param v0 value checked for change. + * @param suffix static value used for concatenation only. + * + * @codeGenApi + */ +export function ɵɵinterpolation1(prefix: string, v0: any, suffix: string): string|NO_CHANGE { + const lView = getLView(); + const different = bindingUpdated(lView, lView[BINDING_INDEX]++, v0); + storeBindingMetadata(lView, prefix, suffix); + return different ? prefix + renderStringify(v0) + suffix : NO_CHANGE; +} + +/** + * Creates an interpolation binding with 2 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation2( + prefix: string, v0: any, i0: string, v1: any, suffix: string): string|NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + const different = bindingUpdated2(lView, bindingIndex, v0, v1); + lView[BINDING_INDEX] += 2; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + lView[TVIEW].data[bindingIndex] = i0; + } + + return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + suffix : NO_CHANGE; +} + +/** + * Creates an interpolation binding with 3 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation3( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string| + NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2); + lView[BINDING_INDEX] += 3; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + const tData = lView[TVIEW].data; + tData[bindingIndex] = i0; + tData[bindingIndex + 1] = i1; + } + + return different ? + prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix : + NO_CHANGE; +} + +/** + * Create an interpolation binding with 4 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation4( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + suffix: string): string|NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); + lView[BINDING_INDEX] += 4; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + const tData = lView[TVIEW].data; + tData[bindingIndex] = i0; + tData[bindingIndex + 1] = i1; + tData[bindingIndex + 2] = i2; + } + + return different ? + prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + + renderStringify(v3) + suffix : + NO_CHANGE; +} + +/** + * Creates an interpolation binding with 5 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation5( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, suffix: string): string|NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); + different = bindingUpdated(lView, bindingIndex + 4, v4) || different; + lView[BINDING_INDEX] += 5; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + const tData = lView[TVIEW].data; + tData[bindingIndex] = i0; + tData[bindingIndex + 1] = i1; + tData[bindingIndex + 2] = i2; + tData[bindingIndex + 3] = i3; + } + + return different ? + prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + + renderStringify(v3) + i3 + renderStringify(v4) + suffix : + NO_CHANGE; +} + +/** + * Creates an interpolation binding with 6 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation6( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, suffix: string): string|NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); + different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different; + lView[BINDING_INDEX] += 6; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + const tData = lView[TVIEW].data; + tData[bindingIndex] = i0; + tData[bindingIndex + 1] = i1; + tData[bindingIndex + 2] = i2; + tData[bindingIndex + 3] = i3; + tData[bindingIndex + 4] = i4; + } + + return different ? + prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + + renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + suffix : + NO_CHANGE; +} + +/** + * Creates an interpolation binding with 7 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation7( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): string| + NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); + different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different; + lView[BINDING_INDEX] += 7; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + const tData = lView[TVIEW].data; + tData[bindingIndex] = i0; + tData[bindingIndex + 1] = i1; + tData[bindingIndex + 2] = i2; + tData[bindingIndex + 3] = i3; + tData[bindingIndex + 4] = i4; + tData[bindingIndex + 5] = i5; + } + + return different ? + prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + + renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 + + renderStringify(v6) + suffix : + NO_CHANGE; +} + +/** + * Creates an interpolation binding with 8 expressions. + * + * @codeGenApi + */ +export function ɵɵinterpolation8( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, + suffix: string): string|NO_CHANGE { + const lView = getLView(); + const bindingIndex = lView[BINDING_INDEX]; + let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); + different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different; + lView[BINDING_INDEX] += 8; + + // Only set static strings the first time (data will be null subsequent runs). + const data = storeBindingMetadata(lView, prefix, suffix); + if (data) { + const tData = lView[TVIEW].data; + tData[bindingIndex] = i0; + tData[bindingIndex + 1] = i1; + tData[bindingIndex + 2] = i2; + tData[bindingIndex + 3] = i3; + tData[bindingIndex + 4] = i4; + tData[bindingIndex + 5] = i5; + tData[bindingIndex + 6] = i6; + } + + return different ? + prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + + renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 + + renderStringify(v6) + i6 + renderStringify(v7) + suffix : + NO_CHANGE; +} diff --git a/packages/core/src/render3/instructions/property_interpolation.ts b/packages/core/src/render3/instructions/property_interpolation.ts index 044d7d2b18..9d92cd853d 100644 --- a/packages/core/src/render3/instructions/property_interpolation.ts +++ b/packages/core/src/render3/instructions/property_interpolation.ts @@ -5,293 +5,14 @@ * 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 {assertEqual, assertLessThan} from '../../util/assert'; -import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; -import {BINDING_INDEX, TVIEW} from '../interfaces/view'; -import {getLView, getSelectedIndex} from '../state'; +import {getSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; -import {renderStringify} from '../util/misc_utils'; -import {TsickleIssue1009, elementPropertyInternal, storeBindingMetadata} from './shared'; +import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './interpolation'; +import {TsickleIssue1009, elementPropertyInternal} from './shared'; - -/** - * Create interpolation bindings with a variable number of expressions. - * - * If there are 1 to 8 expressions `interpolation1()` to `interpolation8()` should be used instead. - * Those are faster because there is no need to create an array of expressions and iterate over it. - * - * `values`: - * - has static text at even indexes, - * - has evaluated expressions at odd indexes. - * - * Returns the concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. - * - * @codeGenApi - */ -export function ɵɵinterpolationV(values: any[]): string|NO_CHANGE { - ngDevMode && assertLessThan(2, values.length, 'should have at least 3 values'); - ngDevMode && assertEqual(values.length % 2, 1, 'should have an odd number of values'); - let different = false; - const lView = getLView(); - const tData = lView[TVIEW].data; - let bindingIndex = lView[BINDING_INDEX]; - - if (tData[bindingIndex] == null) { - // 2 is the index of the first static interstitial value (ie. not prefix) - for (let i = 2; i < values.length; i += 2) { - tData[bindingIndex++] = values[i]; - } - bindingIndex = lView[BINDING_INDEX]; - } - - for (let i = 1; i < values.length; i += 2) { - // Check if bindings (odd indexes) have changed - bindingUpdated(lView, bindingIndex++, values[i]) && (different = true); - } - lView[BINDING_INDEX] = bindingIndex; - storeBindingMetadata(lView, values[0], values[values.length - 1]); - - if (!different) { - return NO_CHANGE; - } - - // Build the updated content - let content = values[0]; - for (let i = 1; i < values.length; i += 2) { - content += renderStringify(values[i]) + values[i + 1]; - } - - return content; -} - -/** - * Creates an interpolation binding with 1 expression. - * - * @param prefix static value used for concatenation only. - * @param v0 value checked for change. - * @param suffix static value used for concatenation only. - * - * @codeGenApi - */ -export function ɵɵinterpolation1(prefix: string, v0: any, suffix: string): string|NO_CHANGE { - const lView = getLView(); - const different = bindingUpdated(lView, lView[BINDING_INDEX]++, v0); - storeBindingMetadata(lView, prefix, suffix); - return different ? prefix + renderStringify(v0) + suffix : NO_CHANGE; -} - -/** - * Creates an interpolation binding with 2 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation2( - prefix: string, v0: any, i0: string, v1: any, suffix: string): string|NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - const different = bindingUpdated2(lView, bindingIndex, v0, v1); - lView[BINDING_INDEX] += 2; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - lView[TVIEW].data[bindingIndex] = i0; - } - - return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + suffix : NO_CHANGE; -} - -/** - * Creates an interpolation binding with 3 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation3( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string| - NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2); - lView[BINDING_INDEX] += 3; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - const tData = lView[TVIEW].data; - tData[bindingIndex] = i0; - tData[bindingIndex + 1] = i1; - } - - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix : - NO_CHANGE; -} - -/** - * Create an interpolation binding with 4 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation4( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - suffix: string): string|NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); - lView[BINDING_INDEX] += 4; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - const tData = lView[TVIEW].data; - tData[bindingIndex] = i0; - tData[bindingIndex + 1] = i1; - tData[bindingIndex + 2] = i2; - } - - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + - renderStringify(v3) + suffix : - NO_CHANGE; -} - -/** - * Creates an interpolation binding with 5 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation5( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, suffix: string): string|NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); - different = bindingUpdated(lView, bindingIndex + 4, v4) || different; - lView[BINDING_INDEX] += 5; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - const tData = lView[TVIEW].data; - tData[bindingIndex] = i0; - tData[bindingIndex + 1] = i1; - tData[bindingIndex + 2] = i2; - tData[bindingIndex + 3] = i3; - } - - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + - renderStringify(v3) + i3 + renderStringify(v4) + suffix : - NO_CHANGE; -} - -/** - * Creates an interpolation binding with 6 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation6( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, suffix: string): string|NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); - different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different; - lView[BINDING_INDEX] += 6; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - const tData = lView[TVIEW].data; - tData[bindingIndex] = i0; - tData[bindingIndex + 1] = i1; - tData[bindingIndex + 2] = i2; - tData[bindingIndex + 3] = i3; - tData[bindingIndex + 4] = i4; - } - - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + - renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + suffix : - NO_CHANGE; -} - -/** - * Creates an interpolation binding with 7 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation7( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): string| - NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); - different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different; - lView[BINDING_INDEX] += 7; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - const tData = lView[TVIEW].data; - tData[bindingIndex] = i0; - tData[bindingIndex + 1] = i1; - tData[bindingIndex + 2] = i2; - tData[bindingIndex + 3] = i3; - tData[bindingIndex + 4] = i4; - tData[bindingIndex + 5] = i5; - } - - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + - renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 + - renderStringify(v6) + suffix : - NO_CHANGE; -} - -/** - * Creates an interpolation binding with 8 expressions. - * - * @codeGenApi - */ -export function ɵɵinterpolation8( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string): string|NO_CHANGE { - const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]; - let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); - different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different; - lView[BINDING_INDEX] += 8; - - // Only set static strings the first time (data will be null subsequent runs). - const data = storeBindingMetadata(lView, prefix, suffix); - if (data) { - const tData = lView[TVIEW].data; - tData[bindingIndex] = i0; - tData[bindingIndex + 1] = i1; - tData[bindingIndex + 2] = i2; - tData[bindingIndex + 3] = i3; - tData[bindingIndex + 4] = i4; - tData[bindingIndex + 5] = i5; - tData[bindingIndex + 6] = i6; - } - - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + - renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 + - renderStringify(v6) + i6 + renderStringify(v7) + suffix : - NO_CHANGE; -} - -///////////////////////////////////////////////////////////////////// -/// NEW INSTRUCTIONS -///////////////////////////////////////////////////////////////////// - /** * * Update an interpolated property on an element with a lone bound value diff --git a/packages/core/src/render3/instructions/text_interpolation.ts b/packages/core/src/render3/instructions/text_interpolation.ts new file mode 100644 index 0000000000..827338249d --- /dev/null +++ b/packages/core/src/render3/instructions/text_interpolation.ts @@ -0,0 +1,298 @@ +/** + * @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 {getSelectedIndex} from '../state'; + +import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './interpolation'; +import {TsickleIssue1009} from './shared'; +import {ɵɵtextBinding} from './text'; + + + +/** + * + * Update text content with a lone bound value + * + * Used when a text node has 1 interpolated value in it, an no additional text + * surrounds that interpolated value: + * + * ```html + *
{{v0}}
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate(v0); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate(v0: any): TsickleIssue1009 { + ɵɵtextInterpolate1('', v0, ''); + return ɵɵtextInterpolate; +} + + +/** + * + * Update text content with single bound value surrounded by other text. + * + * Used when a text node has 1 interpolated value in it: + * + * ```html + *
prefix{{v0}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate1('prefix', v0, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate1(prefix: string, v0: any, suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding(index, ɵɵinterpolation1(prefix, v0, suffix)); + return ɵɵtextInterpolate1; +} + +/** + * + * Update text content with 2 bound values surrounded by other text. + * + * Used when a text node has 2 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate2('prefix', v0, '-', v1, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate2( + prefix: string, v0: any, i0: string, v1: any, suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding(index, ɵɵinterpolation2(prefix, v0, i0, v1, suffix)); + return ɵɵtextInterpolate2; +} + +/** + * + * Update text content with 3 bound values surrounded by other text. + * + * Used when a text node has 3 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate3( + * 'prefix', v0, '-', v1, '-', v2, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate3( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding(index, ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix)); + return ɵɵtextInterpolate3; +} + +/** + * + * Update text content with 4 bound values surrounded by other text. + * + * Used when a text node has 4 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate4( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see ɵɵtextInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate4( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding(index, ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix)); + return ɵɵtextInterpolate4; +} + +/** + * + * Update text content with 5 bound values surrounded by other text. + * + * Used when a text node has 5 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate5( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate5( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding(index, ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix)); + return ɵɵtextInterpolate5; +} + +/** + * + * Update text content with 6 bound values surrounded by other text. + * + * Used when a text node has 6 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate6( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); + * ``` + * + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate6( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding( + index, ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix)); + return ɵɵtextInterpolate6; +} + +/** + * + * Update text content with 7 bound values surrounded by other text. + * + * Used when a text node has 7 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate7( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate7( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, + suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding( + index, ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix)); + return ɵɵtextInterpolate7; +} + +/** + * + * Update text content with 8 bound values surrounded by other text. + * + * Used when a text node has 8 interpolated values in it: + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolate8( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix'); + * ``` + * @returns itself, so that it may be chained. + * @see textInterpolateV + * @codeGenApi + */ +export function ɵɵtextInterpolate8( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, + suffix: string): TsickleIssue1009 { + const index = getSelectedIndex(); + ɵɵtextBinding( + index, + ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix)); + return ɵɵtextInterpolate8; +} + +/** + * Update text content with 9 or more bound values other surrounded by text. + * + * Used when the number of interpolated values exceeds 8. + * + * ```html + *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵtextInterpolateV( + * ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, + * 'suffix']); + * ``` + *. + * @param values The a collection of values and the strings in between those values, beginning with + * a string prefix and ending with a string suffix. + * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) + * + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵtextInterpolateV(values: any[]): TsickleIssue1009 { + const index = getSelectedIndex(); + + ɵɵtextBinding(index, ɵɵinterpolationV(values)); + return ɵɵtextInterpolateV; +} diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 1f1f1eb018..b40dff1843 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -127,6 +127,16 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵtemplate': r3.ɵɵtemplate, 'ɵɵtext': r3.ɵɵtext, 'ɵɵtextBinding': r3.ɵɵtextBinding, + 'ɵɵtextInterpolate': r3.ɵɵtextInterpolate, + 'ɵɵtextInterpolate1': r3.ɵɵtextInterpolate1, + 'ɵɵtextInterpolate2': r3.ɵɵtextInterpolate2, + 'ɵɵtextInterpolate3': r3.ɵɵtextInterpolate3, + 'ɵɵtextInterpolate4': r3.ɵɵtextInterpolate4, + 'ɵɵtextInterpolate5': r3.ɵɵtextInterpolate5, + 'ɵɵtextInterpolate6': r3.ɵɵtextInterpolate6, + 'ɵɵtextInterpolate7': r3.ɵɵtextInterpolate7, + 'ɵɵtextInterpolate8': r3.ɵɵtextInterpolate8, + 'ɵɵtextInterpolateV': r3.ɵɵtextInterpolateV, 'ɵɵembeddedViewStart': r3.ɵɵembeddedViewStart, 'ɵɵembeddedViewEnd': r3.ɵɵembeddedViewEnd, 'ɵɵi18n': r3.ɵɵi18n, diff --git a/packages/core/test/acceptance/text_spec.ts b/packages/core/test/acceptance/text_spec.ts new file mode 100644 index 0000000000..3f3e665f06 --- /dev/null +++ b/packages/core/test/acceptance/text_spec.ts @@ -0,0 +1,115 @@ +/** + * @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 {Component} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {of } from 'rxjs'; + +describe('text instructions', () => { + it('should handle all flavors of interpolated text', () => { + @Component({ + template: ` +
a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g{{seven}}h{{eight}}i{{nine}}j
+
a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g{{seven}}h{{eight}}i
+
a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g{{seven}}h
+
a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g
+
a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f
+
a{{one}}b{{two}}c{{three}}d{{four}}e
+
a{{one}}b{{two}}c{{three}}d
+
a{{one}}b{{two}}c
+
a{{one}}b
+
{{one}}
+ ` + }) + class App { + one = 1; + two = 2; + three = 3; + four = 4; + five = 5; + six = 6; + seven = 7; + eight = 8; + nine = 9; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const allTextContent = Array.from(fixture.nativeElement.querySelectorAll('div')) + .map((div: HTMLDivElement) => div.textContent); + + expect(allTextContent).toEqual([ + 'a1b2c3d4e5f6g7h8i9j', + 'a1b2c3d4e5f6g7h8i', + 'a1b2c3d4e5f6g7h', + 'a1b2c3d4e5f6g', + 'a1b2c3d4e5f', + 'a1b2c3d4e', + 'a1b2c3d', + 'a1b2c', + 'a1b', + '1', + ]); + }); + + it('should handle piped values in interpolated text', () => { + @Component({ + template: ` +

{{who | async}} sells {{(item | async)?.what}} down by the {{(item | async)?.where}}.

+ ` + }) + class App { + who = of ('Sally'); + item = of ({ + what: 'seashells', + where: 'seashore', + }); + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const p = fixture.nativeElement.querySelector('p') as HTMLDivElement; + expect(p.textContent).toBe('Sally sells seashells down by the seashore.'); + }); + + it('should not sanitize urls in interpolated text', () => { + @Component({ + template: '

{{thisisfine}}

', + }) + class App { + thisisfine = 'javascript:alert("image_of_dog_with_coffee_in_burning_building.gif")'; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const p = fixture.nativeElement.querySelector('p'); + + expect(p.textContent) + .toBe('javascript:alert("image_of_dog_with_coffee_in_burning_building.gif")'); + }); + + it('should not allow writing HTML in interpolated text', () => { + @Component({ + template: '
{{test}}
', + }) + class App { + test = '

LOL, big text

'; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const div = fixture.nativeElement.querySelector('div'); + + expect(div.innerHTML).toBe('<h1>LOL, big text</h1>'); + }); +}); diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index d804c23fa1..216119ff48 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -1603,5 +1603,11 @@ }, { "name": "ɵɵtextBinding" + }, + { + "name": "ɵɵtextInterpolate" + }, + { + "name": "ɵɵtextInterpolate1" } ] \ No newline at end of file diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index fa1423a027..d72f29d65e 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -1057,6 +1057,26 @@ export declare function ɵɵtext(index: number, value?: any): void; export declare function ɵɵtextBinding(index: number, value: T | NO_CHANGE): void; +export declare function ɵɵtextInterpolate(v0: any): TsickleIssue1009; + +export declare function ɵɵtextInterpolate1(prefix: string, v0: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate2(prefix: string, v0: any, i0: string, v1: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate3(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate4(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate5(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate6(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate7(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolate8(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string): TsickleIssue1009; + +export declare function ɵɵtextInterpolateV(values: any[]): TsickleIssue1009; + export declare function ɵɵviewQuery(predicate: Type | string[], descend: boolean, read: any): QueryList; export declare const PACKAGE_ROOT_URL: InjectionToken;