@angular/compiler 17.0.5 → 17.0.7

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 (49) hide show
  1. package/esm2022/src/compiler.mjs +2 -1
  2. package/esm2022/src/render3/partial/class_metadata.mjs +1 -1
  3. package/esm2022/src/render3/partial/directive.mjs +1 -1
  4. package/esm2022/src/render3/partial/factory.mjs +1 -1
  5. package/esm2022/src/render3/partial/injectable.mjs +1 -1
  6. package/esm2022/src/render3/partial/injector.mjs +1 -1
  7. package/esm2022/src/render3/partial/ng_module.mjs +1 -1
  8. package/esm2022/src/render3/partial/pipe.mjs +1 -1
  9. package/esm2022/src/render3/view/api.mjs +1 -1
  10. package/esm2022/src/render3/view/compiler.mjs +21 -8
  11. package/esm2022/src/render3/view/t2_api.mjs +1 -1
  12. package/esm2022/src/render3/view/t2_binder.mjs +6 -7
  13. package/esm2022/src/render3/view/template.mjs +1 -20
  14. package/esm2022/src/render3/view/util.mjs +24 -2
  15. package/esm2022/src/template/pipeline/ir/src/enums.mjs +34 -17
  16. package/esm2022/src/template/pipeline/ir/src/expression.mjs +19 -20
  17. package/esm2022/src/template/pipeline/ir/src/ops/create.mjs +37 -11
  18. package/esm2022/src/template/pipeline/ir/src/ops/host.mjs +5 -2
  19. package/esm2022/src/template/pipeline/ir/src/ops/update.mjs +29 -14
  20. package/esm2022/src/template/pipeline/src/emit.mjs +13 -9
  21. package/esm2022/src/template/pipeline/src/ingest.mjs +293 -158
  22. package/esm2022/src/template/pipeline/src/instruction.mjs +18 -14
  23. package/esm2022/src/template/pipeline/src/phases/apply_i18n_expressions.mjs +20 -4
  24. package/esm2022/src/template/pipeline/src/phases/assign_i18n_slot_dependencies.mjs +44 -22
  25. package/esm2022/src/template/pipeline/src/phases/attribute_extraction.mjs +26 -5
  26. package/esm2022/src/template/pipeline/src/phases/binding_specialization.mjs +4 -4
  27. package/esm2022/src/template/pipeline/src/phases/const_collection.mjs +15 -6
  28. package/esm2022/src/template/pipeline/src/phases/convert_i18n_bindings.mjs +52 -0
  29. package/esm2022/src/template/pipeline/src/phases/create_i18n_contexts.mjs +23 -1
  30. package/esm2022/src/template/pipeline/src/phases/extract_i18n_messages.mjs +65 -88
  31. package/esm2022/src/template/pipeline/src/phases/generate_advance.mjs +2 -2
  32. package/esm2022/src/template/pipeline/src/phases/generate_variables.mjs +7 -1
  33. package/esm2022/src/template/pipeline/src/phases/i18n_const_collection.mjs +147 -35
  34. package/esm2022/src/template/pipeline/src/phases/i18n_text_extraction.mjs +5 -3
  35. package/esm2022/src/template/pipeline/src/phases/parse_extracted_styles.mjs +4 -3
  36. package/esm2022/src/template/pipeline/src/phases/phase_remove_content_selectors.mjs +15 -3
  37. package/esm2022/src/template/pipeline/src/phases/propagate_i18n_blocks.mjs +31 -12
  38. package/esm2022/src/template/pipeline/src/phases/reify.mjs +18 -15
  39. package/esm2022/src/template/pipeline/src/phases/remove_unused_i18n_attrs.mjs +33 -0
  40. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.mjs +175 -31
  41. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.mjs +10 -5
  42. package/esm2022/src/template/pipeline/src/phases/resolve_sanitizers.mjs +78 -14
  43. package/esm2022/src/template/pipeline/src/phases/slot_allocation.mjs +3 -1
  44. package/esm2022/src/template/pipeline/src/phases/var_counting.mjs +3 -1
  45. package/esm2022/src/version.mjs +1 -1
  46. package/fesm2022/compiler.mjs +1266 -549
  47. package/fesm2022/compiler.mjs.map +1 -1
  48. package/index.d.ts +21 -1
  49. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v17.0.5
2
+ * @license Angular v17.0.7
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -3679,6 +3679,41 @@ function getInjectFn(target) {
3679
3679
  }
3680
3680
  }
3681
3681
 
3682
+ var TagContentType;
3683
+ (function (TagContentType) {
3684
+ TagContentType[TagContentType["RAW_TEXT"] = 0] = "RAW_TEXT";
3685
+ TagContentType[TagContentType["ESCAPABLE_RAW_TEXT"] = 1] = "ESCAPABLE_RAW_TEXT";
3686
+ TagContentType[TagContentType["PARSABLE_DATA"] = 2] = "PARSABLE_DATA";
3687
+ })(TagContentType || (TagContentType = {}));
3688
+ function splitNsName(elementName) {
3689
+ if (elementName[0] != ':') {
3690
+ return [null, elementName];
3691
+ }
3692
+ const colonIndex = elementName.indexOf(':', 1);
3693
+ if (colonIndex === -1) {
3694
+ throw new Error(`Unsupported format "${elementName}" expecting ":namespace:name"`);
3695
+ }
3696
+ return [elementName.slice(1, colonIndex), elementName.slice(colonIndex + 1)];
3697
+ }
3698
+ // `<ng-container>` tags work the same regardless the namespace
3699
+ function isNgContainer(tagName) {
3700
+ return splitNsName(tagName)[1] === 'ng-container';
3701
+ }
3702
+ // `<ng-content>` tags work the same regardless the namespace
3703
+ function isNgContent(tagName) {
3704
+ return splitNsName(tagName)[1] === 'ng-content';
3705
+ }
3706
+ // `<ng-template>` tags work the same regardless the namespace
3707
+ function isNgTemplate(tagName) {
3708
+ return splitNsName(tagName)[1] === 'ng-template';
3709
+ }
3710
+ function getNsPrefix(fullName) {
3711
+ return fullName === null ? null : splitNsName(fullName)[0];
3712
+ }
3713
+ function mergeNsAndName(prefix, localName) {
3714
+ return prefix ? `:${prefix}:${localName}` : localName;
3715
+ }
3716
+
3682
3717
  /**
3683
3718
  * This is an R3 `Node`-like wrapper for a raw `html.Comment` node. We do not currently
3684
3719
  * require the implementation of a visitor for Comments as they are only collected at
@@ -4677,7 +4712,7 @@ const I18N_ATTR_PREFIX = 'i18n-';
4677
4712
  /** Prefix of var expressions used in ICUs */
4678
4713
  const I18N_ICU_VAR_PREFIX = 'VAR_';
4679
4714
  /** Prefix of ICU expressions for post processing */
4680
- const I18N_ICU_MAPPING_PREFIX = 'I18N_EXP_';
4715
+ const I18N_ICU_MAPPING_PREFIX$1 = 'I18N_EXP_';
4681
4716
  /** Placeholder wrapper for i18n expressions **/
4682
4717
  const I18N_PLACEHOLDER_SYMBOL = '�';
4683
4718
  function isI18nAttribute(name) {
@@ -5019,6 +5054,26 @@ class DefinitionMap {
5019
5054
  return literalMap(this.values);
5020
5055
  }
5021
5056
  }
5057
+ /**
5058
+ * Creates a `CssSelector` from an AST node.
5059
+ */
5060
+ function createCssSelectorFromNode(node) {
5061
+ const elementName = node instanceof Element$1 ? node.name : 'ng-template';
5062
+ const attributes = getAttrsForDirectiveMatching(node);
5063
+ const cssSelector = new CssSelector();
5064
+ const elementNameNoNs = splitNsName(elementName)[1];
5065
+ cssSelector.setElement(elementNameNoNs);
5066
+ Object.getOwnPropertyNames(attributes).forEach((name) => {
5067
+ const nameNoNs = splitNsName(name)[1];
5068
+ const value = attributes[name];
5069
+ cssSelector.addAttribute(nameNoNs, value);
5070
+ if (name.toLowerCase() === 'class') {
5071
+ const classes = value.trim().split(/\s+/);
5072
+ classes.forEach(className => cssSelector.addClassName(className));
5073
+ }
5074
+ });
5075
+ return cssSelector;
5076
+ }
5022
5077
  /**
5023
5078
  * Extract a map of properties to values for a given element or template node, which can be used
5024
5079
  * by the directive matching machinery.
@@ -8915,6 +8970,10 @@ var OpKind;
8915
8970
  * An i18n context containing information needed to generate an i18n message.
8916
8971
  */
8917
8972
  OpKind[OpKind["I18nContext"] = 43] = "I18nContext";
8973
+ /**
8974
+ * A creation op that corresponds to i18n attributes on an element.
8975
+ */
8976
+ OpKind[OpKind["I18nAttributes"] = 44] = "I18nAttributes";
8918
8977
  })(OpKind || (OpKind = {}));
8919
8978
  /**
8920
8979
  * Distinguishes different kinds of IR expressions.
@@ -9005,23 +9064,27 @@ var ExpressionKind;
9005
9064
  * An expression representing a sanitizer function.
9006
9065
  */
9007
9066
  ExpressionKind[ExpressionKind["SanitizerExpr"] = 20] = "SanitizerExpr";
9067
+ /**
9068
+ * An expression representing a function to create trusted values.
9069
+ */
9070
+ ExpressionKind[ExpressionKind["TrustedValueFnExpr"] = 21] = "TrustedValueFnExpr";
9008
9071
  /**
9009
9072
  * An expression that will cause a literal slot index to be emitted.
9010
9073
  */
9011
- ExpressionKind[ExpressionKind["SlotLiteralExpr"] = 21] = "SlotLiteralExpr";
9074
+ ExpressionKind[ExpressionKind["SlotLiteralExpr"] = 22] = "SlotLiteralExpr";
9012
9075
  /**
9013
9076
  * A test expression for a conditional op.
9014
9077
  */
9015
- ExpressionKind[ExpressionKind["ConditionalCase"] = 22] = "ConditionalCase";
9078
+ ExpressionKind[ExpressionKind["ConditionalCase"] = 23] = "ConditionalCase";
9016
9079
  /**
9017
9080
  * A variable for use inside a repeater, providing one of the ambiently-available context
9018
9081
  * properties ($even, $first, etc.).
9019
9082
  */
9020
- ExpressionKind[ExpressionKind["DerivedRepeaterVar"] = 23] = "DerivedRepeaterVar";
9083
+ ExpressionKind[ExpressionKind["DerivedRepeaterVar"] = 24] = "DerivedRepeaterVar";
9021
9084
  /**
9022
9085
  * An expression that will be automatically extracted to the component const array.
9023
9086
  */
9024
- ExpressionKind[ExpressionKind["ConstCollected"] = 24] = "ConstCollected";
9087
+ ExpressionKind[ExpressionKind["ConstCollected"] = 25] = "ConstCollected";
9025
9088
  })(ExpressionKind || (ExpressionKind = {}));
9026
9089
  var VariableFlags;
9027
9090
  (function (VariableFlags) {
@@ -9065,18 +9128,6 @@ var CompatibilityMode;
9065
9128
  CompatibilityMode[CompatibilityMode["Normal"] = 0] = "Normal";
9066
9129
  CompatibilityMode[CompatibilityMode["TemplateDefinitionBuilder"] = 1] = "TemplateDefinitionBuilder";
9067
9130
  })(CompatibilityMode || (CompatibilityMode = {}));
9068
- /**
9069
- * Represents functions used to sanitize different pieces of a template.
9070
- */
9071
- var SanitizerFn;
9072
- (function (SanitizerFn) {
9073
- SanitizerFn[SanitizerFn["Html"] = 0] = "Html";
9074
- SanitizerFn[SanitizerFn["Script"] = 1] = "Script";
9075
- SanitizerFn[SanitizerFn["Style"] = 2] = "Style";
9076
- SanitizerFn[SanitizerFn["Url"] = 3] = "Url";
9077
- SanitizerFn[SanitizerFn["ResourceUrl"] = 4] = "ResourceUrl";
9078
- SanitizerFn[SanitizerFn["IframeAttribute"] = 5] = "IframeAttribute";
9079
- })(SanitizerFn || (SanitizerFn = {}));
9080
9131
  /**
9081
9132
  * Enumeration of the different kinds of `@defer` secondary blocks.
9082
9133
  */
@@ -9136,6 +9187,20 @@ var I18nParamResolutionTime;
9136
9187
  */
9137
9188
  I18nParamResolutionTime[I18nParamResolutionTime["Postproccessing"] = 1] = "Postproccessing";
9138
9189
  })(I18nParamResolutionTime || (I18nParamResolutionTime = {}));
9190
+ /**
9191
+ * The contexts in which an i18n expression can be used.
9192
+ */
9193
+ var I18nExpressionFor;
9194
+ (function (I18nExpressionFor) {
9195
+ /**
9196
+ * This expression is used as a value (i.e. inside an i18n block).
9197
+ */
9198
+ I18nExpressionFor[I18nExpressionFor["I18nText"] = 0] = "I18nText";
9199
+ /**
9200
+ * This expression is used in a binding.
9201
+ */
9202
+ I18nExpressionFor[I18nExpressionFor["I18nAttribute"] = 1] = "I18nAttribute";
9203
+ })(I18nExpressionFor || (I18nExpressionFor = {}));
9139
9204
  /**
9140
9205
  * Flags that describe what an i18n param value. These determine how the value is serialized into
9141
9206
  * the final map.
@@ -9202,7 +9267,14 @@ var I18nContextKind;
9202
9267
  (function (I18nContextKind) {
9203
9268
  I18nContextKind[I18nContextKind["RootI18n"] = 0] = "RootI18n";
9204
9269
  I18nContextKind[I18nContextKind["Icu"] = 1] = "Icu";
9270
+ I18nContextKind[I18nContextKind["Attr"] = 2] = "Attr";
9205
9271
  })(I18nContextKind || (I18nContextKind = {}));
9272
+ var TemplateKind;
9273
+ (function (TemplateKind) {
9274
+ TemplateKind[TemplateKind["NgTemplate"] = 0] = "NgTemplate";
9275
+ TemplateKind[TemplateKind["Structural"] = 1] = "Structural";
9276
+ TemplateKind[TemplateKind["Block"] = 2] = "Block";
9277
+ })(TemplateKind || (TemplateKind = {}));
9206
9278
 
9207
9279
  /**
9208
9280
  * Marker symbol for `ConsumesSlotOpTrait`.
@@ -9310,12 +9382,11 @@ const NEW_OP = {
9310
9382
  /**
9311
9383
  * Create an `InterpolationTextOp`.
9312
9384
  */
9313
- function createInterpolateTextOp(xref, interpolation, i18nPlaceholders, sourceSpan) {
9385
+ function createInterpolateTextOp(xref, interpolation, sourceSpan) {
9314
9386
  return {
9315
9387
  kind: OpKind.InterpolateText,
9316
9388
  target: xref,
9317
9389
  interpolation,
9318
- i18nPlaceholders,
9319
9390
  sourceSpan,
9320
9391
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9321
9392
  ...TRAIT_CONSUMES_VARS,
@@ -9323,15 +9394,19 @@ function createInterpolateTextOp(xref, interpolation, i18nPlaceholders, sourceSp
9323
9394
  };
9324
9395
  }
9325
9396
  class Interpolation {
9326
- constructor(strings, expressions) {
9397
+ constructor(strings, expressions, i18nPlaceholders) {
9327
9398
  this.strings = strings;
9328
9399
  this.expressions = expressions;
9400
+ this.i18nPlaceholders = i18nPlaceholders;
9401
+ if (i18nPlaceholders.length !== 0 && i18nPlaceholders.length !== expressions.length) {
9402
+ throw new Error(`Expected ${expressions.length} placeholders to match interpolation expression count, but got ${i18nPlaceholders.length}`);
9403
+ }
9329
9404
  }
9330
9405
  }
9331
9406
  /**
9332
9407
  * Create a `BindingOp`, not yet transformed into a particular type of binding.
9333
9408
  */
9334
- function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isTemplate, sourceSpan) {
9409
+ function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
9335
9410
  return {
9336
9411
  kind: OpKind.Binding,
9337
9412
  bindingKind: kind,
@@ -9341,7 +9416,10 @@ function createBindingOp(target, kind, name, expression, unit, securityContext,
9341
9416
  unit,
9342
9417
  securityContext,
9343
9418
  isTextAttribute,
9344
- isTemplate,
9419
+ isStructuralTemplateAttribute,
9420
+ templateKind,
9421
+ i18nContext: null,
9422
+ i18nMessage,
9345
9423
  sourceSpan,
9346
9424
  ...NEW_OP,
9347
9425
  };
@@ -9349,7 +9427,7 @@ function createBindingOp(target, kind, name, expression, unit, securityContext,
9349
9427
  /**
9350
9428
  * Create a `PropertyOp`.
9351
9429
  */
9352
- function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isTemplate, sourceSpan) {
9430
+ function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isStructuralTemplateAttribute, templateKind, i18nContext, i18nMessage, sourceSpan) {
9353
9431
  return {
9354
9432
  kind: OpKind.Property,
9355
9433
  target,
@@ -9358,7 +9436,10 @@ function createPropertyOp(target, name, expression, isAnimationTrigger, security
9358
9436
  isAnimationTrigger,
9359
9437
  securityContext,
9360
9438
  sanitizer: null,
9361
- isTemplate,
9439
+ isStructuralTemplateAttribute,
9440
+ templateKind,
9441
+ i18nContext,
9442
+ i18nMessage,
9362
9443
  sourceSpan,
9363
9444
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9364
9445
  ...TRAIT_CONSUMES_VARS,
@@ -9423,7 +9504,7 @@ function createClassMapOp(xref, expression, sourceSpan) {
9423
9504
  /**
9424
9505
  * Create an `AttributeOp`.
9425
9506
  */
9426
- function createAttributeOp(target, name, expression, securityContext, isTextAttribute, isTemplate, sourceSpan) {
9507
+ function createAttributeOp(target, name, expression, securityContext, isTextAttribute, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
9427
9508
  return {
9428
9509
  kind: OpKind.Attribute,
9429
9510
  target,
@@ -9432,7 +9513,10 @@ function createAttributeOp(target, name, expression, securityContext, isTextAttr
9432
9513
  securityContext,
9433
9514
  sanitizer: null,
9434
9515
  isTextAttribute,
9435
- isTemplate,
9516
+ isStructuralTemplateAttribute,
9517
+ templateKind,
9518
+ i18nContext: null,
9519
+ i18nMessage,
9436
9520
  sourceSpan,
9437
9521
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9438
9522
  ...TRAIT_CONSUMES_VARS,
@@ -9493,15 +9577,18 @@ function createDeferWhenOp(target, expr, prefetch, sourceSpan) {
9493
9577
  /**
9494
9578
  * Create an i18n expression op.
9495
9579
  */
9496
- function createI18nExpressionOp(context, target, handle, expression, i18nPlaceholder, resolutionTime, sourceSpan) {
9580
+ function createI18nExpressionOp(context, target, i18nOwner, handle, expression, i18nPlaceholder, resolutionTime, usage, name, sourceSpan) {
9497
9581
  return {
9498
9582
  kind: OpKind.I18nExpression,
9499
9583
  context,
9500
9584
  target,
9585
+ i18nOwner,
9501
9586
  handle,
9502
9587
  expression,
9503
9588
  i18nPlaceholder,
9504
9589
  resolutionTime,
9590
+ usage,
9591
+ name,
9505
9592
  sourceSpan,
9506
9593
  ...NEW_OP,
9507
9594
  ...TRAIT_CONSUMES_VARS,
@@ -9509,12 +9596,12 @@ function createI18nExpressionOp(context, target, handle, expression, i18nPlaceho
9509
9596
  };
9510
9597
  }
9511
9598
  /**
9512
- *Creates an op to apply i18n expression ops
9599
+ * Creates an op to apply i18n expression ops.
9513
9600
  */
9514
- function createI18nApplyOp(target, handle, sourceSpan) {
9601
+ function createI18nApplyOp(owner, handle, sourceSpan) {
9515
9602
  return {
9516
9603
  kind: OpKind.I18nApply,
9517
- target,
9604
+ owner,
9518
9605
  handle,
9519
9606
  sourceSpan,
9520
9607
  ...NEW_OP,
@@ -10063,24 +10150,6 @@ class ReadTemporaryExpr extends ExpressionBase {
10063
10150
  return r;
10064
10151
  }
10065
10152
  }
10066
- class SanitizerExpr extends ExpressionBase {
10067
- constructor(fn) {
10068
- super();
10069
- this.fn = fn;
10070
- this.kind = ExpressionKind.SanitizerExpr;
10071
- }
10072
- visitExpression(visitor, context) { }
10073
- isEquivalent(e) {
10074
- return e instanceof SanitizerExpr && e.fn === this.fn;
10075
- }
10076
- isConstant() {
10077
- return true;
10078
- }
10079
- clone() {
10080
- return new SanitizerExpr(this.fn);
10081
- }
10082
- transformInternalExpressions() { }
10083
- }
10084
10153
  class SlotLiteralExpr extends ExpressionBase {
10085
10154
  constructor(slot) {
10086
10155
  super();
@@ -10211,7 +10280,6 @@ function transformExpressionsInOp(op, transform, flags) {
10211
10280
  case OpKind.ClassProp:
10212
10281
  case OpKind.ClassMap:
10213
10282
  case OpKind.Binding:
10214
- case OpKind.HostProperty:
10215
10283
  if (op.expression instanceof Interpolation) {
10216
10284
  transformExpressionsInInterpolation(op.expression, transform, flags);
10217
10285
  }
@@ -10220,6 +10288,7 @@ function transformExpressionsInOp(op, transform, flags) {
10220
10288
  }
10221
10289
  break;
10222
10290
  case OpKind.Property:
10291
+ case OpKind.HostProperty:
10223
10292
  case OpKind.Attribute:
10224
10293
  if (op.expression instanceof Interpolation) {
10225
10294
  transformExpressionsInInterpolation(op.expression, transform, flags);
@@ -10265,6 +10334,8 @@ function transformExpressionsInOp(op, transform, flags) {
10265
10334
  case OpKind.ExtractedAttribute:
10266
10335
  op.expression =
10267
10336
  op.expression && transformExpressionsInExpression(op.expression, transform, flags);
10337
+ op.trustedValueFn = op.trustedValueFn &&
10338
+ transformExpressionsInExpression(op.trustedValueFn, transform, flags);
10268
10339
  break;
10269
10340
  case OpKind.RepeaterCreate:
10270
10341
  op.track = transformExpressionsInExpression(op.track, transform, flags);
@@ -10318,6 +10389,7 @@ function transformExpressionsInOp(op, transform, flags) {
10318
10389
  case OpKind.ProjectionDef:
10319
10390
  case OpKind.Template:
10320
10391
  case OpKind.Text:
10392
+ case OpKind.I18nAttributes:
10321
10393
  // These operations contain no expressions.
10322
10394
  break;
10323
10395
  default:
@@ -10338,6 +10410,9 @@ function transformExpressionsInExpression(expr, transform, flags) {
10338
10410
  expr.lhs = transformExpressionsInExpression(expr.lhs, transform, flags);
10339
10411
  expr.rhs = transformExpressionsInExpression(expr.rhs, transform, flags);
10340
10412
  }
10413
+ else if (expr instanceof UnaryOperatorExpr) {
10414
+ expr.expr = transformExpressionsInExpression(expr.expr, transform, flags);
10415
+ }
10341
10416
  else if (expr instanceof ReadPropExpr) {
10342
10417
  expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
10343
10418
  }
@@ -10389,6 +10464,17 @@ function transformExpressionsInExpression(expr, transform, flags) {
10389
10464
  expr.expressions[i] = transformExpressionsInExpression(expr.expressions[i], transform, flags);
10390
10465
  }
10391
10466
  }
10467
+ else if (expr instanceof NotExpr) {
10468
+ expr.condition = transformExpressionsInExpression(expr.condition, transform, flags);
10469
+ }
10470
+ else if (expr instanceof TaggedTemplateExpr) {
10471
+ expr.tag = transformExpressionsInExpression(expr.tag, transform, flags);
10472
+ expr.template.expressions =
10473
+ expr.template.expressions.map(e => transformExpressionsInExpression(e, transform, flags));
10474
+ }
10475
+ else if (expr instanceof WrappedNodeExpr) {
10476
+ // TODO: Do we need to transform any TS nodes nested inside of this expression?
10477
+ }
10392
10478
  else if (expr instanceof ReadVarExpr || expr instanceof ExternalExpr ||
10393
10479
  expr instanceof LiteralExpr) {
10394
10480
  // No action for these types.
@@ -10730,10 +10816,11 @@ function createElementStartOp(tag, xref, namespace, i18nPlaceholder, sourceSpan)
10730
10816
  /**
10731
10817
  * Create a `TemplateOp`.
10732
10818
  */
10733
- function createTemplateOp(xref, tag, functionNameSuffix, namespace, i18nPlaceholder, sourceSpan) {
10819
+ function createTemplateOp(xref, templateKind, tag, functionNameSuffix, namespace, i18nPlaceholder, sourceSpan) {
10734
10820
  return {
10735
10821
  kind: OpKind.Template,
10736
10822
  xref,
10823
+ templateKind,
10737
10824
  attributes: null,
10738
10825
  tag,
10739
10826
  handle: new SlotHandle(),
@@ -10749,7 +10836,7 @@ function createTemplateOp(xref, tag, functionNameSuffix, namespace, i18nPlacehol
10749
10836
  ...NEW_OP,
10750
10837
  };
10751
10838
  }
10752
- function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, sourceSpan) {
10839
+ function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, i18nPlaceholder, emptyI18nPlaceholder, sourceSpan) {
10753
10840
  return {
10754
10841
  kind: OpKind.RepeaterCreate,
10755
10842
  attributes: null,
@@ -10767,6 +10854,8 @@ function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, so
10767
10854
  vars: null,
10768
10855
  varNames,
10769
10856
  usesComponentInstance: false,
10857
+ i18nPlaceholder,
10858
+ emptyI18nPlaceholder,
10770
10859
  sourceSpan,
10771
10860
  ...TRAIT_CONSUMES_SLOT,
10772
10861
  ...NEW_OP,
@@ -10815,7 +10904,9 @@ function createTextOp(xref, initialValue, sourceSpan) {
10815
10904
  /**
10816
10905
  * Create a `ListenerOp`. Host bindings reuse all the listener logic.
10817
10906
  */
10818
- function createListenerOp(target, targetSlot, name, tag, animationPhase, hostListener, sourceSpan) {
10907
+ function createListenerOp(target, targetSlot, name, tag, handlerOps, animationPhase, eventTarget, hostListener, sourceSpan) {
10908
+ const handlerList = new OpList();
10909
+ handlerList.push(handlerOps);
10819
10910
  return {
10820
10911
  kind: OpKind.Listener,
10821
10912
  target,
@@ -10823,11 +10914,12 @@ function createListenerOp(target, targetSlot, name, tag, animationPhase, hostLis
10823
10914
  tag,
10824
10915
  hostListener,
10825
10916
  name,
10826
- handlerOps: new OpList(),
10917
+ handlerOps: handlerList,
10827
10918
  handlerFnName: null,
10828
10919
  consumesDollarEvent: false,
10829
10920
  isAnimationListener: animationPhase !== null,
10830
- animationPhase: animationPhase,
10921
+ animationPhase,
10922
+ eventTarget,
10831
10923
  sourceSpan,
10832
10924
  ...NEW_OP,
10833
10925
  };
@@ -10856,14 +10948,15 @@ function createProjectionDefOp(def) {
10856
10948
  ...NEW_OP,
10857
10949
  };
10858
10950
  }
10859
- function createProjectionOp(xref, selector, sourceSpan) {
10951
+ function createProjectionOp(xref, selector, i18nPlaceholder, attributes, sourceSpan) {
10860
10952
  return {
10861
10953
  kind: OpKind.Projection,
10862
10954
  xref,
10863
10955
  handle: new SlotHandle(),
10864
10956
  selector,
10957
+ i18nPlaceholder,
10865
10958
  projectionSlotIndex: 0,
10866
- attributes: [],
10959
+ attributes,
10867
10960
  localRefs: [],
10868
10961
  sourceSpan,
10869
10962
  ...NEW_OP,
@@ -10873,13 +10966,17 @@ function createProjectionOp(xref, selector, sourceSpan) {
10873
10966
  /**
10874
10967
  * Create an `ExtractedAttributeOp`.
10875
10968
  */
10876
- function createExtractedAttributeOp(target, bindingKind, name, expression) {
10969
+ function createExtractedAttributeOp(target, bindingKind, name, expression, i18nContext, i18nMessage, securityContext) {
10877
10970
  return {
10878
10971
  kind: OpKind.ExtractedAttribute,
10879
10972
  target,
10880
10973
  bindingKind,
10881
10974
  name,
10882
10975
  expression,
10976
+ i18nContext,
10977
+ i18nMessage,
10978
+ securityContext,
10979
+ trustedValueFn: null,
10883
10980
  ...NEW_OP,
10884
10981
  };
10885
10982
  }
@@ -10922,10 +11019,11 @@ function createDeferOnOp(defer, trigger, prefetch, sourceSpan) {
10922
11019
  /**
10923
11020
  * Create an `ExtractedMessageOp`.
10924
11021
  */
10925
- function createI18nMessageOp(xref, i18nBlock, message, messagePlaceholder, params, postprocessingParams, needsPostprocessing) {
11022
+ function createI18nMessageOp(xref, i18nContext, i18nBlock, message, messagePlaceholder, params, postprocessingParams, needsPostprocessing) {
10926
11023
  return {
10927
11024
  kind: OpKind.I18nMessage,
10928
11025
  xref,
11026
+ i18nContext,
10929
11027
  i18nBlock,
10930
11028
  message,
10931
11029
  messagePlaceholder,
@@ -10988,6 +11086,9 @@ function createIcuEndOp(xref) {
10988
11086
  };
10989
11087
  }
10990
11088
  function createI18nContextOp(contextKind, xref, i18nBlock, message, sourceSpan) {
11089
+ if (i18nBlock === null && contextKind !== I18nContextKind.Attr) {
11090
+ throw new Error('AssertionError: i18nBlock must be provided for non-attribute contexts.');
11091
+ }
10991
11092
  return {
10992
11093
  kind: OpKind.I18nContext,
10993
11094
  contextKind,
@@ -11000,6 +11101,17 @@ function createI18nContextOp(contextKind, xref, i18nBlock, message, sourceSpan)
11000
11101
  ...NEW_OP,
11001
11102
  };
11002
11103
  }
11104
+ function createI18nAttributesOp(xref, handle, target) {
11105
+ return {
11106
+ kind: OpKind.I18nAttributes,
11107
+ xref,
11108
+ handle,
11109
+ target,
11110
+ i18nAttributesConfig: null,
11111
+ ...NEW_OP,
11112
+ ...TRAIT_CONSUMES_SLOT,
11113
+ };
11114
+ }
11003
11115
  function literalOrArrayLiteral$1(value) {
11004
11116
  if (Array.isArray(value)) {
11005
11117
  return literalArr(value.map(literalOrArrayLiteral$1));
@@ -11007,12 +11119,15 @@ function literalOrArrayLiteral$1(value) {
11007
11119
  return literal(value, INFERRED_TYPE);
11008
11120
  }
11009
11121
 
11010
- function createHostPropertyOp(name, expression, isAnimationTrigger, sourceSpan) {
11122
+ function createHostPropertyOp(name, expression, isAnimationTrigger, i18nContext, securityContext, sourceSpan) {
11011
11123
  return {
11012
11124
  kind: OpKind.HostProperty,
11013
11125
  name,
11014
11126
  expression,
11015
11127
  isAnimationTrigger,
11128
+ i18nContext,
11129
+ securityContext,
11130
+ sanitizer: null,
11016
11131
  sourceSpan,
11017
11132
  ...TRAIT_CONSUMES_VARS,
11018
11133
  ...NEW_OP,
@@ -11249,7 +11364,7 @@ function applyI18nExpressions(job) {
11249
11364
  // Only add apply after expressions that are not followed by more expressions.
11250
11365
  if (op.kind === OpKind.I18nExpression && needsApplication(i18nContexts, op)) {
11251
11366
  // TODO: what should be the source span for the apply op?
11252
- OpList.insertAfter(createI18nApplyOp(op.target, op.handle, null), op);
11367
+ OpList.insertAfter(createI18nApplyOp(op.i18nOwner, op.handle, null), op);
11253
11368
  }
11254
11369
  }
11255
11370
  }
@@ -11262,42 +11377,80 @@ function needsApplication(i18nContexts, op) {
11262
11377
  if (op.next?.kind !== OpKind.I18nExpression) {
11263
11378
  return true;
11264
11379
  }
11265
- // If the next op is an expression targeting a different i18n block, we need to apply.
11266
11380
  const context = i18nContexts.get(op.context);
11267
11381
  const nextContext = i18nContexts.get(op.next.context);
11268
- if (context.i18nBlock !== nextContext.i18nBlock) {
11382
+ if (context === undefined) {
11383
+ throw new Error('AssertionError: expected an I18nContextOp to exist for the I18nExpressionOp\'s context');
11384
+ }
11385
+ if (nextContext === undefined) {
11386
+ throw new Error('AssertionError: expected an I18nContextOp to exist for the next I18nExpressionOp\'s context');
11387
+ }
11388
+ // If the next op is an expression targeting a different i18n block (or different element, in the
11389
+ // case of i18n attributes), we need to apply.
11390
+ // First, handle the case of i18n blocks.
11391
+ if (context.i18nBlock !== null) {
11392
+ // This is a block context. Compare the blocks.
11393
+ if (context.i18nBlock !== nextContext.i18nBlock) {
11394
+ return true;
11395
+ }
11396
+ return false;
11397
+ }
11398
+ // Second, handle the case of i18n attributes.
11399
+ if (op.i18nOwner !== op.next.i18nOwner) {
11269
11400
  return true;
11270
11401
  }
11271
11402
  return false;
11272
11403
  }
11273
11404
 
11274
11405
  /**
11275
- * Updates i18n expression ops to depend on the last slot in their owning i18n block.
11406
+ * Updates i18n expression ops to target the last slot in their owning i18n block, and moves them
11407
+ * after the last update instruction that depends on that slot.
11276
11408
  */
11277
11409
  function assignI18nSlotDependencies(job) {
11278
- const i18nLastSlotConsumers = new Map();
11279
- let lastSlotConsumer = null;
11280
- let currentI18nOp = null;
11281
11410
  for (const unit of job.units) {
11282
- // Record the last consumed slot before each i18n end instruction.
11283
- for (const op of unit.create) {
11284
- if (hasConsumesSlotTrait(op)) {
11285
- lastSlotConsumer = op.xref;
11411
+ // The first update op.
11412
+ let updateOp = unit.update.head;
11413
+ // I18n expressions currently being moved during the iteration.
11414
+ let i18nExpressionsInProgress = [];
11415
+ // Non-null while we are iterating through an i18nStart/i18nEnd pair
11416
+ let state = null;
11417
+ for (const createOp of unit.create) {
11418
+ if (createOp.kind === OpKind.I18nStart) {
11419
+ state = {
11420
+ blockXref: createOp.xref,
11421
+ lastSlotConsumer: createOp.xref,
11422
+ };
11286
11423
  }
11287
- switch (op.kind) {
11288
- case OpKind.I18nStart:
11289
- currentI18nOp = op;
11290
- break;
11291
- case OpKind.I18nEnd:
11292
- i18nLastSlotConsumers.set(currentI18nOp.xref, lastSlotConsumer);
11293
- currentI18nOp = null;
11294
- break;
11424
+ else if (createOp.kind === OpKind.I18nEnd) {
11425
+ for (const op of i18nExpressionsInProgress) {
11426
+ op.target = state.lastSlotConsumer;
11427
+ OpList.insertBefore(op, updateOp);
11428
+ }
11429
+ i18nExpressionsInProgress.length = 0;
11430
+ state = null;
11295
11431
  }
11296
- }
11297
- // Assign i18n expressions to target the last slot in its owning block.
11298
- for (const op of unit.update) {
11299
- if (op.kind === OpKind.I18nExpression) {
11300
- op.target = i18nLastSlotConsumers.get(op.target);
11432
+ if (hasConsumesSlotTrait(createOp)) {
11433
+ if (state !== null) {
11434
+ state.lastSlotConsumer = createOp.xref;
11435
+ }
11436
+ while (true) {
11437
+ if (updateOp.next === null) {
11438
+ break;
11439
+ }
11440
+ if (state !== null && updateOp.kind === OpKind.I18nExpression &&
11441
+ updateOp.usage === I18nExpressionFor.I18nText &&
11442
+ updateOp.i18nOwner === state.blockXref) {
11443
+ const opToRemove = updateOp;
11444
+ updateOp = updateOp.next;
11445
+ OpList.remove(opToRemove);
11446
+ i18nExpressionsInProgress.push(opToRemove);
11447
+ continue;
11448
+ }
11449
+ if (hasDependsOnSlotContextTrait(updateOp) && updateOp.target !== createOp.xref) {
11450
+ break;
11451
+ }
11452
+ updateOp = updateOp.next;
11453
+ }
11301
11454
  }
11302
11455
  }
11303
11456
  }
@@ -11331,22 +11484,42 @@ function extractAttributes(job) {
11331
11484
  break;
11332
11485
  case OpKind.Property:
11333
11486
  if (!op.isAnimationTrigger) {
11334
- OpList.insertBefore(createExtractedAttributeOp(op.target, op.isTemplate ? BindingKind.Template : BindingKind.Property, op.name, null), lookupElement$2(elements, op.target));
11487
+ let bindingKind;
11488
+ if (op.i18nMessage !== null && op.templateKind === null) {
11489
+ // If the binding has an i18n context, it is an i18n attribute, and should have that
11490
+ // kind in the consts array.
11491
+ bindingKind = BindingKind.I18n;
11492
+ }
11493
+ else if (op.isStructuralTemplateAttribute) {
11494
+ bindingKind = BindingKind.Template;
11495
+ }
11496
+ else {
11497
+ bindingKind = BindingKind.Property;
11498
+ }
11499
+ OpList.insertBefore(
11500
+ // Deliberaly null i18nMessage value
11501
+ createExtractedAttributeOp(op.target, bindingKind, op.name, /* expression */ null, /* i18nContext */ null,
11502
+ /* i18nMessage */ null, op.securityContext), lookupElement$2(elements, op.target));
11335
11503
  }
11336
11504
  break;
11337
11505
  case OpKind.StyleProp:
11338
11506
  case OpKind.ClassProp:
11507
+ // TODO: Can style or class bindings be i18n attributes?
11339
11508
  // The old compiler treated empty style bindings as regular bindings for the purpose of
11340
11509
  // directive matching. That behavior is incorrect, but we emulate it in compatibility
11341
11510
  // mode.
11342
11511
  if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
11343
11512
  op.expression instanceof EmptyExpr) {
11344
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null), lookupElement$2(elements, op.target));
11513
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, op.name, /* expression */ null,
11514
+ /* i18nContext */ null,
11515
+ /* i18nMessage */ null, SecurityContext.STYLE), lookupElement$2(elements, op.target));
11345
11516
  }
11346
11517
  break;
11347
11518
  case OpKind.Listener:
11348
11519
  if (!op.isAnimationListener) {
11349
- const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null);
11520
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, op.name, /* expression */ null,
11521
+ /* i18nContext */ null,
11522
+ /* i18nMessage */ null, SecurityContext.NONE);
11350
11523
  if (job.kind === CompilationJobKind.Host) {
11351
11524
  // This attribute will apply to the enclosing host binding compilation unit, so order
11352
11525
  // doesn't matter.
@@ -11395,7 +11568,7 @@ function extractAttributeOp(unit, op, elements) {
11395
11568
  }
11396
11569
  }
11397
11570
  if (extractable) {
11398
- const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isTemplate ? BindingKind.Template : BindingKind.Attribute, op.name, op.expression);
11571
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isStructuralTemplateAttribute ? BindingKind.Template : BindingKind.Attribute, op.name, op.expression, op.i18nContext, op.i18nMessage, op.securityContext);
11399
11572
  if (unit.job.kind === CompilationJobKind.Host) {
11400
11573
  // This attribute will apply to the enclosing host binding compilation unit, so order doesn't
11401
11574
  // matter.
@@ -11442,16 +11615,16 @@ function specializeBindings(job) {
11442
11615
  target.nonBindable = true;
11443
11616
  }
11444
11617
  else {
11445
- OpList.replace(op, createAttributeOp(op.target, op.name, op.expression, op.securityContext, op.isTextAttribute, op.isTemplate, op.sourceSpan));
11618
+ OpList.replace(op, createAttributeOp(op.target, op.name, op.expression, op.securityContext, op.isTextAttribute, op.isStructuralTemplateAttribute, op.templateKind, op.i18nMessage, op.sourceSpan));
11446
11619
  }
11447
11620
  break;
11448
11621
  case BindingKind.Property:
11449
11622
  case BindingKind.Animation:
11450
11623
  if (job.kind === CompilationJobKind.Host) {
11451
- OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.sourceSpan));
11624
+ OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.i18nContext, op.securityContext, op.sourceSpan));
11452
11625
  }
11453
11626
  else {
11454
- OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isTemplate, op.sourceSpan));
11627
+ 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));
11455
11628
  }
11456
11629
  break;
11457
11630
  case BindingKind.I18n:
@@ -11628,41 +11801,6 @@ function generateConditionalExpressions(job) {
11628
11801
  }
11629
11802
  }
11630
11803
 
11631
- var TagContentType;
11632
- (function (TagContentType) {
11633
- TagContentType[TagContentType["RAW_TEXT"] = 0] = "RAW_TEXT";
11634
- TagContentType[TagContentType["ESCAPABLE_RAW_TEXT"] = 1] = "ESCAPABLE_RAW_TEXT";
11635
- TagContentType[TagContentType["PARSABLE_DATA"] = 2] = "PARSABLE_DATA";
11636
- })(TagContentType || (TagContentType = {}));
11637
- function splitNsName(elementName) {
11638
- if (elementName[0] != ':') {
11639
- return [null, elementName];
11640
- }
11641
- const colonIndex = elementName.indexOf(':', 1);
11642
- if (colonIndex === -1) {
11643
- throw new Error(`Unsupported format "${elementName}" expecting ":namespace:name"`);
11644
- }
11645
- return [elementName.slice(1, colonIndex), elementName.slice(colonIndex + 1)];
11646
- }
11647
- // `<ng-container>` tags work the same regardless the namespace
11648
- function isNgContainer(tagName) {
11649
- return splitNsName(tagName)[1] === 'ng-container';
11650
- }
11651
- // `<ng-content>` tags work the same regardless the namespace
11652
- function isNgContent(tagName) {
11653
- return splitNsName(tagName)[1] === 'ng-content';
11654
- }
11655
- // `<ng-template>` tags work the same regardless the namespace
11656
- function isNgTemplate(tagName) {
11657
- return splitNsName(tagName)[1] === 'ng-template';
11658
- }
11659
- function getNsPrefix(fullName) {
11660
- return fullName === null ? null : splitNsName(fullName)[0];
11661
- }
11662
- function mergeNsAndName(prefix, localName) {
11663
- return prefix ? `:${prefix}:${localName}` : localName;
11664
- }
11665
-
11666
11804
  const BINARY_OPERATORS = new Map([
11667
11805
  ['&&', BinaryOperator.And],
11668
11806
  ['>', BinaryOperator.Bigger],
@@ -11723,7 +11861,7 @@ function collectElementConsts(job) {
11723
11861
  if (op.kind === OpKind.ExtractedAttribute) {
11724
11862
  const attributes = allElementAttributes.get(op.target) || new ElementAttributes();
11725
11863
  allElementAttributes.set(op.target, attributes);
11726
- attributes.add(op.bindingKind, op.name, op.expression);
11864
+ attributes.add(op.bindingKind, op.name, op.expression, op.trustedValueFn);
11727
11865
  OpList.remove(op);
11728
11866
  }
11729
11867
  }
@@ -11789,11 +11927,12 @@ class ElementAttributes {
11789
11927
  get i18n() {
11790
11928
  return this.byKind.get(BindingKind.I18n) ?? FLYWEIGHT_ARRAY;
11791
11929
  }
11792
- add(kind, name, value) {
11930
+ add(kind, name, value, trustedValueFn) {
11793
11931
  if (this.known.has(name)) {
11794
11932
  return;
11795
11933
  }
11796
11934
  this.known.add(name);
11935
+ // TODO: Can this be its own phase
11797
11936
  if (name === 'ngProjectAs') {
11798
11937
  if (value === null || !(value instanceof LiteralExpr) || (value.value == null) ||
11799
11938
  (typeof value.value?.toString() !== 'string')) {
@@ -11807,9 +11946,17 @@ class ElementAttributes {
11807
11946
  array.push(...getAttributeNameLiterals$1(name));
11808
11947
  if (kind === BindingKind.Attribute || kind === BindingKind.StyleProperty) {
11809
11948
  if (value === null) {
11810
- throw Error('Attribute & style element attributes must have a value');
11949
+ throw Error('Attribute, i18n attribute, & style element attributes must have a value');
11950
+ }
11951
+ if (trustedValueFn !== null) {
11952
+ if (!isStringLiteral(value)) {
11953
+ throw Error('AssertionError: extracted attribute value should be string literal');
11954
+ }
11955
+ array.push(taggedTemplate(trustedValueFn, new TemplateLiteral([new TemplateLiteralElement(value.value)], []), undefined, value.sourceSpan));
11956
+ }
11957
+ else {
11958
+ array.push(value);
11811
11959
  }
11812
- array.push(value);
11813
11960
  }
11814
11961
  }
11815
11962
  arrayFor(kind) {
@@ -11861,6 +12008,50 @@ function serializeAttributes({ attributes, bindings, classes, i18n, projectAs, s
11861
12008
  return literalArr(attrArray);
11862
12009
  }
11863
12010
 
12011
+ /**
12012
+ * Some binding instructions in the update block may actually correspond to i18n bindings. In that
12013
+ * case, they should be replaced with i18nExp instructions for the dynamic portions.
12014
+ */
12015
+ function convertI18nBindings(job) {
12016
+ const i18nAttributesByElem = new Map();
12017
+ for (const unit of job.units) {
12018
+ for (const op of unit.create) {
12019
+ if (op.kind === OpKind.I18nAttributes) {
12020
+ i18nAttributesByElem.set(op.target, op);
12021
+ }
12022
+ }
12023
+ for (const op of unit.update) {
12024
+ switch (op.kind) {
12025
+ case OpKind.Property:
12026
+ case OpKind.Attribute:
12027
+ if (op.i18nContext === null) {
12028
+ continue;
12029
+ }
12030
+ if (!(op.expression instanceof Interpolation)) {
12031
+ continue;
12032
+ }
12033
+ const i18nAttributesForElem = i18nAttributesByElem.get(op.target);
12034
+ if (i18nAttributesForElem === undefined) {
12035
+ throw new Error('AssertionError: An i18n attribute binding instruction requires the owning element to have an I18nAttributes create instruction');
12036
+ }
12037
+ if (i18nAttributesForElem.target !== op.target) {
12038
+ throw new Error('AssertionError: Expected i18nAttributes target element to match binding target element');
12039
+ }
12040
+ const ops = [];
12041
+ for (let i = 0; i < op.expression.expressions.length; i++) {
12042
+ const expr = op.expression.expressions[i];
12043
+ if (op.expression.i18nPlaceholders.length !== op.expression.expressions.length) {
12044
+ 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`);
12045
+ }
12046
+ ops.push(createI18nExpressionOp(op.i18nContext, i18nAttributesForElem.target, i18nAttributesForElem.xref, i18nAttributesForElem.handle, expr, op.expression.i18nPlaceholders[i], I18nParamResolutionTime.Creation, I18nExpressionFor.I18nAttribute, op.name, op.sourceSpan));
12047
+ }
12048
+ OpList.replaceWithMany(op, ops);
12049
+ break;
12050
+ }
12051
+ }
12052
+ }
12053
+ }
12054
+
11864
12055
  /**
11865
12056
  * Create extracted deps functions for defer ops.
11866
12057
  */
@@ -11909,6 +12100,9 @@ function createI18nContexts(job) {
11909
12100
  const rootContexts = new Map();
11910
12101
  let currentI18nOp = null;
11911
12102
  let xref;
12103
+ // We use the message instead of the message ID, because placeholder values might differ even
12104
+ // when IDs are the same.
12105
+ const messageToContext = new Map();
11912
12106
  for (const unit of job.units) {
11913
12107
  for (const op of unit.create) {
11914
12108
  switch (op.kind) {
@@ -11946,6 +12140,25 @@ function createI18nContexts(job) {
11946
12140
  break;
11947
12141
  }
11948
12142
  }
12143
+ for (const op of unit.ops()) {
12144
+ switch (op.kind) {
12145
+ case OpKind.Binding:
12146
+ case OpKind.Property:
12147
+ case OpKind.Attribute:
12148
+ case OpKind.ExtractedAttribute:
12149
+ if (!op.i18nMessage) {
12150
+ continue;
12151
+ }
12152
+ if (!messageToContext.has(op.i18nMessage)) {
12153
+ // create the context
12154
+ const i18nContext = job.allocateXrefId();
12155
+ unit.create.push(createI18nContextOp(I18nContextKind.Attr, i18nContext, null, op.i18nMessage, null));
12156
+ messageToContext.set(op.i18nMessage, i18nContext);
12157
+ }
12158
+ op.i18nContext = messageToContext.get(op.i18nMessage);
12159
+ break;
12160
+ }
12161
+ }
11949
12162
  }
11950
12163
  // Assign contexts to child i18n blocks, now that all root i18n blocks have their context
11951
12164
  // assigned.
@@ -12304,7 +12517,7 @@ function ternaryTransform(e) {
12304
12517
  /**
12305
12518
  * The escape sequence used indicate message param values.
12306
12519
  */
12307
- const ESCAPE = '\uFFFD';
12520
+ const ESCAPE$1 = '\uFFFD';
12308
12521
  /**
12309
12522
  * Marker used to indicate an element tag.
12310
12523
  */
@@ -12353,6 +12566,18 @@ function extractI18nMessages(job) {
12353
12566
  }
12354
12567
  }
12355
12568
  }
12569
+ // TODO: Miles and I think that contexts have a 1-to-1 correspondence with extracted messages, so
12570
+ // this phase can probably be simplified.
12571
+ // Extract messages from contexts of i18n attributes.
12572
+ for (const unit of job.units) {
12573
+ for (const op of unit.create) {
12574
+ if (op.kind !== OpKind.I18nContext || op.contextKind !== I18nContextKind.Attr) {
12575
+ continue;
12576
+ }
12577
+ const i18nMessageOp = createI18nMessage(job, op);
12578
+ unit.create.push(i18nMessageOp);
12579
+ }
12580
+ }
12356
12581
  // Extract messages from root i18n blocks.
12357
12582
  const i18nBlockMessages = new Map();
12358
12583
  for (const unit of job.units) {
@@ -12377,6 +12602,9 @@ function extractI18nMessages(job) {
12377
12602
  }
12378
12603
  const i18nContext = i18nContexts.get(op.context);
12379
12604
  if (i18nContext.contextKind === I18nContextKind.Icu) {
12605
+ if (i18nContext.i18nBlock === null) {
12606
+ throw Error('ICU context should have its i18n block set.');
12607
+ }
12380
12608
  const subMessage = createI18nMessage(job, i18nContext, op.messagePlaceholder);
12381
12609
  unit.create.push(subMessage);
12382
12610
  const rootI18nId = i18nBlocks.get(i18nContext.i18nBlock).root;
@@ -12396,119 +12624,86 @@ function extractI18nMessages(job) {
12396
12624
  * Create an i18n message op from an i18n context op.
12397
12625
  */
12398
12626
  function createI18nMessage(job, context, messagePlaceholder) {
12399
- let [formattedParams, needsPostprocessing] = formatParams(context.params);
12400
- const [formattedPostprocessingParams] = formatParams(context.postprocessingParams);
12401
- needsPostprocessing ||= formattedPostprocessingParams.size > 0;
12402
- return createI18nMessageOp(job.allocateXrefId(), context.i18nBlock, context.message, messagePlaceholder ?? null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
12627
+ let formattedParams = formatParams(context.params);
12628
+ const formattedPostprocessingParams = formatParams(context.postprocessingParams);
12629
+ let needsPostprocessing = formattedPostprocessingParams.size > 0;
12630
+ for (const values of context.params.values()) {
12631
+ if (values.length > 1) {
12632
+ needsPostprocessing = true;
12633
+ }
12634
+ }
12635
+ return createI18nMessageOp(job.allocateXrefId(), context.xref, context.i18nBlock, context.message, messagePlaceholder ?? null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
12403
12636
  }
12404
12637
  /**
12405
12638
  * Formats a map of `I18nParamValue[]` values into a map of `Expression` values.
12406
- * @return A tuple of the formatted params and a boolean indicating whether postprocessing is needed
12407
- * for any of the params
12408
12639
  */
12409
12640
  function formatParams(params) {
12410
12641
  const formattedParams = new Map();
12411
- let needsPostprocessing = false;
12412
12642
  for (const [placeholder, placeholderValues] of params) {
12413
- const [serializedValues, paramNeedsPostprocessing] = formatParamValues(placeholderValues);
12414
- needsPostprocessing ||= paramNeedsPostprocessing;
12643
+ const serializedValues = formatParamValues(placeholderValues);
12415
12644
  if (serializedValues !== null) {
12416
12645
  formattedParams.set(placeholder, literal(serializedValues));
12417
12646
  }
12418
12647
  }
12419
- return [formattedParams, needsPostprocessing];
12648
+ return formattedParams;
12420
12649
  }
12421
12650
  /**
12422
12651
  * Formats an `I18nParamValue[]` into a string (or null for empty array).
12423
- * @return A tuple of the formatted value and a boolean indicating whether postprocessing is needed
12424
- * for the value
12425
12652
  */
12426
12653
  function formatParamValues(values) {
12427
12654
  if (values.length === 0) {
12428
- return [null, false];
12655
+ return null;
12429
12656
  }
12430
- collapseElementTemplatePairs(values);
12431
12657
  const serializedValues = values.map(value => formatValue(value));
12432
12658
  return serializedValues.length === 1 ?
12433
- [serializedValues[0], false] :
12434
- [`${LIST_START_MARKER}${serializedValues.join(LIST_DELIMITER)}${LIST_END_MARKER}`, true];
12435
- }
12436
- /**
12437
- * Collapses element/template pairs that refer to the same subTemplateIndex, i.e. elements and
12438
- * templates that refer to the same element instance.
12439
- *
12440
- * This accounts for the case of a structural directive inside an i18n block, e.g.:
12441
- * ```
12442
- * <div i18n>
12443
- * <div *ngIf="condition">
12444
- * </div>
12445
- * ```
12446
- *
12447
- * In this case, both the element start and template start placeholders are the same,
12448
- * and we collapse them down into a single compound placeholder value. Rather than produce
12449
- * `[\uFFFD#1:1\uFFFD|\uFFFD*2:1\uFFFD]`, we want to produce `\uFFFD#1:1\uFFFD\uFFFD*2:1\uFFFD`,
12450
- * likewise for the closing of the element/template.
12451
- */
12452
- function collapseElementTemplatePairs(values) {
12453
- // Record the indicies of element and template values in the values array by subTemplateIndex.
12454
- const valueIndiciesBySubTemplateIndex = new Map();
12455
- for (let i = 0; i < values.length; i++) {
12456
- const value = values[i];
12457
- if (value.subTemplateIndex !== null &&
12458
- (value.flags & (I18nParamValueFlags.ElementTag | I18nParamValueFlags.TemplateTag))) {
12459
- const valueIndicies = valueIndiciesBySubTemplateIndex.get(value.subTemplateIndex) ?? [];
12460
- valueIndicies.push(i);
12461
- valueIndiciesBySubTemplateIndex.set(value.subTemplateIndex, valueIndicies);
12462
- }
12463
- }
12464
- // For each subTemplateIndex, check if any values can be collapsed.
12465
- for (const [subTemplateIndex, valueIndicies] of valueIndiciesBySubTemplateIndex) {
12466
- if (valueIndicies.length > 1) {
12467
- const elementIndex = valueIndicies.find(index => values[index].flags & I18nParamValueFlags.ElementTag);
12468
- const templateIndex = valueIndicies.find(index => values[index].flags & I18nParamValueFlags.TemplateTag);
12469
- // If the values list contains both an element and template value, we can collapse.
12470
- if (elementIndex !== undefined && templateIndex !== undefined) {
12471
- const elementValue = values[elementIndex];
12472
- const templateValue = values[templateIndex];
12473
- // To match the TemplateDefinitionBuilder output, flip the order depending on whether the
12474
- // values represent a closing or opening tag (or both).
12475
- // TODO(mmalerba): Figure out if this makes a difference in terms of either functionality,
12476
- // or the resulting message ID. If not, we can remove the special-casing in the future.
12477
- let compundValue;
12478
- if ((elementValue.flags & I18nParamValueFlags.OpenTag) &&
12479
- (elementValue.flags & I18nParamValueFlags.CloseTag)) {
12480
- // TODO(mmalerba): Is this a TDB bug? I don't understand why it would put the template
12481
- // value twice.
12482
- compundValue = `${formatValue(templateValue)}${formatValue(elementValue)}${formatValue(templateValue)}`;
12483
- }
12484
- else if (elementValue.flags & I18nParamValueFlags.OpenTag) {
12485
- compundValue = `${formatValue(templateValue)}${formatValue(elementValue)}`;
12486
- }
12487
- else {
12488
- compundValue = `${formatValue(elementValue)}${formatValue(templateValue)}`;
12489
- }
12490
- // Replace the element value with the combined value.
12491
- values.splice(elementIndex, 1, { value: compundValue, subTemplateIndex, flags: I18nParamValueFlags.None });
12492
- // Replace the template value with null to preserve the indicies we calculated earlier.
12493
- values.splice(templateIndex, 1, null);
12494
- }
12495
- }
12496
- }
12497
- // Strip out any nulled out values we introduced above.
12498
- for (let i = values.length - 1; i >= 0; i--) {
12499
- if (values[i] === null) {
12500
- values.splice(i, 1);
12501
- }
12502
- }
12659
+ serializedValues[0] :
12660
+ `${LIST_START_MARKER}${serializedValues.join(LIST_DELIMITER)}${LIST_END_MARKER}`;
12503
12661
  }
12504
12662
  /**
12505
12663
  * Formats a single `I18nParamValue` into a string
12506
12664
  */
12507
12665
  function formatValue(value) {
12666
+ // Element tags with a structural directive use a special form that concatenates the element and
12667
+ // template values.
12668
+ if ((value.flags & I18nParamValueFlags.ElementTag) &&
12669
+ (value.flags & I18nParamValueFlags.TemplateTag)) {
12670
+ if (typeof value.value !== 'object') {
12671
+ throw Error('AssertionError: Expected i18n param value to have an element and template slot');
12672
+ }
12673
+ const elementValue = formatValue({
12674
+ ...value,
12675
+ value: value.value.element,
12676
+ flags: value.flags & ~I18nParamValueFlags.TemplateTag
12677
+ });
12678
+ const templateValue = formatValue({
12679
+ ...value,
12680
+ value: value.value.template,
12681
+ flags: value.flags & ~I18nParamValueFlags.ElementTag
12682
+ });
12683
+ // TODO(mmalerba): This is likely a bug in TemplateDefinitionBuilder, we should not need to
12684
+ // record the template value twice. For now I'm re-implementing the behavior here to keep the
12685
+ // output consistent with TemplateDefinitionBuilder.
12686
+ if ((value.flags & I18nParamValueFlags.OpenTag) &&
12687
+ (value.flags & I18nParamValueFlags.CloseTag)) {
12688
+ return `${templateValue}${elementValue}${templateValue}`;
12689
+ }
12690
+ // To match the TemplateDefinitionBuilder output, flip the order depending on whether the
12691
+ // values represent a closing or opening tag (or both).
12692
+ // TODO(mmalerba): Figure out if this makes a difference in terms of either functionality,
12693
+ // or the resulting message ID. If not, we can remove the special-casing in the future.
12694
+ return value.flags & I18nParamValueFlags.CloseTag ? `${elementValue}${templateValue}` :
12695
+ `${templateValue}${elementValue}`;
12696
+ }
12697
+ // Self-closing tags use a special form that concatenates the start and close tag values.
12698
+ if ((value.flags & I18nParamValueFlags.OpenTag) &&
12699
+ (value.flags & I18nParamValueFlags.CloseTag)) {
12700
+ return `${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.CloseTag })}${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.OpenTag })}`;
12701
+ }
12508
12702
  // If there are no special flags, just return the raw value.
12509
12703
  if (value.flags === I18nParamValueFlags.None) {
12510
12704
  return `${value.value}`;
12511
12705
  }
12706
+ // Encode the remaining flags as part of the value.
12512
12707
  let tagMarker = '';
12513
12708
  let closeMarker = '';
12514
12709
  if (value.flags & I18nParamValueFlags.ElementTag) {
@@ -12521,12 +12716,7 @@ function formatValue(value) {
12521
12716
  closeMarker = value.flags & I18nParamValueFlags.CloseTag ? TAG_CLOSE_MARKER : '';
12522
12717
  }
12523
12718
  const context = value.subTemplateIndex === null ? '' : `${CONTEXT_MARKER}${value.subTemplateIndex}`;
12524
- // Self-closing tags use a special form that concatenates the start and close tag values.
12525
- if ((value.flags & I18nParamValueFlags.OpenTag) &&
12526
- (value.flags & I18nParamValueFlags.CloseTag)) {
12527
- return `${ESCAPE}${tagMarker}${value.value}${context}${ESCAPE}${ESCAPE}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE}`;
12528
- }
12529
- return `${ESCAPE}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE}`;
12719
+ return `${ESCAPE$1}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE$1}`;
12530
12720
  }
12531
12721
 
12532
12722
  /**
@@ -12560,7 +12750,7 @@ function generateAdvance(job) {
12560
12750
  else if (!slotMap.has(op.target)) {
12561
12751
  // We expect ops that _do_ depend on the slot counter to point at declarations that exist in
12562
12752
  // the `slotMap`.
12563
- throw new Error(`AssertionError: reference to unknown slot for var ${op.target}`);
12753
+ throw new Error(`AssertionError: reference to unknown slot for target ${op.target}`);
12564
12754
  }
12565
12755
  const slot = slotMap.get(op.target);
12566
12756
  // Does the slot counter need to be adjusted?
@@ -12643,9 +12833,15 @@ function recursivelyProcessView(view, parentScope) {
12643
12833
  for (const op of view.create) {
12644
12834
  switch (op.kind) {
12645
12835
  case OpKind.Template:
12836
+ // Descend into child embedded views.
12837
+ recursivelyProcessView(view.job.views.get(op.xref), scope);
12838
+ break;
12646
12839
  case OpKind.RepeaterCreate:
12647
12840
  // Descend into child embedded views.
12648
12841
  recursivelyProcessView(view.job.views.get(op.xref), scope);
12842
+ if (op.emptyView) {
12843
+ recursivelyProcessView(view.job.views.get(op.emptyView), scope);
12844
+ }
12649
12845
  break;
12650
12846
  case OpKind.Listener:
12651
12847
  // Prepend variables to listener handler functions.
@@ -19770,35 +19966,136 @@ const NG_I18N_CLOSURE_MODE$1 = 'ngI18nClosureMode';
19770
19966
  * considers variables like `I18N_0` as constants and throws an error when their value changes.
19771
19967
  */
19772
19968
  const TRANSLATION_VAR_PREFIX = 'i18n_';
19969
+ /** Prefix of ICU expressions for post processing */
19970
+ const I18N_ICU_MAPPING_PREFIX = 'I18N_EXP_';
19971
+ /**
19972
+ * The escape sequence used for message param values.
19973
+ */
19974
+ const ESCAPE = '\uFFFD';
19773
19975
  /**
19774
19976
  * Lifts i18n properties into the consts array.
19775
19977
  * TODO: Can we use `ConstCollectedExpr`?
19978
+ * TODO: The way the various attributes are linked together is very complex. Perhaps we could
19979
+ * simplify the process, maybe by combining the context and message ops?
19776
19980
  */
19777
19981
  function collectI18nConsts(job) {
19778
19982
  const fileBasedI18nSuffix = job.relativeContextFilePath.replace(/[^A-Za-z0-9]/g, '_').toUpperCase() + '_';
19779
- const messageConstIndices = new Map();
19780
- // Remove all of the i18n message ops into a map.
19983
+ // Step One: Build up various lookup maps we need to collect all the consts.
19984
+ // Context Xref -> Extracted Attribute Ops
19985
+ const extractedAttributesByI18nContext = new Map();
19986
+ // Element/ElementStart Xref -> I18n Attributes config op
19987
+ const i18nAttributesByElement = new Map();
19988
+ // Element/ElementStart Xref -> All I18n Expression ops for attrs on that target
19989
+ const i18nExpressionsByElement = new Map();
19990
+ // I18n Message Xref -> I18n Message Op (TODO: use a central op map)
19781
19991
  const messages = new Map();
19992
+ for (const unit of job.units) {
19993
+ for (const op of unit.ops()) {
19994
+ if (op.kind === OpKind.ExtractedAttribute && op.i18nContext !== null) {
19995
+ const attributes = extractedAttributesByI18nContext.get(op.i18nContext) ?? [];
19996
+ attributes.push(op);
19997
+ extractedAttributesByI18nContext.set(op.i18nContext, attributes);
19998
+ }
19999
+ else if (op.kind === OpKind.I18nAttributes) {
20000
+ i18nAttributesByElement.set(op.target, op);
20001
+ }
20002
+ else if (op.kind === OpKind.I18nExpression && op.usage === I18nExpressionFor.I18nAttribute) {
20003
+ const expressions = i18nExpressionsByElement.get(op.target) ?? [];
20004
+ expressions.push(op);
20005
+ i18nExpressionsByElement.set(op.target, expressions);
20006
+ }
20007
+ else if (op.kind === OpKind.I18nMessage) {
20008
+ messages.set(op.xref, op);
20009
+ }
20010
+ }
20011
+ }
20012
+ // Step Two: Serialize the extracted i18n messages for root i18n blocks and i18n attributes into
20013
+ // the const array.
20014
+ //
20015
+ // Also, each i18n message will have a variable expression that can refer to its
20016
+ // value. Store these expressions in the appropriate place:
20017
+ // 1. For normal i18n content, it also goes in the const array. We save the const index to use
20018
+ // later.
20019
+ // 2. For extracted attributes, it becomes the value of the extracted attribute instruction.
20020
+ // 3. For i18n bindings, it will go in a separate const array instruction below; for now, we just
20021
+ // save it.
20022
+ const i18nValuesByContext = new Map();
20023
+ const messageConstIndices = new Map();
19782
20024
  for (const unit of job.units) {
19783
20025
  for (const op of unit.create) {
19784
20026
  if (op.kind === OpKind.I18nMessage) {
19785
- messages.set(op.xref, op);
20027
+ if (op.messagePlaceholder === null) {
20028
+ const { mainVar, statements } = collectMessage(job, fileBasedI18nSuffix, messages, op);
20029
+ if (op.i18nBlock !== null) {
20030
+ // This is a regular i18n message with a corresponding i18n block. Collect it into the
20031
+ // const array.
20032
+ const i18nConst = job.addConst(mainVar, statements);
20033
+ messageConstIndices.set(op.i18nBlock, i18nConst);
20034
+ }
20035
+ else {
20036
+ // This is an i18n attribute. Extract the initializers into the const pool.
20037
+ job.constsInitializers.push(...statements);
20038
+ // Save the i18n variable value for later.
20039
+ i18nValuesByContext.set(op.i18nContext, mainVar);
20040
+ // This i18n message may correspond to an individual extracted attribute. If so, The
20041
+ // value of that attribute is updated to read the extracted i18n variable.
20042
+ const attributesForMessage = extractedAttributesByI18nContext.get(op.i18nContext);
20043
+ if (attributesForMessage !== undefined) {
20044
+ for (const attr of attributesForMessage) {
20045
+ attr.expression = mainVar.clone();
20046
+ }
20047
+ }
20048
+ }
20049
+ }
19786
20050
  OpList.remove(op);
19787
20051
  }
19788
20052
  }
19789
20053
  }
19790
- // Serialize the extracted messages for root i18n blocks into the const array.
19791
- for (const op of messages.values()) {
19792
- if (op.kind === OpKind.I18nMessage && op.messagePlaceholder === null) {
19793
- const { mainVar, statements } = collectMessage(job, fileBasedI18nSuffix, messages, op);
19794
- messageConstIndices.set(op.i18nBlock, job.addConst(mainVar, statements));
20054
+ // Step Three: Serialize I18nAttributes configurations into the const array. Each I18nAttributes
20055
+ // instruction has a config array, which contains k-v pairs describing each binding name, and the
20056
+ // i18n variable that provides the value.
20057
+ for (const unit of job.units) {
20058
+ for (const elem of unit.create) {
20059
+ if (isElementOrContainerOp(elem)) {
20060
+ const i18nAttributes = i18nAttributesByElement.get(elem.xref);
20061
+ if (i18nAttributes === undefined) {
20062
+ // This element is not associated with an i18n attributes configuration instruction.
20063
+ continue;
20064
+ }
20065
+ let i18nExpressions = i18nExpressionsByElement.get(elem.xref);
20066
+ if (i18nExpressions === undefined) {
20067
+ // Unused i18nAttributes should have already been removed.
20068
+ // TODO: Should the removal of those dead instructions be merged with this phase?
20069
+ throw new Error('AssertionError: Could not find any i18n expressions associated with an I18nAttributes instruction');
20070
+ }
20071
+ // Find expressions for all the unique property names, removing duplicates.
20072
+ const seenPropertyNames = new Set();
20073
+ i18nExpressions = i18nExpressions.filter(i18nExpr => {
20074
+ const seen = (seenPropertyNames.has(i18nExpr.name));
20075
+ seenPropertyNames.add(i18nExpr.name);
20076
+ return !seen;
20077
+ });
20078
+ const i18nAttributeConfig = i18nExpressions.flatMap(i18nExpr => {
20079
+ const i18nExprValue = i18nValuesByContext.get(i18nExpr.context);
20080
+ if (i18nExprValue === undefined) {
20081
+ throw new Error('AssertionError: Could not find i18n expression\'s value');
20082
+ }
20083
+ return [literal(i18nExpr.name), i18nExprValue];
20084
+ });
20085
+ i18nAttributes.i18nAttributesConfig =
20086
+ job.addConst(new LiteralArrayExpr(i18nAttributeConfig));
20087
+ }
19795
20088
  }
19796
20089
  }
19797
- // Assign const index to i18n ops that messages were extracted from.
20090
+ // Step Four: Propagate the extracted const index into i18n ops that messages were extracted from.
19798
20091
  for (const unit of job.units) {
19799
20092
  for (const op of unit.create) {
19800
20093
  if (op.kind === OpKind.I18nStart) {
19801
- op.messageIndex = messageConstIndices.get(op.root);
20094
+ const msgIndex = messageConstIndices.get(op.root);
20095
+ if (msgIndex === undefined) {
20096
+ throw new Error('AssertionError: Could not find corresponding i18n block index for an i18n message op; was an i18n message incorrectly assumed to correspond to an attribute?');
20097
+ }
20098
+ op.messageIndex = msgIndex;
19802
20099
  }
19803
20100
  }
19804
20101
  }
@@ -19808,18 +20105,23 @@ function collectI18nConsts(job) {
19808
20105
  * This will recursively collect any sub-messages referenced from the parent message as well.
19809
20106
  */
19810
20107
  function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19811
- // Recursively collect any sub-messages, and fill in their placeholders in this message.
20108
+ // Recursively collect any sub-messages, record each sub-message's main variable under its
20109
+ // placeholder so that we can add them to the params for the parent message. It is possible
20110
+ // that multiple sub-messages will share the same placeholder, so we need to track an array of
20111
+ // variables for each placeholder.
19812
20112
  const statements = [];
20113
+ const subMessagePlaceholders = new Map();
19813
20114
  for (const subMessageId of messageOp.subMessages) {
19814
20115
  const subMessage = messages.get(subMessageId);
19815
20116
  const { mainVar: subMessageVar, statements: subMessageStatements } = collectMessage(job, fileBasedI18nSuffix, messages, subMessage);
19816
20117
  statements.push(...subMessageStatements);
19817
- messageOp.params.set(subMessage.messagePlaceholder, subMessageVar);
20118
+ const subMessages = subMessagePlaceholders.get(subMessage.messagePlaceholder) ?? [];
20119
+ subMessages.push(subMessageVar);
20120
+ subMessagePlaceholders.set(subMessage.messagePlaceholder, subMessages);
19818
20121
  }
20122
+ addSubMessageParams(messageOp, subMessagePlaceholders);
19819
20123
  // Sort the params for consistency with TemaplateDefinitionBuilder output.
19820
20124
  messageOp.params = new Map([...messageOp.params.entries()].sort());
19821
- // Check that the message has all of its parameters filled out.
19822
- assertAllParamsResolved(messageOp);
19823
20125
  const mainVar = variable(job.pool.uniqueName(TRANSLATION_VAR_PREFIX));
19824
20126
  // Closure Compiler requires const names to start with `MSG_` but disallows any other
19825
20127
  // const to start with `MSG_`. We define a variable starting with `MSG_` just for the
@@ -19830,10 +20132,11 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19830
20132
  // set in post-processing.
19831
20133
  if (messageOp.needsPostprocessing) {
19832
20134
  // Sort the post-processing params for consistency with TemaplateDefinitionBuilder output.
19833
- messageOp.postprocessingParams = new Map([...messageOp.postprocessingParams.entries()].sort());
20135
+ const postprocessingParams = Object.fromEntries([...messageOp.postprocessingParams.entries()].sort());
20136
+ const formattedPostprocessingParams = formatI18nPlaceholderNamesInMap(postprocessingParams, /* useCamelCase */ false);
19834
20137
  const extraTransformFnParams = [];
19835
20138
  if (messageOp.postprocessingParams.size > 0) {
19836
- extraTransformFnParams.push(literalMap([...messageOp.postprocessingParams].map(([key, value]) => ({ key, value, quoted: true }))));
20139
+ extraTransformFnParams.push(mapLiteral(formattedPostprocessingParams, /* quoted */ true));
19837
20140
  }
19838
20141
  transformFn = (expr) => importExpr(Identifiers.i18nPostprocess).callFn([expr, ...extraTransformFnParams]);
19839
20142
  }
@@ -19841,6 +20144,26 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19841
20144
  statements.push(...getTranslationDeclStmts$1(messageOp.message, mainVar, closureVar, messageOp.params, transformFn));
19842
20145
  return { mainVar, statements };
19843
20146
  }
20147
+ /**
20148
+ * Adds the given subMessage placeholders to the given message op.
20149
+ *
20150
+ * If a placeholder only corresponds to a single sub-message variable, we just set that variable
20151
+ * as the param value. However, if the placeholder corresponds to multiple sub-message
20152
+ * variables, we need to add a special placeholder value that is handled by the post-processing
20153
+ * step. We then add the array of variables as a post-processing param.
20154
+ */
20155
+ function addSubMessageParams(messageOp, subMessagePlaceholders) {
20156
+ for (const [placeholder, subMessages] of subMessagePlaceholders) {
20157
+ if (subMessages.length === 1) {
20158
+ messageOp.params.set(placeholder, subMessages[0]);
20159
+ }
20160
+ else {
20161
+ messageOp.params.set(placeholder, literal(`${ESCAPE}${I18N_ICU_MAPPING_PREFIX}${placeholder}${ESCAPE}`));
20162
+ messageOp.postprocessingParams.set(placeholder, literalArr(subMessages));
20163
+ messageOp.needsPostprocessing = true;
20164
+ }
20165
+ }
20166
+ }
19844
20167
  /**
19845
20168
  * Generate statements that define a given translation message.
19846
20169
  *
@@ -19863,7 +20186,8 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19863
20186
  * @param closureVar The variable for Closure `goog.getMsg` calls, e.g. `MSG_EXTERNAL_XXX`.
19864
20187
  * @param params Object mapping placeholder names to their values (e.g.
19865
20188
  * `{ "interpolation": "\uFFFD0\uFFFD" }`).
19866
- * @param transformFn Optional transformation function that will be applied to the translation (e.g.
20189
+ * @param transformFn Optional transformation function that will be applied to the translation
20190
+ * (e.g.
19867
20191
  * post-processing).
19868
20192
  * @returns An array of statements that defined a given translation.
19869
20193
  */
@@ -19908,28 +20232,13 @@ function i18nGenerateClosureVar(pool, messageId, fileBasedI18nSuffix, useExterna
19908
20232
  }
19909
20233
  return variable(name);
19910
20234
  }
19911
- /**
19912
- * Asserts that all of the message's placeholders have values.
19913
- */
19914
- function assertAllParamsResolved(op) {
19915
- for (let placeholder in op.message.placeholders) {
19916
- placeholder = placeholder.trimEnd();
19917
- if (!op.params.has(placeholder) && !op.postprocessingParams.has(placeholder)) {
19918
- throw Error(`Failed to resolve i18n placeholder: ${placeholder}`);
19919
- }
19920
- }
19921
- for (let placeholder in op.message.placeholderToMessage) {
19922
- placeholder = placeholder.trimEnd();
19923
- if (!op.params.has(placeholder) && !op.postprocessingParams.has(placeholder)) {
19924
- throw Error(`Failed to resolve i18n message placeholder: ${placeholder}`);
19925
- }
19926
- }
19927
- }
19928
20235
 
19929
20236
  /**
19930
20237
  * Removes text nodes within i18n blocks since they are already hardcoded into the i18n message.
20238
+ * Also, replaces interpolations on these text nodes with i18n expressions of the non-text portions,
20239
+ * which will be applied later.
19931
20240
  */
19932
- function extractI18nText(job) {
20241
+ function convertI18nText(job) {
19933
20242
  for (const unit of job.units) {
19934
20243
  // Remove all text nodes within i18n blocks, their content is already captured in the i18n
19935
20244
  // message.
@@ -19984,7 +20293,7 @@ function extractI18nText(job) {
19984
20293
  const expr = op.interpolation.expressions[i];
19985
20294
  // For now, this i18nExpression depends on the slot context of the enclosing i18n block.
19986
20295
  // Later, we will modify this, and advance to a different point.
19987
- ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.handle, expr, op.i18nPlaceholders[i], resolutionTime, expr.sourceSpan ?? op.sourceSpan));
20296
+ ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.xref, i18nOp.handle, expr, op.interpolation.i18nPlaceholders[i], resolutionTime, I18nExpressionFor.I18nText, '', expr.sourceSpan ?? op.sourceSpan));
19988
20297
  }
19989
20298
  OpList.replaceWithMany(op, ops);
19990
20299
  break;
@@ -20522,14 +20831,14 @@ function parseExtractedStyles(job) {
20522
20831
  if (op.name === 'style') {
20523
20832
  const parsedStyles = parse(op.expression.value);
20524
20833
  for (let i = 0; i < parsedStyles.length - 1; i += 2) {
20525
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, parsedStyles[i], literal(parsedStyles[i + 1])), op);
20834
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, parsedStyles[i], literal(parsedStyles[i + 1]), null, null, SecurityContext.STYLE), op);
20526
20835
  }
20527
20836
  OpList.remove(op);
20528
20837
  }
20529
20838
  else if (op.name === 'class') {
20530
20839
  const parsedClasses = op.expression.value.trim().split(/\s+/g);
20531
20840
  for (const parsedClass of parsedClasses) {
20532
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, parsedClass, null), op);
20841
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, parsedClass, null, null, null, SecurityContext.NONE), op);
20533
20842
  }
20534
20843
  OpList.remove(op);
20535
20844
  }
@@ -20545,18 +20854,30 @@ function parseExtractedStyles(job) {
20545
20854
  function removeContentSelectors(job) {
20546
20855
  for (const unit of job.units) {
20547
20856
  const elements = createOpXrefMap(unit);
20548
- for (const op of unit.update) {
20857
+ for (const op of unit.ops()) {
20549
20858
  switch (op.kind) {
20550
20859
  case OpKind.Binding:
20551
20860
  const target = lookupInXrefMap(elements, op.target);
20552
- if (op.name.toLowerCase() === 'select' && target.kind === OpKind.Projection) {
20861
+ if (isSelectAttribute(op.name) && target.kind === OpKind.Projection) {
20553
20862
  OpList.remove(op);
20554
20863
  }
20555
20864
  break;
20865
+ case OpKind.Projection:
20866
+ // op.attributes is an array of [attr1-name, attr1-value, attr2-name, attr2-value, ...],
20867
+ // find the "select" attribute and remove its name and corresponding value.
20868
+ for (let i = op.attributes.length - 2; i >= 0; i -= 2) {
20869
+ if (isSelectAttribute(op.attributes[i])) {
20870
+ op.attributes.splice(i, 2);
20871
+ }
20872
+ }
20873
+ break;
20556
20874
  }
20557
20875
  }
20558
20876
  }
20559
20877
  }
20878
+ function isSelectAttribute(name) {
20879
+ return name.toLowerCase() === 'select';
20880
+ }
20560
20881
  /**
20561
20882
  * Looks up an element in the given map by xref ID.
20562
20883
  */
@@ -20676,25 +20997,44 @@ function propagateI18nBlocksToTemplates(unit, subTemplateIndex) {
20676
20997
  i18nBlock = op;
20677
20998
  break;
20678
20999
  case OpKind.I18nEnd:
21000
+ // When we exit a root-level i18n block, reset the sub-template index counter.
21001
+ if (i18nBlock.subTemplateIndex === null) {
21002
+ subTemplateIndex = 0;
21003
+ }
20679
21004
  i18nBlock = null;
20680
21005
  break;
20681
21006
  case OpKind.Template:
20682
- const templateView = unit.job.views.get(op.xref);
20683
- // We found an <ng-template> inside an i18n block; increment the sub-template counter and
20684
- // wrap the template's view in a child i18n block.
20685
- if (op.i18nPlaceholder !== undefined) {
20686
- if (i18nBlock === null) {
20687
- throw Error('Expected template with i18n placeholder to be in an i18n block.');
20688
- }
20689
- subTemplateIndex++;
20690
- wrapTemplateWithI18n(templateView, i18nBlock);
21007
+ subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.xref), i18nBlock, op.i18nPlaceholder, subTemplateIndex);
21008
+ break;
21009
+ case OpKind.RepeaterCreate:
21010
+ // Propagate i18n blocks to the @for template.
21011
+ const forView = unit.job.views.get(op.xref);
21012
+ subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.xref), i18nBlock, op.i18nPlaceholder, subTemplateIndex);
21013
+ // Then if there's an @empty template, propagate the i18n blocks for it as well.
21014
+ if (op.emptyView !== null) {
21015
+ subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.emptyView), i18nBlock, op.emptyI18nPlaceholder, subTemplateIndex);
20691
21016
  }
20692
- // Continue traversing inside the template's view.
20693
- subTemplateIndex = propagateI18nBlocksToTemplates(templateView, subTemplateIndex);
21017
+ break;
20694
21018
  }
20695
21019
  }
20696
21020
  return subTemplateIndex;
20697
21021
  }
21022
+ /**
21023
+ * Propagate i18n blocks for a view.
21024
+ */
21025
+ function propagateI18nBlocksForView(view, i18nBlock, i18nPlaceholder, subTemplateIndex) {
21026
+ // We found an <ng-template> inside an i18n block; increment the sub-template counter and
21027
+ // wrap the template's view in a child i18n block.
21028
+ if (i18nPlaceholder !== undefined) {
21029
+ if (i18nBlock === null) {
21030
+ throw Error('Expected template with i18n placeholder to be in an i18n block.');
21031
+ }
21032
+ subTemplateIndex++;
21033
+ wrapTemplateWithI18n(view, i18nBlock);
21034
+ }
21035
+ // Continue traversing inside the template's view.
21036
+ return propagateI18nBlocksToTemplates(view, subTemplateIndex);
21037
+ }
20698
21038
  /**
20699
21039
  * Wraps a template view with i18n start and end ops.
20700
21040
  */
@@ -20860,17 +21200,13 @@ function disableBindings() {
20860
21200
  function enableBindings() {
20861
21201
  return call(Identifiers.enableBindings, [], null);
20862
21202
  }
20863
- function listener(name, handlerFn, sourceSpan) {
20864
- return call(Identifiers.listener, [
20865
- literal(name),
20866
- handlerFn,
20867
- ], sourceSpan);
20868
- }
20869
- function syntheticHostListener(name, handlerFn, sourceSpan) {
20870
- return call(Identifiers.syntheticHostListener, [
20871
- literal(name),
20872
- handlerFn,
20873
- ], sourceSpan);
21203
+ function listener(name, handlerFn, eventTargetResolver, syntheticHost, sourceSpan) {
21204
+ const args = [literal(name), handlerFn];
21205
+ if (eventTargetResolver !== null) {
21206
+ args.push(literal(false)); // `useCapture` flag, defaults to `false`
21207
+ args.push(importExpr(eventTargetResolver));
21208
+ }
21209
+ return call(syntheticHost ? Identifiers.syntheticHostListener : Identifiers.listener, args, sourceSpan);
20874
21210
  }
20875
21211
  function pipe(slot, name) {
20876
21212
  return call(Identifiers.pipe, [
@@ -21017,6 +21353,10 @@ function i18n(slot, constIndex, subTemplateIndex) {
21017
21353
  function i18nEnd() {
21018
21354
  return call(Identifiers.i18nEnd, [], null);
21019
21355
  }
21356
+ function i18nAttributes(slot, i18nAttributesConfig) {
21357
+ const args = [literal(slot), literal(i18nAttributesConfig)];
21358
+ return call(Identifiers.i18nAttributes, args, null);
21359
+ }
21020
21360
  function property(name, expression, sanitizer, sourceSpan) {
21021
21361
  const args = [literal(name), expression];
21022
21362
  if (sanitizer !== null) {
@@ -21127,8 +21467,12 @@ function classMapInterpolate(strings, expressions, sourceSpan) {
21127
21467
  const interpolationArgs = collateInterpolationArgs(strings, expressions);
21128
21468
  return callVariadicInstruction(CLASS_MAP_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
21129
21469
  }
21130
- function hostProperty(name, expression, sourceSpan) {
21131
- return call(Identifiers.hostProperty, [literal(name), expression], sourceSpan);
21470
+ function hostProperty(name, expression, sanitizer, sourceSpan) {
21471
+ const args = [literal(name), expression];
21472
+ if (sanitizer !== null) {
21473
+ args.push(sanitizer);
21474
+ }
21475
+ return call(Identifiers.hostProperty, args, sourceSpan);
21132
21476
  }
21133
21477
  function syntheticHostProperty(name, expression, sourceSpan) {
21134
21478
  return call(Identifiers.syntheticHostProperty, [literal(name), expression], sourceSpan);
@@ -21346,14 +21690,12 @@ function callVariadicInstruction(config, baseArgs, interpolationArgs, extraArgs,
21346
21690
  }
21347
21691
 
21348
21692
  /**
21349
- * Map of sanitizers to their identifier.
21693
+ * Map of target resolvers for event listeners.
21350
21694
  */
21351
- const sanitizerIdentifierMap = new Map([
21352
- [SanitizerFn.Html, Identifiers.sanitizeHtml],
21353
- [SanitizerFn.IframeAttribute, Identifiers.validateIframeAttribute],
21354
- [SanitizerFn.ResourceUrl, Identifiers.sanitizeResourceUrl],
21355
- [SanitizerFn.Script, Identifiers.sanitizeScript],
21356
- [SanitizerFn.Style, Identifiers.sanitizeStyle], [SanitizerFn.Url, Identifiers.sanitizeUrl]
21695
+ const GLOBAL_TARGET_RESOLVERS$1 = new Map([
21696
+ ['window', Identifiers.resolveWindow],
21697
+ ['document', Identifiers.resolveDocument],
21698
+ ['body', Identifiers.resolveBody],
21357
21699
  ]);
21358
21700
  /**
21359
21701
  * Compiles semantic operations across all views and generates output `o.Statement`s with actual
@@ -21403,6 +21745,12 @@ function reifyCreateOperations(unit, ops) {
21403
21745
  case OpKind.I18n:
21404
21746
  OpList.replace(op, i18n(op.handle.slot, op.messageIndex, op.subTemplateIndex));
21405
21747
  break;
21748
+ case OpKind.I18nAttributes:
21749
+ if (op.i18nAttributesConfig === null) {
21750
+ throw new Error(`AssertionError: i18nAttributesConfig was not set`);
21751
+ }
21752
+ OpList.replace(op, i18nAttributes(op.handle.slot, op.i18nAttributesConfig));
21753
+ break;
21406
21754
  case OpKind.Template:
21407
21755
  if (!(unit instanceof ViewCompilationUnit)) {
21408
21756
  throw new Error(`AssertionError: must be compiling a component`);
@@ -21424,10 +21772,11 @@ function reifyCreateOperations(unit, ops) {
21424
21772
  break;
21425
21773
  case OpKind.Listener:
21426
21774
  const listenerFn = reifyListenerHandler(unit, op.handlerFnName, op.handlerOps, op.consumesDollarEvent);
21427
- const reified = op.hostListener && op.isAnimationListener ?
21428
- syntheticHostListener(op.name, listenerFn, op.sourceSpan) :
21429
- listener(op.name, listenerFn, op.sourceSpan);
21430
- OpList.replace(op, reified);
21775
+ const eventTargetResolver = op.eventTarget ? GLOBAL_TARGET_RESOLVERS$1.get(op.eventTarget) : null;
21776
+ if (eventTargetResolver === undefined) {
21777
+ throw new Error(`AssertionError: unknown event target ${op.eventTarget}`);
21778
+ }
21779
+ OpList.replace(op, listener(op.name, listenerFn, eventTargetResolver, op.hostListener && op.isAnimationListener, op.sourceSpan));
21431
21780
  break;
21432
21781
  case OpKind.Variable:
21433
21782
  if (op.variable.name === null) {
@@ -21590,7 +21939,7 @@ function reifyUpdateOperations(_unit, ops) {
21590
21939
  OpList.replace(op, syntheticHostProperty(op.name, op.expression, op.sourceSpan));
21591
21940
  }
21592
21941
  else {
21593
- OpList.replace(op, hostProperty(op.name, op.expression, op.sourceSpan));
21942
+ OpList.replace(op, hostProperty(op.name, op.expression, op.sanitizer, op.sourceSpan));
21594
21943
  }
21595
21944
  }
21596
21945
  break;
@@ -21669,8 +22018,6 @@ function reifyIrExpression(expr) {
21669
22018
  return pipeBind(expr.targetSlot.slot, expr.varOffset, expr.args);
21670
22019
  case ExpressionKind.PipeBindingVariadic:
21671
22020
  return pipeBindV(expr.targetSlot.slot, expr.varOffset, expr.args);
21672
- case ExpressionKind.SanitizerExpr:
21673
- return importExpr(sanitizerIdentifierMap.get(expr.fn));
21674
22021
  case ExpressionKind.SlotLiteralExpr:
21675
22022
  return literal(expr.slot.slot);
21676
22023
  default:
@@ -21744,6 +22091,31 @@ function removeI18nContexts(job) {
21744
22091
  }
21745
22092
  }
21746
22093
 
22094
+ /**
22095
+ * i18nAttributes ops will be generated for each i18n attribute. However, not all i18n attribues
22096
+ * will contain dynamic content, and so some of these i18nAttributes ops may be unnecessary.
22097
+ */
22098
+ function removeUnusedI18nAttributesOps(job) {
22099
+ for (const unit of job.units) {
22100
+ const ownersWithI18nExpressions = new Set();
22101
+ for (const op of unit.update) {
22102
+ switch (op.kind) {
22103
+ case OpKind.I18nExpression:
22104
+ ownersWithI18nExpressions.add(op.i18nOwner);
22105
+ }
22106
+ }
22107
+ for (const op of unit.create) {
22108
+ switch (op.kind) {
22109
+ case OpKind.I18nAttributes:
22110
+ if (ownersWithI18nExpressions.has(op.xref)) {
22111
+ continue;
22112
+ }
22113
+ OpList.remove(op);
22114
+ }
22115
+ }
22116
+ }
22117
+ }
22118
+
21747
22119
  /**
21748
22120
  * Inside the body of a repeater, certain context variables (such as `$first`) are ambiently
21749
22121
  * available. This phase finds those variable usages, and replaces them with the appropriate
@@ -21875,10 +22247,14 @@ function resolveI18nElementPlaceholders(job) {
21875
22247
  }
21876
22248
  resolvePlaceholdersForView(job, job.root, i18nContexts, elements);
21877
22249
  }
21878
- function resolvePlaceholdersForView(job, unit, i18nContexts, elements) {
22250
+ /**
22251
+ * Recursively resolves element and template tag placeholders in the given view.
22252
+ */
22253
+ function resolvePlaceholdersForView(job, unit, i18nContexts, elements, pendingStructuralDirective) {
21879
22254
  // Track the current i18n op and corresponding i18n context op as we step through the creation
21880
22255
  // IR.
21881
22256
  let currentOps = null;
22257
+ let pendingStructuralDirectiveCloses = new Map();
21882
22258
  for (const op of unit.create) {
21883
22259
  switch (op.kind) {
21884
22260
  case OpKind.I18nStart:
@@ -21897,14 +22273,14 @@ function resolvePlaceholdersForView(job, unit, i18nContexts, elements) {
21897
22273
  if (currentOps === null) {
21898
22274
  throw Error('i18n tag placeholder should only occur inside an i18n block');
21899
22275
  }
21900
- const { startName, closeName } = op.i18nPlaceholder;
21901
- let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.OpenTag;
21902
- // For self-closing tags, there is no close tag placeholder. Instead, the start tag
21903
- // placeholder accounts for the start and close of the element.
21904
- if (closeName === '') {
21905
- flags |= I18nParamValueFlags.CloseTag;
22276
+ recordElementStart(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22277
+ // If there is a separate close tag placeholder for this element, save the pending
22278
+ // structural directive so we can pass it to the closing tag as well.
22279
+ if (pendingStructuralDirective && op.i18nPlaceholder.closeName) {
22280
+ pendingStructuralDirectiveCloses.set(op.xref, pendingStructuralDirective);
21906
22281
  }
21907
- addParam(currentOps.i18nContext.params, startName, op.handle.slot, currentOps.i18nBlock.subTemplateIndex, flags);
22282
+ // Clear out the pending structural directive now that its been accounted for.
22283
+ pendingStructuralDirective = undefined;
21908
22284
  }
21909
22285
  break;
21910
22286
  case OpKind.ElementEnd:
@@ -21913,55 +22289,195 @@ function resolvePlaceholdersForView(job, unit, i18nContexts, elements) {
21913
22289
  const startOp = elements.get(op.xref);
21914
22290
  if (startOp && startOp.i18nPlaceholder !== undefined) {
21915
22291
  if (currentOps === null) {
21916
- throw Error('i18n tag placeholder should only occur inside an i18n block');
22292
+ throw Error('AssertionError: i18n tag placeholder should only occur inside an i18n block');
21917
22293
  }
21918
- const { closeName } = startOp.i18nPlaceholder;
21919
- // Self-closing tags don't have a closing tag placeholder.
21920
- if (closeName !== '') {
21921
- addParam(currentOps.i18nContext.params, closeName, startOp.handle.slot, currentOps.i18nBlock.subTemplateIndex, I18nParamValueFlags.ElementTag | I18nParamValueFlags.CloseTag);
22294
+ recordElementClose(startOp, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirectiveCloses.get(op.xref));
22295
+ // Clear out the pending structural directive close that was accounted for.
22296
+ pendingStructuralDirectiveCloses.delete(op.xref);
22297
+ }
22298
+ break;
22299
+ case OpKind.Projection:
22300
+ // For content projections with i18n placeholders, record its slot value in the params map
22301
+ // under the corresponding tag start and close placeholders.
22302
+ if (op.i18nPlaceholder !== undefined) {
22303
+ if (currentOps === null) {
22304
+ throw Error('i18n tag placeholder should only occur inside an i18n block');
21922
22305
  }
22306
+ recordElementStart(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22307
+ recordElementClose(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22308
+ // Clear out the pending structural directive now that its been accounted for.
22309
+ pendingStructuralDirective = undefined;
21923
22310
  }
21924
22311
  break;
21925
22312
  case OpKind.Template:
21926
- // For templates with i18n placeholders, record its slot value in the params map under the
21927
- // corresponding template start and close placeholders.
21928
- if (op.i18nPlaceholder !== undefined) {
22313
+ const view = job.views.get(op.xref);
22314
+ if (op.i18nPlaceholder === undefined) {
22315
+ // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22316
+ // blocks.
22317
+ resolvePlaceholdersForView(job, view, i18nContexts, elements);
22318
+ }
22319
+ else {
21929
22320
  if (currentOps === null) {
21930
22321
  throw Error('i18n tag placeholder should only occur inside an i18n block');
21931
22322
  }
21932
- let startFlags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.OpenTag;
21933
- const subTemplateIndex = getSubTemplateIndexForTemplateTag(job, currentOps.i18nBlock, op);
21934
- const { startName, closeName } = op.i18nPlaceholder;
21935
- const isSelfClosing = closeName === '';
21936
- if (isSelfClosing) {
21937
- startFlags |= I18nParamValueFlags.CloseTag;
22323
+ if (op.templateKind === TemplateKind.Structural) {
22324
+ // If this is a structural directive template, don't record anything yet. Instead pass
22325
+ // the current template as a pending structural directive to be recorded when we find
22326
+ // the element, content, or template it belongs to. This allows us to create combined
22327
+ // values that represent, e.g. the start of a template and element at the same time.
22328
+ resolvePlaceholdersForView(job, view, i18nContexts, elements, op);
21938
22329
  }
21939
- addParam(currentOps.i18nContext.params, startName, op.handle.slot, subTemplateIndex, startFlags);
21940
- resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
21941
- if (!isSelfClosing) {
21942
- addParam(currentOps.i18nContext.params, closeName, op.handle.slot, subTemplateIndex, I18nParamValueFlags.TemplateTag | I18nParamValueFlags.CloseTag);
22330
+ else {
22331
+ // If this is some other kind of template, we can record its start, recurse into its
22332
+ // view, and then record its end.
22333
+ recordTemplateStart(job, view, op.handle.slot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22334
+ resolvePlaceholdersForView(job, view, i18nContexts, elements);
22335
+ recordTemplateClose(job, view, op.handle.slot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22336
+ pendingStructuralDirective = undefined;
21943
22337
  }
21944
22338
  }
22339
+ break;
22340
+ case OpKind.RepeaterCreate:
22341
+ if (pendingStructuralDirective !== undefined) {
22342
+ throw Error('AssertionError: Unexpected structural directive associated with @for block');
22343
+ }
22344
+ // RepeaterCreate has 3 slots: the first is for the op itself, the second is for the @for
22345
+ // template and the (optional) third is for the @empty template.
22346
+ const forSlot = op.handle.slot + 1;
22347
+ const forView = job.views.get(op.xref);
22348
+ // First record all of the placeholders for the @for template.
22349
+ if (op.i18nPlaceholder === undefined) {
22350
+ // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22351
+ // blocks.
22352
+ resolvePlaceholdersForView(job, forView, i18nContexts, elements);
22353
+ }
21945
22354
  else {
21946
- resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
22355
+ if (currentOps === null) {
22356
+ throw Error('i18n tag placeholder should only occur inside an i18n block');
22357
+ }
22358
+ recordTemplateStart(job, forView, forSlot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22359
+ resolvePlaceholdersForView(job, forView, i18nContexts, elements);
22360
+ recordTemplateClose(job, forView, forSlot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22361
+ pendingStructuralDirective = undefined;
22362
+ }
22363
+ // Then if there's an @empty template, add its placeholders as well.
22364
+ if (op.emptyView !== null) {
22365
+ // RepeaterCreate has 3 slots: the first is for the op itself, the second is for the @for
22366
+ // template and the (optional) third is for the @empty template.
22367
+ const emptySlot = op.handle.slot + 2;
22368
+ const emptyView = job.views.get(op.emptyView);
22369
+ if (op.emptyI18nPlaceholder === undefined) {
22370
+ // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22371
+ // blocks.
22372
+ resolvePlaceholdersForView(job, emptyView, i18nContexts, elements);
22373
+ }
22374
+ else {
22375
+ if (currentOps === null) {
22376
+ throw Error('i18n tag placeholder should only occur inside an i18n block');
22377
+ }
22378
+ recordTemplateStart(job, emptyView, emptySlot, op.emptyI18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22379
+ resolvePlaceholdersForView(job, emptyView, i18nContexts, elements);
22380
+ recordTemplateClose(job, emptyView, emptySlot, op.emptyI18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22381
+ pendingStructuralDirective = undefined;
22382
+ }
21947
22383
  }
21948
22384
  break;
21949
22385
  }
21950
22386
  }
21951
22387
  }
22388
+ /**
22389
+ * Records an i18n param value for the start of an element.
22390
+ */
22391
+ function recordElementStart(op, i18nContext, i18nBlock, structuralDirective) {
22392
+ const { startName, closeName } = op.i18nPlaceholder;
22393
+ let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.OpenTag;
22394
+ let value = op.handle.slot;
22395
+ // If the element is associated with a structural directive, start it as well.
22396
+ if (structuralDirective !== undefined) {
22397
+ flags |= I18nParamValueFlags.TemplateTag;
22398
+ value = { element: value, template: structuralDirective.handle.slot };
22399
+ }
22400
+ // For self-closing tags, there is no close tag placeholder. Instead, the start tag
22401
+ // placeholder accounts for the start and close of the element.
22402
+ if (!closeName) {
22403
+ flags |= I18nParamValueFlags.CloseTag;
22404
+ }
22405
+ addParam(i18nContext.params, startName, value, i18nBlock.subTemplateIndex, flags);
22406
+ }
22407
+ /**
22408
+ * Records an i18n param value for the closing of an element.
22409
+ */
22410
+ function recordElementClose(op, i18nContext, i18nBlock, structuralDirective) {
22411
+ const { closeName } = op.i18nPlaceholder;
22412
+ // Self-closing tags don't have a closing tag placeholder, instead the element closing is
22413
+ // recorded via an additional flag on the element start value.
22414
+ if (closeName) {
22415
+ let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.CloseTag;
22416
+ let value = op.handle.slot;
22417
+ // If the element is associated with a structural directive, close it as well.
22418
+ if (structuralDirective !== undefined) {
22419
+ flags |= I18nParamValueFlags.TemplateTag;
22420
+ value = { element: value, template: structuralDirective.handle.slot };
22421
+ }
22422
+ addParam(i18nContext.params, closeName, value, i18nBlock.subTemplateIndex, flags);
22423
+ }
22424
+ }
22425
+ /**
22426
+ * Records an i18n param value for the start of a template.
22427
+ */
22428
+ function recordTemplateStart(job, view, slot, i18nPlaceholder, i18nContext, i18nBlock, structuralDirective) {
22429
+ let { startName, closeName } = i18nPlaceholder;
22430
+ let flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.OpenTag;
22431
+ // For self-closing tags, there is no close tag placeholder. Instead, the start tag
22432
+ // placeholder accounts for the start and close of the element.
22433
+ if (!closeName) {
22434
+ flags |= I18nParamValueFlags.CloseTag;
22435
+ }
22436
+ // If the template is associated with a structural directive, record the structural directive's
22437
+ // start first. Since this template must be in the structural directive's view, we can just
22438
+ // directly use the current i18n block's sub-template index.
22439
+ if (structuralDirective !== undefined) {
22440
+ addParam(i18nContext.params, startName, structuralDirective.handle.slot, i18nBlock.subTemplateIndex, flags);
22441
+ }
22442
+ // Record the start of the template. For the sub-template index, pass the index for the template's
22443
+ // view, rather than the current i18n block's index.
22444
+ addParam(i18nContext.params, startName, slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, view), flags);
22445
+ }
22446
+ /**
22447
+ * Records an i18n param value for the closing of a template.
22448
+ */
22449
+ function recordTemplateClose(job, view, slot, i18nPlaceholder, i18nContext, i18nBlock, structuralDirective) {
22450
+ const { startName, closeName } = i18nPlaceholder;
22451
+ const flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.CloseTag;
22452
+ // Self-closing tags don't have a closing tag placeholder, instead the template's closing is
22453
+ // recorded via an additional flag on the template start value.
22454
+ if (closeName) {
22455
+ // Record the closing of the template. For the sub-template index, pass the index for the
22456
+ // template's view, rather than the current i18n block's index.
22457
+ addParam(i18nContext.params, closeName, slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, view), flags);
22458
+ // If the template is associated with a structural directive, record the structural directive's
22459
+ // closing after. Since this template must be in the structural directive's view, we can just
22460
+ // directly use the current i18n block's sub-template index.
22461
+ if (structuralDirective !== undefined) {
22462
+ addParam(i18nContext.params, closeName, structuralDirective.handle.slot, i18nBlock.subTemplateIndex, flags);
22463
+ }
22464
+ }
22465
+ }
21952
22466
  /**
21953
22467
  * Get the subTemplateIndex for the given template op. For template ops, use the subTemplateIndex of
21954
22468
  * the child i18n block inside the template.
21955
22469
  */
21956
- function getSubTemplateIndexForTemplateTag(job, i18nOp, op) {
21957
- for (const childOp of job.views.get(op.xref).create) {
22470
+ function getSubTemplateIndexForTemplateTag(job, i18nOp, view) {
22471
+ for (const childOp of view.create) {
21958
22472
  if (childOp.kind === OpKind.I18nStart) {
21959
22473
  return childOp.subTemplateIndex;
21960
22474
  }
21961
22475
  }
21962
22476
  return i18nOp.subTemplateIndex;
21963
22477
  }
21964
- /** Add a param value to the given params map. */
22478
+ /**
22479
+ * Add a param value to the given params map.
22480
+ */
21965
22481
  function addParam(params, placeholder, value, subTemplateIndex, flags) {
21966
22482
  const values = params.get(placeholder) ?? [];
21967
22483
  values.push({ value, subTemplateIndex, flags });
@@ -21987,14 +22503,19 @@ function resolveI18nExpressionPlaceholders(job) {
21987
22503
  }
21988
22504
  }
21989
22505
  }
21990
- // Keep track of the next available expression index per i18n block.
22506
+ // Keep track of the next available expression index for each i18n message.
21991
22507
  const expressionIndices = new Map();
22508
+ // Keep track of a reference index for each expression.
22509
+ // We use different references for normal i18n expressio and attribute i18n expressions. This is
22510
+ // because child i18n blocks in templates don't get their own context, since they're rolled into
22511
+ // the translated message of the parent, but they may target a different slot.
22512
+ const referenceIndex = (op) => op.usage === I18nExpressionFor.I18nText ? op.i18nOwner : op.context;
21992
22513
  for (const unit of job.units) {
21993
22514
  for (const op of unit.update) {
21994
22515
  if (op.kind === OpKind.I18nExpression) {
21995
22516
  const i18nContext = i18nContexts.get(op.context);
21996
- const index = expressionIndices.get(op.target) || 0;
21997
- const subTemplateIndex = subTemplateIndicies.get(op.target);
22517
+ const index = expressionIndices.get(referenceIndex(op)) || 0;
22518
+ const subTemplateIndex = subTemplateIndicies.get(op.i18nOwner) ?? null;
21998
22519
  // Add the expression index in the appropriate params map.
21999
22520
  const params = op.resolutionTime === I18nParamResolutionTime.Creation ?
22000
22521
  i18nContext.params :
@@ -22006,7 +22527,7 @@ function resolveI18nExpressionPlaceholders(job) {
22006
22527
  flags: I18nParamValueFlags.ExpressionIndex
22007
22528
  });
22008
22529
  params.set(op.i18nPlaceholder, values);
22009
- expressionIndices.set(op.target, index + 1);
22530
+ expressionIndices.set(referenceIndex(op), index + 1);
22010
22531
  }
22011
22532
  }
22012
22533
  }
@@ -22164,12 +22685,20 @@ function processLexicalScope(unit, ops, savedView) {
22164
22685
  }
22165
22686
 
22166
22687
  /**
22167
- * Mapping of security contexts to sanitizer function for that context.
22688
+ * Map of security contexts to their sanitizer function.
22689
+ */
22690
+ const sanitizerFns = new Map([
22691
+ [SecurityContext.HTML, Identifiers.sanitizeHtml],
22692
+ [SecurityContext.RESOURCE_URL, Identifiers.sanitizeResourceUrl],
22693
+ [SecurityContext.SCRIPT, Identifiers.sanitizeScript],
22694
+ [SecurityContext.STYLE, Identifiers.sanitizeStyle], [SecurityContext.URL, Identifiers.sanitizeUrl]
22695
+ ]);
22696
+ /**
22697
+ * Map of security contexts to their trusted value function.
22168
22698
  */
22169
- const sanitizers = new Map([
22170
- [SecurityContext.HTML, SanitizerFn.Html], [SecurityContext.SCRIPT, SanitizerFn.Script],
22171
- [SecurityContext.STYLE, SanitizerFn.Style], [SecurityContext.URL, SanitizerFn.Url],
22172
- [SecurityContext.RESOURCE_URL, SanitizerFn.ResourceUrl]
22699
+ const trustedValueFns = new Map([
22700
+ [SecurityContext.HTML, Identifiers.trustConstantHtml],
22701
+ [SecurityContext.RESOURCE_URL, Identifiers.trustConstantResourceUrl],
22173
22702
  ]);
22174
22703
  /**
22175
22704
  * Resolves sanitization functions for ops that need them.
@@ -22177,24 +22706,61 @@ const sanitizers = new Map([
22177
22706
  function resolveSanitizers(job) {
22178
22707
  for (const unit of job.units) {
22179
22708
  const elements = createOpXrefMap(unit);
22180
- let sanitizerFn;
22709
+ // For normal element bindings we create trusted values for security sensitive constant
22710
+ // attributes. However, for host bindings we skip this step (this matches what
22711
+ // TemplateDefinitionBuilder does).
22712
+ // TODO: Is the TDB behavior correct here?
22713
+ if (job.kind !== CompilationJobKind.Host) {
22714
+ for (const op of unit.create) {
22715
+ if (op.kind === OpKind.ExtractedAttribute) {
22716
+ const trustedValueFn = trustedValueFns.get(getOnlySecurityContext(op.securityContext)) ?? null;
22717
+ op.trustedValueFn = trustedValueFn !== null ? importExpr(trustedValueFn) : null;
22718
+ }
22719
+ }
22720
+ }
22181
22721
  for (const op of unit.update) {
22182
22722
  switch (op.kind) {
22183
22723
  case OpKind.Property:
22184
22724
  case OpKind.Attribute:
22185
- sanitizerFn = sanitizers.get(op.securityContext) || null;
22186
- op.sanitizer = sanitizerFn ? new SanitizerExpr(sanitizerFn) : null;
22725
+ case OpKind.HostProperty:
22726
+ let sanitizerFn = null;
22727
+ if (Array.isArray(op.securityContext) && op.securityContext.length === 2 &&
22728
+ op.securityContext.indexOf(SecurityContext.URL) > -1 &&
22729
+ op.securityContext.indexOf(SecurityContext.RESOURCE_URL) > -1) {
22730
+ // When the host element isn't known, some URL attributes (such as "src" and "href") may
22731
+ // be part of multiple different security contexts. In this case we use special
22732
+ // sanitization function and select the actual sanitizer at runtime based on a tag name
22733
+ // that is provided while invoking sanitization function.
22734
+ sanitizerFn = Identifiers.sanitizeUrlOrResourceUrl;
22735
+ }
22736
+ else {
22737
+ sanitizerFn = sanitizerFns.get(getOnlySecurityContext(op.securityContext)) ?? null;
22738
+ }
22739
+ op.sanitizer = sanitizerFn !== null ? importExpr(sanitizerFn) : null;
22187
22740
  // If there was no sanitization function found based on the security context of an
22188
22741
  // attribute/property, check whether this attribute/property is one of the
22189
22742
  // security-sensitive <iframe> attributes (and that the current element is actually an
22190
22743
  // <iframe>).
22191
22744
  if (op.sanitizer === null) {
22192
- const ownerOp = elements.get(op.target);
22193
- if (ownerOp === undefined || !isElementOrContainerOp(ownerOp)) {
22194
- throw Error('Property should have an element-like owner');
22745
+ let isIframe = false;
22746
+ if (job.kind === CompilationJobKind.Host || op.kind === OpKind.HostProperty) {
22747
+ // Note: for host bindings defined on a directive, we do not try to find all
22748
+ // possible places where it can be matched, so we can not determine whether
22749
+ // the host element is an <iframe>. In this case, we just assume it is and append a
22750
+ // validation function, which is invoked at runtime and would have access to the
22751
+ // underlying DOM element to check if it's an <iframe> and if so - run extra checks.
22752
+ isIframe = true;
22195
22753
  }
22196
- if (isIframeElement$1(ownerOp) && isIframeSecuritySensitiveAttr(op.name)) {
22197
- op.sanitizer = new SanitizerExpr(SanitizerFn.IframeAttribute);
22754
+ else {
22755
+ // For a normal binding we can just check if the element its on is an iframe.
22756
+ const ownerOp = elements.get(op.target);
22757
+ if (ownerOp === undefined || !isElementOrContainerOp(ownerOp)) {
22758
+ throw Error('Property should have an element-like owner');
22759
+ }
22760
+ isIframe = isIframeElement$1(ownerOp);
22761
+ }
22762
+ if (isIframe && isIframeSecuritySensitiveAttr(op.name)) {
22763
+ op.sanitizer = importExpr(Identifiers.validateIframeAttribute);
22198
22764
  }
22199
22765
  }
22200
22766
  break;
@@ -22208,6 +22774,22 @@ function resolveSanitizers(job) {
22208
22774
  function isIframeElement$1(op) {
22209
22775
  return op.kind === OpKind.ElementStart && op.tag?.toLowerCase() === 'iframe';
22210
22776
  }
22777
+ /**
22778
+ * Asserts that there is only a single security context and returns it.
22779
+ */
22780
+ function getOnlySecurityContext(securityContext) {
22781
+ if (Array.isArray(securityContext)) {
22782
+ if (securityContext.length > 1) {
22783
+ // TODO: What should we do here? TDB just took the first one, but this feels like something we
22784
+ // would want to know about and create a special case for like we did for Url/ResourceUrl. My
22785
+ // guess is that, outside of the Url/ResourceUrl case, this never actually happens. If there
22786
+ // do turn out to be other cases, throwing an error until we can address it feels safer.
22787
+ throw Error(`AssertionError: Ambiguous security context`);
22788
+ }
22789
+ return securityContext[0] || SecurityContext.NONE;
22790
+ }
22791
+ return securityContext;
22792
+ }
22211
22793
 
22212
22794
  /**
22213
22795
  * When inside of a listener, we may need access to one or more enclosing views. Therefore, each
@@ -22311,6 +22893,8 @@ function allocateSlots(job) {
22311
22893
  // operation itself, so it can be emitted later.
22312
22894
  const childView = job.views.get(op.xref);
22313
22895
  op.decls = childView.decls;
22896
+ // TODO: currently we handle the decls for the RepeaterCreate empty template in the reify
22897
+ // phase. We should handle that here instead.
22314
22898
  }
22315
22899
  }
22316
22900
  }
@@ -22634,6 +23218,8 @@ function countVariables(job) {
22634
23218
  }
22635
23219
  const childView = job.views.get(op.xref);
22636
23220
  op.vars = childView.vars;
23221
+ // TODO: currently we handle the vars for the RepeaterCreate empty template in the reify
23222
+ // phase. We should handle that here instead.
22637
23223
  }
22638
23224
  }
22639
23225
  }
@@ -23153,12 +23739,12 @@ const phases = [
23153
23739
  { kind: CompilationJobKind.Tmpl, fn: removeContentSelectors },
23154
23740
  { kind: CompilationJobKind.Host, fn: parseHostStyleProperties },
23155
23741
  { kind: CompilationJobKind.Tmpl, fn: emitNamespaceChanges },
23156
- { kind: CompilationJobKind.Both, fn: specializeStyleBindings },
23157
- { kind: CompilationJobKind.Both, fn: specializeBindings },
23158
23742
  { kind: CompilationJobKind.Tmpl, fn: propagateI18nBlocks },
23159
23743
  { kind: CompilationJobKind.Tmpl, fn: wrapI18nIcus },
23160
- { kind: CompilationJobKind.Tmpl, fn: createI18nContexts },
23744
+ { kind: CompilationJobKind.Both, fn: specializeStyleBindings },
23745
+ { kind: CompilationJobKind.Both, fn: specializeBindings },
23161
23746
  { kind: CompilationJobKind.Both, fn: extractAttributes },
23747
+ { kind: CompilationJobKind.Tmpl, fn: createI18nContexts },
23162
23748
  { kind: CompilationJobKind.Both, fn: parseExtractedStyles },
23163
23749
  { kind: CompilationJobKind.Tmpl, fn: removeEmptyBindings },
23164
23750
  { kind: CompilationJobKind.Both, fn: collapseSingletonInterpolations },
@@ -23166,14 +23752,17 @@ const phases = [
23166
23752
  { kind: CompilationJobKind.Tmpl, fn: generateConditionalExpressions },
23167
23753
  { kind: CompilationJobKind.Tmpl, fn: createPipes },
23168
23754
  { kind: CompilationJobKind.Tmpl, fn: configureDeferInstructions },
23169
- { kind: CompilationJobKind.Tmpl, fn: extractI18nText },
23755
+ { kind: CompilationJobKind.Tmpl, fn: convertI18nText },
23756
+ { kind: CompilationJobKind.Tmpl, fn: convertI18nBindings },
23757
+ { kind: CompilationJobKind.Tmpl, fn: removeUnusedI18nAttributesOps },
23758
+ { kind: CompilationJobKind.Tmpl, fn: assignI18nSlotDependencies },
23170
23759
  { kind: CompilationJobKind.Tmpl, fn: applyI18nExpressions },
23171
23760
  { kind: CompilationJobKind.Tmpl, fn: createVariadicPipes },
23172
23761
  { kind: CompilationJobKind.Both, fn: generatePureLiteralStructures },
23173
23762
  { kind: CompilationJobKind.Tmpl, fn: generateProjectionDefs },
23174
23763
  { kind: CompilationJobKind.Tmpl, fn: generateVariables },
23175
23764
  { kind: CompilationJobKind.Tmpl, fn: saveAndRestoreView },
23176
- { kind: CompilationJobKind.Tmpl, fn: deleteAnyCasts },
23765
+ { kind: CompilationJobKind.Both, fn: deleteAnyCasts },
23177
23766
  { kind: CompilationJobKind.Both, fn: resolveDollarEvent },
23178
23767
  { kind: CompilationJobKind.Tmpl, fn: generateRepeaterDerivedVars },
23179
23768
  { kind: CompilationJobKind.Tmpl, fn: generateTrackVariables },
@@ -23181,7 +23770,7 @@ const phases = [
23181
23770
  { kind: CompilationJobKind.Tmpl, fn: resolveDeferTargetNames },
23182
23771
  { kind: CompilationJobKind.Tmpl, fn: optimizeTrackFns },
23183
23772
  { kind: CompilationJobKind.Both, fn: resolveContexts },
23184
- { kind: CompilationJobKind.Tmpl, fn: resolveSanitizers },
23773
+ { kind: CompilationJobKind.Both, fn: resolveSanitizers },
23185
23774
  { kind: CompilationJobKind.Tmpl, fn: liftLocalRefs },
23186
23775
  { kind: CompilationJobKind.Both, fn: generateNullishCoalesceExpressions },
23187
23776
  { kind: CompilationJobKind.Both, fn: expandSafeReads },
@@ -23196,7 +23785,6 @@ const phases = [
23196
23785
  { kind: CompilationJobKind.Tmpl, fn: collectI18nConsts },
23197
23786
  { kind: CompilationJobKind.Tmpl, fn: collectConstExpressions },
23198
23787
  { kind: CompilationJobKind.Both, fn: collectElementConsts },
23199
- { kind: CompilationJobKind.Tmpl, fn: assignI18nSlotDependencies },
23200
23788
  { kind: CompilationJobKind.Tmpl, fn: removeI18nContexts },
23201
23789
  { kind: CompilationJobKind.Both, fn: countVariables },
23202
23790
  { kind: CompilationJobKind.Tmpl, fn: generateAdvance },
@@ -23318,6 +23906,10 @@ function emitHostBindingFunction(job) {
23318
23906
  }
23319
23907
 
23320
23908
  const compatibilityMode = CompatibilityMode.TemplateDefinitionBuilder;
23909
+ // Schema containing DOM elements and their properties.
23910
+ const domSchema = new DomElementSchemaRegistry();
23911
+ // Tag name of the `ng-template` element.
23912
+ const NG_TEMPLATE_TAG_NAME$1 = 'ng-template';
23321
23913
  /**
23322
23914
  * Process a template AST and convert it into a `ComponentCompilation` in the intermediate
23323
23915
  * representation.
@@ -23335,10 +23927,24 @@ function ingestComponent(componentName, template, constantPool, relativeContextF
23335
23927
  function ingestHostBinding(input, bindingParser, constantPool) {
23336
23928
  const job = new HostBindingCompilationJob(input.componentName, constantPool, compatibilityMode);
23337
23929
  for (const property of input.properties ?? []) {
23338
- ingestHostProperty(job, property, false);
23930
+ let bindingKind = BindingKind.Property;
23931
+ // TODO: this should really be handled in the parser.
23932
+ if (property.name.startsWith('attr.')) {
23933
+ property.name = property.name.substring('attr.'.length);
23934
+ bindingKind = BindingKind.Attribute;
23935
+ }
23936
+ if (property.isAnimation) {
23937
+ bindingKind = BindingKind.Animation;
23938
+ }
23939
+ const securityContexts = bindingParser
23940
+ .calcPossibleSecurityContexts(input.componentSelector, property.name, bindingKind === BindingKind.Attribute)
23941
+ .filter(context => context !== SecurityContext.NONE);
23942
+ ingestHostProperty(job, property, bindingKind, false, securityContexts);
23339
23943
  }
23340
23944
  for (const [name, expr] of Object.entries(input.attributes) ?? []) {
23341
- ingestHostAttribute(job, name, expr);
23945
+ const securityContexts = bindingParser.calcPossibleSecurityContexts(input.componentSelector, name, true)
23946
+ .filter(context => context !== SecurityContext.NONE);
23947
+ ingestHostAttribute(job, name, expr, securityContexts);
23342
23948
  }
23343
23949
  for (const event of input.events ?? []) {
23344
23950
  ingestHostEvent(job, event);
@@ -23347,34 +23953,27 @@ function ingestHostBinding(input, bindingParser, constantPool) {
23347
23953
  }
23348
23954
  // TODO: We should refactor the parser to use the same types and structures for host bindings as
23349
23955
  // with ordinary components. This would allow us to share a lot more ingestion code.
23350
- function ingestHostProperty(job, property, isTextAttribute) {
23956
+ function ingestHostProperty(job, property, bindingKind, isTextAttribute, securityContexts) {
23351
23957
  let expression;
23352
23958
  const ast = property.expression.ast;
23353
23959
  if (ast instanceof Interpolation$1) {
23354
- expression = new Interpolation(ast.strings, ast.expressions.map(expr => convertAst(expr, job, property.sourceSpan)));
23960
+ expression = new Interpolation(ast.strings, ast.expressions.map(expr => convertAst(expr, job, property.sourceSpan)), []);
23355
23961
  }
23356
23962
  else {
23357
23963
  expression = convertAst(ast, job, property.sourceSpan);
23358
23964
  }
23359
- let bindingKind = BindingKind.Property;
23360
- // TODO: this should really be handled in the parser.
23361
- if (property.name.startsWith('attr.')) {
23362
- property.name = property.name.substring('attr.'.length);
23363
- bindingKind = BindingKind.Attribute;
23364
- }
23365
- if (property.isAnimation) {
23366
- bindingKind = BindingKind.Animation;
23367
- }
23368
- job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, SecurityContext
23369
- .NONE /* TODO: what should we pass as security context? Passing NONE for now. */, isTextAttribute, false, property.sourceSpan));
23965
+ job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, securityContexts, isTextAttribute, false, null, /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
23370
23966
  }
23371
- function ingestHostAttribute(job, name, value) {
23372
- const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, SecurityContext.NONE, true, false,
23967
+ function ingestHostAttribute(job, name, value, securityContexts) {
23968
+ const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, securityContexts, true, false, null,
23969
+ /* TODO */ null,
23373
23970
  /* TODO: host attribute source spans */ null);
23374
23971
  job.root.update.push(attrBinding);
23375
23972
  }
23376
23973
  function ingestHostEvent(job, event) {
23377
- const eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, event.targetOrPhase, true, event.sourceSpan);
23974
+ const [phase, target] = event.type === 0 /* e.ParsedEventType.Regular */ ? [null, event.targetOrPhase] :
23975
+ [event.targetOrPhase, null];
23976
+ const eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, [], phase, target, true, event.sourceSpan);
23378
23977
  // TODO: Can this be a chain?
23379
23978
  eventBinding.handlerOps.push(createStatementOp(new ReturnStatement(convertAst(event.handler.ast, job, event.sourceSpan), event.handlerSpan)));
23380
23979
  job.root.create.push(eventBinding);
@@ -23431,8 +24030,14 @@ function ingestElement(unit, element) {
23431
24030
  const [namespaceKey, elementName] = splitNsName(element.name);
23432
24031
  const startOp = createElementStartOp(elementName, id, namespaceForKey(namespaceKey), element.i18n instanceof TagPlaceholder ? element.i18n : undefined, element.startSourceSpan);
23433
24032
  unit.create.push(startOp);
23434
- ingestBindings(unit, startOp, element);
24033
+ ingestElementBindings(unit, startOp, element);
23435
24034
  ingestReferences(startOp, element);
24035
+ // Start i18n, if needed, goes after the element create and bindings, but before the nodes
24036
+ let i18nBlockId = null;
24037
+ if (element.i18n instanceof Message) {
24038
+ i18nBlockId = unit.job.allocateXrefId();
24039
+ unit.create.push(createI18nStartOp(i18nBlockId, element.i18n));
24040
+ }
23436
24041
  ingestNodes(unit, element.children);
23437
24042
  // The source span for the end op is typically the element closing tag. However, if no closing tag
23438
24043
  // exists, such as in `<input>`, we use the start source span instead. Usually the start and end
@@ -23442,9 +24047,7 @@ function ingestElement(unit, element) {
23442
24047
  const endOp = createElementEndOp(id, element.endSourceSpan ?? element.startSourceSpan);
23443
24048
  unit.create.push(endOp);
23444
24049
  // If there is an i18n message associated with this element, insert i18n start and end ops.
23445
- if (element.i18n instanceof Message) {
23446
- const i18nBlockId = unit.job.allocateXrefId();
23447
- OpList.insertAfter(createI18nStartOp(i18nBlockId, element.i18n), startOp);
24050
+ if (i18nBlockId !== null) {
23448
24051
  OpList.insertBefore(createI18nEndOp(i18nBlockId), endOp);
23449
24052
  }
23450
24053
  }
@@ -23467,10 +24070,11 @@ function ingestTemplate(unit, tmpl) {
23467
24070
  const functionNameSuffix = tagNameWithoutNamespace === null ?
23468
24071
  '' :
23469
24072
  prefixWithNamespace(tagNameWithoutNamespace, namespace);
23470
- const tplOp = createTemplateOp(childView.xref, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan);
23471
- unit.create.push(tplOp);
23472
- ingestBindings(unit, tplOp, tmpl);
23473
- ingestReferences(tplOp, tmpl);
24073
+ const templateKind = isPlainTemplate(tmpl) ? TemplateKind.NgTemplate : TemplateKind.Structural;
24074
+ const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan);
24075
+ unit.create.push(templateOp);
24076
+ ingestTemplateBindings(unit, templateOp, tmpl, templateKind);
24077
+ ingestReferences(templateOp, tmpl);
23474
24078
  ingestNodes(childView, tmpl.children);
23475
24079
  for (const { name, value } of tmpl.variables) {
23476
24080
  childView.contextVariables.set(name, value !== '' ? value : '$implicit');
@@ -23478,19 +24082,24 @@ function ingestTemplate(unit, tmpl) {
23478
24082
  // If this is a plain template and there is an i18n message associated with it, insert i18n start
23479
24083
  // and end ops. For structural directive templates, the i18n ops will be added when ingesting the
23480
24084
  // element/template the directive is placed on.
23481
- if (isPlainTemplate(tmpl) && tmpl.i18n instanceof Message) {
24085
+ if (templateKind === TemplateKind.NgTemplate && tmpl.i18n instanceof Message) {
23482
24086
  const id = unit.job.allocateXrefId();
23483
24087
  OpList.insertAfter(createI18nStartOp(id, tmpl.i18n), childView.create.head);
23484
24088
  OpList.insertBefore(createI18nEndOp(id), childView.create.tail);
23485
24089
  }
23486
24090
  }
23487
24091
  /**
23488
- * Ingest a literal text node from the AST into the given `ViewCompilation`.
24092
+ * Ingest a content node from the AST into the given `ViewCompilation`.
23489
24093
  */
23490
24094
  function ingestContent(unit, content) {
23491
- const op = createProjectionOp(unit.job.allocateXrefId(), content.selector, content.sourceSpan);
24095
+ if (content.i18n !== undefined && !(content.i18n instanceof TagPlaceholder)) {
24096
+ throw Error(`Unhandled i18n metadata type for element: ${content.i18n.constructor.name}`);
24097
+ }
24098
+ const attrs = content.attributes.flatMap(a => [a.name, a.value]);
24099
+ const op = createProjectionOp(unit.job.allocateXrefId(), content.selector, content.i18n, attrs, content.sourceSpan);
23492
24100
  for (const attr of content.attributes) {
23493
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue);
24101
+ const securityContext = domSchema.securityContext(content.name, attr.name, true);
24102
+ unit.update.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
23494
24103
  }
23495
24104
  unit.create.push(op);
23496
24105
  }
@@ -23515,6 +24124,7 @@ function ingestBoundText(unit, text, i18nPlaceholders) {
23515
24124
  throw Error(`Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`);
23516
24125
  }
23517
24126
  if (i18nPlaceholders === undefined) {
24127
+ // TODO: We probably can just use the placeholders field, instead of walking the AST.
23518
24128
  i18nPlaceholders = text.i18n instanceof Container ?
23519
24129
  text.i18n.children
23520
24130
  .filter((node) => node instanceof Placeholder)
@@ -23530,7 +24140,7 @@ function ingestBoundText(unit, text, i18nPlaceholders) {
23530
24140
  // interpolation. We copy that behavior in compatibility mode.
23531
24141
  // TODO: is it actually correct to generate these extra maps in modern mode?
23532
24142
  const baseSourceSpan = unit.job.compatibility ? null : text.sourceSpan;
23533
- unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, unit.job, baseSourceSpan))), i18nPlaceholders, text.sourceSpan));
24143
+ unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, unit.job, baseSourceSpan)), i18nPlaceholders), text.sourceSpan));
23534
24144
  }
23535
24145
  /**
23536
24146
  * Ingest an `@if` block into the given `ViewCompilation`.
@@ -23551,14 +24161,21 @@ function ingestIfBlock(unit, ifBlock) {
23551
24161
  if (ifCase.expressionAlias !== null) {
23552
24162
  cView.contextVariables.set(ifCase.expressionAlias.name, CTX_REF);
23553
24163
  }
23554
- const tmplOp = createTemplateOp(cView.xref, tagName, 'Conditional', Namespace.HTML, undefined /* TODO: figure out how i18n works with new control flow */, ifCase.sourceSpan);
23555
- unit.create.push(tmplOp);
24164
+ let ifCaseI18nMeta = undefined;
24165
+ if (ifCase.i18n !== undefined) {
24166
+ if (!(ifCase.i18n instanceof BlockPlaceholder)) {
24167
+ throw Error(`Unhandled i18n metadata type for if block: ${ifCase.i18n?.constructor.name}`);
24168
+ }
24169
+ ifCaseI18nMeta = ifCase.i18n;
24170
+ }
24171
+ const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.sourceSpan);
24172
+ unit.create.push(templateOp);
23556
24173
  if (firstXref === null) {
23557
24174
  firstXref = cView.xref;
23558
- firstSlotHandle = tmplOp.handle;
24175
+ firstSlotHandle = templateOp.handle;
23559
24176
  }
23560
24177
  const caseExpr = ifCase.expression ? convertAst(ifCase.expression, unit.job, null) : null;
23561
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, tmplOp.xref, tmplOp.handle, ifCase.expressionAlias);
24178
+ const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, templateOp.xref, templateOp.handle, ifCase.expressionAlias);
23562
24179
  conditions.push(conditionalCaseExpr);
23563
24180
  ingestNodes(cView, ifCase.children);
23564
24181
  }
@@ -23574,29 +24191,39 @@ function ingestSwitchBlock(unit, switchBlock) {
23574
24191
  let conditions = [];
23575
24192
  for (const switchCase of switchBlock.cases) {
23576
24193
  const cView = unit.job.allocateView(unit.xref);
23577
- const tmplOp = createTemplateOp(cView.xref, null, 'Case', Namespace.HTML, undefined /* TODO: figure out how i18n works with new control flow */, switchCase.sourceSpan);
23578
- unit.create.push(tmplOp);
24194
+ let switchCaseI18nMeta = undefined;
24195
+ if (switchCase.i18n !== undefined) {
24196
+ if (!(switchCase.i18n instanceof BlockPlaceholder)) {
24197
+ throw Error(`Unhandled i18n metadata type for switch block: ${switchCase.i18n?.constructor.name}`);
24198
+ }
24199
+ switchCaseI18nMeta = switchCase.i18n;
24200
+ }
24201
+ const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, null, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.sourceSpan);
24202
+ unit.create.push(templateOp);
23579
24203
  if (firstXref === null) {
23580
24204
  firstXref = cView.xref;
23581
- firstSlotHandle = tmplOp.handle;
24205
+ firstSlotHandle = templateOp.handle;
23582
24206
  }
23583
24207
  const caseExpr = switchCase.expression ?
23584
24208
  convertAst(switchCase.expression, unit.job, switchBlock.startSourceSpan) :
23585
24209
  null;
23586
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, tmplOp.xref, tmplOp.handle);
24210
+ const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, templateOp.xref, templateOp.handle);
23587
24211
  conditions.push(conditionalCaseExpr);
23588
24212
  ingestNodes(cView, switchCase.children);
23589
24213
  }
23590
24214
  const conditional = createConditionalOp(firstXref, firstSlotHandle, convertAst(switchBlock.expression, unit.job, null), conditions, switchBlock.sourceSpan);
23591
24215
  unit.update.push(conditional);
23592
24216
  }
23593
- function ingestDeferView(unit, suffix, children, sourceSpan) {
24217
+ function ingestDeferView(unit, suffix, i18nMeta, children, sourceSpan) {
24218
+ if (i18nMeta !== undefined && !(i18nMeta instanceof BlockPlaceholder)) {
24219
+ throw Error('Unhandled i18n metadata type for defer block');
24220
+ }
23594
24221
  if (children === undefined) {
23595
24222
  return null;
23596
24223
  }
23597
24224
  const secondaryView = unit.job.allocateView(unit.xref);
23598
24225
  ingestNodes(secondaryView, children);
23599
- const templateOp = createTemplateOp(secondaryView.xref, null, `Defer${suffix}`, Namespace.HTML, undefined, sourceSpan);
24226
+ const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, i18nMeta, sourceSpan);
23600
24227
  unit.create.push(templateOp);
23601
24228
  return templateOp;
23602
24229
  }
@@ -23606,10 +24233,10 @@ function ingestDeferBlock(unit, deferBlock) {
23606
24233
  throw new Error(`AssertionError: unable to find metadata for deferred block`);
23607
24234
  }
23608
24235
  // Generate the defer main view and all secondary views.
23609
- const main = ingestDeferView(unit, '', deferBlock.children, deferBlock.sourceSpan);
23610
- const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
23611
- const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
23612
- const error = ingestDeferView(unit, 'Error', deferBlock.error?.children, deferBlock.error?.sourceSpan);
24236
+ const main = ingestDeferView(unit, '', deferBlock.i18n, deferBlock.children, deferBlock.sourceSpan);
24237
+ const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.i18n, deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
24238
+ const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.i18n, deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
24239
+ const error = ingestDeferView(unit, 'Error', deferBlock.error?.i18n, deferBlock.error?.children, deferBlock.error?.sourceSpan);
23613
24240
  // Create the main defer op, and ops for all secondary views.
23614
24241
  const deferXref = unit.job.allocateXrefId();
23615
24242
  const deferOp = createDeferOp(deferXref, main.xref, main.handle, blockMeta, deferBlock.sourceSpan);
@@ -23691,12 +24318,7 @@ function ingestIcu(unit, icu) {
23691
24318
  const xref = unit.job.allocateXrefId();
23692
24319
  const icuNode = icu.i18n.nodes[0];
23693
24320
  unit.create.push(createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null));
23694
- const expressionPlaceholder = icuNode.expressionPlaceholder?.trimEnd();
23695
- if (expressionPlaceholder === undefined || icu.vars[expressionPlaceholder] === undefined) {
23696
- throw Error('ICU should have a text binding');
23697
- }
23698
- ingestBoundText(unit, icu.vars[expressionPlaceholder], [expressionPlaceholder]);
23699
- for (const [placeholder, text] of Object.entries(icu.placeholders)) {
24321
+ for (const [placeholder, text] of Object.entries({ ...icu.vars, ...icu.placeholders })) {
23700
24322
  if (text instanceof BoundText) {
23701
24323
  ingestBoundText(unit, text, [placeholder]);
23702
24324
  }
@@ -23748,8 +24370,17 @@ function ingestForBlock(unit, forBlock) {
23748
24370
  $odd: forBlock.contextVariables.$odd.name,
23749
24371
  $implicit: forBlock.item.name,
23750
24372
  };
24373
+ if (forBlock.i18n !== undefined && !(forBlock.i18n instanceof BlockPlaceholder)) {
24374
+ throw Error('AssertionError: Unhandled i18n metadata type or @for');
24375
+ }
24376
+ if (forBlock.empty?.i18n !== undefined &&
24377
+ !(forBlock.empty.i18n instanceof BlockPlaceholder)) {
24378
+ throw Error('AssertionError: Unhandled i18n metadata type or @empty');
24379
+ }
24380
+ const i18nPlaceholder = forBlock.i18n;
24381
+ const emptyI18nPlaceholder = forBlock.empty?.i18n;
23751
24382
  const tagName = ingestControlFlowInsertionPoint(unit, repeaterView.xref, forBlock);
23752
- const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, forBlock.sourceSpan);
24383
+ const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, i18nPlaceholder, emptyI18nPlaceholder, forBlock.sourceSpan);
23753
24384
  unit.create.push(repeaterCreate);
23754
24385
  const expression = convertAst(forBlock.expression, unit.job, convertSourceSpan(forBlock.expression.span, forBlock.sourceSpan));
23755
24386
  const repeater = createRepeaterOp(repeaterCreate.xref, repeaterCreate.handle, expression, forBlock.sourceSpan);
@@ -23792,6 +24423,16 @@ function convertAst(ast, job, baseSourceSpan) {
23792
24423
  else if (ast instanceof LiteralPrimitive) {
23793
24424
  return literal(ast.value, undefined, convertSourceSpan(ast.span, baseSourceSpan));
23794
24425
  }
24426
+ else if (ast instanceof Unary) {
24427
+ switch (ast.operator) {
24428
+ case '+':
24429
+ return new UnaryOperatorExpr(UnaryOperator.Plus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
24430
+ case '-':
24431
+ return new UnaryOperatorExpr(UnaryOperator.Minus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
24432
+ default:
24433
+ throw new Error(`AssertionError: unknown unary operator ${ast.operator}`);
24434
+ }
24435
+ }
23795
24436
  else if (ast instanceof Binary) {
23796
24437
  const operator = BINARY_OPERATORS.get(ast.operation);
23797
24438
  if (operator === undefined) {
@@ -23850,10 +24491,34 @@ function convertAst(ast, job, baseSourceSpan) {
23850
24491
  else if (ast instanceof EmptyExpr$1) {
23851
24492
  return new EmptyExpr(convertSourceSpan(ast.span, baseSourceSpan));
23852
24493
  }
24494
+ else if (ast instanceof PrefixNot) {
24495
+ return not(convertAst(ast.expression, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
24496
+ }
23853
24497
  else {
23854
24498
  throw new Error(`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`);
23855
24499
  }
23856
24500
  }
24501
+ function convertAstWithInterpolation(job, value, i18nMeta) {
24502
+ let expression;
24503
+ if (value instanceof Interpolation$1) {
24504
+ expression = new Interpolation(value.strings, value.expressions.map(e => convertAst(e, job, null)), Object.keys(asMessage(i18nMeta)?.placeholders ?? {}));
24505
+ }
24506
+ else if (value instanceof AST) {
24507
+ expression = convertAst(value, job, null);
24508
+ }
24509
+ else {
24510
+ expression = literal(value);
24511
+ }
24512
+ return expression;
24513
+ }
24514
+ // TODO: Can we populate Template binding kinds in ingest?
24515
+ const BINDING_KINDS = new Map([
24516
+ [0 /* e.BindingType.Property */, BindingKind.Property],
24517
+ [1 /* e.BindingType.Attribute */, BindingKind.Attribute],
24518
+ [2 /* e.BindingType.Class */, BindingKind.ClassName],
24519
+ [3 /* e.BindingType.Style */, BindingKind.StyleProperty],
24520
+ [4 /* e.BindingType.Animation */, BindingKind.Animation],
24521
+ ]);
23857
24522
  /**
23858
24523
  * Checks whether the given template is a plain ng-template (as opposed to another kind of template
23859
24524
  * such as a structural directive template or control flow template). This is checked based on the
@@ -23872,128 +24537,184 @@ function convertAst(ast, job, baseSourceSpan) {
23872
24537
  * | `<ng-template *ngIf>` (structural) | null |
23873
24538
  */
23874
24539
  function isPlainTemplate(tmpl) {
23875
- return splitNsName(tmpl.tagName ?? '')[1] === 'ng-template';
24540
+ return splitNsName(tmpl.tagName ?? '')[1] === NG_TEMPLATE_TAG_NAME$1;
23876
24541
  }
23877
24542
  /**
23878
- * Process all of the bindings on an element-like structure in the template AST and convert them
23879
- * to their IR representation.
24543
+ * Ensures that the i18nMeta, if provided, is an i18n.Message.
23880
24544
  */
23881
- function ingestBindings(unit, op, element) {
23882
- let flags = BindingFlags.None;
23883
- if (element instanceof Template) {
23884
- flags |= BindingFlags.OnNgTemplateElement;
23885
- if (element instanceof Template && isPlainTemplate(element)) {
23886
- flags |= BindingFlags.BindingTargetsTemplate;
23887
- }
23888
- const templateAttrFlags = flags | BindingFlags.BindingTargetsTemplate | BindingFlags.IsStructuralTemplateAttribute;
23889
- for (const attr of element.templateAttrs) {
23890
- if (attr instanceof TextAttribute) {
23891
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, templateAttrFlags | BindingFlags.TextValue);
23892
- }
23893
- else {
23894
- ingestBinding(unit, op.xref, attr.name, attr.value, attr.type, attr.unit, attr.securityContext, attr.sourceSpan, templateAttrFlags);
23895
- }
23896
- }
24545
+ function asMessage(i18nMeta) {
24546
+ if (i18nMeta == null) {
24547
+ return null;
24548
+ }
24549
+ if (!(i18nMeta instanceof Message)) {
24550
+ throw Error(`Expected i18n meta to be a Message, but got: ${i18nMeta.constructor.name}`);
23897
24551
  }
24552
+ return i18nMeta;
24553
+ }
24554
+ /**
24555
+ * Process all of the bindings on an element in the template AST and convert them to their IR
24556
+ * representation.
24557
+ */
24558
+ function ingestElementBindings(unit, op, element) {
24559
+ let bindings = new Array();
23898
24560
  for (const attr of element.attributes) {
23899
- // This is only attribute TextLiteral bindings, such as `attr.foo="bar"`. This can never be
23900
- // `[attr.foo]="bar"` or `attr.foo="{{bar}}"`, both of which will be handled as inputs with
23901
- // `BindingType.Attribute`.
23902
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, flags | BindingFlags.TextValue);
24561
+ // Attribute literal bindings, such as `attr.foo="bar"`.
24562
+ const securityContext = domSchema.securityContext(element.name, attr.name, true);
24563
+ 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));
23903
24564
  }
23904
24565
  for (const input of element.inputs) {
23905
- ingestBinding(unit, op.xref, input.name, input.value, input.type, input.unit, input.securityContext, input.sourceSpan, flags);
24566
+ // All dynamic bindings (both attribute and property bindings).
24567
+ 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));
23906
24568
  }
24569
+ unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
24570
+ unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
23907
24571
  for (const output of element.outputs) {
23908
- let listenerOp;
23909
- if (output.type === 1 /* e.ParsedEventType.Animation */) {
23910
- if (output.phase === null) {
23911
- throw Error('Animation listener should have a phase');
23912
- }
23913
- }
23914
- if (element instanceof Template && !isPlainTemplate(element)) {
23915
- unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, output.name, null));
23916
- continue;
23917
- }
23918
- listenerOp = createListenerOp(op.xref, op.handle, output.name, op.tag, output.phase, false, output.sourceSpan);
23919
- // if output.handler is a chain, then push each statement from the chain separately, and
23920
- // return the last one?
23921
- let handlerExprs;
23922
- let handler = output.handler;
23923
- if (handler instanceof ASTWithSource) {
23924
- handler = handler.ast;
24572
+ if (output.type === 1 /* e.ParsedEventType.Animation */ && output.phase === null) {
24573
+ throw Error('Animation listener should have a phase');
23925
24574
  }
23926
- if (handler instanceof Chain) {
23927
- handlerExprs = handler.expressions;
24575
+ 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));
24576
+ }
24577
+ // If any of the bindings on this element have an i18n message, then an i18n attrs configuration
24578
+ // op is also required.
24579
+ if (bindings.some(b => b?.i18nMessage) !== null) {
24580
+ unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
24581
+ }
24582
+ }
24583
+ /**
24584
+ * Process all of the bindings on a template in the template AST and convert them to their IR
24585
+ * representation.
24586
+ */
24587
+ function ingestTemplateBindings(unit, op, template, templateKind) {
24588
+ let bindings = new Array();
24589
+ for (const attr of template.templateAttrs) {
24590
+ if (attr instanceof TextAttribute) {
24591
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, attr.name, true);
24592
+ bindings.push(createTemplateBinding(unit, op.xref, 1 /* e.BindingType.Attribute */, attr.name, attr.value, null, securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
23928
24593
  }
23929
24594
  else {
23930
- handlerExprs = [handler];
24595
+ bindings.push(createTemplateBinding(unit, op.xref, attr.type, attr.name, astOf(attr.value), attr.unit, attr.securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
23931
24596
  }
23932
- if (handlerExprs.length === 0) {
23933
- throw new Error('Expected listener to have non-empty expression list.');
24597
+ }
24598
+ for (const attr of template.attributes) {
24599
+ // Attribute literal bindings, such as `attr.foo="bar"`.
24600
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, attr.name, true);
24601
+ bindings.push(createTemplateBinding(unit, op.xref, 1 /* e.BindingType.Attribute */, attr.name, attr.value, null, securityContext, false, templateKind, asMessage(attr.i18n), attr.sourceSpan));
24602
+ }
24603
+ for (const input of template.inputs) {
24604
+ // Dynamic bindings (both attribute and property bindings).
24605
+ bindings.push(createTemplateBinding(unit, op.xref, input.type, input.name, astOf(input.value), input.unit, input.securityContext, false, templateKind, asMessage(input.i18n), input.sourceSpan));
24606
+ }
24607
+ unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
24608
+ unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
24609
+ for (const output of template.outputs) {
24610
+ if (output.type === 1 /* e.ParsedEventType.Animation */ && output.phase === null) {
24611
+ throw Error('Animation listener should have a phase');
23934
24612
  }
23935
- const expressions = handlerExprs.map(expr => convertAst(expr, unit.job, output.handlerSpan));
23936
- const returnExpr = expressions.pop();
23937
- for (const expr of expressions) {
23938
- const stmtOp = createStatementOp(new ExpressionStatement(expr, expr.sourceSpan));
23939
- listenerOp.handlerOps.push(stmtOp);
24613
+ if (templateKind === TemplateKind.NgTemplate) {
24614
+ 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));
24615
+ }
24616
+ if (templateKind === TemplateKind.Structural &&
24617
+ output.type !== 1 /* e.ParsedEventType.Animation */) {
24618
+ // Animation bindings are excluded from the structural template's const array.
24619
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, output.name, false);
24620
+ unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, output.name, null, null, null, securityContext));
23940
24621
  }
23941
- listenerOp.handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
23942
- unit.create.push(listenerOp);
23943
- }
23944
- }
23945
- const BINDING_KINDS = new Map([
23946
- [0 /* e.BindingType.Property */, BindingKind.Property],
23947
- [1 /* e.BindingType.Attribute */, BindingKind.Attribute],
23948
- [2 /* e.BindingType.Class */, BindingKind.ClassName],
23949
- [3 /* e.BindingType.Style */, BindingKind.StyleProperty],
23950
- [4 /* e.BindingType.Animation */, BindingKind.Animation],
23951
- ]);
23952
- var BindingFlags;
23953
- (function (BindingFlags) {
23954
- BindingFlags[BindingFlags["None"] = 0] = "None";
23955
- /**
23956
- * The binding is to a static text literal and not to an expression.
23957
- */
23958
- BindingFlags[BindingFlags["TextValue"] = 1] = "TextValue";
23959
- /**
23960
- * The binding belongs to the `<ng-template>` side of a `t.Template`.
23961
- */
23962
- BindingFlags[BindingFlags["BindingTargetsTemplate"] = 2] = "BindingTargetsTemplate";
23963
- /**
23964
- * The binding is on a structural directive.
23965
- */
23966
- BindingFlags[BindingFlags["IsStructuralTemplateAttribute"] = 4] = "IsStructuralTemplateAttribute";
23967
- /**
23968
- * The binding is on a `t.Template`.
23969
- */
23970
- BindingFlags[BindingFlags["OnNgTemplateElement"] = 8] = "OnNgTemplateElement";
23971
- })(BindingFlags || (BindingFlags = {}));
23972
- function ingestBinding(view, xref, name, value, type, unit, securityContext, sourceSpan, flags) {
23973
- if (value instanceof ASTWithSource) {
23974
- value = value.ast;
23975
24622
  }
23976
- if (flags & BindingFlags.OnNgTemplateElement && !(flags & BindingFlags.BindingTargetsTemplate) &&
23977
- type === 0 /* e.BindingType.Property */) {
23978
- // This binding only exists for later const extraction, and is not an actual binding to be
23979
- // created.
23980
- view.create.push(createExtractedAttributeOp(xref, BindingKind.Property, name, null));
23981
- return;
24623
+ // TODO: Perhaps we could do this in a phase? (It likely wouldn't change the slot indices.)
24624
+ if (bindings.some(b => b?.i18nMessage) !== null) {
24625
+ unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
23982
24626
  }
23983
- let expression;
23984
- // TODO: We could easily generate source maps for subexpressions in these cases, but
23985
- // TemplateDefinitionBuilder does not. Should we do so?
23986
- if (value instanceof Interpolation$1) {
23987
- expression = new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, view.job, null)));
24627
+ }
24628
+ /**
24629
+ * Helper to ingest an individual binding on a template, either an explicit `ng-template`, or an
24630
+ * implicit template created via structural directive.
24631
+ *
24632
+ * Bindings on templates are *extremely* tricky. I have tried to isolate all of the confusing edge
24633
+ * cases into this function, and to comment it well to document the behavior.
24634
+ *
24635
+ * Some of this behavior is intuitively incorrect, and we should consider changing it in the future.
24636
+ *
24637
+ * @param view The compilation unit for the view containing the template.
24638
+ * @param xref The xref of the template op.
24639
+ * @param type The binding type, according to the parser. This is fairly reasonable, e.g. both
24640
+ * dynamic and static attributes have e.BindingType.Attribute.
24641
+ * @param name The binding's name.
24642
+ * @param value The bindings's value, which will either be an input AST expression, or a string
24643
+ * literal. Note that the input AST expression may or may not be const -- it will only be a
24644
+ * string literal if the parser considered it a text binding.
24645
+ * @param unit If the binding has a unit (e.g. `px` for style bindings), then this is the unit.
24646
+ * @param securityContext The security context of the binding.
24647
+ * @param isStructuralTemplateAttribute Whether this binding actually applies to the structural
24648
+ * ng-template. For example, an `ngFor` would actually apply to the structural template. (Most
24649
+ * bindings on structural elements target the inner element, not the template.)
24650
+ * @param templateKind Whether this is an explicit `ng-template` or an implicit template created by
24651
+ * a structural directive. This should never be a block template.
24652
+ * @param i18nMessage The i18n metadata for the binding, if any.
24653
+ * @param sourceSpan The source span of the binding.
24654
+ * @returns An IR binding op, or null if the binding should be skipped.
24655
+ */
24656
+ function createTemplateBinding(view, xref, type, name, value, unit, securityContext, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
24657
+ const isTextBinding = typeof value === 'string';
24658
+ // If this is a structural template, then several kinds of bindings should not result in an
24659
+ // update instruction.
24660
+ if (templateKind === TemplateKind.Structural) {
24661
+ if (!isStructuralTemplateAttribute &&
24662
+ (type === 0 /* e.BindingType.Property */ || type === 2 /* e.BindingType.Class */ ||
24663
+ type === 3 /* e.BindingType.Style */)) {
24664
+ // Because this binding doesn't really target the ng-template, it must be a binding on an
24665
+ // inner node of a structural template. We can't skip it entirely, because we still need it on
24666
+ // the ng-template's consts (e.g. for the purposes of directive matching). However, we should
24667
+ // not generate an update instruction for it.
24668
+ return createExtractedAttributeOp(xref, BindingKind.Property, name, null, null, i18nMessage, securityContext);
24669
+ }
24670
+ if (!isTextBinding && (type === 1 /* e.BindingType.Attribute */ || type === 4 /* e.BindingType.Animation */)) {
24671
+ // Again, this binding doesn't really target the ng-template; it actually targets the element
24672
+ // inside the structural template. In the case of non-text attribute or animation bindings,
24673
+ // the binding doesn't even show up on the ng-template const array, so we just skip it
24674
+ // entirely.
24675
+ return null;
24676
+ }
23988
24677
  }
23989
- else if (value instanceof AST) {
23990
- expression = convertAst(value, view.job, null);
24678
+ let bindingType = BINDING_KINDS.get(type);
24679
+ if (templateKind === TemplateKind.NgTemplate) {
24680
+ // We know we are dealing with bindings directly on an explicit ng-template.
24681
+ // Static attribute bindings should be collected into the const array as k/v pairs. Property
24682
+ // bindings should result in a `property` instruction, and `AttributeMarker.Bindings` const
24683
+ // entries.
24684
+ //
24685
+ // The difficulty is with dynamic attribute, style, and class bindings. These don't really make
24686
+ // sense on an `ng-template` and should probably be parser errors. However,
24687
+ // TemplateDefinitionBuilder generates `property` instructions for them, and so we do that as
24688
+ // well.
24689
+ //
24690
+ // Note that we do have a slight behavior difference with TemplateDefinitionBuilder: although
24691
+ // TDB emits `property` instructions for dynamic attributes, styles, and classes, only styles
24692
+ // and classes also get const collected into the `AttributeMarker.Bindings` field. Dynamic
24693
+ // attribute bindings are missing from the consts entirely. We choose to emit them into the
24694
+ // consts field anyway, to avoid creating special cases for something so arcane and nonsensical.
24695
+ if (type === 2 /* e.BindingType.Class */ || type === 3 /* e.BindingType.Style */ ||
24696
+ (type === 1 /* e.BindingType.Attribute */ && !isTextBinding)) {
24697
+ // TODO: These cases should be parse errors.
24698
+ bindingType = BindingKind.Property;
24699
+ }
23991
24700
  }
23992
- else {
23993
- expression = value;
24701
+ return createBindingOp(xref, bindingType, name, convertAstWithInterpolation(view.job, value, i18nMessage), unit, securityContext, isTextBinding, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan);
24702
+ }
24703
+ function makeListenerHandlerOps(unit, handler, handlerSpan) {
24704
+ handler = astOf(handler);
24705
+ const handlerOps = new Array();
24706
+ let handlerExprs = handler instanceof Chain ? handler.expressions : [handler];
24707
+ if (handlerExprs.length === 0) {
24708
+ throw new Error('Expected listener to have non-empty expression list.');
23994
24709
  }
23995
- const kind = BINDING_KINDS.get(type);
23996
- view.update.push(createBindingOp(xref, kind, name, expression, unit, securityContext, !!(flags & BindingFlags.TextValue), !!(flags & BindingFlags.IsStructuralTemplateAttribute), sourceSpan));
24710
+ const expressions = handlerExprs.map(expr => convertAst(expr, unit.job, handlerSpan));
24711
+ const returnExpr = expressions.pop();
24712
+ handlerOps.push(...expressions.map(e => createStatementOp(new ExpressionStatement(e, e.sourceSpan))));
24713
+ handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
24714
+ return handlerOps;
24715
+ }
24716
+ function astOf(ast) {
24717
+ return ast instanceof ASTWithSource ? ast.ast : ast;
23997
24718
  }
23998
24719
  /**
23999
24720
  * Process all of the local references on an element-like structure in the template AST and
@@ -24081,11 +24802,12 @@ function ingestControlFlowInsertionPoint(unit, xref, node) {
24081
24802
  // and they can be used in directive matching (in the case of `Template.templateAttrs`).
24082
24803
  if (root !== null) {
24083
24804
  for (const attr of root.attributes) {
24084
- ingestBinding(unit, xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue);
24805
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME$1, attr.name, true);
24806
+ unit.update.push(createBindingOp(xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
24085
24807
  }
24086
24808
  const tagName = root instanceof Element$1 ? root.name : root.tagName;
24087
24809
  // Don't pass along `ng-template` tag name since it enables directive matching.
24088
- return tagName === 'ng-template' ? null : tagName;
24810
+ return tagName === NG_TEMPLATE_TAG_NAME$1 ? null : tagName;
24089
24811
  }
24090
24812
  return null;
24091
24813
  }
@@ -27164,7 +27886,7 @@ class TemplateDefinitionBuilder {
27164
27886
  else {
27165
27887
  // ... otherwise we need to activate post-processing
27166
27888
  // to replace ICU placeholders with proper values
27167
- const placeholder = wrapI18nPlaceholder(`${I18N_ICU_MAPPING_PREFIX}${key}`);
27889
+ const placeholder = wrapI18nPlaceholder(`${I18N_ICU_MAPPING_PREFIX$1}${key}`);
27168
27890
  params[key] = literal(placeholder);
27169
27891
  icuMapping[key] = literalArr(refs);
27170
27892
  }
@@ -28784,24 +29506,6 @@ class TrackByBindingScope extends BindingScope {
28784
29506
  return this.componentAccessCount;
28785
29507
  }
28786
29508
  }
28787
- /**
28788
- * Creates a `CssSelector` given a tag name and a map of attributes
28789
- */
28790
- function createCssSelector(elementName, attributes) {
28791
- const cssSelector = new CssSelector();
28792
- const elementNameNoNs = splitNsName(elementName)[1];
28793
- cssSelector.setElement(elementNameNoNs);
28794
- Object.getOwnPropertyNames(attributes).forEach((name) => {
28795
- const nameNoNs = splitNsName(name)[1];
28796
- const value = attributes[name];
28797
- cssSelector.addAttribute(nameNoNs, value);
28798
- if (name.toLowerCase() === 'class') {
28799
- const classes = value.trim().split(/\s+/);
28800
- classes.forEach(className => cssSelector.addClassName(className));
28801
- }
28802
- });
28803
- return cssSelector;
28804
- }
28805
29509
  /**
28806
29510
  * Creates an array of expressions out of an `ngProjectAs` attributes
28807
29511
  * which can be added to the instruction parameters.
@@ -29530,6 +30234,7 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29530
30234
  }
29531
30235
  const hostJob = ingestHostBinding({
29532
30236
  componentName: name,
30237
+ componentSelector: selector,
29533
30238
  properties: bindings,
29534
30239
  events: eventBindings,
29535
30240
  attributes: hostBindingsMetadata.attributes,
@@ -29542,6 +30247,8 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29542
30247
  }
29543
30248
  return emitHostBindingFunction(hostJob);
29544
30249
  }
30250
+ let bindingId = 0;
30251
+ const getNextBindingId = () => `${bindingId++}`;
29545
30252
  const bindingContext = variable(CONTEXT_NAME);
29546
30253
  const styleBuilder = new StylingBuilder(bindingContext);
29547
30254
  const { styleAttr, classAttr } = hostBindingsMetadata.specialAttributes;
@@ -29594,7 +30301,7 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29594
30301
  for (const binding of allOtherBindings) {
29595
30302
  // resolve literal arrays and literal objects
29596
30303
  const value = binding.expression.visit(getValueConverter());
29597
- const bindingExpr = bindingFn(bindingContext, value);
30304
+ const bindingExpr = bindingFn(bindingContext, value, getNextBindingId);
29598
30305
  const { bindingName, instruction, isAttribute } = getBindingNameAndInstruction(binding);
29599
30306
  const securityContexts = bindingParser.calcPossibleSecurityContexts(selector, bindingName, isAttribute)
29600
30307
  .filter(context => context !== SecurityContext.NONE);
@@ -29673,10 +30380,12 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29673
30380
  // at the top of this method when all the input bindings were counted.
29674
30381
  totalHostVarsCount +=
29675
30382
  Math.max(call.allocateBindingSlots - MIN_STYLING_BINDING_SLOTS_REQUIRED, 0);
30383
+ const { params, stmts } = convertStylingCall(call, bindingContext, bindingFn, getNextBindingId);
30384
+ updateVariables.push(...stmts);
29676
30385
  updateInstructions.push({
29677
30386
  reference: instruction.reference,
29678
- paramsOrFn: convertStylingCall(call, bindingContext, bindingFn),
29679
- span: null
30387
+ paramsOrFn: params,
30388
+ span: null,
29680
30389
  });
29681
30390
  }
29682
30391
  });
@@ -29697,11 +30406,19 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29697
30406
  }
29698
30407
  return null;
29699
30408
  }
29700
- function bindingFn(implicit, value) {
29701
- return convertPropertyBinding(null, implicit, value, 'b');
30409
+ function bindingFn(implicit, value, getNextBindingIdFn) {
30410
+ return convertPropertyBinding(null, implicit, value, getNextBindingIdFn());
29702
30411
  }
29703
- function convertStylingCall(call, bindingContext, bindingFn) {
29704
- return call.params(value => bindingFn(bindingContext, value).currValExpr);
30412
+ function convertStylingCall(call, bindingContext, bindingFn, getNextBindingIdFn) {
30413
+ const stmts = [];
30414
+ const params = call.params(value => {
30415
+ const result = bindingFn(bindingContext, value, getNextBindingIdFn);
30416
+ if (Array.isArray(result.stmts) && result.stmts.length > 0) {
30417
+ stmts.push(...result.stmts);
30418
+ }
30419
+ return result.currValExpr;
30420
+ });
30421
+ return { params, stmts };
29705
30422
  }
29706
30423
  function getBindingNameAndInstruction(binding) {
29707
30424
  let bindingName = binding.name;
@@ -30140,15 +30857,15 @@ class DirectiveBinder {
30140
30857
  template.forEach(node => node.visit(this));
30141
30858
  }
30142
30859
  visitElement(element) {
30143
- this.visitElementOrTemplate(element.name, element);
30860
+ this.visitElementOrTemplate(element);
30144
30861
  }
30145
30862
  visitTemplate(template) {
30146
- this.visitElementOrTemplate('ng-template', template);
30863
+ this.visitElementOrTemplate(template);
30147
30864
  }
30148
- visitElementOrTemplate(elementName, node) {
30865
+ visitElementOrTemplate(node) {
30149
30866
  // First, determine the HTML shape of the node for the purpose of directive matching.
30150
30867
  // Do this by building up a `CssSelector` for the node.
30151
- const cssSelector = createCssSelector(elementName, getAttrsForDirectiveMatching(node));
30868
+ const cssSelector = createCssSelectorFromNode(node);
30152
30869
  // Next, use the `SelectorMatcher` to get the list of directives on the node.
30153
30870
  const directives = [];
30154
30871
  this.matcher.match(cssSelector, (_selector, results) => directives.push(...results));
@@ -31277,7 +31994,7 @@ function publishFacade(global) {
31277
31994
  * @description
31278
31995
  * Entry point for all public APIs of the compiler package.
31279
31996
  */
31280
- const VERSION = new Version('17.0.5');
31997
+ const VERSION = new Version('17.0.7');
31281
31998
 
31282
31999
  class CompilerConfig {
31283
32000
  constructor({ defaultEncapsulation = ViewEncapsulation.Emulated, preserveWhitespaces, strictInjectionParameters } = {}) {
@@ -32843,7 +33560,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$6 = '12.0.0';
32843
33560
  function compileDeclareClassMetadata(metadata) {
32844
33561
  const definitionMap = new DefinitionMap();
32845
33562
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$6));
32846
- definitionMap.set('version', literal('17.0.5'));
33563
+ definitionMap.set('version', literal('17.0.7'));
32847
33564
  definitionMap.set('ngImport', importExpr(Identifiers.core));
32848
33565
  definitionMap.set('type', metadata.type);
32849
33566
  definitionMap.set('decorators', metadata.decorators);
@@ -32951,7 +33668,7 @@ function createDirectiveDefinitionMap(meta) {
32951
33668
  // in 16.1 is actually used.
32952
33669
  const minVersion = hasTransformFunctions ? MINIMUM_PARTIAL_LINKER_VERSION$5 : '14.0.0';
32953
33670
  definitionMap.set('minVersion', literal(minVersion));
32954
- definitionMap.set('version', literal('17.0.5'));
33671
+ definitionMap.set('version', literal('17.0.7'));
32955
33672
  // e.g. `type: MyDirective`
32956
33673
  definitionMap.set('type', meta.type.value);
32957
33674
  if (meta.isStandalone) {
@@ -33228,7 +33945,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
33228
33945
  function compileDeclareFactoryFunction(meta) {
33229
33946
  const definitionMap = new DefinitionMap();
33230
33947
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
33231
- definitionMap.set('version', literal('17.0.5'));
33948
+ definitionMap.set('version', literal('17.0.7'));
33232
33949
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33233
33950
  definitionMap.set('type', meta.type.value);
33234
33951
  definitionMap.set('deps', compileDependencies(meta.deps));
@@ -33263,7 +33980,7 @@ function compileDeclareInjectableFromMetadata(meta) {
33263
33980
  function createInjectableDefinitionMap(meta) {
33264
33981
  const definitionMap = new DefinitionMap();
33265
33982
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
33266
- definitionMap.set('version', literal('17.0.5'));
33983
+ definitionMap.set('version', literal('17.0.7'));
33267
33984
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33268
33985
  definitionMap.set('type', meta.type.value);
33269
33986
  // Only generate providedIn property if it has a non-null value
@@ -33314,7 +34031,7 @@ function compileDeclareInjectorFromMetadata(meta) {
33314
34031
  function createInjectorDefinitionMap(meta) {
33315
34032
  const definitionMap = new DefinitionMap();
33316
34033
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
33317
- definitionMap.set('version', literal('17.0.5'));
34034
+ definitionMap.set('version', literal('17.0.7'));
33318
34035
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33319
34036
  definitionMap.set('type', meta.type.value);
33320
34037
  definitionMap.set('providers', meta.providers);
@@ -33347,7 +34064,7 @@ function createNgModuleDefinitionMap(meta) {
33347
34064
  throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
33348
34065
  }
33349
34066
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
33350
- definitionMap.set('version', literal('17.0.5'));
34067
+ definitionMap.set('version', literal('17.0.7'));
33351
34068
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33352
34069
  definitionMap.set('type', meta.type.value);
33353
34070
  // We only generate the keys in the metadata if the arrays contain values.
@@ -33398,7 +34115,7 @@ function compileDeclarePipeFromMetadata(meta) {
33398
34115
  function createPipeDefinitionMap(meta) {
33399
34116
  const definitionMap = new DefinitionMap();
33400
34117
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION));
33401
- definitionMap.set('version', literal('17.0.5'));
34118
+ definitionMap.set('version', literal('17.0.7'));
33402
34119
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33403
34120
  // e.g. `type: MyPipe`
33404
34121
  definitionMap.set('type', meta.type.value);
@@ -33431,5 +34148,5 @@ publishFacade(_global);
33431
34148
 
33432
34149
  // This file is not used to build this module. It is only used during editing
33433
34150
 
33434
- export { AST, ASTWithName, ASTWithSource, AbsoluteSourceSpan, ArrayType, ArrowFunctionExpr, AstMemoryEfficientTransformer, AstTransformer, Attribute, Binary, BinaryOperator, BinaryOperatorExpr, BindingPipe, Block, BlockParameter, BoundElementProperty, BuiltinType, BuiltinTypeName, CUSTOM_ELEMENTS_SCHEMA, Call, Chain, ChangeDetectionStrategy, CommaExpr, Comment, CompilerConfig, Conditional, ConditionalExpr, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DYNAMIC_TYPE, DeclareFunctionStmt, DeclareVarStmt, DomElementSchemaRegistry, DynamicImportExpr, EOF, Element, ElementSchemaRegistry, EmitterVisitorContext, EmptyExpr$1 as EmptyExpr, Expansion, ExpansionCase, Expression, ExpressionBinding, ExpressionStatement, ExpressionType, ExternalExpr, ExternalReference, FactoryTarget$1 as FactoryTarget, FunctionExpr, HtmlParser, HtmlTagDefinition, I18NHtmlParser, IfStmt, ImplicitReceiver, InstantiateExpr, Interpolation$1 as Interpolation, InterpolationConfig, InvokeFunctionExpr, JSDocComment, JitEvaluator, KeyedRead, KeyedWrite, LeadingComment, Lexer, LiteralArray, LiteralArrayExpr, LiteralExpr, LiteralMap, LiteralMapExpr, LiteralPrimitive, LocalizedString, MapType, MessageBundle, NONE_TYPE, NO_ERRORS_SCHEMA, NodeWithI18n, NonNullAssert, NotExpr, ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan, ParseSpan, ParseTreeResult, ParsedEvent, ParsedProperty, ParsedPropertyType, ParsedVariable, Parser$1 as Parser, ParserError, PrefixNot, PropertyRead, PropertyWrite, R3BoundTarget, Identifiers as R3Identifiers, R3NgModuleMetadataKind, R3SelectorScopeMode, R3TargetBinder, R3TemplateDependencyKind, ReadKeyExpr, ReadPropExpr, ReadVarExpr, RecursiveAstVisitor, RecursiveVisitor, ResourceLoader, ReturnStatement, STRING_TYPE, SafeCall, SafeKeyedRead, SafePropertyRead, SelectorContext, SelectorListContext, SelectorMatcher, Serializer, SplitInterpolation, Statement, StmtModifier, TagContentType, TaggedTemplateExpr, TemplateBindingParseResult, TemplateLiteral, TemplateLiteralElement, Text, ThisReceiver, BoundAttribute as TmplAstBoundAttribute, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, DeferredBlock as TmplAstDeferredBlock, DeferredBlockError as TmplAstDeferredBlockError, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredTrigger as TmplAstDeferredTrigger, Element$1 as TmplAstElement, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, Icu$1 as TmplAstIcu, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, RecursiveVisitor$1 as TmplAstRecursiveVisitor, Reference as TmplAstReference, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, Template as TmplAstTemplate, Text$3 as TmplAstText, TextAttribute as TmplAstTextAttribute, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, UnknownBlock as TmplAstUnknownBlock, Variable as TmplAstVariable, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, Token, TokenType, TransplantedType, TreeError, Type, TypeModifier, TypeofExpr, Unary, UnaryOperator, UnaryOperatorExpr, VERSION, VariableBinding, Version, ViewEncapsulation, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, Xliff, Xliff2, Xmb, XmlParser, Xtb, _ParseAST, compileClassDebugInfo, compileClassMetadata, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, compileDeclareDirectiveFromMetadata, compileDeclareFactoryFunction, compileDeclareInjectableFromMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileDeclarePipeFromMetadata, compileDirectiveFromMetadata, compileFactoryFunction, compileInjectable, compileInjector, compileNgModule, compilePipeFromMetadata, computeMsgId, core, createInjectableType, createMayBeForwardRefExpression, devOnlyGuardedExpression, emitDistinctChangesOnlyDefaultValue, getHtmlTagDefinition, getNsPrefix, getSafePropertyAccessString, identifierName, isIdentifier, isNgContainer, isNgContent, isNgTemplate, jsDocComment, leadingComment, literal, literalMap, makeBindingParser, mergeNsAndName, output_ast as outputAst, parseHostBindings, parseTemplate, preserveWhitespacesDefault, publishFacade, r3JitTypeSourceSpan, sanitizeIdentifier, splitNsName, verifyHostBindings, visitAll };
34151
+ export { AST, ASTWithName, ASTWithSource, AbsoluteSourceSpan, ArrayType, ArrowFunctionExpr, AstMemoryEfficientTransformer, AstTransformer, Attribute, Binary, BinaryOperator, BinaryOperatorExpr, BindingPipe, Block, BlockParameter, BoundElementProperty, BuiltinType, BuiltinTypeName, CUSTOM_ELEMENTS_SCHEMA, Call, Chain, ChangeDetectionStrategy, CommaExpr, Comment, CompilerConfig, Conditional, ConditionalExpr, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DYNAMIC_TYPE, DeclareFunctionStmt, DeclareVarStmt, DomElementSchemaRegistry, DynamicImportExpr, EOF, Element, ElementSchemaRegistry, EmitterVisitorContext, EmptyExpr$1 as EmptyExpr, Expansion, ExpansionCase, Expression, ExpressionBinding, ExpressionStatement, ExpressionType, ExternalExpr, ExternalReference, FactoryTarget$1 as FactoryTarget, FunctionExpr, HtmlParser, HtmlTagDefinition, I18NHtmlParser, IfStmt, ImplicitReceiver, InstantiateExpr, Interpolation$1 as Interpolation, InterpolationConfig, InvokeFunctionExpr, JSDocComment, JitEvaluator, KeyedRead, KeyedWrite, LeadingComment, Lexer, LiteralArray, LiteralArrayExpr, LiteralExpr, LiteralMap, LiteralMapExpr, LiteralPrimitive, LocalizedString, MapType, MessageBundle, NONE_TYPE, NO_ERRORS_SCHEMA, NodeWithI18n, NonNullAssert, NotExpr, ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan, ParseSpan, ParseTreeResult, ParsedEvent, ParsedProperty, ParsedPropertyType, ParsedVariable, Parser$1 as Parser, ParserError, PrefixNot, PropertyRead, PropertyWrite, R3BoundTarget, Identifiers as R3Identifiers, R3NgModuleMetadataKind, R3SelectorScopeMode, R3TargetBinder, R3TemplateDependencyKind, ReadKeyExpr, ReadPropExpr, ReadVarExpr, RecursiveAstVisitor, RecursiveVisitor, ResourceLoader, ReturnStatement, STRING_TYPE, SafeCall, SafeKeyedRead, SafePropertyRead, SelectorContext, SelectorListContext, SelectorMatcher, Serializer, SplitInterpolation, Statement, StmtModifier, TagContentType, TaggedTemplateExpr, TemplateBindingParseResult, TemplateLiteral, TemplateLiteralElement, Text, ThisReceiver, BoundAttribute as TmplAstBoundAttribute, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, DeferredBlock as TmplAstDeferredBlock, DeferredBlockError as TmplAstDeferredBlockError, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredTrigger as TmplAstDeferredTrigger, Element$1 as TmplAstElement, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, Icu$1 as TmplAstIcu, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, RecursiveVisitor$1 as TmplAstRecursiveVisitor, Reference as TmplAstReference, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, Template as TmplAstTemplate, Text$3 as TmplAstText, TextAttribute as TmplAstTextAttribute, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, UnknownBlock as TmplAstUnknownBlock, Variable as TmplAstVariable, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, Token, TokenType, TransplantedType, TreeError, Type, TypeModifier, TypeofExpr, Unary, UnaryOperator, UnaryOperatorExpr, VERSION, VariableBinding, Version, ViewEncapsulation, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, Xliff, Xliff2, Xmb, XmlParser, Xtb, _ParseAST, compileClassDebugInfo, compileClassMetadata, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, compileDeclareDirectiveFromMetadata, compileDeclareFactoryFunction, compileDeclareInjectableFromMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileDeclarePipeFromMetadata, compileDirectiveFromMetadata, compileFactoryFunction, compileInjectable, compileInjector, compileNgModule, compilePipeFromMetadata, computeMsgId, core, createCssSelectorFromNode, createInjectableType, createMayBeForwardRefExpression, devOnlyGuardedExpression, emitDistinctChangesOnlyDefaultValue, getHtmlTagDefinition, getNsPrefix, getSafePropertyAccessString, identifierName, isIdentifier, isNgContainer, isNgContent, isNgTemplate, jsDocComment, leadingComment, literal, literalMap, makeBindingParser, mergeNsAndName, output_ast as outputAst, parseHostBindings, parseTemplate, preserveWhitespacesDefault, publishFacade, r3JitTypeSourceSpan, sanitizeIdentifier, splitNsName, verifyHostBindings, visitAll };
33435
34152
  //# sourceMappingURL=compiler.mjs.map