@angular/compiler 17.0.6 → 17.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/esm2022/src/constant_pool.mjs +5 -3
  2. package/esm2022/src/ml_parser/tags.mjs +8 -3
  3. package/esm2022/src/render3/partial/class_metadata.mjs +1 -1
  4. package/esm2022/src/render3/partial/directive.mjs +1 -1
  5. package/esm2022/src/render3/partial/factory.mjs +1 -1
  6. package/esm2022/src/render3/partial/injectable.mjs +1 -1
  7. package/esm2022/src/render3/partial/injector.mjs +1 -1
  8. package/esm2022/src/render3/partial/ng_module.mjs +1 -1
  9. package/esm2022/src/render3/partial/pipe.mjs +1 -1
  10. package/esm2022/src/render3/view/compiler.mjs +2 -1
  11. package/esm2022/src/render3/view/template.mjs +7 -7
  12. package/esm2022/src/template/pipeline/ir/src/enums.mjs +15 -19
  13. package/esm2022/src/template/pipeline/ir/src/expression.mjs +16 -20
  14. package/esm2022/src/template/pipeline/ir/src/ops/create.mjs +39 -13
  15. package/esm2022/src/template/pipeline/ir/src/ops/host.mjs +4 -2
  16. package/esm2022/src/template/pipeline/ir/src/ops/update.mjs +18 -10
  17. package/esm2022/src/template/pipeline/src/emit.mjs +6 -6
  18. package/esm2022/src/template/pipeline/src/ingest.mjs +266 -178
  19. package/esm2022/src/template/pipeline/src/instruction.mjs +14 -14
  20. package/esm2022/src/template/pipeline/src/phases/assign_i18n_slot_dependencies.mjs +40 -56
  21. package/esm2022/src/template/pipeline/src/phases/attribute_extraction.mjs +19 -22
  22. package/esm2022/src/template/pipeline/src/phases/binding_specialization.mjs +4 -4
  23. package/esm2022/src/template/pipeline/src/phases/const_collection.mjs +37 -13
  24. package/esm2022/src/template/pipeline/src/phases/convert_i18n_bindings.mjs +2 -2
  25. package/esm2022/src/template/pipeline/src/phases/create_defer_deps_fns.mjs +3 -2
  26. package/esm2022/src/template/pipeline/src/phases/create_i18n_contexts.mjs +63 -29
  27. package/esm2022/src/template/pipeline/src/phases/deduplicate_text_bindings.mjs +40 -0
  28. package/esm2022/src/template/pipeline/src/phases/extract_i18n_messages.mjs +52 -49
  29. package/esm2022/src/template/pipeline/src/phases/generate_variables.mjs +7 -1
  30. package/esm2022/src/template/pipeline/src/phases/host_style_property_parsing.mjs +2 -2
  31. package/esm2022/src/template/pipeline/src/phases/i18n_const_collection.mjs +11 -8
  32. package/esm2022/src/template/pipeline/src/phases/i18n_text_extraction.mjs +22 -3
  33. package/esm2022/src/template/pipeline/src/phases/parse_extracted_styles.mjs +22 -3
  34. package/esm2022/src/template/pipeline/src/phases/propagate_i18n_blocks.mjs +27 -12
  35. package/esm2022/src/template/pipeline/src/phases/reify.mjs +18 -21
  36. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.mjs +60 -14
  37. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.mjs +23 -10
  38. package/esm2022/src/template/pipeline/src/phases/resolve_sanitizers.mjs +78 -14
  39. package/esm2022/src/template/pipeline/src/phases/slot_allocation.mjs +3 -1
  40. package/esm2022/src/template/pipeline/src/phases/var_counting.mjs +10 -1
  41. package/esm2022/src/version.mjs +1 -1
  42. package/fesm2022/compiler.mjs +894 -561
  43. package/fesm2022/compiler.mjs.map +1 -1
  44. package/index.d.ts +3 -3
  45. package/package.json +2 -2
  46. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.mjs +0 -62
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v17.0.6
2
+ * @license Angular v17.0.8
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -2308,7 +2308,9 @@ class ConstantPool {
2308
2308
  }))));
2309
2309
  }
2310
2310
  }
2311
- getSharedFunctionReference(fn, prefix) {
2311
+ // TODO: useUniqueName(false) is necessary for naming compatibility with
2312
+ // TemplateDefinitionBuilder, but should be removed once Template Pipeline is the default.
2313
+ getSharedFunctionReference(fn, prefix, useUniqueName = true) {
2312
2314
  const isArrow = fn instanceof ArrowFunctionExpr;
2313
2315
  for (const current of this.statements) {
2314
2316
  // Arrow functions are saved as variables so we check if the
@@ -2323,7 +2325,7 @@ class ConstantPool {
2323
2325
  }
2324
2326
  }
2325
2327
  // Otherwise declare the function.
2326
- const name = this.uniqueName(prefix);
2328
+ const name = useUniqueName ? this.uniqueName(prefix) : prefix;
2327
2329
  this.statements.push(fn.toDeclStmt(name, StmtModifier.Final));
2328
2330
  return variable(name);
2329
2331
  }
@@ -3685,13 +3687,18 @@ var TagContentType;
3685
3687
  TagContentType[TagContentType["ESCAPABLE_RAW_TEXT"] = 1] = "ESCAPABLE_RAW_TEXT";
3686
3688
  TagContentType[TagContentType["PARSABLE_DATA"] = 2] = "PARSABLE_DATA";
3687
3689
  })(TagContentType || (TagContentType = {}));
3688
- function splitNsName(elementName) {
3690
+ function splitNsName(elementName, fatal = true) {
3689
3691
  if (elementName[0] != ':') {
3690
3692
  return [null, elementName];
3691
3693
  }
3692
3694
  const colonIndex = elementName.indexOf(':', 1);
3693
3695
  if (colonIndex === -1) {
3694
- throw new Error(`Unsupported format "${elementName}" expecting ":namespace:name"`);
3696
+ if (fatal) {
3697
+ throw new Error(`Unsupported format "${elementName}" expecting ":namespace:name"`);
3698
+ }
3699
+ else {
3700
+ return [null, elementName];
3701
+ }
3695
3702
  }
3696
3703
  return [elementName.slice(1, colonIndex), elementName.slice(colonIndex + 1)];
3697
3704
  }
@@ -8966,14 +8973,18 @@ var OpKind;
8966
8973
  * An instruction to update an ICU expression.
8967
8974
  */
8968
8975
  OpKind[OpKind["IcuEnd"] = 42] = "IcuEnd";
8976
+ /**
8977
+ * An instruction representing a placeholder in an ICU expression.
8978
+ */
8979
+ OpKind[OpKind["IcuPlaceholder"] = 43] = "IcuPlaceholder";
8969
8980
  /**
8970
8981
  * An i18n context containing information needed to generate an i18n message.
8971
8982
  */
8972
- OpKind[OpKind["I18nContext"] = 43] = "I18nContext";
8983
+ OpKind[OpKind["I18nContext"] = 44] = "I18nContext";
8973
8984
  /**
8974
8985
  * A creation op that corresponds to i18n attributes on an element.
8975
8986
  */
8976
- OpKind[OpKind["I18nAttributes"] = 44] = "I18nAttributes";
8987
+ OpKind[OpKind["I18nAttributes"] = 45] = "I18nAttributes";
8977
8988
  })(OpKind || (OpKind = {}));
8978
8989
  /**
8979
8990
  * Distinguishes different kinds of IR expressions.
@@ -9064,23 +9075,27 @@ var ExpressionKind;
9064
9075
  * An expression representing a sanitizer function.
9065
9076
  */
9066
9077
  ExpressionKind[ExpressionKind["SanitizerExpr"] = 20] = "SanitizerExpr";
9078
+ /**
9079
+ * An expression representing a function to create trusted values.
9080
+ */
9081
+ ExpressionKind[ExpressionKind["TrustedValueFnExpr"] = 21] = "TrustedValueFnExpr";
9067
9082
  /**
9068
9083
  * An expression that will cause a literal slot index to be emitted.
9069
9084
  */
9070
- ExpressionKind[ExpressionKind["SlotLiteralExpr"] = 21] = "SlotLiteralExpr";
9085
+ ExpressionKind[ExpressionKind["SlotLiteralExpr"] = 22] = "SlotLiteralExpr";
9071
9086
  /**
9072
9087
  * A test expression for a conditional op.
9073
9088
  */
9074
- ExpressionKind[ExpressionKind["ConditionalCase"] = 22] = "ConditionalCase";
9089
+ ExpressionKind[ExpressionKind["ConditionalCase"] = 23] = "ConditionalCase";
9075
9090
  /**
9076
9091
  * A variable for use inside a repeater, providing one of the ambiently-available context
9077
9092
  * properties ($even, $first, etc.).
9078
9093
  */
9079
- ExpressionKind[ExpressionKind["DerivedRepeaterVar"] = 23] = "DerivedRepeaterVar";
9094
+ ExpressionKind[ExpressionKind["DerivedRepeaterVar"] = 24] = "DerivedRepeaterVar";
9080
9095
  /**
9081
9096
  * An expression that will be automatically extracted to the component const array.
9082
9097
  */
9083
- ExpressionKind[ExpressionKind["ConstCollected"] = 24] = "ConstCollected";
9098
+ ExpressionKind[ExpressionKind["ConstCollected"] = 25] = "ConstCollected";
9084
9099
  })(ExpressionKind || (ExpressionKind = {}));
9085
9100
  var VariableFlags;
9086
9101
  (function (VariableFlags) {
@@ -9124,18 +9139,6 @@ var CompatibilityMode;
9124
9139
  CompatibilityMode[CompatibilityMode["Normal"] = 0] = "Normal";
9125
9140
  CompatibilityMode[CompatibilityMode["TemplateDefinitionBuilder"] = 1] = "TemplateDefinitionBuilder";
9126
9141
  })(CompatibilityMode || (CompatibilityMode = {}));
9127
- /**
9128
- * Represents functions used to sanitize different pieces of a template.
9129
- */
9130
- var SanitizerFn;
9131
- (function (SanitizerFn) {
9132
- SanitizerFn[SanitizerFn["Html"] = 0] = "Html";
9133
- SanitizerFn[SanitizerFn["Script"] = 1] = "Script";
9134
- SanitizerFn[SanitizerFn["Style"] = 2] = "Style";
9135
- SanitizerFn[SanitizerFn["Url"] = 3] = "Url";
9136
- SanitizerFn[SanitizerFn["ResourceUrl"] = 4] = "ResourceUrl";
9137
- SanitizerFn[SanitizerFn["IframeAttribute"] = 5] = "IframeAttribute";
9138
- })(SanitizerFn || (SanitizerFn = {}));
9139
9142
  /**
9140
9143
  * Enumeration of the different kinds of `@defer` secondary blocks.
9141
9144
  */
@@ -9414,7 +9417,7 @@ class Interpolation {
9414
9417
  /**
9415
9418
  * Create a `BindingOp`, not yet transformed into a particular type of binding.
9416
9419
  */
9417
- function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isStructuralTemplate, i18nContext, sourceSpan) {
9420
+ function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
9418
9421
  return {
9419
9422
  kind: OpKind.Binding,
9420
9423
  bindingKind: kind,
@@ -9424,8 +9427,10 @@ function createBindingOp(target, kind, name, expression, unit, securityContext,
9424
9427
  unit,
9425
9428
  securityContext,
9426
9429
  isTextAttribute,
9427
- isStructuralTemplate: isStructuralTemplate,
9428
- i18nContext,
9430
+ isStructuralTemplateAttribute,
9431
+ templateKind,
9432
+ i18nContext: null,
9433
+ i18nMessage,
9429
9434
  sourceSpan,
9430
9435
  ...NEW_OP,
9431
9436
  };
@@ -9433,7 +9438,7 @@ function createBindingOp(target, kind, name, expression, unit, securityContext,
9433
9438
  /**
9434
9439
  * Create a `PropertyOp`.
9435
9440
  */
9436
- function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isStructuralTemplate, i18nContext, sourceSpan) {
9441
+ function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isStructuralTemplateAttribute, templateKind, i18nContext, i18nMessage, sourceSpan) {
9437
9442
  return {
9438
9443
  kind: OpKind.Property,
9439
9444
  target,
@@ -9442,8 +9447,10 @@ function createPropertyOp(target, name, expression, isAnimationTrigger, security
9442
9447
  isAnimationTrigger,
9443
9448
  securityContext,
9444
9449
  sanitizer: null,
9445
- isStructuralTemplate,
9450
+ isStructuralTemplateAttribute,
9451
+ templateKind,
9446
9452
  i18nContext,
9453
+ i18nMessage,
9447
9454
  sourceSpan,
9448
9455
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9449
9456
  ...TRAIT_CONSUMES_VARS,
@@ -9508,7 +9515,7 @@ function createClassMapOp(xref, expression, sourceSpan) {
9508
9515
  /**
9509
9516
  * Create an `AttributeOp`.
9510
9517
  */
9511
- function createAttributeOp(target, name, expression, securityContext, isTextAttribute, isStructuralTemplate, i18nContext, sourceSpan) {
9518
+ function createAttributeOp(target, name, expression, securityContext, isTextAttribute, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
9512
9519
  return {
9513
9520
  kind: OpKind.Attribute,
9514
9521
  target,
@@ -9517,8 +9524,10 @@ function createAttributeOp(target, name, expression, securityContext, isTextAttr
9517
9524
  securityContext,
9518
9525
  sanitizer: null,
9519
9526
  isTextAttribute,
9520
- isStructuralTemplate,
9521
- i18nContext,
9527
+ isStructuralTemplateAttribute,
9528
+ templateKind,
9529
+ i18nContext: null,
9530
+ i18nMessage,
9522
9531
  sourceSpan,
9523
9532
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9524
9533
  ...TRAIT_CONSUMES_VARS,
@@ -9574,12 +9583,13 @@ function createDeferWhenOp(target, expr, prefetch, sourceSpan) {
9574
9583
  sourceSpan,
9575
9584
  ...NEW_OP,
9576
9585
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9586
+ ...TRAIT_CONSUMES_VARS,
9577
9587
  };
9578
9588
  }
9579
9589
  /**
9580
9590
  * Create an i18n expression op.
9581
9591
  */
9582
- function createI18nExpressionOp(context, target, i18nOwner, handle, expression, i18nPlaceholder, resolutionTime, usage, name, sourceSpan) {
9592
+ function createI18nExpressionOp(context, target, i18nOwner, handle, expression, icuPlaceholder, i18nPlaceholder, resolutionTime, usage, name, sourceSpan) {
9583
9593
  return {
9584
9594
  kind: OpKind.I18nExpression,
9585
9595
  context,
@@ -9587,6 +9597,7 @@ function createI18nExpressionOp(context, target, i18nOwner, handle, expression,
9587
9597
  i18nOwner,
9588
9598
  handle,
9589
9599
  expression,
9600
+ icuPlaceholder,
9590
9601
  i18nPlaceholder,
9591
9602
  resolutionTime,
9592
9603
  usage,
@@ -10152,24 +10163,6 @@ class ReadTemporaryExpr extends ExpressionBase {
10152
10163
  return r;
10153
10164
  }
10154
10165
  }
10155
- class SanitizerExpr extends ExpressionBase {
10156
- constructor(fn) {
10157
- super();
10158
- this.fn = fn;
10159
- this.kind = ExpressionKind.SanitizerExpr;
10160
- }
10161
- visitExpression(visitor, context) { }
10162
- isEquivalent(e) {
10163
- return e instanceof SanitizerExpr && e.fn === this.fn;
10164
- }
10165
- isConstant() {
10166
- return true;
10167
- }
10168
- clone() {
10169
- return new SanitizerExpr(this.fn);
10170
- }
10171
- transformInternalExpressions() { }
10172
- }
10173
10166
  class SlotLiteralExpr extends ExpressionBase {
10174
10167
  constructor(slot) {
10175
10168
  super();
@@ -10300,7 +10293,6 @@ function transformExpressionsInOp(op, transform, flags) {
10300
10293
  case OpKind.ClassProp:
10301
10294
  case OpKind.ClassMap:
10302
10295
  case OpKind.Binding:
10303
- case OpKind.HostProperty:
10304
10296
  if (op.expression instanceof Interpolation) {
10305
10297
  transformExpressionsInInterpolation(op.expression, transform, flags);
10306
10298
  }
@@ -10309,6 +10301,7 @@ function transformExpressionsInOp(op, transform, flags) {
10309
10301
  }
10310
10302
  break;
10311
10303
  case OpKind.Property:
10304
+ case OpKind.HostProperty:
10312
10305
  case OpKind.Attribute:
10313
10306
  if (op.expression instanceof Interpolation) {
10314
10307
  transformExpressionsInInterpolation(op.expression, transform, flags);
@@ -10354,6 +10347,8 @@ function transformExpressionsInOp(op, transform, flags) {
10354
10347
  case OpKind.ExtractedAttribute:
10355
10348
  op.expression =
10356
10349
  op.expression && transformExpressionsInExpression(op.expression, transform, flags);
10350
+ op.trustedValueFn = op.trustedValueFn &&
10351
+ transformExpressionsInExpression(op.trustedValueFn, transform, flags);
10357
10352
  break;
10358
10353
  case OpKind.RepeaterCreate:
10359
10354
  op.track = transformExpressionsInExpression(op.track, transform, flags);
@@ -10372,6 +10367,9 @@ function transformExpressionsInOp(op, transform, flags) {
10372
10367
  op.placeholderConfig =
10373
10368
  transformExpressionsInExpression(op.placeholderConfig, transform, flags);
10374
10369
  }
10370
+ if (op.resolverFn !== null) {
10371
+ op.resolverFn = transformExpressionsInExpression(op.resolverFn, transform, flags);
10372
+ }
10375
10373
  break;
10376
10374
  case OpKind.I18nMessage:
10377
10375
  for (const [placeholder, expr] of op.params) {
@@ -10408,6 +10406,7 @@ function transformExpressionsInOp(op, transform, flags) {
10408
10406
  case OpKind.Template:
10409
10407
  case OpKind.Text:
10410
10408
  case OpKind.I18nAttributes:
10409
+ case OpKind.IcuPlaceholder:
10411
10410
  // These operations contain no expressions.
10412
10411
  break;
10413
10412
  default:
@@ -10485,6 +10484,14 @@ function transformExpressionsInExpression(expr, transform, flags) {
10485
10484
  else if (expr instanceof NotExpr) {
10486
10485
  expr.condition = transformExpressionsInExpression(expr.condition, transform, flags);
10487
10486
  }
10487
+ else if (expr instanceof TaggedTemplateExpr) {
10488
+ expr.tag = transformExpressionsInExpression(expr.tag, transform, flags);
10489
+ expr.template.expressions =
10490
+ expr.template.expressions.map(e => transformExpressionsInExpression(e, transform, flags));
10491
+ }
10492
+ else if (expr instanceof WrappedNodeExpr) {
10493
+ // TODO: Do we need to transform any TS nodes nested inside of this expression?
10494
+ }
10488
10495
  else if (expr instanceof ReadVarExpr || expr instanceof ExternalExpr ||
10489
10496
  expr instanceof LiteralExpr) {
10490
10497
  // No action for these types.
@@ -10807,7 +10814,7 @@ function isElementOrContainerOp(op) {
10807
10814
  /**
10808
10815
  * Create an `ElementStartOp`.
10809
10816
  */
10810
- function createElementStartOp(tag, xref, namespace, i18nPlaceholder, sourceSpan) {
10817
+ function createElementStartOp(tag, xref, namespace, i18nPlaceholder, startSourceSpan, wholeSourceSpan) {
10811
10818
  return {
10812
10819
  kind: OpKind.ElementStart,
10813
10820
  xref,
@@ -10818,7 +10825,8 @@ function createElementStartOp(tag, xref, namespace, i18nPlaceholder, sourceSpan)
10818
10825
  nonBindable: false,
10819
10826
  namespace,
10820
10827
  i18nPlaceholder,
10821
- sourceSpan,
10828
+ startSourceSpan,
10829
+ wholeSourceSpan,
10822
10830
  ...TRAIT_CONSUMES_SLOT,
10823
10831
  ...NEW_OP,
10824
10832
  };
@@ -10826,7 +10834,7 @@ function createElementStartOp(tag, xref, namespace, i18nPlaceholder, sourceSpan)
10826
10834
  /**
10827
10835
  * Create a `TemplateOp`.
10828
10836
  */
10829
- function createTemplateOp(xref, templateKind, tag, functionNameSuffix, namespace, i18nPlaceholder, sourceSpan) {
10837
+ function createTemplateOp(xref, templateKind, tag, functionNameSuffix, namespace, i18nPlaceholder, startSourceSpan, wholeSourceSpan) {
10830
10838
  return {
10831
10839
  kind: OpKind.Template,
10832
10840
  xref,
@@ -10841,12 +10849,13 @@ function createTemplateOp(xref, templateKind, tag, functionNameSuffix, namespace
10841
10849
  nonBindable: false,
10842
10850
  namespace,
10843
10851
  i18nPlaceholder,
10844
- sourceSpan,
10852
+ startSourceSpan,
10853
+ wholeSourceSpan,
10845
10854
  ...TRAIT_CONSUMES_SLOT,
10846
10855
  ...NEW_OP,
10847
10856
  };
10848
10857
  }
10849
- function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, sourceSpan) {
10858
+ function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, i18nPlaceholder, emptyI18nPlaceholder, startSourceSpan, wholeSourceSpan) {
10850
10859
  return {
10851
10860
  kind: OpKind.RepeaterCreate,
10852
10861
  attributes: null,
@@ -10864,9 +10873,13 @@ function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, so
10864
10873
  vars: null,
10865
10874
  varNames,
10866
10875
  usesComponentInstance: false,
10867
- sourceSpan,
10876
+ i18nPlaceholder,
10877
+ emptyI18nPlaceholder,
10878
+ startSourceSpan,
10879
+ wholeSourceSpan,
10868
10880
  ...TRAIT_CONSUMES_SLOT,
10869
10881
  ...NEW_OP,
10882
+ ...TRAIT_CONSUMES_VARS,
10870
10883
  numSlotsUsed: emptyView === null ? 2 : 3,
10871
10884
  };
10872
10885
  }
@@ -10898,12 +10911,13 @@ function createEnableBindingsOp(xref) {
10898
10911
  /**
10899
10912
  * Create a `TextOp`.
10900
10913
  */
10901
- function createTextOp(xref, initialValue, sourceSpan) {
10914
+ function createTextOp(xref, initialValue, icuPlaceholder, sourceSpan) {
10902
10915
  return {
10903
10916
  kind: OpKind.Text,
10904
10917
  xref,
10905
10918
  handle: new SlotHandle(),
10906
10919
  initialValue,
10920
+ icuPlaceholder,
10907
10921
  sourceSpan,
10908
10922
  ...TRAIT_CONSUMES_SLOT,
10909
10923
  ...NEW_OP,
@@ -10912,7 +10926,9 @@ function createTextOp(xref, initialValue, sourceSpan) {
10912
10926
  /**
10913
10927
  * Create a `ListenerOp`. Host bindings reuse all the listener logic.
10914
10928
  */
10915
- function createListenerOp(target, targetSlot, name, tag, animationPhase, hostListener, sourceSpan) {
10929
+ function createListenerOp(target, targetSlot, name, tag, handlerOps, animationPhase, eventTarget, hostListener, sourceSpan) {
10930
+ const handlerList = new OpList();
10931
+ handlerList.push(handlerOps);
10916
10932
  return {
10917
10933
  kind: OpKind.Listener,
10918
10934
  target,
@@ -10920,11 +10936,12 @@ function createListenerOp(target, targetSlot, name, tag, animationPhase, hostLis
10920
10936
  tag,
10921
10937
  hostListener,
10922
10938
  name,
10923
- handlerOps: new OpList(),
10939
+ handlerOps: handlerList,
10924
10940
  handlerFnName: null,
10925
10941
  consumesDollarEvent: false,
10926
10942
  isAnimationListener: animationPhase !== null,
10927
- animationPhase: animationPhase,
10943
+ animationPhase,
10944
+ eventTarget,
10928
10945
  sourceSpan,
10929
10946
  ...NEW_OP,
10930
10947
  };
@@ -10971,7 +10988,7 @@ function createProjectionOp(xref, selector, i18nPlaceholder, attributes, sourceS
10971
10988
  /**
10972
10989
  * Create an `ExtractedAttributeOp`.
10973
10990
  */
10974
- function createExtractedAttributeOp(target, bindingKind, name, expression, i18nContext) {
10991
+ function createExtractedAttributeOp(target, bindingKind, name, expression, i18nContext, i18nMessage, securityContext) {
10975
10992
  return {
10976
10993
  kind: OpKind.ExtractedAttribute,
10977
10994
  target,
@@ -10979,6 +10996,9 @@ function createExtractedAttributeOp(target, bindingKind, name, expression, i18nC
10979
10996
  name,
10980
10997
  expression,
10981
10998
  i18nContext,
10999
+ i18nMessage,
11000
+ securityContext,
11001
+ trustedValueFn: null,
10982
11002
  ...NEW_OP,
10983
11003
  };
10984
11004
  }
@@ -11087,6 +11107,19 @@ function createIcuEndOp(xref) {
11087
11107
  ...NEW_OP,
11088
11108
  };
11089
11109
  }
11110
+ /**
11111
+ * Creates an ICU placeholder op.
11112
+ */
11113
+ function createIcuPlaceholderOp(xref, name, strings) {
11114
+ return {
11115
+ kind: OpKind.IcuPlaceholder,
11116
+ xref,
11117
+ name,
11118
+ strings,
11119
+ expressionPlaceholders: [],
11120
+ ...NEW_OP,
11121
+ };
11122
+ }
11090
11123
  function createI18nContextOp(contextKind, xref, i18nBlock, message, sourceSpan) {
11091
11124
  if (i18nBlock === null && contextKind !== I18nContextKind.Attr) {
11092
11125
  throw new Error('AssertionError: i18nBlock must be provided for non-attribute contexts.');
@@ -11121,13 +11154,15 @@ function literalOrArrayLiteral$1(value) {
11121
11154
  return literal(value, INFERRED_TYPE);
11122
11155
  }
11123
11156
 
11124
- function createHostPropertyOp(name, expression, isAnimationTrigger, i18nContext, sourceSpan) {
11157
+ function createHostPropertyOp(name, expression, isAnimationTrigger, i18nContext, securityContext, sourceSpan) {
11125
11158
  return {
11126
11159
  kind: OpKind.HostProperty,
11127
11160
  name,
11128
11161
  expression,
11129
11162
  isAnimationTrigger,
11130
11163
  i18nContext,
11164
+ securityContext,
11165
+ sanitizer: null,
11131
11166
  sourceSpan,
11132
11167
  ...TRAIT_CONSUMES_VARS,
11133
11168
  ...NEW_OP,
@@ -11407,68 +11442,52 @@ function needsApplication(i18nContexts, op) {
11407
11442
  * after the last update instruction that depends on that slot.
11408
11443
  */
11409
11444
  function assignI18nSlotDependencies(job) {
11410
- const i18nLastSlotConsumers = new Map();
11411
- let lastSlotConsumer = null;
11412
- let currentI18nOp = null;
11413
11445
  for (const unit of job.units) {
11414
- // Record the last consumed slot before each i18n end instruction.
11415
- for (const op of unit.create) {
11416
- if (hasConsumesSlotTrait(op)) {
11417
- lastSlotConsumer = op.xref;
11446
+ // The first update op.
11447
+ let updateOp = unit.update.head;
11448
+ // I18n expressions currently being moved during the iteration.
11449
+ let i18nExpressionsInProgress = [];
11450
+ // Non-null while we are iterating through an i18nStart/i18nEnd pair
11451
+ let state = null;
11452
+ for (const createOp of unit.create) {
11453
+ if (createOp.kind === OpKind.I18nStart) {
11454
+ state = {
11455
+ blockXref: createOp.xref,
11456
+ lastSlotConsumer: createOp.xref,
11457
+ };
11418
11458
  }
11419
- switch (op.kind) {
11420
- case OpKind.I18nStart:
11421
- currentI18nOp = op;
11422
- break;
11423
- case OpKind.I18nEnd:
11424
- if (currentI18nOp === null) {
11425
- throw new Error('AssertionError: Expected an active I18n block while calculating last slot consumers');
11459
+ else if (createOp.kind === OpKind.I18nEnd) {
11460
+ for (const op of i18nExpressionsInProgress) {
11461
+ op.target = state.lastSlotConsumer;
11462
+ OpList.insertBefore(op, updateOp);
11463
+ }
11464
+ i18nExpressionsInProgress.length = 0;
11465
+ state = null;
11466
+ }
11467
+ if (hasConsumesSlotTrait(createOp)) {
11468
+ if (state !== null) {
11469
+ state.lastSlotConsumer = createOp.xref;
11470
+ }
11471
+ while (true) {
11472
+ if (updateOp.next === null) {
11473
+ break;
11426
11474
  }
11427
- if (lastSlotConsumer === null) {
11428
- throw new Error('AssertionError: Expected a last slot consumer while calculating last slot consumers');
11475
+ if (state !== null && updateOp.kind === OpKind.I18nExpression &&
11476
+ updateOp.usage === I18nExpressionFor.I18nText &&
11477
+ updateOp.i18nOwner === state.blockXref) {
11478
+ const opToRemove = updateOp;
11479
+ updateOp = updateOp.next;
11480
+ OpList.remove(opToRemove);
11481
+ i18nExpressionsInProgress.push(opToRemove);
11482
+ continue;
11429
11483
  }
11430
- i18nLastSlotConsumers.set(currentI18nOp.xref, lastSlotConsumer);
11431
- currentI18nOp = null;
11432
- break;
11433
- }
11434
- }
11435
- // Expresions that are currently being moved.
11436
- let opsToMove = [];
11437
- // Previously we found the last slot-consuming create mode op in the i18n block. That op will be
11438
- // the new target for any moved i18n expresion inside the i18n block, and that op's slot is
11439
- // stored here.
11440
- let moveAfterTarget = null;
11441
- // This is the last target in the create IR that we saw during iteration. Eventally, it will be
11442
- // equal to moveAfterTarget. But wait! We need to find the *last* such op whose target is equal
11443
- // to `moveAfterTarget`.
11444
- let previousTarget = null;
11445
- for (const op of unit.update) {
11446
- if (hasDependsOnSlotContextTrait(op)) {
11447
- // We've found an op that depends on another slot other than the one that we want to move
11448
- // the expressions to, after previously having seen the one we want to move to.
11449
- if (moveAfterTarget !== null && previousTarget === moveAfterTarget &&
11450
- op.target !== previousTarget) {
11451
- OpList.insertBefore(opsToMove, op);
11452
- moveAfterTarget = null;
11453
- opsToMove = [];
11454
- }
11455
- previousTarget = op.target;
11456
- }
11457
- if (op.kind === OpKind.I18nExpression && op.usage === I18nExpressionFor.I18nText) {
11458
- // This is an I18nExpressionOps that is used for text (not attributes).
11459
- OpList.remove(op);
11460
- opsToMove.push(op);
11461
- const target = i18nLastSlotConsumers.get(op.i18nOwner);
11462
- if (target === undefined) {
11463
- throw new Error('AssertionError: Expected to find a last slot consumer for an I18nExpressionOp');
11484
+ if (hasDependsOnSlotContextTrait(updateOp) && updateOp.target !== createOp.xref) {
11485
+ break;
11486
+ }
11487
+ updateOp = updateOp.next;
11464
11488
  }
11465
- op.target = target;
11466
- moveAfterTarget = op.target;
11467
11489
  }
11468
11490
  }
11469
- if (moveAfterTarget !== null) {
11470
- unit.update.push(opsToMove);
11471
- }
11472
11491
  }
11473
11492
  }
11474
11493
 
@@ -11501,19 +11520,21 @@ function extractAttributes(job) {
11501
11520
  case OpKind.Property:
11502
11521
  if (!op.isAnimationTrigger) {
11503
11522
  let bindingKind;
11504
- if (op.i18nContext !== null) {
11523
+ if (op.i18nMessage !== null && op.templateKind === null) {
11505
11524
  // If the binding has an i18n context, it is an i18n attribute, and should have that
11506
11525
  // kind in the consts array.
11507
11526
  bindingKind = BindingKind.I18n;
11508
11527
  }
11509
- else if (op.isStructuralTemplate) {
11510
- // TODO: How do i18n attributes on templates work?!
11528
+ else if (op.isStructuralTemplateAttribute) {
11511
11529
  bindingKind = BindingKind.Template;
11512
11530
  }
11513
11531
  else {
11514
11532
  bindingKind = BindingKind.Property;
11515
11533
  }
11516
- OpList.insertBefore(createExtractedAttributeOp(op.target, bindingKind, op.name, null, null), lookupElement$2(elements, op.target));
11534
+ OpList.insertBefore(
11535
+ // Deliberaly null i18nMessage value
11536
+ createExtractedAttributeOp(op.target, bindingKind, op.name, /* expression */ null, /* i18nContext */ null,
11537
+ /* i18nMessage */ null, op.securityContext), lookupElement$2(elements, op.target));
11517
11538
  }
11518
11539
  break;
11519
11540
  case OpKind.StyleProp:
@@ -11524,12 +11545,16 @@ function extractAttributes(job) {
11524
11545
  // mode.
11525
11546
  if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
11526
11547
  op.expression instanceof EmptyExpr) {
11527
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null, null), lookupElement$2(elements, op.target));
11548
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, op.name, /* expression */ null,
11549
+ /* i18nContext */ null,
11550
+ /* i18nMessage */ null, SecurityContext.STYLE), lookupElement$2(elements, op.target));
11528
11551
  }
11529
11552
  break;
11530
11553
  case OpKind.Listener:
11531
11554
  if (!op.isAnimationListener) {
11532
- const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null, null);
11555
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, op.name, /* expression */ null,
11556
+ /* i18nContext */ null,
11557
+ /* i18nMessage */ null, SecurityContext.NONE);
11533
11558
  if (job.kind === CompilationJobKind.Host) {
11534
11559
  // This attribute will apply to the enclosing host binding compilation unit, so order
11535
11560
  // doesn't matter.
@@ -11561,24 +11586,14 @@ function extractAttributeOp(unit, op, elements) {
11561
11586
  if (op.expression instanceof Interpolation) {
11562
11587
  return;
11563
11588
  }
11564
- let extractable = op.expression.isConstant();
11589
+ let extractable = op.isTextAttribute || op.expression.isConstant();
11565
11590
  if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
11566
- // TemplateDefinitionBuilder only extracted attributes that were string literals.
11567
- extractable = isStringLiteral(op.expression);
11568
- if (op.name === 'style' || op.name === 'class') {
11569
- // For style and class attributes, TemplateDefinitionBuilder only extracted them if they were
11570
- // text attributes. For example, `[attr.class]="'my-class'"` was not extracted despite being a
11571
- // string literal, because it is not a text attribute.
11572
- extractable &&= op.isTextAttribute;
11573
- }
11574
- if (unit.job.kind === CompilationJobKind.Host) {
11575
- // TemplateDefinitionBuilder also does not seem to extract string literals if they are part of
11576
- // a host attribute.
11577
- extractable &&= op.isTextAttribute;
11578
- }
11591
+ // TemplateDefinitionBuilder only extracts text attributes. It does not extract attriibute
11592
+ // bindings, even if they are constants.
11593
+ extractable &&= op.isTextAttribute;
11579
11594
  }
11580
11595
  if (extractable) {
11581
- const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isStructuralTemplate ? BindingKind.Template : BindingKind.Attribute, op.name, op.expression, op.i18nContext);
11596
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isStructuralTemplateAttribute ? BindingKind.Template : BindingKind.Attribute, op.name, op.expression, op.i18nContext, op.i18nMessage, op.securityContext);
11582
11597
  if (unit.job.kind === CompilationJobKind.Host) {
11583
11598
  // This attribute will apply to the enclosing host binding compilation unit, so order doesn't
11584
11599
  // matter.
@@ -11625,16 +11640,16 @@ function specializeBindings(job) {
11625
11640
  target.nonBindable = true;
11626
11641
  }
11627
11642
  else {
11628
- OpList.replace(op, createAttributeOp(op.target, op.name, op.expression, op.securityContext, op.isTextAttribute, op.isStructuralTemplate, op.i18nContext, op.sourceSpan));
11643
+ OpList.replace(op, createAttributeOp(op.target, op.name, op.expression, op.securityContext, op.isTextAttribute, op.isStructuralTemplateAttribute, op.templateKind, op.i18nMessage, op.sourceSpan));
11629
11644
  }
11630
11645
  break;
11631
11646
  case BindingKind.Property:
11632
11647
  case BindingKind.Animation:
11633
11648
  if (job.kind === CompilationJobKind.Host) {
11634
- OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.i18nContext, op.sourceSpan));
11649
+ OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.i18nContext, op.securityContext, op.sourceSpan));
11635
11650
  }
11636
11651
  else {
11637
- OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isStructuralTemplate, op.i18nContext, op.sourceSpan));
11652
+ OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isStructuralTemplateAttribute, op.templateKind, op.i18nContext, op.i18nMessage, op.sourceSpan));
11638
11653
  }
11639
11654
  break;
11640
11655
  case BindingKind.I18n:
@@ -11869,9 +11884,9 @@ function collectElementConsts(job) {
11869
11884
  for (const unit of job.units) {
11870
11885
  for (const op of unit.create) {
11871
11886
  if (op.kind === OpKind.ExtractedAttribute) {
11872
- const attributes = allElementAttributes.get(op.target) || new ElementAttributes();
11887
+ const attributes = allElementAttributes.get(op.target) || new ElementAttributes(job.compatibility);
11873
11888
  allElementAttributes.set(op.target, attributes);
11874
- attributes.add(op.bindingKind, op.name, op.expression);
11889
+ attributes.add(op.bindingKind, op.name, op.expression, op.trustedValueFn);
11875
11890
  OpList.remove(op);
11876
11891
  }
11877
11892
  }
@@ -11914,11 +11929,6 @@ const FLYWEIGHT_ARRAY = Object.freeze([]);
11914
11929
  * Container for all of the various kinds of attributes which are applied on an element.
11915
11930
  */
11916
11931
  class ElementAttributes {
11917
- constructor() {
11918
- this.known = new Set();
11919
- this.byKind = new Map;
11920
- this.projectAs = null;
11921
- }
11922
11932
  get attributes() {
11923
11933
  return this.byKind.get(BindingKind.Attribute) ?? FLYWEIGHT_ARRAY;
11924
11934
  }
@@ -11937,11 +11947,32 @@ class ElementAttributes {
11937
11947
  get i18n() {
11938
11948
  return this.byKind.get(BindingKind.I18n) ?? FLYWEIGHT_ARRAY;
11939
11949
  }
11940
- add(kind, name, value) {
11941
- if (this.known.has(name)) {
11950
+ constructor(compatibility) {
11951
+ this.compatibility = compatibility;
11952
+ this.known = new Map();
11953
+ this.byKind = new Map;
11954
+ this.projectAs = null;
11955
+ }
11956
+ isKnown(kind, name, value) {
11957
+ const nameToValue = this.known.get(kind) ?? new Set();
11958
+ this.known.set(kind, nameToValue);
11959
+ if (nameToValue.has(name)) {
11960
+ return true;
11961
+ }
11962
+ nameToValue.add(name);
11963
+ return false;
11964
+ }
11965
+ add(kind, name, value, trustedValueFn) {
11966
+ // TemplateDefinitionBuilder puts duplicate attribute, class, and style values into the consts
11967
+ // array. This seems inefficient, we can probably keep just the first one or the last value
11968
+ // (whichever actually gets applied when multiple values are listed for the same attribute).
11969
+ const allowDuplicates = this.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
11970
+ (kind === BindingKind.Attribute || kind === BindingKind.ClassName ||
11971
+ kind === BindingKind.StyleProperty);
11972
+ if (!allowDuplicates && this.isKnown(kind, name, value)) {
11942
11973
  return;
11943
11974
  }
11944
- this.known.add(name);
11975
+ // TODO: Can this be its own phase
11945
11976
  if (name === 'ngProjectAs') {
11946
11977
  if (value === null || !(value instanceof LiteralExpr) || (value.value == null) ||
11947
11978
  (typeof value.value?.toString() !== 'string')) {
@@ -11957,7 +11988,15 @@ class ElementAttributes {
11957
11988
  if (value === null) {
11958
11989
  throw Error('Attribute, i18n attribute, & style element attributes must have a value');
11959
11990
  }
11960
- array.push(value);
11991
+ if (trustedValueFn !== null) {
11992
+ if (!isStringLiteral(value)) {
11993
+ throw Error('AssertionError: extracted attribute value should be string literal');
11994
+ }
11995
+ array.push(taggedTemplate(trustedValueFn, new TemplateLiteral([new TemplateLiteralElement(value.value)], []), undefined, value.sourceSpan));
11996
+ }
11997
+ else {
11998
+ array.push(value);
11999
+ }
11961
12000
  }
11962
12001
  }
11963
12002
  arrayFor(kind) {
@@ -11971,7 +12010,7 @@ class ElementAttributes {
11971
12010
  * Gets an array of literal expressions representing the attribute's namespaced name.
11972
12011
  */
11973
12012
  function getAttributeNameLiterals$1(name) {
11974
- const [attributeNamespace, attributeName] = splitNsName(name);
12013
+ const [attributeNamespace, attributeName] = splitNsName(name, false);
11975
12014
  const nameLiteral = literal(attributeName);
11976
12015
  if (attributeNamespace) {
11977
12016
  return [
@@ -12044,7 +12083,7 @@ function convertI18nBindings(job) {
12044
12083
  if (op.expression.i18nPlaceholders.length !== op.expression.expressions.length) {
12045
12084
  throw new Error(`AssertionError: An i18n attribute binding instruction requires the same number of expressions and placeholders, but found ${op.expression.i18nPlaceholders.length} placeholders and ${op.expression.expressions.length} expressions`);
12046
12085
  }
12047
- ops.push(createI18nExpressionOp(op.i18nContext, i18nAttributesForElem.target, i18nAttributesForElem.xref, i18nAttributesForElem.handle, expr, op.expression.i18nPlaceholders[i], I18nParamResolutionTime.Creation, I18nExpressionFor.I18nAttribute, op.name, op.sourceSpan));
12086
+ ops.push(createI18nExpressionOp(op.i18nContext, i18nAttributesForElem.target, i18nAttributesForElem.xref, i18nAttributesForElem.handle, expr, null, op.expression.i18nPlaceholders[i], I18nParamResolutionTime.Creation, I18nExpressionFor.I18nAttribute, op.name, op.sourceSpan));
12048
12087
  }
12049
12088
  OpList.replaceWithMany(op, ops);
12050
12089
  break;
@@ -12081,7 +12120,8 @@ function createDeferDepsFns(job) {
12081
12120
  if (op.handle.slot === null) {
12082
12121
  throw new Error('AssertionError: slot must be assigned bfore extracting defer deps functions');
12083
12122
  }
12084
- op.resolverFn = job.pool.getSharedFunctionReference(depsFnExpr, `${job.componentName}_Defer_${op.handle.slot}_DepsFn`);
12123
+ op.resolverFn = job.pool.getSharedFunctionReference(depsFnExpr, `${job.componentName}_Defer_${op.handle.slot}_DepsFn`,
12124
+ /* Don't use unique names for TDB compatibility */ false);
12085
12125
  }
12086
12126
  }
12087
12127
  }
@@ -12098,53 +12138,119 @@ function createDeferDepsFns(job) {
12098
12138
  * message.)
12099
12139
  */
12100
12140
  function createI18nContexts(job) {
12101
- const rootContexts = new Map();
12102
- let currentI18nOp = null;
12103
- let xref;
12141
+ // Create i18n context ops for i18n attrs.
12142
+ const attrContextByMessage = new Map();
12143
+ for (const unit of job.units) {
12144
+ for (const op of unit.ops()) {
12145
+ switch (op.kind) {
12146
+ case OpKind.Binding:
12147
+ case OpKind.Property:
12148
+ case OpKind.Attribute:
12149
+ case OpKind.ExtractedAttribute:
12150
+ if (op.i18nMessage === null) {
12151
+ continue;
12152
+ }
12153
+ if (!attrContextByMessage.has(op.i18nMessage)) {
12154
+ const i18nContext = createI18nContextOp(I18nContextKind.Attr, job.allocateXrefId(), null, op.i18nMessage, null);
12155
+ unit.create.push(i18nContext);
12156
+ attrContextByMessage.set(op.i18nMessage, i18nContext.xref);
12157
+ }
12158
+ op.i18nContext = attrContextByMessage.get(op.i18nMessage);
12159
+ break;
12160
+ }
12161
+ }
12162
+ }
12163
+ // Create i18n context ops for root i18n blocks.
12164
+ const blockContextByI18nBlock = new Map();
12104
12165
  for (const unit of job.units) {
12105
12166
  for (const op of unit.create) {
12106
12167
  switch (op.kind) {
12107
12168
  case OpKind.I18nStart:
12108
- currentI18nOp = op;
12109
- // Each root i18n block gets its own context, child ones refer to the context for their
12110
- // root block.
12111
12169
  if (op.xref === op.root) {
12112
- xref = job.allocateXrefId();
12113
- unit.create.push(createI18nContextOp(I18nContextKind.RootI18n, xref, op.xref, op.message, null));
12114
- op.context = xref;
12115
- rootContexts.set(op.xref, xref);
12170
+ const contextOp = createI18nContextOp(I18nContextKind.RootI18n, job.allocateXrefId(), op.xref, op.message, null);
12171
+ unit.create.push(contextOp);
12172
+ op.context = contextOp.xref;
12173
+ blockContextByI18nBlock.set(op.xref, contextOp);
12116
12174
  }
12117
12175
  break;
12176
+ }
12177
+ }
12178
+ }
12179
+ // Assign i18n contexts for child i18n blocks. These don't need their own conext, instead they
12180
+ // should inherit from their root i18n block.
12181
+ for (const unit of job.units) {
12182
+ for (const op of unit.create) {
12183
+ if (op.kind === OpKind.I18nStart && op.xref !== op.root) {
12184
+ const rootContext = blockContextByI18nBlock.get(op.root);
12185
+ if (rootContext === undefined) {
12186
+ throw Error('AssertionError: Root i18n block i18n context should have been created.');
12187
+ }
12188
+ op.context = rootContext.xref;
12189
+ blockContextByI18nBlock.set(op.xref, rootContext);
12190
+ }
12191
+ }
12192
+ }
12193
+ // Create or assign i18n contexts for ICUs.
12194
+ let currentI18nOp = null;
12195
+ for (const unit of job.units) {
12196
+ for (const op of unit.create) {
12197
+ switch (op.kind) {
12198
+ case OpKind.I18nStart:
12199
+ currentI18nOp = op;
12200
+ break;
12118
12201
  case OpKind.I18nEnd:
12119
12202
  currentI18nOp = null;
12120
12203
  break;
12121
12204
  case OpKind.IcuStart:
12122
- // If an ICU represents a different message than its containing block, we give it its own
12123
- // i18n context.
12124
12205
  if (currentI18nOp === null) {
12125
- throw Error('Unexpected ICU outside of an i18n block.');
12206
+ throw Error('AssertionError: Unexpected ICU outside of an i18n block.');
12126
12207
  }
12127
12208
  if (op.message.id !== currentI18nOp.message.id) {
12128
- // There was an enclosing i18n block around this ICU somewhere.
12129
- xref = job.allocateXrefId();
12130
- unit.create.push(createI18nContextOp(I18nContextKind.Icu, xref, currentI18nOp.xref, op.message, null));
12131
- op.context = xref;
12209
+ // This ICU is a sub-message inside its parent i18n block message. We need to give it
12210
+ // its own context.
12211
+ const contextOp = createI18nContextOp(I18nContextKind.Icu, job.allocateXrefId(), currentI18nOp.xref, op.message, null);
12212
+ unit.create.push(contextOp);
12213
+ op.context = contextOp.xref;
12132
12214
  }
12133
12215
  else {
12134
- // The i18n block was generated because of this ICU, OR it was explicit, but the ICU is
12135
- // the only localizable content inside of it.
12216
+ // This ICU is the only translatable content in its parent i18n block. We need to
12217
+ // convert the parent's context into an ICU context.
12136
12218
  op.context = currentI18nOp.context;
12219
+ blockContextByI18nBlock.get(currentI18nOp.xref).contextKind = I18nContextKind.Icu;
12137
12220
  }
12138
12221
  break;
12139
12222
  }
12140
12223
  }
12141
12224
  }
12142
- // Assign contexts to child i18n blocks, now that all root i18n blocks have their context
12143
- // assigned.
12225
+ }
12226
+
12227
+ /**
12228
+ * Deduplicate text bindings, e.g. <div class="cls1" class="cls2">
12229
+ */
12230
+ function deduplicateTextBindings(job) {
12231
+ const seen = new Map();
12144
12232
  for (const unit of job.units) {
12145
- for (const op of unit.create) {
12146
- if (op.kind === OpKind.I18nStart && op.xref !== op.root) {
12147
- op.context = rootContexts.get(op.root);
12233
+ for (const op of unit.update.reversed()) {
12234
+ if (op.kind === OpKind.Binding && op.isTextAttribute) {
12235
+ const seenForElement = seen.get(op.target) || new Set();
12236
+ if (seenForElement.has(op.name)) {
12237
+ if (job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
12238
+ // For most duplicated attributes, TemplateDefinitionBuilder lists all of the values in
12239
+ // the consts array. However, for style and class attributes it only keeps the last one.
12240
+ // We replicate that behavior here since it has actual consequences for apps with
12241
+ // duplicate class or style attrs.
12242
+ if (op.name === 'style' || op.name === 'class') {
12243
+ OpList.remove(op);
12244
+ }
12245
+ }
12246
+ else {
12247
+ // TODO: Determine the correct behavior. It would probably make sense to merge multiple
12248
+ // style and class attributes. Alternatively we could just throw an error, as HTML
12249
+ // doesn't permit duplicate attributes.
12250
+ }
12251
+ }
12252
+ seenForElement.add(op.name);
12253
+ seen.set(op.target, seenForElement);
12148
12254
  }
12149
12255
  }
12150
12256
  }
@@ -12530,13 +12636,18 @@ const LIST_DELIMITER = '|';
12530
12636
  * used in the final output.
12531
12637
  */
12532
12638
  function extractI18nMessages(job) {
12533
- // Save the i18n start and i18n context ops for later use.
12534
- const i18nContexts = new Map();
12639
+ // Create an i18n message for each context.
12640
+ // TODO: Merge the context op with the message op since they're 1:1 anyways.
12641
+ const i18nMessagesByContext = new Map();
12535
12642
  const i18nBlocks = new Map();
12643
+ const i18nContexts = new Map();
12536
12644
  for (const unit of job.units) {
12537
12645
  for (const op of unit.create) {
12538
12646
  switch (op.kind) {
12539
12647
  case OpKind.I18nContext:
12648
+ const i18nMessageOp = createI18nMessage(job, op);
12649
+ unit.create.push(i18nMessageOp);
12650
+ i18nMessagesByContext.set(op.xref, i18nMessageOp);
12540
12651
  i18nContexts.set(op.xref, op);
12541
12652
  break;
12542
12653
  case OpKind.I18nStart:
@@ -12545,54 +12656,47 @@ function extractI18nMessages(job) {
12545
12656
  }
12546
12657
  }
12547
12658
  }
12548
- // TODO: Miles and I think that contexts have a 1-to-1 correspondence with extracted messages, so
12549
- // this phase can probably be simplified.
12550
- // Extract messages from contexts of i18n attributes.
12551
- for (const unit of job.units) {
12552
- for (const op of unit.create) {
12553
- if (op.kind !== OpKind.I18nContext || op.contextKind !== I18nContextKind.Attr) {
12554
- continue;
12555
- }
12556
- const i18nMessageOp = createI18nMessage(job, op);
12557
- unit.create.push(i18nMessageOp);
12558
- }
12559
- }
12560
- // Extract messages from root i18n blocks.
12561
- const i18nBlockMessages = new Map();
12562
- for (const unit of job.units) {
12563
- for (const op of unit.create) {
12564
- if (op.kind === OpKind.I18nStart && op.xref === op.root) {
12565
- if (!op.context) {
12566
- throw Error('I18n start op should have its context set.');
12567
- }
12568
- const i18nMessageOp = createI18nMessage(job, i18nContexts.get(op.context));
12569
- i18nBlockMessages.set(op.xref, i18nMessageOp);
12570
- unit.create.push(i18nMessageOp);
12571
- }
12572
- }
12573
- }
12574
- // Extract messages from ICUs with their own sub-context.
12659
+ // Associate sub-messages for ICUs with their root message. At this point we can also remove the
12660
+ // ICU start/end ops, as they are no longer needed.
12661
+ let currentIcu = null;
12575
12662
  for (const unit of job.units) {
12576
12663
  for (const op of unit.create) {
12577
12664
  switch (op.kind) {
12578
12665
  case OpKind.IcuStart:
12579
- if (!op.context) {
12580
- throw Error('ICU op should have its context set.');
12666
+ currentIcu = op;
12667
+ OpList.remove(op);
12668
+ // Skip any contexts not associated with an ICU.
12669
+ const icuContext = i18nContexts.get(op.context);
12670
+ if (icuContext.contextKind !== I18nContextKind.Icu) {
12671
+ continue;
12581
12672
  }
12582
- const i18nContext = i18nContexts.get(op.context);
12583
- if (i18nContext.contextKind === I18nContextKind.Icu) {
12584
- if (i18nContext.i18nBlock === null) {
12585
- throw Error('ICU context should have its i18n block set.');
12586
- }
12587
- const subMessage = createI18nMessage(job, i18nContext, op.messagePlaceholder);
12588
- unit.create.push(subMessage);
12589
- const rootI18nId = i18nBlocks.get(i18nContext.i18nBlock).root;
12590
- const parentMessage = i18nBlockMessages.get(rootI18nId);
12591
- parentMessage?.subMessages.push(subMessage.xref);
12673
+ // Skip ICUs that share a context with their i18n message. These represent root-level
12674
+ // ICUs, not sub-messages.
12675
+ const i18nBlock = i18nBlocks.get(icuContext.i18nBlock);
12676
+ if (i18nBlock.context === icuContext.xref) {
12677
+ continue;
12592
12678
  }
12593
- OpList.remove(op);
12679
+ // Find the root message and push this ICUs message as a sub-message.
12680
+ const rootI18nBlock = i18nBlocks.get(i18nBlock.root);
12681
+ const rootMessage = i18nMessagesByContext.get(rootI18nBlock.context);
12682
+ if (rootMessage === undefined) {
12683
+ throw Error('AssertionError: ICU sub-message should belong to a root message.');
12684
+ }
12685
+ const subMessage = i18nMessagesByContext.get(icuContext.xref);
12686
+ subMessage.messagePlaceholder = op.messagePlaceholder;
12687
+ rootMessage.subMessages.push(subMessage.xref);
12594
12688
  break;
12595
12689
  case OpKind.IcuEnd:
12690
+ currentIcu = null;
12691
+ OpList.remove(op);
12692
+ break;
12693
+ case OpKind.IcuPlaceholder:
12694
+ // Add ICU placeholders to the message, then remove the ICU placeholder ops.
12695
+ if (currentIcu === null || currentIcu.context == null) {
12696
+ throw Error('AssertionError: Unexpected ICU placeholder outside of i18n context');
12697
+ }
12698
+ const msg = i18nMessagesByContext.get(currentIcu.context);
12699
+ msg.postprocessingParams.set(op.name, literal(formatIcuPlaceholder(op)));
12596
12700
  OpList.remove(op);
12597
12701
  break;
12598
12702
  }
@@ -12605,14 +12709,19 @@ function extractI18nMessages(job) {
12605
12709
  function createI18nMessage(job, context, messagePlaceholder) {
12606
12710
  let formattedParams = formatParams(context.params);
12607
12711
  const formattedPostprocessingParams = formatParams(context.postprocessingParams);
12608
- let needsPostprocessing = formattedPostprocessingParams.size > 0;
12609
- for (const values of context.params.values()) {
12610
- if (values.length > 1) {
12611
- needsPostprocessing = true;
12612
- }
12613
- }
12712
+ let needsPostprocessing = [...context.params.values()].some(v => v.length > 1);
12614
12713
  return createI18nMessageOp(job.allocateXrefId(), context.xref, context.i18nBlock, context.message, messagePlaceholder ?? null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
12615
12714
  }
12715
+ /**
12716
+ * Formats an ICU placeholder into a single string with expression placeholders.
12717
+ */
12718
+ function formatIcuPlaceholder(op) {
12719
+ if (op.strings.length !== op.expressionPlaceholders.length + 1) {
12720
+ throw Error(`AsserionError: Invalid ICU placeholder with ${op.strings.length} strings and ${op.expressionPlaceholders.length} expressions`);
12721
+ }
12722
+ const values = op.expressionPlaceholders.map(formatValue);
12723
+ return op.strings.flatMap((str, i) => [str, values[i] || '']).join('');
12724
+ }
12616
12725
  /**
12617
12726
  * Formats a map of `I18nParamValue[]` values into a map of `Expression` values.
12618
12727
  */
@@ -12812,9 +12921,15 @@ function recursivelyProcessView(view, parentScope) {
12812
12921
  for (const op of view.create) {
12813
12922
  switch (op.kind) {
12814
12923
  case OpKind.Template:
12924
+ // Descend into child embedded views.
12925
+ recursivelyProcessView(view.job.views.get(op.xref), scope);
12926
+ break;
12815
12927
  case OpKind.RepeaterCreate:
12816
12928
  // Descend into child embedded views.
12817
12929
  recursivelyProcessView(view.job.views.get(op.xref), scope);
12930
+ if (op.emptyView) {
12931
+ recursivelyProcessView(view.job.views.get(op.emptyView), scope);
12932
+ }
12818
12933
  break;
12819
12934
  case OpKind.Listener:
12820
12935
  // Prepend variables to listener handler functions.
@@ -12945,7 +13060,7 @@ const BANG_IMPORTANT = '!important';
12945
13060
  */
12946
13061
  function parseHostStyleProperties(job) {
12947
13062
  for (const op of job.root.update) {
12948
- if (op.kind !== OpKind.Binding) {
13063
+ if (!(op.kind === OpKind.Binding && op.bindingKind === BindingKind.Property)) {
12949
13064
  continue;
12950
13065
  }
12951
13066
  if (op.name.endsWith(BANG_IMPORTANT)) {
@@ -19954,7 +20069,7 @@ const ESCAPE = '\uFFFD';
19954
20069
  function collectI18nConsts(job) {
19955
20070
  const fileBasedI18nSuffix = job.relativeContextFilePath.replace(/[^A-Za-z0-9]/g, '_').toUpperCase() + '_';
19956
20071
  // Step One: Build up various lookup maps we need to collect all the consts.
19957
- // Context Xref -> Extracted Attribute Op
20072
+ // Context Xref -> Extracted Attribute Ops
19958
20073
  const extractedAttributesByI18nContext = new Map();
19959
20074
  // Element/ElementStart Xref -> I18n Attributes config op
19960
20075
  const i18nAttributesByElement = new Map();
@@ -19965,7 +20080,9 @@ function collectI18nConsts(job) {
19965
20080
  for (const unit of job.units) {
19966
20081
  for (const op of unit.ops()) {
19967
20082
  if (op.kind === OpKind.ExtractedAttribute && op.i18nContext !== null) {
19968
- extractedAttributesByI18nContext.set(op.i18nContext, op);
20083
+ const attributes = extractedAttributesByI18nContext.get(op.i18nContext) ?? [];
20084
+ attributes.push(op);
20085
+ extractedAttributesByI18nContext.set(op.i18nContext, attributes);
19969
20086
  }
19970
20087
  else if (op.kind === OpKind.I18nAttributes) {
19971
20088
  i18nAttributesByElement.set(op.target, op);
@@ -20010,9 +20127,11 @@ function collectI18nConsts(job) {
20010
20127
  i18nValuesByContext.set(op.i18nContext, mainVar);
20011
20128
  // This i18n message may correspond to an individual extracted attribute. If so, The
20012
20129
  // value of that attribute is updated to read the extracted i18n variable.
20013
- const attributeForMessage = extractedAttributesByI18nContext.get(op.i18nContext);
20014
- if (attributeForMessage !== undefined) {
20015
- attributeForMessage.expression = mainVar;
20130
+ const attributesForMessage = extractedAttributesByI18nContext.get(op.i18nContext);
20131
+ if (attributesForMessage !== undefined) {
20132
+ for (const attr of attributesForMessage) {
20133
+ attr.expression = mainVar.clone();
20134
+ }
20016
20135
  }
20017
20136
  }
20018
20137
  }
@@ -20099,7 +20218,7 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
20099
20218
  let transformFn = undefined;
20100
20219
  // If nescessary, add a post-processing step and resolve any placeholder params that are
20101
20220
  // set in post-processing.
20102
- if (messageOp.needsPostprocessing) {
20221
+ if (messageOp.needsPostprocessing || messageOp.postprocessingParams.size > 0) {
20103
20222
  // Sort the post-processing params for consistency with TemaplateDefinitionBuilder output.
20104
20223
  const postprocessingParams = Object.fromEntries([...messageOp.postprocessingParams.entries()].sort());
20105
20224
  const formattedPostprocessingParams = formatI18nPlaceholderNamesInMap(postprocessingParams, /* useCamelCase */ false);
@@ -20129,7 +20248,6 @@ function addSubMessageParams(messageOp, subMessagePlaceholders) {
20129
20248
  else {
20130
20249
  messageOp.params.set(placeholder, literal(`${ESCAPE}${I18N_ICU_MAPPING_PREFIX}${placeholder}${ESCAPE}`));
20131
20250
  messageOp.postprocessingParams.set(placeholder, literalArr(subMessages));
20132
- messageOp.needsPostprocessing = true;
20133
20251
  }
20134
20252
  }
20135
20253
  }
@@ -20215,6 +20333,7 @@ function convertI18nText(job) {
20215
20333
  let currentIcu = null;
20216
20334
  const textNodeI18nBlocks = new Map();
20217
20335
  const textNodeIcus = new Map();
20336
+ const icuPlaceholderByText = new Map();
20218
20337
  for (const op of unit.create) {
20219
20338
  switch (op.kind) {
20220
20339
  case OpKind.I18nStart:
@@ -20239,7 +20358,19 @@ function convertI18nText(job) {
20239
20358
  if (currentI18n !== null) {
20240
20359
  textNodeI18nBlocks.set(op.xref, currentI18n);
20241
20360
  textNodeIcus.set(op.xref, currentIcu);
20242
- OpList.remove(op);
20361
+ if (op.icuPlaceholder !== null) {
20362
+ // Create an op to represent the ICU placeholder. Initially set its static text to the
20363
+ // value of the text op, though this may be overwritten later if this text op is a
20364
+ // placeholder for an interpolation.
20365
+ const icuPlaceholderOp = createIcuPlaceholderOp(job.allocateXrefId(), op.icuPlaceholder, [op.initialValue]);
20366
+ OpList.replace(op, icuPlaceholderOp);
20367
+ icuPlaceholderByText.set(op.xref, icuPlaceholderOp);
20368
+ }
20369
+ else {
20370
+ // Otherwise just remove the text op, since its value is already accounted for in the
20371
+ // translated message.
20372
+ OpList.remove(op);
20373
+ }
20243
20374
  }
20244
20375
  break;
20245
20376
  }
@@ -20254,6 +20385,7 @@ function convertI18nText(job) {
20254
20385
  }
20255
20386
  const i18nOp = textNodeI18nBlocks.get(op.target);
20256
20387
  const icuOp = textNodeIcus.get(op.target);
20388
+ const icuPlaceholder = icuPlaceholderByText.get(op.target);
20257
20389
  const contextId = icuOp ? icuOp.context : i18nOp.context;
20258
20390
  const resolutionTime = icuOp ? I18nParamResolutionTime.Postproccessing :
20259
20391
  I18nParamResolutionTime.Creation;
@@ -20262,9 +20394,14 @@ function convertI18nText(job) {
20262
20394
  const expr = op.interpolation.expressions[i];
20263
20395
  // For now, this i18nExpression depends on the slot context of the enclosing i18n block.
20264
20396
  // Later, we will modify this, and advance to a different point.
20265
- ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.xref, i18nOp.handle, expr, op.interpolation.i18nPlaceholders[i], resolutionTime, I18nExpressionFor.I18nText, '', expr.sourceSpan ?? op.sourceSpan));
20397
+ ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.xref, i18nOp.handle, expr, icuPlaceholder?.xref ?? null, op.interpolation.i18nPlaceholders[i] ?? null, resolutionTime, I18nExpressionFor.I18nText, '', expr.sourceSpan ?? op.sourceSpan));
20266
20398
  }
20267
20399
  OpList.replaceWithMany(op, ops);
20400
+ // If this interpolation is part of an ICU placeholder, add the strings and expressions to
20401
+ // the placeholder.
20402
+ if (icuPlaceholder !== undefined) {
20403
+ icuPlaceholder.strings = op.interpolation.strings;
20404
+ }
20268
20405
  break;
20269
20406
  }
20270
20407
  }
@@ -20793,21 +20930,39 @@ function keepLast(ops) {
20793
20930
  * class property.
20794
20931
  */
20795
20932
  function parseExtractedStyles(job) {
20933
+ const elements = new Map();
20934
+ for (const unit of job.units) {
20935
+ for (const op of unit.create) {
20936
+ if (isElementOrContainerOp(op)) {
20937
+ elements.set(op.xref, op);
20938
+ }
20939
+ }
20940
+ }
20796
20941
  for (const unit of job.units) {
20797
20942
  for (const op of unit.create) {
20798
20943
  if (op.kind === OpKind.ExtractedAttribute && op.bindingKind === BindingKind.Attribute &&
20799
20944
  isStringLiteral(op.expression)) {
20945
+ const target = elements.get(op.target);
20946
+ if (target !== undefined && target.kind === OpKind.Template &&
20947
+ target.templateKind === TemplateKind.Structural) {
20948
+ // TemplateDefinitionBuilder will not apply class and style bindings to structural
20949
+ // directives; instead, it will leave them as attributes.
20950
+ // (It's not clear what that would mean, anyway -- classes and styles on a structural
20951
+ // element should probably be a parse error.)
20952
+ // TODO: We may be able to remove this once Template Pipeline is the default.
20953
+ continue;
20954
+ }
20800
20955
  if (op.name === 'style') {
20801
20956
  const parsedStyles = parse(op.expression.value);
20802
20957
  for (let i = 0; i < parsedStyles.length - 1; i += 2) {
20803
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, parsedStyles[i], literal(parsedStyles[i + 1]), null), op);
20958
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, parsedStyles[i], literal(parsedStyles[i + 1]), null, null, SecurityContext.STYLE), op);
20804
20959
  }
20805
20960
  OpList.remove(op);
20806
20961
  }
20807
20962
  else if (op.name === 'class') {
20808
20963
  const parsedClasses = op.expression.value.trim().split(/\s+/g);
20809
20964
  for (const parsedClass of parsedClasses) {
20810
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, parsedClass, null, null), op);
20965
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, parsedClass, null, null, null, SecurityContext.NONE), op);
20811
20966
  }
20812
20967
  OpList.remove(op);
20813
20968
  }
@@ -20973,22 +21128,37 @@ function propagateI18nBlocksToTemplates(unit, subTemplateIndex) {
20973
21128
  i18nBlock = null;
20974
21129
  break;
20975
21130
  case OpKind.Template:
20976
- const templateView = unit.job.views.get(op.xref);
20977
- // We found an <ng-template> inside an i18n block; increment the sub-template counter and
20978
- // wrap the template's view in a child i18n block.
20979
- if (op.i18nPlaceholder !== undefined) {
20980
- if (i18nBlock === null) {
20981
- throw Error('Expected template with i18n placeholder to be in an i18n block.');
20982
- }
20983
- subTemplateIndex++;
20984
- wrapTemplateWithI18n(templateView, i18nBlock);
21131
+ subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.xref), i18nBlock, op.i18nPlaceholder, subTemplateIndex);
21132
+ break;
21133
+ case OpKind.RepeaterCreate:
21134
+ // Propagate i18n blocks to the @for template.
21135
+ const forView = unit.job.views.get(op.xref);
21136
+ subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.xref), i18nBlock, op.i18nPlaceholder, subTemplateIndex);
21137
+ // Then if there's an @empty template, propagate the i18n blocks for it as well.
21138
+ if (op.emptyView !== null) {
21139
+ subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.emptyView), i18nBlock, op.emptyI18nPlaceholder, subTemplateIndex);
20985
21140
  }
20986
- // Continue traversing inside the template's view.
20987
- subTemplateIndex = propagateI18nBlocksToTemplates(templateView, subTemplateIndex);
21141
+ break;
20988
21142
  }
20989
21143
  }
20990
21144
  return subTemplateIndex;
20991
21145
  }
21146
+ /**
21147
+ * Propagate i18n blocks for a view.
21148
+ */
21149
+ function propagateI18nBlocksForView(view, i18nBlock, i18nPlaceholder, subTemplateIndex) {
21150
+ // We found an <ng-template> inside an i18n block; increment the sub-template counter and
21151
+ // wrap the template's view in a child i18n block.
21152
+ if (i18nPlaceholder !== undefined) {
21153
+ if (i18nBlock === null) {
21154
+ throw Error('Expected template with i18n placeholder to be in an i18n block.');
21155
+ }
21156
+ subTemplateIndex++;
21157
+ wrapTemplateWithI18n(view, i18nBlock);
21158
+ }
21159
+ // Continue traversing inside the template's view.
21160
+ return propagateI18nBlocksToTemplates(view, subTemplateIndex);
21161
+ }
20992
21162
  /**
20993
21163
  * Wraps a template view with i18n start and end ops.
20994
21164
  */
@@ -21154,17 +21324,13 @@ function disableBindings() {
21154
21324
  function enableBindings() {
21155
21325
  return call(Identifiers.enableBindings, [], null);
21156
21326
  }
21157
- function listener(name, handlerFn, sourceSpan) {
21158
- return call(Identifiers.listener, [
21159
- literal(name),
21160
- handlerFn,
21161
- ], sourceSpan);
21162
- }
21163
- function syntheticHostListener(name, handlerFn, sourceSpan) {
21164
- return call(Identifiers.syntheticHostListener, [
21165
- literal(name),
21166
- handlerFn,
21167
- ], sourceSpan);
21327
+ function listener(name, handlerFn, eventTargetResolver, syntheticHost, sourceSpan) {
21328
+ const args = [literal(name), handlerFn];
21329
+ if (eventTargetResolver !== null) {
21330
+ args.push(literal(false)); // `useCapture` flag, defaults to `false`
21331
+ args.push(importExpr(eventTargetResolver));
21332
+ }
21333
+ return call(syntheticHost ? Identifiers.syntheticHostListener : Identifiers.listener, args, sourceSpan);
21168
21334
  }
21169
21335
  function pipe(slot, name) {
21170
21336
  return call(Identifiers.pipe, [
@@ -21425,8 +21591,12 @@ function classMapInterpolate(strings, expressions, sourceSpan) {
21425
21591
  const interpolationArgs = collateInterpolationArgs(strings, expressions);
21426
21592
  return callVariadicInstruction(CLASS_MAP_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
21427
21593
  }
21428
- function hostProperty(name, expression, sourceSpan) {
21429
- return call(Identifiers.hostProperty, [literal(name), expression], sourceSpan);
21594
+ function hostProperty(name, expression, sanitizer, sourceSpan) {
21595
+ const args = [literal(name), expression];
21596
+ if (sanitizer !== null) {
21597
+ args.push(sanitizer);
21598
+ }
21599
+ return call(Identifiers.hostProperty, args, sourceSpan);
21430
21600
  }
21431
21601
  function syntheticHostProperty(name, expression, sourceSpan) {
21432
21602
  return call(Identifiers.syntheticHostProperty, [literal(name), expression], sourceSpan);
@@ -21644,14 +21814,12 @@ function callVariadicInstruction(config, baseArgs, interpolationArgs, extraArgs,
21644
21814
  }
21645
21815
 
21646
21816
  /**
21647
- * Map of sanitizers to their identifier.
21817
+ * Map of target resolvers for event listeners.
21648
21818
  */
21649
- const sanitizerIdentifierMap = new Map([
21650
- [SanitizerFn.Html, Identifiers.sanitizeHtml],
21651
- [SanitizerFn.IframeAttribute, Identifiers.validateIframeAttribute],
21652
- [SanitizerFn.ResourceUrl, Identifiers.sanitizeResourceUrl],
21653
- [SanitizerFn.Script, Identifiers.sanitizeScript],
21654
- [SanitizerFn.Style, Identifiers.sanitizeStyle], [SanitizerFn.Url, Identifiers.sanitizeUrl]
21819
+ const GLOBAL_TARGET_RESOLVERS$1 = new Map([
21820
+ ['window', Identifiers.resolveWindow],
21821
+ ['document', Identifiers.resolveDocument],
21822
+ ['body', Identifiers.resolveBody],
21655
21823
  ]);
21656
21824
  /**
21657
21825
  * Compiles semantic operations across all views and generates output `o.Statement`s with actual
@@ -21675,19 +21843,19 @@ function reifyCreateOperations(unit, ops) {
21675
21843
  OpList.replace(op, text(op.handle.slot, op.initialValue, op.sourceSpan));
21676
21844
  break;
21677
21845
  case OpKind.ElementStart:
21678
- OpList.replace(op, elementStart(op.handle.slot, op.tag, op.attributes, op.localRefs, op.sourceSpan));
21846
+ OpList.replace(op, elementStart(op.handle.slot, op.tag, op.attributes, op.localRefs, op.startSourceSpan));
21679
21847
  break;
21680
21848
  case OpKind.Element:
21681
- OpList.replace(op, element(op.handle.slot, op.tag, op.attributes, op.localRefs, op.sourceSpan));
21849
+ OpList.replace(op, element(op.handle.slot, op.tag, op.attributes, op.localRefs, op.wholeSourceSpan));
21682
21850
  break;
21683
21851
  case OpKind.ElementEnd:
21684
21852
  OpList.replace(op, elementEnd(op.sourceSpan));
21685
21853
  break;
21686
21854
  case OpKind.ContainerStart:
21687
- OpList.replace(op, elementContainerStart(op.handle.slot, op.attributes, op.localRefs, op.sourceSpan));
21855
+ OpList.replace(op, elementContainerStart(op.handle.slot, op.attributes, op.localRefs, op.startSourceSpan));
21688
21856
  break;
21689
21857
  case OpKind.Container:
21690
- OpList.replace(op, elementContainer(op.handle.slot, op.attributes, op.localRefs, op.sourceSpan));
21858
+ OpList.replace(op, elementContainer(op.handle.slot, op.attributes, op.localRefs, op.wholeSourceSpan));
21691
21859
  break;
21692
21860
  case OpKind.ContainerEnd:
21693
21861
  OpList.replace(op, elementContainerEnd());
@@ -21715,7 +21883,7 @@ function reifyCreateOperations(unit, ops) {
21715
21883
  throw new Error(`AssertionError: local refs array should have been extracted into a constant`);
21716
21884
  }
21717
21885
  const childView = unit.job.views.get(op.xref);
21718
- OpList.replace(op, template(op.handle.slot, variable(childView.fnName), childView.decls, childView.vars, op.tag, op.attributes, op.localRefs, op.sourceSpan));
21886
+ OpList.replace(op, template(op.handle.slot, variable(childView.fnName), childView.decls, childView.vars, op.tag, op.attributes, op.localRefs, op.startSourceSpan));
21719
21887
  break;
21720
21888
  case OpKind.DisableBindings:
21721
21889
  OpList.replace(op, disableBindings());
@@ -21728,10 +21896,11 @@ function reifyCreateOperations(unit, ops) {
21728
21896
  break;
21729
21897
  case OpKind.Listener:
21730
21898
  const listenerFn = reifyListenerHandler(unit, op.handlerFnName, op.handlerOps, op.consumesDollarEvent);
21731
- const reified = op.hostListener && op.isAnimationListener ?
21732
- syntheticHostListener(op.name, listenerFn, op.sourceSpan) :
21733
- listener(op.name, listenerFn, op.sourceSpan);
21734
- OpList.replace(op, reified);
21899
+ const eventTargetResolver = op.eventTarget ? GLOBAL_TARGET_RESOLVERS$1.get(op.eventTarget) : null;
21900
+ if (eventTargetResolver === undefined) {
21901
+ throw new Error(`Unexpected global target '${op.eventTarget}' defined for '${op.name}' event. Supported list of global targets: window,document,body.`);
21902
+ }
21903
+ OpList.replace(op, listener(op.name, listenerFn, eventTargetResolver, op.hostListener && op.isAnimationListener, op.sourceSpan));
21735
21904
  break;
21736
21905
  case OpKind.Variable:
21737
21906
  if (op.variable.name === null) {
@@ -21816,7 +21985,7 @@ function reifyCreateOperations(unit, ops) {
21816
21985
  emptyDecls = emptyView.decls;
21817
21986
  emptyVars = emptyView.vars;
21818
21987
  }
21819
- OpList.replace(op, repeaterCreate(op.handle.slot, repeaterView.fnName, op.decls, op.vars, op.tag, op.attributes, op.trackByFn, op.usesComponentInstance, emptyViewFnName, emptyDecls, emptyVars, op.sourceSpan));
21988
+ OpList.replace(op, repeaterCreate(op.handle.slot, repeaterView.fnName, op.decls, op.vars, op.tag, op.attributes, op.trackByFn, op.usesComponentInstance, emptyViewFnName, emptyDecls, emptyVars, op.wholeSourceSpan));
21820
21989
  break;
21821
21990
  case OpKind.Statement:
21822
21991
  // Pass statement operations directly through.
@@ -21894,7 +22063,7 @@ function reifyUpdateOperations(_unit, ops) {
21894
22063
  OpList.replace(op, syntheticHostProperty(op.name, op.expression, op.sourceSpan));
21895
22064
  }
21896
22065
  else {
21897
- OpList.replace(op, hostProperty(op.name, op.expression, op.sourceSpan));
22066
+ OpList.replace(op, hostProperty(op.name, op.expression, op.sanitizer, op.sourceSpan));
21898
22067
  }
21899
22068
  }
21900
22069
  break;
@@ -21973,8 +22142,6 @@ function reifyIrExpression(expr) {
21973
22142
  return pipeBind(expr.targetSlot.slot, expr.varOffset, expr.args);
21974
22143
  case ExpressionKind.PipeBindingVariadic:
21975
22144
  return pipeBindV(expr.targetSlot.slot, expr.varOffset, expr.args);
21976
- case ExpressionKind.SanitizerExpr:
21977
- return importExpr(sanitizerIdentifierMap.get(expr.fn));
21978
22145
  case ExpressionKind.SlotLiteralExpr:
21979
22146
  return literal(expr.slot.slot);
21980
22147
  default:
@@ -22267,10 +22434,11 @@ function resolvePlaceholdersForView(job, unit, i18nContexts, elements, pendingSt
22267
22434
  }
22268
22435
  break;
22269
22436
  case OpKind.Template:
22437
+ const view = job.views.get(op.xref);
22270
22438
  if (op.i18nPlaceholder === undefined) {
22271
22439
  // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22272
22440
  // blocks.
22273
- resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
22441
+ resolvePlaceholdersForView(job, view, i18nContexts, elements);
22274
22442
  }
22275
22443
  else {
22276
22444
  if (currentOps === null) {
@@ -22281,14 +22449,59 @@ function resolvePlaceholdersForView(job, unit, i18nContexts, elements, pendingSt
22281
22449
  // the current template as a pending structural directive to be recorded when we find
22282
22450
  // the element, content, or template it belongs to. This allows us to create combined
22283
22451
  // values that represent, e.g. the start of a template and element at the same time.
22284
- resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements, op);
22452
+ resolvePlaceholdersForView(job, view, i18nContexts, elements, op);
22285
22453
  }
22286
22454
  else {
22287
22455
  // If this is some other kind of template, we can record its start, recurse into its
22288
22456
  // view, and then record its end.
22289
- recordTemplateStart(job, op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22290
- resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
22291
- recordTemplateClose(job, op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22457
+ recordTemplateStart(job, view, op.handle.slot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22458
+ resolvePlaceholdersForView(job, view, i18nContexts, elements);
22459
+ recordTemplateClose(job, view, op.handle.slot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22460
+ pendingStructuralDirective = undefined;
22461
+ }
22462
+ }
22463
+ break;
22464
+ case OpKind.RepeaterCreate:
22465
+ if (pendingStructuralDirective !== undefined) {
22466
+ throw Error('AssertionError: Unexpected structural directive associated with @for block');
22467
+ }
22468
+ // RepeaterCreate has 3 slots: the first is for the op itself, the second is for the @for
22469
+ // template and the (optional) third is for the @empty template.
22470
+ const forSlot = op.handle.slot + 1;
22471
+ const forView = job.views.get(op.xref);
22472
+ // First record all of the placeholders for the @for template.
22473
+ if (op.i18nPlaceholder === undefined) {
22474
+ // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22475
+ // blocks.
22476
+ resolvePlaceholdersForView(job, forView, i18nContexts, elements);
22477
+ }
22478
+ else {
22479
+ if (currentOps === null) {
22480
+ throw Error('i18n tag placeholder should only occur inside an i18n block');
22481
+ }
22482
+ recordTemplateStart(job, forView, forSlot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22483
+ resolvePlaceholdersForView(job, forView, i18nContexts, elements);
22484
+ recordTemplateClose(job, forView, forSlot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22485
+ pendingStructuralDirective = undefined;
22486
+ }
22487
+ // Then if there's an @empty template, add its placeholders as well.
22488
+ if (op.emptyView !== null) {
22489
+ // RepeaterCreate has 3 slots: the first is for the op itself, the second is for the @for
22490
+ // template and the (optional) third is for the @empty template.
22491
+ const emptySlot = op.handle.slot + 2;
22492
+ const emptyView = job.views.get(op.emptyView);
22493
+ if (op.emptyI18nPlaceholder === undefined) {
22494
+ // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22495
+ // blocks.
22496
+ resolvePlaceholdersForView(job, emptyView, i18nContexts, elements);
22497
+ }
22498
+ else {
22499
+ if (currentOps === null) {
22500
+ throw Error('i18n tag placeholder should only occur inside an i18n block');
22501
+ }
22502
+ recordTemplateStart(job, emptyView, emptySlot, op.emptyI18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22503
+ resolvePlaceholdersForView(job, emptyView, i18nContexts, elements);
22504
+ recordTemplateClose(job, emptyView, emptySlot, op.emptyI18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22292
22505
  pendingStructuralDirective = undefined;
22293
22506
  }
22294
22507
  }
@@ -22336,8 +22549,8 @@ function recordElementClose(op, i18nContext, i18nBlock, structuralDirective) {
22336
22549
  /**
22337
22550
  * Records an i18n param value for the start of a template.
22338
22551
  */
22339
- function recordTemplateStart(job, op, i18nContext, i18nBlock, structuralDirective) {
22340
- let { startName, closeName } = op.i18nPlaceholder;
22552
+ function recordTemplateStart(job, view, slot, i18nPlaceholder, i18nContext, i18nBlock, structuralDirective) {
22553
+ let { startName, closeName } = i18nPlaceholder;
22341
22554
  let flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.OpenTag;
22342
22555
  // For self-closing tags, there is no close tag placeholder. Instead, the start tag
22343
22556
  // placeholder accounts for the start and close of the element.
@@ -22352,20 +22565,20 @@ function recordTemplateStart(job, op, i18nContext, i18nBlock, structuralDirectiv
22352
22565
  }
22353
22566
  // Record the start of the template. For the sub-template index, pass the index for the template's
22354
22567
  // view, rather than the current i18n block's index.
22355
- addParam(i18nContext.params, startName, op.handle.slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, op), flags);
22568
+ addParam(i18nContext.params, startName, slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, view), flags);
22356
22569
  }
22357
22570
  /**
22358
22571
  * Records an i18n param value for the closing of a template.
22359
22572
  */
22360
- function recordTemplateClose(job, op, i18nContext, i18nBlock, structuralDirective) {
22361
- const { startName, closeName } = op.i18nPlaceholder;
22573
+ function recordTemplateClose(job, view, slot, i18nPlaceholder, i18nContext, i18nBlock, structuralDirective) {
22574
+ const { startName, closeName } = i18nPlaceholder;
22362
22575
  const flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.CloseTag;
22363
22576
  // Self-closing tags don't have a closing tag placeholder, instead the template's closing is
22364
22577
  // recorded via an additional flag on the template start value.
22365
22578
  if (closeName) {
22366
22579
  // Record the closing of the template. For the sub-template index, pass the index for the
22367
22580
  // template's view, rather than the current i18n block's index.
22368
- addParam(i18nContext.params, closeName, op.handle.slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, op), flags);
22581
+ addParam(i18nContext.params, closeName, slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, view), flags);
22369
22582
  // If the template is associated with a structural directive, record the structural directive's
22370
22583
  // closing after. Since this template must be in the structural directive's view, we can just
22371
22584
  // directly use the current i18n block's sub-template index.
@@ -22378,8 +22591,8 @@ function recordTemplateClose(job, op, i18nContext, i18nBlock, structuralDirectiv
22378
22591
  * Get the subTemplateIndex for the given template op. For template ops, use the subTemplateIndex of
22379
22592
  * the child i18n block inside the template.
22380
22593
  */
22381
- function getSubTemplateIndexForTemplateTag(job, i18nOp, op) {
22382
- for (const childOp of job.views.get(op.xref).create) {
22594
+ function getSubTemplateIndexForTemplateTag(job, i18nOp, view) {
22595
+ for (const childOp of view.create) {
22383
22596
  if (childOp.kind === OpKind.I18nStart) {
22384
22597
  return childOp.subTemplateIndex;
22385
22598
  }
@@ -22402,6 +22615,7 @@ function resolveI18nExpressionPlaceholders(job) {
22402
22615
  // Record all of the i18n context ops, and the sub-template index for each i18n op.
22403
22616
  const subTemplateIndicies = new Map();
22404
22617
  const i18nContexts = new Map();
22618
+ const icuPlaceholders = new Map();
22405
22619
  for (const unit of job.units) {
22406
22620
  for (const op of unit.create) {
22407
22621
  switch (op.kind) {
@@ -22411,6 +22625,9 @@ function resolveI18nExpressionPlaceholders(job) {
22411
22625
  case OpKind.I18nContext:
22412
22626
  i18nContexts.set(op.xref, op);
22413
22627
  break;
22628
+ case OpKind.IcuPlaceholder:
22629
+ icuPlaceholders.set(op.xref, op);
22630
+ break;
22414
22631
  }
22415
22632
  }
22416
22633
  }
@@ -22424,76 +22641,32 @@ function resolveI18nExpressionPlaceholders(job) {
22424
22641
  for (const unit of job.units) {
22425
22642
  for (const op of unit.update) {
22426
22643
  if (op.kind === OpKind.I18nExpression) {
22427
- const i18nContext = i18nContexts.get(op.context);
22428
22644
  const index = expressionIndices.get(referenceIndex(op)) || 0;
22429
22645
  const subTemplateIndex = subTemplateIndicies.get(op.i18nOwner) ?? null;
22430
- // Add the expression index in the appropriate params map.
22431
- const params = op.resolutionTime === I18nParamResolutionTime.Creation ?
22432
- i18nContext.params :
22433
- i18nContext.postprocessingParams;
22434
- const values = params.get(op.i18nPlaceholder) || [];
22435
- values.push({
22646
+ const value = {
22436
22647
  value: index,
22437
22648
  subTemplateIndex: subTemplateIndex,
22438
22649
  flags: I18nParamValueFlags.ExpressionIndex
22439
- });
22440
- params.set(op.i18nPlaceholder, values);
22650
+ };
22651
+ updatePlaceholder(op, value, i18nContexts, icuPlaceholders);
22441
22652
  expressionIndices.set(referenceIndex(op), index + 1);
22442
22653
  }
22443
22654
  }
22444
22655
  }
22445
22656
  }
22446
-
22447
- /**
22448
- * Resolves placeholders for element tags inside of an ICU.
22449
- */
22450
- function resolveI18nIcuPlaceholders(job) {
22451
- for (const unit of job.units) {
22452
- for (const op of unit.create) {
22453
- if (op.kind === OpKind.I18nContext && op.contextKind === I18nContextKind.Icu) {
22454
- for (const node of op.message.nodes) {
22455
- node.visit(new ResolveIcuPlaceholdersVisitor(op.postprocessingParams));
22456
- }
22457
- }
22458
- }
22657
+ function updatePlaceholder(op, value, i18nContexts, icuPlaceholders) {
22658
+ if (op.i18nPlaceholder !== null) {
22659
+ const i18nContext = i18nContexts.get(op.context);
22660
+ const params = op.resolutionTime === I18nParamResolutionTime.Creation ?
22661
+ i18nContext.params :
22662
+ i18nContext.postprocessingParams;
22663
+ const values = params.get(op.i18nPlaceholder) || [];
22664
+ values.push(value);
22665
+ params.set(op.i18nPlaceholder, values);
22459
22666
  }
22460
- }
22461
- /**
22462
- * Visitor for i18n AST that resolves ICU params into the given map.
22463
- */
22464
- class ResolveIcuPlaceholdersVisitor extends RecurseVisitor {
22465
- constructor(params) {
22466
- super();
22467
- this.params = params;
22468
- }
22469
- visitContainerPlaceholder(placeholder) {
22470
- // Add the start and end source span for container placeholders. These need to be recorded for
22471
- // elements inside ICUs. The slots for the nodes were recorded separately under the i18n
22472
- // block's context as part of the `resolveI18nElementPlaceholders` phase.
22473
- if (placeholder.startName && placeholder.startSourceSpan &&
22474
- !this.params.has(placeholder.startName)) {
22475
- this.params.set(placeholder.startName, [{
22476
- value: placeholder.startSourceSpan?.toString(),
22477
- subTemplateIndex: null,
22478
- flags: I18nParamValueFlags.None
22479
- }]);
22480
- }
22481
- if (placeholder.closeName && placeholder.endSourceSpan &&
22482
- !this.params.has(placeholder.closeName)) {
22483
- this.params.set(placeholder.closeName, [{
22484
- value: placeholder.endSourceSpan?.toString(),
22485
- subTemplateIndex: null,
22486
- flags: I18nParamValueFlags.None
22487
- }]);
22488
- }
22489
- }
22490
- visitTagPlaceholder(placeholder) {
22491
- super.visitTagPlaceholder(placeholder);
22492
- this.visitContainerPlaceholder(placeholder);
22493
- }
22494
- visitBlockPlaceholder(placeholder) {
22495
- super.visitBlockPlaceholder(placeholder);
22496
- this.visitContainerPlaceholder(placeholder);
22667
+ if (op.icuPlaceholder !== null) {
22668
+ const icuPlaceholderOp = icuPlaceholders.get(op.icuPlaceholder);
22669
+ icuPlaceholderOp?.expressionPlaceholders.push(value);
22497
22670
  }
22498
22671
  }
22499
22672
 
@@ -22596,12 +22769,20 @@ function processLexicalScope(unit, ops, savedView) {
22596
22769
  }
22597
22770
 
22598
22771
  /**
22599
- * Mapping of security contexts to sanitizer function for that context.
22772
+ * Map of security contexts to their sanitizer function.
22600
22773
  */
22601
- const sanitizers = new Map([
22602
- [SecurityContext.HTML, SanitizerFn.Html], [SecurityContext.SCRIPT, SanitizerFn.Script],
22603
- [SecurityContext.STYLE, SanitizerFn.Style], [SecurityContext.URL, SanitizerFn.Url],
22604
- [SecurityContext.RESOURCE_URL, SanitizerFn.ResourceUrl]
22774
+ const sanitizerFns = new Map([
22775
+ [SecurityContext.HTML, Identifiers.sanitizeHtml],
22776
+ [SecurityContext.RESOURCE_URL, Identifiers.sanitizeResourceUrl],
22777
+ [SecurityContext.SCRIPT, Identifiers.sanitizeScript],
22778
+ [SecurityContext.STYLE, Identifiers.sanitizeStyle], [SecurityContext.URL, Identifiers.sanitizeUrl]
22779
+ ]);
22780
+ /**
22781
+ * Map of security contexts to their trusted value function.
22782
+ */
22783
+ const trustedValueFns = new Map([
22784
+ [SecurityContext.HTML, Identifiers.trustConstantHtml],
22785
+ [SecurityContext.RESOURCE_URL, Identifiers.trustConstantResourceUrl],
22605
22786
  ]);
22606
22787
  /**
22607
22788
  * Resolves sanitization functions for ops that need them.
@@ -22609,24 +22790,61 @@ const sanitizers = new Map([
22609
22790
  function resolveSanitizers(job) {
22610
22791
  for (const unit of job.units) {
22611
22792
  const elements = createOpXrefMap(unit);
22612
- let sanitizerFn;
22793
+ // For normal element bindings we create trusted values for security sensitive constant
22794
+ // attributes. However, for host bindings we skip this step (this matches what
22795
+ // TemplateDefinitionBuilder does).
22796
+ // TODO: Is the TDB behavior correct here?
22797
+ if (job.kind !== CompilationJobKind.Host) {
22798
+ for (const op of unit.create) {
22799
+ if (op.kind === OpKind.ExtractedAttribute) {
22800
+ const trustedValueFn = trustedValueFns.get(getOnlySecurityContext(op.securityContext)) ?? null;
22801
+ op.trustedValueFn = trustedValueFn !== null ? importExpr(trustedValueFn) : null;
22802
+ }
22803
+ }
22804
+ }
22613
22805
  for (const op of unit.update) {
22614
22806
  switch (op.kind) {
22615
22807
  case OpKind.Property:
22616
22808
  case OpKind.Attribute:
22617
- sanitizerFn = sanitizers.get(op.securityContext) || null;
22618
- op.sanitizer = sanitizerFn ? new SanitizerExpr(sanitizerFn) : null;
22809
+ case OpKind.HostProperty:
22810
+ let sanitizerFn = null;
22811
+ if (Array.isArray(op.securityContext) && op.securityContext.length === 2 &&
22812
+ op.securityContext.indexOf(SecurityContext.URL) > -1 &&
22813
+ op.securityContext.indexOf(SecurityContext.RESOURCE_URL) > -1) {
22814
+ // When the host element isn't known, some URL attributes (such as "src" and "href") may
22815
+ // be part of multiple different security contexts. In this case we use special
22816
+ // sanitization function and select the actual sanitizer at runtime based on a tag name
22817
+ // that is provided while invoking sanitization function.
22818
+ sanitizerFn = Identifiers.sanitizeUrlOrResourceUrl;
22819
+ }
22820
+ else {
22821
+ sanitizerFn = sanitizerFns.get(getOnlySecurityContext(op.securityContext)) ?? null;
22822
+ }
22823
+ op.sanitizer = sanitizerFn !== null ? importExpr(sanitizerFn) : null;
22619
22824
  // If there was no sanitization function found based on the security context of an
22620
22825
  // attribute/property, check whether this attribute/property is one of the
22621
22826
  // security-sensitive <iframe> attributes (and that the current element is actually an
22622
22827
  // <iframe>).
22623
22828
  if (op.sanitizer === null) {
22624
- const ownerOp = elements.get(op.target);
22625
- if (ownerOp === undefined || !isElementOrContainerOp(ownerOp)) {
22626
- throw Error('Property should have an element-like owner');
22829
+ let isIframe = false;
22830
+ if (job.kind === CompilationJobKind.Host || op.kind === OpKind.HostProperty) {
22831
+ // Note: for host bindings defined on a directive, we do not try to find all
22832
+ // possible places where it can be matched, so we can not determine whether
22833
+ // the host element is an <iframe>. In this case, we just assume it is and append a
22834
+ // validation function, which is invoked at runtime and would have access to the
22835
+ // underlying DOM element to check if it's an <iframe> and if so - run extra checks.
22836
+ isIframe = true;
22627
22837
  }
22628
- if (isIframeElement$1(ownerOp) && isIframeSecuritySensitiveAttr(op.name)) {
22629
- op.sanitizer = new SanitizerExpr(SanitizerFn.IframeAttribute);
22838
+ else {
22839
+ // For a normal binding we can just check if the element its on is an iframe.
22840
+ const ownerOp = elements.get(op.target);
22841
+ if (ownerOp === undefined || !isElementOrContainerOp(ownerOp)) {
22842
+ throw Error('Property should have an element-like owner');
22843
+ }
22844
+ isIframe = isIframeElement$1(ownerOp);
22845
+ }
22846
+ if (isIframe && isIframeSecuritySensitiveAttr(op.name)) {
22847
+ op.sanitizer = importExpr(Identifiers.validateIframeAttribute);
22630
22848
  }
22631
22849
  }
22632
22850
  break;
@@ -22640,6 +22858,22 @@ function resolveSanitizers(job) {
22640
22858
  function isIframeElement$1(op) {
22641
22859
  return op.kind === OpKind.ElementStart && op.tag?.toLowerCase() === 'iframe';
22642
22860
  }
22861
+ /**
22862
+ * Asserts that there is only a single security context and returns it.
22863
+ */
22864
+ function getOnlySecurityContext(securityContext) {
22865
+ if (Array.isArray(securityContext)) {
22866
+ if (securityContext.length > 1) {
22867
+ // TODO: What should we do here? TDB just took the first one, but this feels like something we
22868
+ // would want to know about and create a special case for like we did for Url/ResourceUrl. My
22869
+ // guess is that, outside of the Url/ResourceUrl case, this never actually happens. If there
22870
+ // do turn out to be other cases, throwing an error until we can address it feels safer.
22871
+ throw Error(`AssertionError: Ambiguous security context`);
22872
+ }
22873
+ return securityContext[0] || SecurityContext.NONE;
22874
+ }
22875
+ return securityContext;
22876
+ }
22643
22877
 
22644
22878
  /**
22645
22879
  * When inside of a listener, we may need access to one or more enclosing views. Therefore, each
@@ -22743,6 +22977,8 @@ function allocateSlots(job) {
22743
22977
  // operation itself, so it can be emitted later.
22744
22978
  const childView = job.views.get(op.xref);
22745
22979
  op.decls = childView.decls;
22980
+ // TODO: currently we handle the decls for the RepeaterCreate empty template in the reify
22981
+ // phase. We should handle that here instead.
22746
22982
  }
22747
22983
  }
22748
22984
  }
@@ -23066,6 +23302,8 @@ function countVariables(job) {
23066
23302
  }
23067
23303
  const childView = job.views.get(op.xref);
23068
23304
  op.vars = childView.vars;
23305
+ // TODO: currently we handle the vars for the RepeaterCreate empty template in the reify
23306
+ // phase. We should handle that here instead.
23069
23307
  }
23070
23308
  }
23071
23309
  }
@@ -23103,7 +23341,14 @@ function varsUsedByOp(op) {
23103
23341
  return op.interpolation.expressions.length;
23104
23342
  case OpKind.I18nExpression:
23105
23343
  case OpKind.Conditional:
23344
+ case OpKind.DeferWhen:
23106
23345
  return 1;
23346
+ case OpKind.RepeaterCreate:
23347
+ // Repeaters may require an extra variable binding slot, if they have an empty view, for the
23348
+ // empty block tracking.
23349
+ // TODO: It's a bit odd to have a create mode instruction consume variable slots. Maybe we can
23350
+ // find a way to use the Repeater update op instead.
23351
+ return op.emptyView ? 1 : 0;
23107
23352
  default:
23108
23353
  throw new Error(`Unhandled op: ${OpKind[op.kind]}`);
23109
23354
  }
@@ -23587,10 +23832,11 @@ const phases = [
23587
23832
  { kind: CompilationJobKind.Tmpl, fn: emitNamespaceChanges },
23588
23833
  { kind: CompilationJobKind.Tmpl, fn: propagateI18nBlocks },
23589
23834
  { kind: CompilationJobKind.Tmpl, fn: wrapI18nIcus },
23590
- { kind: CompilationJobKind.Tmpl, fn: createI18nContexts },
23835
+ { kind: CompilationJobKind.Both, fn: deduplicateTextBindings },
23591
23836
  { kind: CompilationJobKind.Both, fn: specializeStyleBindings },
23592
23837
  { kind: CompilationJobKind.Both, fn: specializeBindings },
23593
23838
  { kind: CompilationJobKind.Both, fn: extractAttributes },
23839
+ { kind: CompilationJobKind.Tmpl, fn: createI18nContexts },
23594
23840
  { kind: CompilationJobKind.Both, fn: parseExtractedStyles },
23595
23841
  { kind: CompilationJobKind.Tmpl, fn: removeEmptyBindings },
23596
23842
  { kind: CompilationJobKind.Both, fn: collapseSingletonInterpolations },
@@ -23608,7 +23854,7 @@ const phases = [
23608
23854
  { kind: CompilationJobKind.Tmpl, fn: generateProjectionDefs },
23609
23855
  { kind: CompilationJobKind.Tmpl, fn: generateVariables },
23610
23856
  { kind: CompilationJobKind.Tmpl, fn: saveAndRestoreView },
23611
- { kind: CompilationJobKind.Tmpl, fn: deleteAnyCasts },
23857
+ { kind: CompilationJobKind.Both, fn: deleteAnyCasts },
23612
23858
  { kind: CompilationJobKind.Both, fn: resolveDollarEvent },
23613
23859
  { kind: CompilationJobKind.Tmpl, fn: generateRepeaterDerivedVars },
23614
23860
  { kind: CompilationJobKind.Tmpl, fn: generateTrackVariables },
@@ -23616,7 +23862,7 @@ const phases = [
23616
23862
  { kind: CompilationJobKind.Tmpl, fn: resolveDeferTargetNames },
23617
23863
  { kind: CompilationJobKind.Tmpl, fn: optimizeTrackFns },
23618
23864
  { kind: CompilationJobKind.Both, fn: resolveContexts },
23619
- { kind: CompilationJobKind.Tmpl, fn: resolveSanitizers },
23865
+ { kind: CompilationJobKind.Both, fn: resolveSanitizers },
23620
23866
  { kind: CompilationJobKind.Tmpl, fn: liftLocalRefs },
23621
23867
  { kind: CompilationJobKind.Both, fn: generateNullishCoalesceExpressions },
23622
23868
  { kind: CompilationJobKind.Both, fn: expandSafeReads },
@@ -23625,7 +23871,6 @@ const phases = [
23625
23871
  { kind: CompilationJobKind.Tmpl, fn: createDeferDepsFns },
23626
23872
  { kind: CompilationJobKind.Tmpl, fn: resolveI18nElementPlaceholders },
23627
23873
  { kind: CompilationJobKind.Tmpl, fn: resolveI18nExpressionPlaceholders },
23628
- { kind: CompilationJobKind.Tmpl, fn: resolveI18nIcuPlaceholders },
23629
23874
  { kind: CompilationJobKind.Tmpl, fn: extractI18nMessages },
23630
23875
  { kind: CompilationJobKind.Tmpl, fn: generateTrackFns },
23631
23876
  { kind: CompilationJobKind.Tmpl, fn: collectI18nConsts },
@@ -23752,6 +23997,10 @@ function emitHostBindingFunction(job) {
23752
23997
  }
23753
23998
 
23754
23999
  const compatibilityMode = CompatibilityMode.TemplateDefinitionBuilder;
24000
+ // Schema containing DOM elements and their properties.
24001
+ const domSchema = new DomElementSchemaRegistry();
24002
+ // Tag name of the `ng-template` element.
24003
+ const NG_TEMPLATE_TAG_NAME$1 = 'ng-template';
23755
24004
  /**
23756
24005
  * Process a template AST and convert it into a `ComponentCompilation` in the intermediate
23757
24006
  * representation.
@@ -23769,10 +24018,24 @@ function ingestComponent(componentName, template, constantPool, relativeContextF
23769
24018
  function ingestHostBinding(input, bindingParser, constantPool) {
23770
24019
  const job = new HostBindingCompilationJob(input.componentName, constantPool, compatibilityMode);
23771
24020
  for (const property of input.properties ?? []) {
23772
- ingestHostProperty(job, property, false);
24021
+ let bindingKind = BindingKind.Property;
24022
+ // TODO: this should really be handled in the parser.
24023
+ if (property.name.startsWith('attr.')) {
24024
+ property.name = property.name.substring('attr.'.length);
24025
+ bindingKind = BindingKind.Attribute;
24026
+ }
24027
+ if (property.isAnimation) {
24028
+ bindingKind = BindingKind.Animation;
24029
+ }
24030
+ const securityContexts = bindingParser
24031
+ .calcPossibleSecurityContexts(input.componentSelector, property.name, bindingKind === BindingKind.Attribute)
24032
+ .filter(context => context !== SecurityContext.NONE);
24033
+ ingestHostProperty(job, property, bindingKind, securityContexts);
23773
24034
  }
23774
24035
  for (const [name, expr] of Object.entries(input.attributes) ?? []) {
23775
- ingestHostAttribute(job, name, expr);
24036
+ const securityContexts = bindingParser.calcPossibleSecurityContexts(input.componentSelector, name, true)
24037
+ .filter(context => context !== SecurityContext.NONE);
24038
+ ingestHostAttribute(job, name, expr, securityContexts);
23776
24039
  }
23777
24040
  for (const event of input.events ?? []) {
23778
24041
  ingestHostEvent(job, event);
@@ -23781,7 +24044,7 @@ function ingestHostBinding(input, bindingParser, constantPool) {
23781
24044
  }
23782
24045
  // TODO: We should refactor the parser to use the same types and structures for host bindings as
23783
24046
  // with ordinary components. This would allow us to share a lot more ingestion code.
23784
- function ingestHostProperty(job, property, isTextAttribute) {
24047
+ function ingestHostProperty(job, property, bindingKind, securityContexts) {
23785
24048
  let expression;
23786
24049
  const ast = property.expression.ast;
23787
24050
  if (ast instanceof Interpolation$1) {
@@ -23790,28 +24053,21 @@ function ingestHostProperty(job, property, isTextAttribute) {
23790
24053
  else {
23791
24054
  expression = convertAst(ast, job, property.sourceSpan);
23792
24055
  }
23793
- let bindingKind = BindingKind.Property;
23794
- // TODO: this should really be handled in the parser.
23795
- if (property.name.startsWith('attr.')) {
23796
- property.name = property.name.substring('attr.'.length);
23797
- bindingKind = BindingKind.Attribute;
23798
- }
23799
- if (property.isAnimation) {
23800
- bindingKind = BindingKind.Animation;
23801
- }
23802
- job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, SecurityContext
23803
- .NONE /* TODO: what should we pass as security context? Passing NONE for now. */, isTextAttribute, false, /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
24056
+ job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, securityContexts, false, false, null, /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
23804
24057
  }
23805
- function ingestHostAttribute(job, name, value) {
23806
- const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, SecurityContext.NONE, true, false,
24058
+ function ingestHostAttribute(job, name, value, securityContexts) {
24059
+ const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, securityContexts,
24060
+ /* Host attributes should always be extracted to const hostAttrs, even if they are not
24061
+ *strictly* text literals */
24062
+ true, false, null,
23807
24063
  /* TODO */ null,
23808
- /* TODO: host attribute source spans */ null);
24064
+ /** TODO: May be null? */ value.sourceSpan);
23809
24065
  job.root.update.push(attrBinding);
23810
24066
  }
23811
24067
  function ingestHostEvent(job, event) {
23812
- const eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, event.targetOrPhase, true, event.sourceSpan);
23813
- // TODO: Can this be a chain?
23814
- eventBinding.handlerOps.push(createStatementOp(new ReturnStatement(convertAst(event.handler.ast, job, event.sourceSpan), event.handlerSpan)));
24068
+ const [phase, target] = event.type === 0 /* e.ParsedEventType.Regular */ ? [null, event.targetOrPhase] :
24069
+ [event.targetOrPhase, null];
24070
+ const eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, makeListenerHandlerOps(job.root, event.handler, event.handlerSpan), phase, target, true, event.sourceSpan);
23815
24071
  job.root.create.push(eventBinding);
23816
24072
  }
23817
24073
  /**
@@ -23829,10 +24085,10 @@ function ingestNodes(unit, template) {
23829
24085
  ingestContent(unit, node);
23830
24086
  }
23831
24087
  else if (node instanceof Text$3) {
23832
- ingestText(unit, node);
24088
+ ingestText(unit, node, null);
23833
24089
  }
23834
24090
  else if (node instanceof BoundText) {
23835
- ingestBoundText(unit, node);
24091
+ ingestBoundText(unit, node, null);
23836
24092
  }
23837
24093
  else if (node instanceof IfBlock) {
23838
24094
  ingestIfBlock(unit, node);
@@ -23864,10 +24120,16 @@ function ingestElement(unit, element) {
23864
24120
  }
23865
24121
  const id = unit.job.allocateXrefId();
23866
24122
  const [namespaceKey, elementName] = splitNsName(element.name);
23867
- const startOp = createElementStartOp(elementName, id, namespaceForKey(namespaceKey), element.i18n instanceof TagPlaceholder ? element.i18n : undefined, element.startSourceSpan);
24123
+ const startOp = createElementStartOp(elementName, id, namespaceForKey(namespaceKey), element.i18n instanceof TagPlaceholder ? element.i18n : undefined, element.startSourceSpan, element.sourceSpan);
23868
24124
  unit.create.push(startOp);
23869
- ingestBindings(unit, startOp, element);
24125
+ ingestElementBindings(unit, startOp, element);
23870
24126
  ingestReferences(startOp, element);
24127
+ // Start i18n, if needed, goes after the element create and bindings, but before the nodes
24128
+ let i18nBlockId = null;
24129
+ if (element.i18n instanceof Message) {
24130
+ i18nBlockId = unit.job.allocateXrefId();
24131
+ unit.create.push(createI18nStartOp(i18nBlockId, element.i18n));
24132
+ }
23871
24133
  ingestNodes(unit, element.children);
23872
24134
  // The source span for the end op is typically the element closing tag. However, if no closing tag
23873
24135
  // exists, such as in `<input>`, we use the start source span instead. Usually the start and end
@@ -23877,9 +24139,7 @@ function ingestElement(unit, element) {
23877
24139
  const endOp = createElementEndOp(id, element.endSourceSpan ?? element.startSourceSpan);
23878
24140
  unit.create.push(endOp);
23879
24141
  // If there is an i18n message associated with this element, insert i18n start and end ops.
23880
- if (element.i18n instanceof Message) {
23881
- const i18nBlockId = unit.job.allocateXrefId();
23882
- OpList.insertAfter(createI18nStartOp(i18nBlockId, element.i18n), startOp);
24142
+ if (i18nBlockId !== null) {
23883
24143
  OpList.insertBefore(createI18nEndOp(i18nBlockId), endOp);
23884
24144
  }
23885
24145
  }
@@ -23903,9 +24163,9 @@ function ingestTemplate(unit, tmpl) {
23903
24163
  '' :
23904
24164
  prefixWithNamespace(tagNameWithoutNamespace, namespace);
23905
24165
  const templateKind = isPlainTemplate(tmpl) ? TemplateKind.NgTemplate : TemplateKind.Structural;
23906
- const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan);
24166
+ const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan, tmpl.sourceSpan);
23907
24167
  unit.create.push(templateOp);
23908
- ingestBindings(unit, templateOp, tmpl);
24168
+ ingestTemplateBindings(unit, templateOp, tmpl, templateKind);
23909
24169
  ingestReferences(templateOp, tmpl);
23910
24170
  ingestNodes(childView, tmpl.children);
23911
24171
  for (const { name, value } of tmpl.variables) {
@@ -23921,7 +24181,7 @@ function ingestTemplate(unit, tmpl) {
23921
24181
  }
23922
24182
  }
23923
24183
  /**
23924
- * Ingest a literal text node from the AST into the given `ViewCompilation`.
24184
+ * Ingest a content node from the AST into the given `ViewCompilation`.
23925
24185
  */
23926
24186
  function ingestContent(unit, content) {
23927
24187
  if (content.i18n !== undefined && !(content.i18n instanceof TagPlaceholder)) {
@@ -23930,20 +24190,21 @@ function ingestContent(unit, content) {
23930
24190
  const attrs = content.attributes.flatMap(a => [a.name, a.value]);
23931
24191
  const op = createProjectionOp(unit.job.allocateXrefId(), content.selector, content.i18n, attrs, content.sourceSpan);
23932
24192
  for (const attr of content.attributes) {
23933
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue, attr.i18n);
24193
+ const securityContext = domSchema.securityContext(content.name, attr.name, true);
24194
+ unit.update.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
23934
24195
  }
23935
24196
  unit.create.push(op);
23936
24197
  }
23937
24198
  /**
23938
24199
  * Ingest a literal text node from the AST into the given `ViewCompilation`.
23939
24200
  */
23940
- function ingestText(unit, text) {
23941
- unit.create.push(createTextOp(unit.job.allocateXrefId(), text.value, text.sourceSpan));
24201
+ function ingestText(unit, text, icuPlaceholder) {
24202
+ unit.create.push(createTextOp(unit.job.allocateXrefId(), text.value, icuPlaceholder, text.sourceSpan));
23942
24203
  }
23943
24204
  /**
23944
24205
  * Ingest an interpolated text node from the AST into the given `ViewCompilation`.
23945
24206
  */
23946
- function ingestBoundText(unit, text, i18nPlaceholders) {
24207
+ function ingestBoundText(unit, text, icuPlaceholder) {
23947
24208
  let value = text.value;
23948
24209
  if (value instanceof ASTWithSource) {
23949
24210
  value = value.ast;
@@ -23954,19 +24215,16 @@ function ingestBoundText(unit, text, i18nPlaceholders) {
23954
24215
  if (text.i18n !== undefined && !(text.i18n instanceof Container)) {
23955
24216
  throw Error(`Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`);
23956
24217
  }
23957
- if (i18nPlaceholders === undefined) {
23958
- // TODO: We probably can just use the placeholders field, instead of walking the AST.
23959
- i18nPlaceholders = text.i18n instanceof Container ?
23960
- text.i18n.children
23961
- .filter((node) => node instanceof Placeholder)
23962
- .map(placeholder => placeholder.name) :
23963
- [];
23964
- }
24218
+ const i18nPlaceholders = text.i18n instanceof Container ?
24219
+ text.i18n.children
24220
+ .filter((node) => node instanceof Placeholder)
24221
+ .map(placeholder => placeholder.name) :
24222
+ [];
23965
24223
  if (i18nPlaceholders.length > 0 && i18nPlaceholders.length !== value.expressions.length) {
23966
24224
  throw Error(`Unexpected number of i18n placeholders (${value.expressions.length}) for BoundText with ${value.expressions.length} expressions`);
23967
24225
  }
23968
24226
  const textXref = unit.job.allocateXrefId();
23969
- unit.create.push(createTextOp(textXref, '', text.sourceSpan));
24227
+ unit.create.push(createTextOp(textXref, '', icuPlaceholder, text.sourceSpan));
23970
24228
  // TemplateDefinitionBuilder does not generate source maps for sub-expressions inside an
23971
24229
  // interpolation. We copy that behavior in compatibility mode.
23972
24230
  // TODO: is it actually correct to generate these extra maps in modern mode?
@@ -23999,7 +24257,7 @@ function ingestIfBlock(unit, ifBlock) {
23999
24257
  }
24000
24258
  ifCaseI18nMeta = ifCase.i18n;
24001
24259
  }
24002
- const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.sourceSpan);
24260
+ const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.startSourceSpan, ifCase.sourceSpan);
24003
24261
  unit.create.push(templateOp);
24004
24262
  if (firstXref === null) {
24005
24263
  firstXref = cView.xref;
@@ -24029,7 +24287,7 @@ function ingestSwitchBlock(unit, switchBlock) {
24029
24287
  }
24030
24288
  switchCaseI18nMeta = switchCase.i18n;
24031
24289
  }
24032
- const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, null, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.sourceSpan);
24290
+ const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, null, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.startSourceSpan, switchCase.sourceSpan);
24033
24291
  unit.create.push(templateOp);
24034
24292
  if (firstXref === null) {
24035
24293
  firstXref = cView.xref;
@@ -24045,13 +24303,16 @@ function ingestSwitchBlock(unit, switchBlock) {
24045
24303
  const conditional = createConditionalOp(firstXref, firstSlotHandle, convertAst(switchBlock.expression, unit.job, null), conditions, switchBlock.sourceSpan);
24046
24304
  unit.update.push(conditional);
24047
24305
  }
24048
- function ingestDeferView(unit, suffix, children, sourceSpan) {
24306
+ function ingestDeferView(unit, suffix, i18nMeta, children, sourceSpan) {
24307
+ if (i18nMeta !== undefined && !(i18nMeta instanceof BlockPlaceholder)) {
24308
+ throw Error('Unhandled i18n metadata type for defer block');
24309
+ }
24049
24310
  if (children === undefined) {
24050
24311
  return null;
24051
24312
  }
24052
24313
  const secondaryView = unit.job.allocateView(unit.xref);
24053
24314
  ingestNodes(secondaryView, children);
24054
- const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, undefined, sourceSpan);
24315
+ const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, i18nMeta, sourceSpan, sourceSpan);
24055
24316
  unit.create.push(templateOp);
24056
24317
  return templateOp;
24057
24318
  }
@@ -24061,10 +24322,10 @@ function ingestDeferBlock(unit, deferBlock) {
24061
24322
  throw new Error(`AssertionError: unable to find metadata for deferred block`);
24062
24323
  }
24063
24324
  // Generate the defer main view and all secondary views.
24064
- const main = ingestDeferView(unit, '', deferBlock.children, deferBlock.sourceSpan);
24065
- const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
24066
- const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
24067
- const error = ingestDeferView(unit, 'Error', deferBlock.error?.children, deferBlock.error?.sourceSpan);
24325
+ const main = ingestDeferView(unit, '', deferBlock.i18n, deferBlock.children, deferBlock.sourceSpan);
24326
+ const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.i18n, deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
24327
+ const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.i18n, deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
24328
+ const error = ingestDeferView(unit, 'Error', deferBlock.error?.i18n, deferBlock.error?.children, deferBlock.error?.sourceSpan);
24068
24329
  // Create the main defer op, and ops for all secondary views.
24069
24330
  const deferXref = unit.job.allocateXrefId();
24070
24331
  const deferOp = createDeferOp(deferXref, main.xref, main.handle, blockMeta, deferBlock.sourceSpan);
@@ -24129,6 +24390,11 @@ function ingestDeferBlock(unit, deferBlock) {
24129
24390
  deferOnOps.push(deferOnOp);
24130
24391
  }
24131
24392
  if (triggers.when !== undefined) {
24393
+ if (triggers.when.value instanceof Interpolation$1) {
24394
+ // TemplateDefinitionBuilder supports this case, but it's very strange to me. What would it
24395
+ // even mean?
24396
+ throw new Error(`Unexpected interpolation in defer block when trigger`);
24397
+ }
24132
24398
  const deferOnOp = createDeferWhenOp(deferXref, convertAst(triggers.when.value, unit.job, triggers.when.sourceSpan), prefetch, triggers.when.sourceSpan);
24133
24399
  deferWhenOps.push(deferOnOp);
24134
24400
  }
@@ -24148,10 +24414,10 @@ function ingestIcu(unit, icu) {
24148
24414
  unit.create.push(createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null));
24149
24415
  for (const [placeholder, text] of Object.entries({ ...icu.vars, ...icu.placeholders })) {
24150
24416
  if (text instanceof BoundText) {
24151
- ingestBoundText(unit, text, [placeholder]);
24417
+ ingestBoundText(unit, text, placeholder);
24152
24418
  }
24153
24419
  else {
24154
- ingestText(unit, text);
24420
+ ingestText(unit, text, placeholder);
24155
24421
  }
24156
24422
  }
24157
24423
  unit.create.push(createIcuEndOp(xref));
@@ -24198,8 +24464,17 @@ function ingestForBlock(unit, forBlock) {
24198
24464
  $odd: forBlock.contextVariables.$odd.name,
24199
24465
  $implicit: forBlock.item.name,
24200
24466
  };
24467
+ if (forBlock.i18n !== undefined && !(forBlock.i18n instanceof BlockPlaceholder)) {
24468
+ throw Error('AssertionError: Unhandled i18n metadata type or @for');
24469
+ }
24470
+ if (forBlock.empty?.i18n !== undefined &&
24471
+ !(forBlock.empty.i18n instanceof BlockPlaceholder)) {
24472
+ throw Error('AssertionError: Unhandled i18n metadata type or @empty');
24473
+ }
24474
+ const i18nPlaceholder = forBlock.i18n;
24475
+ const emptyI18nPlaceholder = forBlock.empty?.i18n;
24201
24476
  const tagName = ingestControlFlowInsertionPoint(unit, repeaterView.xref, forBlock);
24202
- const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, forBlock.sourceSpan);
24477
+ const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, i18nPlaceholder, emptyI18nPlaceholder, forBlock.startSourceSpan, forBlock.sourceSpan);
24203
24478
  unit.create.push(repeaterCreate);
24204
24479
  const expression = convertAst(forBlock.expression, unit.job, convertSourceSpan(forBlock.expression.span, forBlock.sourceSpan));
24205
24480
  const repeater = createRepeaterOp(repeaterCreate.xref, repeaterCreate.handle, expression, forBlock.sourceSpan);
@@ -24317,6 +24592,27 @@ function convertAst(ast, job, baseSourceSpan) {
24317
24592
  throw new Error(`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`);
24318
24593
  }
24319
24594
  }
24595
+ function convertAstWithInterpolation(job, value, i18nMeta, sourceSpan) {
24596
+ let expression;
24597
+ if (value instanceof Interpolation$1) {
24598
+ expression = new Interpolation(value.strings, value.expressions.map(e => convertAst(e, job, sourceSpan ?? null)), Object.keys(asMessage(i18nMeta)?.placeholders ?? {}));
24599
+ }
24600
+ else if (value instanceof AST) {
24601
+ expression = convertAst(value, job, sourceSpan ?? null);
24602
+ }
24603
+ else {
24604
+ expression = literal(value);
24605
+ }
24606
+ return expression;
24607
+ }
24608
+ // TODO: Can we populate Template binding kinds in ingest?
24609
+ const BINDING_KINDS = new Map([
24610
+ [0 /* e.BindingType.Property */, BindingKind.Property],
24611
+ [1 /* e.BindingType.Attribute */, BindingKind.Attribute],
24612
+ [2 /* e.BindingType.Class */, BindingKind.ClassName],
24613
+ [3 /* e.BindingType.Style */, BindingKind.StyleProperty],
24614
+ [4 /* e.BindingType.Animation */, BindingKind.Animation],
24615
+ ]);
24320
24616
  /**
24321
24617
  * Checks whether the given template is a plain ng-template (as opposed to another kind of template
24322
24618
  * such as a structural directive template or control flow template). This is checked based on the
@@ -24335,149 +24631,184 @@ function convertAst(ast, job, baseSourceSpan) {
24335
24631
  * | `<ng-template *ngIf>` (structural) | null |
24336
24632
  */
24337
24633
  function isPlainTemplate(tmpl) {
24338
- return splitNsName(tmpl.tagName ?? '')[1] === 'ng-template';
24634
+ return splitNsName(tmpl.tagName ?? '')[1] === NG_TEMPLATE_TAG_NAME$1;
24339
24635
  }
24340
24636
  /**
24341
- * Process all of the bindings on an element-like structure in the template AST and convert them
24342
- * to their IR representation.
24637
+ * Ensures that the i18nMeta, if provided, is an i18n.Message.
24343
24638
  */
24344
- function ingestBindings(unit, op, element) {
24345
- let flags = BindingFlags.None;
24346
- let hasI18nAttributes = false;
24347
- if (element instanceof Template) {
24348
- flags |= BindingFlags.OnNgTemplateElement;
24349
- if (element instanceof Template && isPlainTemplate(element)) {
24350
- flags |= BindingFlags.BindingTargetsTemplate;
24351
- }
24352
- const templateAttrFlags = flags | BindingFlags.BindingTargetsTemplate | BindingFlags.IsStructuralTemplateAttribute;
24353
- for (const attr of element.templateAttrs) {
24354
- if (attr instanceof TextAttribute) {
24355
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, templateAttrFlags | BindingFlags.TextValue, attr.i18n);
24356
- hasI18nAttributes ||= attr.i18n !== undefined;
24357
- }
24358
- else {
24359
- ingestBinding(unit, op.xref, attr.name, attr.value, attr.type, attr.unit, attr.securityContext, attr.sourceSpan, templateAttrFlags, attr.i18n);
24360
- hasI18nAttributes ||= attr.i18n !== undefined;
24361
- }
24362
- }
24639
+ function asMessage(i18nMeta) {
24640
+ if (i18nMeta == null) {
24641
+ return null;
24363
24642
  }
24643
+ if (!(i18nMeta instanceof Message)) {
24644
+ throw Error(`Expected i18n meta to be a Message, but got: ${i18nMeta.constructor.name}`);
24645
+ }
24646
+ return i18nMeta;
24647
+ }
24648
+ /**
24649
+ * Process all of the bindings on an element in the template AST and convert them to their IR
24650
+ * representation.
24651
+ */
24652
+ function ingestElementBindings(unit, op, element) {
24653
+ let bindings = new Array();
24364
24654
  for (const attr of element.attributes) {
24365
- // This is only attribute TextLiteral bindings, such as `attr.foo="bar"`. This can never be
24366
- // `[attr.foo]="bar"` or `attr.foo="{{bar}}"`, both of which will be handled as inputs with
24367
- // `BindingType.Attribute`.
24368
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, flags | BindingFlags.TextValue, attr.i18n);
24369
- hasI18nAttributes ||= attr.i18n !== undefined;
24655
+ // Attribute literal bindings, such as `attr.foo="bar"`.
24656
+ const securityContext = domSchema.securityContext(element.name, attr.name, true);
24657
+ bindings.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, convertAstWithInterpolation(unit.job, attr.value, attr.i18n), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
24370
24658
  }
24371
24659
  for (const input of element.inputs) {
24372
- ingestBinding(unit, op.xref, input.name, input.value, input.type, input.unit, input.securityContext, input.sourceSpan, flags, input.i18n);
24373
- hasI18nAttributes ||= input.i18n !== undefined;
24660
+ // All dynamic bindings (both attribute and property bindings).
24661
+ bindings.push(createBindingOp(op.xref, BINDING_KINDS.get(input.type), input.name, convertAstWithInterpolation(unit.job, astOf(input.value), input.i18n), input.unit, input.securityContext, false, false, null, asMessage(input.i18n) ?? null, input.sourceSpan));
24374
24662
  }
24663
+ unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
24664
+ unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
24375
24665
  for (const output of element.outputs) {
24376
- let listenerOp;
24377
- if (output.type === 1 /* e.ParsedEventType.Animation */) {
24378
- if (output.phase === null) {
24379
- throw Error('Animation listener should have a phase');
24380
- }
24666
+ if (output.type === 1 /* e.ParsedEventType.Animation */ && output.phase === null) {
24667
+ throw Error('Animation listener should have a phase');
24381
24668
  }
24382
- if (element instanceof Template && !isPlainTemplate(element)) {
24383
- unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, output.name, null, null));
24384
- continue;
24385
- }
24386
- listenerOp = createListenerOp(op.xref, op.handle, output.name, op.tag, output.phase, false, output.sourceSpan);
24387
- // if output.handler is a chain, then push each statement from the chain separately, and
24388
- // return the last one?
24389
- let handlerExprs;
24390
- let handler = output.handler;
24391
- if (handler instanceof ASTWithSource) {
24392
- handler = handler.ast;
24393
- }
24394
- if (handler instanceof Chain) {
24395
- handlerExprs = handler.expressions;
24669
+ unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
24670
+ }
24671
+ // If any of the bindings on this element have an i18n message, then an i18n attrs configuration
24672
+ // op is also required.
24673
+ if (bindings.some(b => b?.i18nMessage) !== null) {
24674
+ unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
24675
+ }
24676
+ }
24677
+ /**
24678
+ * Process all of the bindings on a template in the template AST and convert them to their IR
24679
+ * representation.
24680
+ */
24681
+ function ingestTemplateBindings(unit, op, template, templateKind) {
24682
+ let bindings = new Array();
24683
+ for (const attr of template.templateAttrs) {
24684
+ if (attr instanceof TextAttribute) {
24685
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, attr.name, true);
24686
+ bindings.push(createTemplateBinding(unit, op.xref, 1 /* e.BindingType.Attribute */, attr.name, attr.value, null, securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
24396
24687
  }
24397
24688
  else {
24398
- handlerExprs = [handler];
24689
+ bindings.push(createTemplateBinding(unit, op.xref, attr.type, attr.name, astOf(attr.value), attr.unit, attr.securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
24690
+ }
24691
+ }
24692
+ for (const attr of template.attributes) {
24693
+ // Attribute literal bindings, such as `attr.foo="bar"`.
24694
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, attr.name, true);
24695
+ bindings.push(createTemplateBinding(unit, op.xref, 1 /* e.BindingType.Attribute */, attr.name, attr.value, null, securityContext, false, templateKind, asMessage(attr.i18n), attr.sourceSpan));
24696
+ }
24697
+ for (const input of template.inputs) {
24698
+ // Dynamic bindings (both attribute and property bindings).
24699
+ bindings.push(createTemplateBinding(unit, op.xref, input.type, input.name, astOf(input.value), input.unit, input.securityContext, false, templateKind, asMessage(input.i18n), input.sourceSpan));
24700
+ }
24701
+ unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
24702
+ unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
24703
+ for (const output of template.outputs) {
24704
+ if (output.type === 1 /* e.ParsedEventType.Animation */ && output.phase === null) {
24705
+ throw Error('Animation listener should have a phase');
24399
24706
  }
24400
- if (handlerExprs.length === 0) {
24401
- throw new Error('Expected listener to have non-empty expression list.');
24707
+ if (templateKind === TemplateKind.NgTemplate) {
24708
+ unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
24402
24709
  }
24403
- const expressions = handlerExprs.map(expr => convertAst(expr, unit.job, output.handlerSpan));
24404
- const returnExpr = expressions.pop();
24405
- for (const expr of expressions) {
24406
- const stmtOp = createStatementOp(new ExpressionStatement(expr, expr.sourceSpan));
24407
- listenerOp.handlerOps.push(stmtOp);
24710
+ if (templateKind === TemplateKind.Structural &&
24711
+ output.type !== 1 /* e.ParsedEventType.Animation */) {
24712
+ // Animation bindings are excluded from the structural template's const array.
24713
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, output.name, false);
24714
+ unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, output.name, null, null, null, securityContext));
24408
24715
  }
24409
- listenerOp.handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
24410
- unit.create.push(listenerOp);
24411
24716
  }
24412
24717
  // TODO: Perhaps we could do this in a phase? (It likely wouldn't change the slot indices.)
24413
- if (hasI18nAttributes) {
24718
+ if (bindings.some(b => b?.i18nMessage) !== null) {
24414
24719
  unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
24415
24720
  }
24416
24721
  }
24417
- const BINDING_KINDS = new Map([
24418
- [0 /* e.BindingType.Property */, BindingKind.Property],
24419
- [1 /* e.BindingType.Attribute */, BindingKind.Attribute],
24420
- [2 /* e.BindingType.Class */, BindingKind.ClassName],
24421
- [3 /* e.BindingType.Style */, BindingKind.StyleProperty],
24422
- [4 /* e.BindingType.Animation */, BindingKind.Animation],
24423
- ]);
24424
- var BindingFlags;
24425
- (function (BindingFlags) {
24426
- BindingFlags[BindingFlags["None"] = 0] = "None";
24427
- /**
24428
- * The binding is to a static text literal and not to an expression.
24429
- */
24430
- BindingFlags[BindingFlags["TextValue"] = 1] = "TextValue";
24431
- /**
24432
- * The binding belongs to the `<ng-template>` side of a `t.Template`.
24433
- */
24434
- BindingFlags[BindingFlags["BindingTargetsTemplate"] = 2] = "BindingTargetsTemplate";
24435
- /**
24436
- * The binding is on a structural directive.
24437
- */
24438
- BindingFlags[BindingFlags["IsStructuralTemplateAttribute"] = 4] = "IsStructuralTemplateAttribute";
24439
- /**
24440
- * The binding is on a `t.Template`.
24441
- */
24442
- BindingFlags[BindingFlags["OnNgTemplateElement"] = 8] = "OnNgTemplateElement";
24443
- })(BindingFlags || (BindingFlags = {}));
24444
- function ingestBinding(view, xref, name, value, type, unit, securityContext, sourceSpan, flags, i18nMeta) {
24445
- if (value instanceof ASTWithSource) {
24446
- value = value.ast;
24447
- }
24448
- let i18nContext = null;
24449
- if (i18nMeta !== undefined) {
24450
- if (!(i18nMeta instanceof Message)) {
24451
- throw Error(`Unhandled i18n metadata type for binding: ${i18nMeta.constructor.name}`);
24722
+ /**
24723
+ * Helper to ingest an individual binding on a template, either an explicit `ng-template`, or an
24724
+ * implicit template created via structural directive.
24725
+ *
24726
+ * Bindings on templates are *extremely* tricky. I have tried to isolate all of the confusing edge
24727
+ * cases into this function, and to comment it well to document the behavior.
24728
+ *
24729
+ * Some of this behavior is intuitively incorrect, and we should consider changing it in the future.
24730
+ *
24731
+ * @param view The compilation unit for the view containing the template.
24732
+ * @param xref The xref of the template op.
24733
+ * @param type The binding type, according to the parser. This is fairly reasonable, e.g. both
24734
+ * dynamic and static attributes have e.BindingType.Attribute.
24735
+ * @param name The binding's name.
24736
+ * @param value The bindings's value, which will either be an input AST expression, or a string
24737
+ * literal. Note that the input AST expression may or may not be const -- it will only be a
24738
+ * string literal if the parser considered it a text binding.
24739
+ * @param unit If the binding has a unit (e.g. `px` for style bindings), then this is the unit.
24740
+ * @param securityContext The security context of the binding.
24741
+ * @param isStructuralTemplateAttribute Whether this binding actually applies to the structural
24742
+ * ng-template. For example, an `ngFor` would actually apply to the structural template. (Most
24743
+ * bindings on structural elements target the inner element, not the template.)
24744
+ * @param templateKind Whether this is an explicit `ng-template` or an implicit template created by
24745
+ * a structural directive. This should never be a block template.
24746
+ * @param i18nMessage The i18n metadata for the binding, if any.
24747
+ * @param sourceSpan The source span of the binding.
24748
+ * @returns An IR binding op, or null if the binding should be skipped.
24749
+ */
24750
+ function createTemplateBinding(view, xref, type, name, value, unit, securityContext, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
24751
+ const isTextBinding = typeof value === 'string';
24752
+ // If this is a structural template, then several kinds of bindings should not result in an
24753
+ // update instruction.
24754
+ if (templateKind === TemplateKind.Structural) {
24755
+ if (!isStructuralTemplateAttribute &&
24756
+ (type === 0 /* e.BindingType.Property */ || type === 2 /* e.BindingType.Class */ ||
24757
+ type === 3 /* e.BindingType.Style */)) {
24758
+ // Because this binding doesn't really target the ng-template, it must be a binding on an
24759
+ // inner node of a structural template. We can't skip it entirely, because we still need it on
24760
+ // the ng-template's consts (e.g. for the purposes of directive matching). However, we should
24761
+ // not generate an update instruction for it.
24762
+ return createExtractedAttributeOp(xref, BindingKind.Property, name, null, null, i18nMessage, securityContext);
24763
+ }
24764
+ if (!isTextBinding && (type === 1 /* e.BindingType.Attribute */ || type === 4 /* e.BindingType.Animation */)) {
24765
+ // Again, this binding doesn't really target the ng-template; it actually targets the element
24766
+ // inside the structural template. In the case of non-text attribute or animation bindings,
24767
+ // the binding doesn't even show up on the ng-template const array, so we just skip it
24768
+ // entirely.
24769
+ return null;
24452
24770
  }
24453
- i18nContext = view.job.allocateXrefId();
24454
- view.create.push(createI18nContextOp(I18nContextKind.Attr, i18nContext, null, i18nMeta, null));
24455
- }
24456
- if (flags & BindingFlags.OnNgTemplateElement && !(flags & BindingFlags.BindingTargetsTemplate) &&
24457
- type === 0 /* e.BindingType.Property */) {
24458
- // This binding only exists for later const extraction, and is not an actual binding to be
24459
- // created.
24460
- view.create.push(createExtractedAttributeOp(xref, BindingKind.Property, name, null, i18nContext));
24461
- return;
24462
24771
  }
24463
- let expression;
24464
- // TODO: We could easily generate source maps for subexpressions in these cases, but
24465
- // TemplateDefinitionBuilder does not. Should we do so?
24466
- if (value instanceof Interpolation$1) {
24467
- let i18nPlaceholders = [];
24468
- if (i18nMeta !== undefined) {
24469
- i18nPlaceholders = Object.keys(i18nMeta.placeholders);
24772
+ let bindingType = BINDING_KINDS.get(type);
24773
+ if (templateKind === TemplateKind.NgTemplate) {
24774
+ // We know we are dealing with bindings directly on an explicit ng-template.
24775
+ // Static attribute bindings should be collected into the const array as k/v pairs. Property
24776
+ // bindings should result in a `property` instruction, and `AttributeMarker.Bindings` const
24777
+ // entries.
24778
+ //
24779
+ // The difficulty is with dynamic attribute, style, and class bindings. These don't really make
24780
+ // sense on an `ng-template` and should probably be parser errors. However,
24781
+ // TemplateDefinitionBuilder generates `property` instructions for them, and so we do that as
24782
+ // well.
24783
+ //
24784
+ // Note that we do have a slight behavior difference with TemplateDefinitionBuilder: although
24785
+ // TDB emits `property` instructions for dynamic attributes, styles, and classes, only styles
24786
+ // and classes also get const collected into the `AttributeMarker.Bindings` field. Dynamic
24787
+ // attribute bindings are missing from the consts entirely. We choose to emit them into the
24788
+ // consts field anyway, to avoid creating special cases for something so arcane and nonsensical.
24789
+ if (type === 2 /* e.BindingType.Class */ || type === 3 /* e.BindingType.Style */ ||
24790
+ (type === 1 /* e.BindingType.Attribute */ && !isTextBinding)) {
24791
+ // TODO: These cases should be parse errors.
24792
+ bindingType = BindingKind.Property;
24470
24793
  }
24471
- expression = new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, view.job, null)), i18nPlaceholders);
24472
- }
24473
- else if (value instanceof AST) {
24474
- expression = convertAst(value, view.job, null);
24475
24794
  }
24476
- else {
24477
- expression = value;
24795
+ return createBindingOp(xref, bindingType, name, convertAstWithInterpolation(view.job, value, i18nMessage), unit, securityContext, isTextBinding, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan);
24796
+ }
24797
+ function makeListenerHandlerOps(unit, handler, handlerSpan) {
24798
+ handler = astOf(handler);
24799
+ const handlerOps = new Array();
24800
+ let handlerExprs = handler instanceof Chain ? handler.expressions : [handler];
24801
+ if (handlerExprs.length === 0) {
24802
+ throw new Error('Expected listener to have non-empty expression list.');
24478
24803
  }
24479
- const kind = BINDING_KINDS.get(type);
24480
- view.update.push(createBindingOp(xref, kind, name, expression, unit, securityContext, !!(flags & BindingFlags.TextValue), !!(flags & BindingFlags.IsStructuralTemplateAttribute), i18nContext, sourceSpan));
24804
+ const expressions = handlerExprs.map(expr => convertAst(expr, unit.job, handlerSpan));
24805
+ const returnExpr = expressions.pop();
24806
+ handlerOps.push(...expressions.map(e => createStatementOp(new ExpressionStatement(e, e.sourceSpan))));
24807
+ handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
24808
+ return handlerOps;
24809
+ }
24810
+ function astOf(ast) {
24811
+ return ast instanceof ASTWithSource ? ast.ast : ast;
24481
24812
  }
24482
24813
  /**
24483
24814
  * Process all of the local references on an element-like structure in the template AST and
@@ -24565,11 +24896,12 @@ function ingestControlFlowInsertionPoint(unit, xref, node) {
24565
24896
  // and they can be used in directive matching (in the case of `Template.templateAttrs`).
24566
24897
  if (root !== null) {
24567
24898
  for (const attr of root.attributes) {
24568
- ingestBinding(unit, xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue, attr.i18n);
24899
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, attr.name, true);
24900
+ unit.update.push(createBindingOp(xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
24569
24901
  }
24570
24902
  const tagName = root instanceof Element$1 ? root.name : root.tagName;
24571
24903
  // Don't pass along `ng-template` tag name since it enables directive matching.
24572
- return tagName === 'ng-template' ? null : tagName;
24904
+ return tagName === NG_TEMPLATE_TAG_NAME$1 ? null : tagName;
24573
24905
  }
24574
24906
  return null;
24575
24907
  }
@@ -29241,12 +29573,16 @@ class BindingScope {
29241
29573
  }
29242
29574
  /** Binding scope of a `track` function inside a `for` loop block. */
29243
29575
  class TrackByBindingScope extends BindingScope {
29244
- constructor(parentScope, globalAliases) {
29576
+ constructor(parentScope, globalOverrides) {
29245
29577
  super(parentScope.bindingLevel + 1, parentScope);
29246
- this.globalAliases = globalAliases;
29578
+ this.globalOverrides = globalOverrides;
29247
29579
  this.componentAccessCount = 0;
29248
29580
  }
29249
29581
  get(name) {
29582
+ // Intercept any overridden globals.
29583
+ if (this.globalOverrides.hasOwnProperty(name)) {
29584
+ return variable(this.globalOverrides[name]);
29585
+ }
29250
29586
  let current = this.parent;
29251
29587
  // Prevent accesses of template variables outside the `for` loop.
29252
29588
  while (current) {
@@ -29255,10 +29591,6 @@ class TrackByBindingScope extends BindingScope {
29255
29591
  }
29256
29592
  current = current.parent;
29257
29593
  }
29258
- // Intercept any aliased globals.
29259
- if (this.globalAliases[name]) {
29260
- return variable(this.globalAliases[name]);
29261
- }
29262
29594
  // When the component scope is accessed, we redirect it through `this`.
29263
29595
  this.componentAccessCount++;
29264
29596
  return variable('this').prop(name);
@@ -29996,6 +30328,7 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29996
30328
  }
29997
30329
  const hostJob = ingestHostBinding({
29998
30330
  componentName: name,
30331
+ componentSelector: selector,
29999
30332
  properties: bindings,
30000
30333
  events: eventBindings,
30001
30334
  attributes: hostBindingsMetadata.attributes,
@@ -31755,7 +32088,7 @@ function publishFacade(global) {
31755
32088
  * @description
31756
32089
  * Entry point for all public APIs of the compiler package.
31757
32090
  */
31758
- const VERSION = new Version('17.0.6');
32091
+ const VERSION = new Version('17.0.8');
31759
32092
 
31760
32093
  class CompilerConfig {
31761
32094
  constructor({ defaultEncapsulation = ViewEncapsulation.Emulated, preserveWhitespaces, strictInjectionParameters } = {}) {
@@ -33321,7 +33654,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$6 = '12.0.0';
33321
33654
  function compileDeclareClassMetadata(metadata) {
33322
33655
  const definitionMap = new DefinitionMap();
33323
33656
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$6));
33324
- definitionMap.set('version', literal('17.0.6'));
33657
+ definitionMap.set('version', literal('17.0.8'));
33325
33658
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33326
33659
  definitionMap.set('type', metadata.type);
33327
33660
  definitionMap.set('decorators', metadata.decorators);
@@ -33429,7 +33762,7 @@ function createDirectiveDefinitionMap(meta) {
33429
33762
  // in 16.1 is actually used.
33430
33763
  const minVersion = hasTransformFunctions ? MINIMUM_PARTIAL_LINKER_VERSION$5 : '14.0.0';
33431
33764
  definitionMap.set('minVersion', literal(minVersion));
33432
- definitionMap.set('version', literal('17.0.6'));
33765
+ definitionMap.set('version', literal('17.0.8'));
33433
33766
  // e.g. `type: MyDirective`
33434
33767
  definitionMap.set('type', meta.type.value);
33435
33768
  if (meta.isStandalone) {
@@ -33706,7 +34039,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
33706
34039
  function compileDeclareFactoryFunction(meta) {
33707
34040
  const definitionMap = new DefinitionMap();
33708
34041
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
33709
- definitionMap.set('version', literal('17.0.6'));
34042
+ definitionMap.set('version', literal('17.0.8'));
33710
34043
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33711
34044
  definitionMap.set('type', meta.type.value);
33712
34045
  definitionMap.set('deps', compileDependencies(meta.deps));
@@ -33741,7 +34074,7 @@ function compileDeclareInjectableFromMetadata(meta) {
33741
34074
  function createInjectableDefinitionMap(meta) {
33742
34075
  const definitionMap = new DefinitionMap();
33743
34076
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
33744
- definitionMap.set('version', literal('17.0.6'));
34077
+ definitionMap.set('version', literal('17.0.8'));
33745
34078
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33746
34079
  definitionMap.set('type', meta.type.value);
33747
34080
  // Only generate providedIn property if it has a non-null value
@@ -33792,7 +34125,7 @@ function compileDeclareInjectorFromMetadata(meta) {
33792
34125
  function createInjectorDefinitionMap(meta) {
33793
34126
  const definitionMap = new DefinitionMap();
33794
34127
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
33795
- definitionMap.set('version', literal('17.0.6'));
34128
+ definitionMap.set('version', literal('17.0.8'));
33796
34129
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33797
34130
  definitionMap.set('type', meta.type.value);
33798
34131
  definitionMap.set('providers', meta.providers);
@@ -33825,7 +34158,7 @@ function createNgModuleDefinitionMap(meta) {
33825
34158
  throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
33826
34159
  }
33827
34160
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
33828
- definitionMap.set('version', literal('17.0.6'));
34161
+ definitionMap.set('version', literal('17.0.8'));
33829
34162
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33830
34163
  definitionMap.set('type', meta.type.value);
33831
34164
  // We only generate the keys in the metadata if the arrays contain values.
@@ -33876,7 +34209,7 @@ function compileDeclarePipeFromMetadata(meta) {
33876
34209
  function createPipeDefinitionMap(meta) {
33877
34210
  const definitionMap = new DefinitionMap();
33878
34211
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION));
33879
- definitionMap.set('version', literal('17.0.6'));
34212
+ definitionMap.set('version', literal('17.0.8'));
33880
34213
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33881
34214
  // e.g. `type: MyPipe`
33882
34215
  definitionMap.set('type', meta.type.value);