@wavemaker-ai/angular-codegen 1.0.0-rc.332 → 1.0.0-rc.334

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.
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * @license Angular v20.3.19
4
+ * @license Angular v20.3.28
5
5
  * (c) 2010-2025 Google LLC. https://angular.dev/
6
6
  * License: MIT
7
7
  */
@@ -408,6 +408,151 @@ class SelectorlessMatcher {
408
408
  return this.registry.has(name) ? this.registry.get(name) : [];
409
409
  }
410
410
  }
411
+
412
+ /**
413
+ * A SecurityContext marks a location that has dangerous security implications, e.g. a DOM property
414
+ * like `innerHTML` that could cause Cross Site Scripting (XSS) security bugs when improperly
415
+ * handled.
416
+ *
417
+ * See DomSanitizer for more details on security in Angular applications.
418
+ *
419
+ * @publicApi
420
+ */
421
+ var SecurityContext;
422
+ (function (SecurityContext) {
423
+ SecurityContext[SecurityContext["NONE"] = 0] = "NONE";
424
+ SecurityContext[SecurityContext["HTML"] = 1] = "HTML";
425
+ SecurityContext[SecurityContext["STYLE"] = 2] = "STYLE";
426
+ SecurityContext[SecurityContext["SCRIPT"] = 3] = "SCRIPT";
427
+ SecurityContext[SecurityContext["URL"] = 4] = "URL";
428
+ SecurityContext[SecurityContext["RESOURCE_URL"] = 5] = "RESOURCE_URL";
429
+ SecurityContext[SecurityContext["ATTRIBUTE_NO_BINDING"] = 6] = "ATTRIBUTE_NO_BINDING";
430
+ })(SecurityContext || (SecurityContext = {}));
431
+ // =================================================================================================
432
+ // =================================================================================================
433
+ // =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
434
+ // =================================================================================================
435
+ // =================================================================================================
436
+ //
437
+ // DO NOT EDIT THIS LIST OF SECURITY SENSITIVE PROPERTIES WITHOUT A SECURITY REVIEW!
438
+ //
439
+ // =================================================================================================
440
+ /**
441
+ * Map from tagName|propertyName to SecurityContext. Properties applying to all tags use '*'.
442
+ */
443
+ let _SECURITY_SCHEMA;
444
+ const SVG_NAMESPACE$1 = 'svg';
445
+ const MATH_ML_NAMESPACE$1 = 'math';
446
+ /**
447
+ * @remarks Keep is a copy of DOM Security Schema.
448
+ * @see [SECURITY_SCHEMA](../../../compiler/src/schema/dom_security_schema.ts)
449
+ */
450
+ function SECURITY_SCHEMA() {
451
+ if (!_SECURITY_SCHEMA) {
452
+ _SECURITY_SCHEMA = {};
453
+ // Case is insignificant below, all element and attribute names are lower-cased for lookup.
454
+ registerContext(SecurityContext.HTML, /** Namespace */ undefined, [
455
+ ['iframe', ['srcdoc']],
456
+ ['*', ['innerHTML', 'outerHTML']],
457
+ ]);
458
+ registerContext(SecurityContext.STYLE, /** Namespace */ undefined, [['*', ['style']]]);
459
+ // NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them.
460
+ registerContext(SecurityContext.URL, /** Namespace */ undefined, [
461
+ ['*', ['formAction']],
462
+ ['area', ['href']],
463
+ ['a', ['href', 'xlink:href']],
464
+ ['form', ['action']],
465
+ // The below two items are safe and should be removed but they require a G3 clean-up as a small number of tests fail.
466
+ ['img', ['src']],
467
+ ['video', ['src']],
468
+ ]);
469
+ registerContext(SecurityContext.URL, MATH_ML_NAMESPACE$1, [
470
+ // MathML namespace
471
+ // https://crsrc.org/c/third_party/blink/renderer/core/sanitizer/sanitizer.cc;l=753-768;drc=b3eb16372dcd3317d65e9e0265015e322494edcd;bpv=1;bpt=1
472
+ ['*', ['href', 'xlink:href']],
473
+ ['annotation', ['href', 'xlink:href']],
474
+ ['annotation-xml', ['href', 'xlink:href']],
475
+ ['maction', ['href', 'xlink:href']],
476
+ ['malignmark', ['href', 'xlink:href']],
477
+ ['math', ['href', 'xlink:href']],
478
+ ['mroot', ['href', 'xlink:href']],
479
+ ['msqrt', ['href', 'xlink:href']],
480
+ ['merror', ['href', 'xlink:href']],
481
+ ['mfrac', ['href', 'xlink:href']],
482
+ ['mglyph', ['href', 'xlink:href']],
483
+ ['msub', ['href', 'xlink:href']],
484
+ ['msup', ['href', 'xlink:href']],
485
+ ['msubsup', ['href', 'xlink:href']],
486
+ ['mmultiscripts', ['href', 'xlink:href']],
487
+ ['mprescripts', ['href', 'xlink:href']],
488
+ ['mi', ['href', 'xlink:href']],
489
+ ['mn', ['href', 'xlink:href']],
490
+ ['mo', ['href', 'xlink:href']],
491
+ ['mpadded', ['href', 'xlink:href']],
492
+ ['mphantom', ['href', 'xlink:href']],
493
+ ['mrow', ['href', 'xlink:href']],
494
+ ['ms', ['href', 'xlink:href']],
495
+ ['mspace', ['href', 'xlink:href']],
496
+ ['mstyle', ['href', 'xlink:href']],
497
+ ['mtable', ['href', 'xlink:href']],
498
+ ['mtd', ['href', 'xlink:href']],
499
+ ['mtr', ['href', 'xlink:href']],
500
+ ['mtext', ['href', 'xlink:href']],
501
+ ['mover', ['href', 'xlink:href']],
502
+ ['munder', ['href', 'xlink:href']],
503
+ ['munderover', ['href', 'xlink:href']],
504
+ ['semantics', ['href', 'xlink:href']],
505
+ ['none', ['href', 'xlink:href']],
506
+ ]);
507
+ registerContext(SecurityContext.RESOURCE_URL, /** Namespace */ undefined, [
508
+ ['base', ['href']],
509
+ ['embed', ['src']],
510
+ ['frame', ['src']],
511
+ ['iframe', ['src']],
512
+ ['link', ['href']],
513
+ ['object', ['codebase', 'data']],
514
+ ]);
515
+ registerContext(SecurityContext.URL, SVG_NAMESPACE$1, [['a', ['href', 'xlink:href']]]);
516
+ // Keep this in sync with SECURITY_SENSITIVE_ELEMENTS in packages/core/src/sanitization/sanitization.ts
517
+ // Unknown is the internal tag name for unknown elements example used for host-bindings.
518
+ // These are unsafe as `attributeName` can be `href` or `xlink:href`
519
+ // See: http://b/463880509#comment7
520
+ registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, SVG_NAMESPACE$1, [
521
+ ['animate', ['attributeName', 'values', 'to', 'from']],
522
+ ['set', ['to', 'attributeName']],
523
+ ['animateMotion', ['attributeName']],
524
+ ['animateTransform', ['attributeName']],
525
+ ]);
526
+ registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, /** Namespace */ undefined, [
527
+ [
528
+ 'unknown',
529
+ [
530
+ 'attributeName',
531
+ 'values',
532
+ 'to',
533
+ 'from',
534
+ 'sandbox',
535
+ 'allow',
536
+ 'allowFullscreen',
537
+ 'referrerPolicy',
538
+ 'csp',
539
+ 'fetchPriority',
540
+ ],
541
+ ],
542
+ ['iframe', ['sandbox', 'allow', 'allowFullscreen', 'referrerPolicy', 'csp', 'fetchPriority']],
543
+ ]);
544
+ }
545
+ return _SECURITY_SCHEMA;
546
+ }
547
+ function registerContext(ctx, namespace, specs) {
548
+ for (const [element, attributeNames] of specs) {
549
+ let tagName = namespace && element !== 'unknown' ? `:${namespace}:${element}` : element;
550
+ tagName = tagName.toLowerCase();
551
+ for (const attr of attributeNames) {
552
+ _SECURITY_SCHEMA[`${tagName}|${attr.toLowerCase()}`] = ctx;
553
+ }
554
+ }
555
+ }
411
556
  var ViewEncapsulation$1;
412
557
  (function (ViewEncapsulation) {
413
558
  ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
@@ -433,16 +578,6 @@ const CUSTOM_ELEMENTS_SCHEMA = {
433
578
  const NO_ERRORS_SCHEMA = {
434
579
  name: 'no-errors-schema',
435
580
  };
436
- var SecurityContext;
437
- (function (SecurityContext) {
438
- SecurityContext[SecurityContext["NONE"] = 0] = "NONE";
439
- SecurityContext[SecurityContext["HTML"] = 1] = "HTML";
440
- SecurityContext[SecurityContext["STYLE"] = 2] = "STYLE";
441
- SecurityContext[SecurityContext["SCRIPT"] = 3] = "SCRIPT";
442
- SecurityContext[SecurityContext["URL"] = 4] = "URL";
443
- SecurityContext[SecurityContext["RESOURCE_URL"] = 5] = "RESOURCE_URL";
444
- SecurityContext[SecurityContext["ATTRIBUTE_NO_BINDING"] = 6] = "ATTRIBUTE_NO_BINDING";
445
- })(SecurityContext || (SecurityContext = {}));
446
581
  var MissingTranslationStrategy;
447
582
  (function (MissingTranslationStrategy) {
448
583
  MissingTranslationStrategy[MissingTranslationStrategy["Error"] = 0] = "Error";
@@ -11451,10 +11586,7 @@ class ElementAttributes {
11451
11586
  if (value === null) {
11452
11587
  throw Error('Attribute, i18n attribute, & style element attributes must have a value');
11453
11588
  }
11454
- if (trustedValueFn !== null) {
11455
- if (!isStringLiteral(value)) {
11456
- throw Error('AssertionError: extracted attribute value should be string literal');
11457
- }
11589
+ if (trustedValueFn !== null && isStringLiteral(value)) {
11458
11590
  array.push(taggedTemplate(trustedValueFn, new TemplateLiteralExpr([new TemplateLiteralElementExpr(value.value)], []), undefined, value.sourceSpan));
11459
11591
  }
11460
11592
  else {
@@ -19409,162 +19541,8 @@ function interleave(left, right) {
19409
19541
  return result;
19410
19542
  }
19411
19543
 
19412
- // =================================================================================================
19413
- // =================================================================================================
19414
- // =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
19415
- // =================================================================================================
19416
- // =================================================================================================
19417
- //
19418
- // DO NOT EDIT THIS LIST OF SECURITY SENSITIVE PROPERTIES WITHOUT A SECURITY REVIEW!
19419
- //
19420
- // =================================================================================================
19421
- /** Map from tagName|propertyName to SecurityContext. Properties applying to all tags use '*'. */
19422
- let _SECURITY_SCHEMA;
19423
- function SECURITY_SCHEMA() {
19424
- if (!_SECURITY_SCHEMA) {
19425
- _SECURITY_SCHEMA = {};
19426
- // Case is insignificant below, all element and attribute names are lower-cased for lookup.
19427
- registerContext(SecurityContext.HTML, ['iframe|srcdoc', '*|innerHTML', '*|outerHTML']);
19428
- registerContext(SecurityContext.STYLE, ['*|style']);
19429
- // NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them.
19430
- registerContext(SecurityContext.URL, [
19431
- '*|formAction',
19432
- 'area|href',
19433
- 'area|ping',
19434
- 'audio|src',
19435
- 'a|href',
19436
- 'a|xlink:href',
19437
- 'a|ping',
19438
- 'blockquote|cite',
19439
- 'body|background',
19440
- 'del|cite',
19441
- 'form|action',
19442
- 'img|src',
19443
- 'input|src',
19444
- 'ins|cite',
19445
- 'q|cite',
19446
- 'source|src',
19447
- 'track|src',
19448
- 'video|poster',
19449
- 'video|src',
19450
- // MathML namespace
19451
- // https://crsrc.org/c/third_party/blink/renderer/core/sanitizer/sanitizer.cc;l=753-768;drc=b3eb16372dcd3317d65e9e0265015e322494edcd;bpv=1;bpt=1
19452
- 'annotation|href',
19453
- 'annotation|xlink:href',
19454
- 'annotation-xml|href',
19455
- 'annotation-xml|xlink:href',
19456
- 'maction|href',
19457
- 'maction|xlink:href',
19458
- 'malignmark|href',
19459
- 'malignmark|xlink:href',
19460
- 'math|href',
19461
- 'math|xlink:href',
19462
- 'mroot|href',
19463
- 'mroot|xlink:href',
19464
- 'msqrt|href',
19465
- 'msqrt|xlink:href',
19466
- 'merror|href',
19467
- 'merror|xlink:href',
19468
- 'mfrac|href',
19469
- 'mfrac|xlink:href',
19470
- 'mglyph|href',
19471
- 'mglyph|xlink:href',
19472
- 'msub|href',
19473
- 'msub|xlink:href',
19474
- 'msup|href',
19475
- 'msup|xlink:href',
19476
- 'msubsup|href',
19477
- 'msubsup|xlink:href',
19478
- 'mmultiscripts|href',
19479
- 'mmultiscripts|xlink:href',
19480
- 'mprescripts|href',
19481
- 'mprescripts|xlink:href',
19482
- 'mi|href',
19483
- 'mi|xlink:href',
19484
- 'mn|href',
19485
- 'mn|xlink:href',
19486
- 'mo|href',
19487
- 'mo|xlink:href',
19488
- 'mpadded|href',
19489
- 'mpadded|xlink:href',
19490
- 'mphantom|href',
19491
- 'mphantom|xlink:href',
19492
- 'mrow|href',
19493
- 'mrow|xlink:href',
19494
- 'ms|href',
19495
- 'ms|xlink:href',
19496
- 'mspace|href',
19497
- 'mspace|xlink:href',
19498
- 'mstyle|href',
19499
- 'mstyle|xlink:href',
19500
- 'mtable|href',
19501
- 'mtable|xlink:href',
19502
- 'mtd|href',
19503
- 'mtd|xlink:href',
19504
- 'mtr|href',
19505
- 'mtr|xlink:href',
19506
- 'mtext|href',
19507
- 'mtext|xlink:href',
19508
- 'mover|href',
19509
- 'mover|xlink:href',
19510
- 'munder|href',
19511
- 'munder|xlink:href',
19512
- 'munderover|href',
19513
- 'munderover|xlink:href',
19514
- 'semantics|href',
19515
- 'semantics|xlink:href',
19516
- 'none|href',
19517
- 'none|xlink:href',
19518
- ]);
19519
- registerContext(SecurityContext.RESOURCE_URL, [
19520
- 'applet|code',
19521
- 'applet|codebase',
19522
- 'base|href',
19523
- 'embed|src',
19524
- 'frame|src',
19525
- 'head|profile',
19526
- 'html|manifest',
19527
- 'iframe|src',
19528
- 'link|href',
19529
- 'media|src',
19530
- 'object|codebase',
19531
- 'object|data',
19532
- 'script|src',
19533
- // The below two are for Script SVG
19534
- // See: https://developer.mozilla.org/en-US/docs/Web/API/SVGScriptElement/href
19535
- 'script|href',
19536
- 'script|xlink:href',
19537
- ]);
19538
- // Keep this in sync with SECURITY_SENSITIVE_ELEMENTS in packages/core/src/sanitization/sanitization.ts
19539
- // Unknown is the internal tag name for unknown elements example used for host-bindings.
19540
- // These are unsafe as `attributeName` can be `href` or `xlink:href`
19541
- // See: http://b/463880509#comment7
19542
- registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, [
19543
- 'animate|attributeName',
19544
- 'set|attributeName',
19545
- 'animateMotion|attributeName',
19546
- 'animateTransform|attributeName',
19547
- 'unknown|attributeName',
19548
- 'iframe|sandbox',
19549
- 'iframe|allow',
19550
- 'iframe|allowFullscreen',
19551
- 'iframe|referrerPolicy',
19552
- 'iframe|csp',
19553
- 'iframe|fetchPriority',
19554
- 'unknown|sandbox',
19555
- 'unknown|allow',
19556
- 'unknown|allowFullscreen',
19557
- 'unknown|referrerPolicy',
19558
- 'unknown|csp',
19559
- 'unknown|fetchPriority',
19560
- ]);
19561
- }
19562
- return _SECURITY_SCHEMA;
19563
- }
19564
- function registerContext(ctx, specs) {
19565
- for (const spec of specs)
19566
- _SECURITY_SCHEMA[spec.toLowerCase()] = ctx;
19567
- }
19544
+ const SVG_NAMESPACE = 'svg';
19545
+ const MATH_ML_NAMESPACE = 'math';
19568
19546
 
19569
19547
  class ElementSchemaRegistry {
19570
19548
  }
@@ -19573,6 +19551,11 @@ const BOOLEAN = 'boolean';
19573
19551
  const NUMBER = 'number';
19574
19552
  const STRING = 'string';
19575
19553
  const OBJECT = 'object';
19554
+ function normalizeTagName(tagName) {
19555
+ const tagNameLower = tagName.toLowerCase();
19556
+ const [ns, name] = splitNsName(tagNameLower, false);
19557
+ return ns === SVG_NAMESPACE || ns === MATH_ML_NAMESPACE ? `:${ns}:${name}` : name;
19558
+ }
19576
19559
  /**
19577
19560
  * This array represents the DOM schema. It encodes inheritance, properties, and events.
19578
19561
  *
@@ -19918,8 +19901,9 @@ class DomElementSchemaRegistry extends ElementSchemaRegistry {
19918
19901
  if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
19919
19902
  return true;
19920
19903
  }
19921
- if (tagName.indexOf('-') > -1) {
19922
- if (isNgContainer(tagName) || isNgContent(tagName)) {
19904
+ const normalizedTag = normalizeTagName(tagName);
19905
+ if (normalizedTag.includes('-')) {
19906
+ if (isNgContainer(normalizedTag) || isNgContent(normalizedTag)) {
19923
19907
  return false;
19924
19908
  }
19925
19909
  if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
@@ -19928,15 +19912,16 @@ class DomElementSchemaRegistry extends ElementSchemaRegistry {
19928
19912
  return true;
19929
19913
  }
19930
19914
  }
19931
- const elementProperties = this._schema.get(tagName.toLowerCase()) || this._schema.get('unknown');
19915
+ const elementProperties = this._schema.get(normalizedTag) || this._schema.get('unknown');
19932
19916
  return elementProperties.has(propName);
19933
19917
  }
19934
19918
  hasElement(tagName, schemaMetas) {
19935
19919
  if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
19936
19920
  return true;
19937
19921
  }
19938
- if (tagName.indexOf('-') > -1) {
19939
- if (isNgContainer(tagName) || isNgContent(tagName)) {
19922
+ const normalizedTag = normalizeTagName(tagName);
19923
+ if (normalizedTag.includes('-')) {
19924
+ if (isNgContainer(normalizedTag) || isNgContent(normalizedTag)) {
19940
19925
  return true;
19941
19926
  }
19942
19927
  if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
@@ -19944,7 +19929,7 @@ class DomElementSchemaRegistry extends ElementSchemaRegistry {
19944
19929
  return true;
19945
19930
  }
19946
19931
  }
19947
- return this._schema.has(tagName.toLowerCase());
19932
+ return this._schema.has(normalizedTag);
19948
19933
  }
19949
19934
  /**
19950
19935
  * securityContext returns the security context for the given property on the given DOM tag.
@@ -19961,16 +19946,15 @@ class DomElementSchemaRegistry extends ElementSchemaRegistry {
19961
19946
  // NB: For security purposes, use the mapped property name, not the attribute name.
19962
19947
  propName = this.getMappedPropName(propName);
19963
19948
  }
19964
- // Make sure comparisons are case insensitive, so that case differences between attribute and
19965
- // property names do not have a security impact.
19966
- tagName = tagName.toLowerCase();
19949
+ const normalizedTag = normalizeTagName(tagName);
19967
19950
  propName = propName.toLowerCase();
19968
- let ctx = SECURITY_SCHEMA()[tagName + '|' + propName];
19969
- if (ctx) {
19970
- return ctx;
19971
- }
19972
- ctx = SECURITY_SCHEMA()['*|' + propName];
19973
- return ctx ? ctx : SecurityContext.NONE;
19951
+ const [namespace] = splitNsName(normalizedTag, false);
19952
+ const securitySchema = SECURITY_SCHEMA();
19953
+ const ctx = securitySchema[normalizedTag + '|' + propName] ??
19954
+ (namespace ? securitySchema[`:${namespace}:*|${propName}`] : undefined) ??
19955
+ securitySchema['*|' + propName] ??
19956
+ SecurityContext.NONE;
19957
+ return ctx;
19974
19958
  }
19975
19959
  getMappedPropName(propName) {
19976
19960
  return _ATTR_TO_PROP.get(propName) ?? propName;
@@ -20004,12 +19988,14 @@ class DomElementSchemaRegistry extends ElementSchemaRegistry {
20004
19988
  return Array.from(this._schema.keys());
20005
19989
  }
20006
19990
  allKnownAttributesOfElement(tagName) {
20007
- const elementProperties = this._schema.get(tagName.toLowerCase()) || this._schema.get('unknown');
19991
+ const normalizedTag = normalizeTagName(tagName);
19992
+ const elementProperties = this._schema.get(normalizedTag) || this._schema.get('unknown');
20008
19993
  // Convert properties to attributes.
20009
19994
  return Array.from(elementProperties.keys()).map((prop) => _PROP_TO_ATTR.get(prop) ?? prop);
20010
19995
  }
20011
19996
  allKnownEventsOfElement(tagName) {
20012
- return Array.from(this._eventSchema.get(tagName.toLowerCase()) ?? []);
19997
+ const normalizedTag = normalizeTagName(tagName);
19998
+ return Array.from(this._eventSchema.get(normalizedTag) ?? []);
20013
19999
  }
20014
20000
  normalizeAnimationStyleProperty(propName) {
20015
20001
  return dashCaseToCamelCase(propName);
@@ -20858,7 +20844,7 @@ class I18nMetaVisitor {
20858
20844
  else {
20859
20845
  isTrustedType = isTrustedTypesSink(node.name, name);
20860
20846
  }
20861
- if (isTrustedType) {
20847
+ if (isTrustedType || isPossibleEventHandler(name)) {
20862
20848
  this._reportError(attr, `Translating attribute '${name}' is disallowed for security reasons.`);
20863
20849
  }
20864
20850
  else {
@@ -20989,6 +20975,16 @@ function i18nMetaToJSDoc(meta) {
20989
20975
  }
20990
20976
  return jsDocComment(tags);
20991
20977
  }
20978
+ /**
20979
+ * Check if the propertyName is a potential event handler.
20980
+ * We consider a property to be a potential event handler if its name is longer than 2 characters and starts with 'on' (e.g. 'onclick', 'onload', etc.).
20981
+ * @param propertyName The name of the property to check.
20982
+ * @returns True if the property is a potential event handler, false otherwise.
20983
+ */
20984
+ function isPossibleEventHandler(propertyName) {
20985
+ const name = propertyName.toLowerCase();
20986
+ return name.length > 2 && name !== 'only' && name.startsWith('on');
20987
+ }
20992
20988
 
20993
20989
  /** Closure uses `goog.getMsg(message)` to lookup translations */
20994
20990
  const GOOG_GET_MSG = 'goog.getMsg';
@@ -24214,6 +24210,61 @@ function updatePlaceholder(op, value, i18nContexts, icuPlaceholders) {
24214
24210
  }
24215
24211
  }
24216
24212
 
24213
+ /**
24214
+ * Wraps static i18n extracted attributes in their corresponding sanitizers/validators.
24215
+ */
24216
+ function resolveI18nAttrSanitizers(job) {
24217
+ const tagNamesByElement = new Map();
24218
+ for (const unit of job.units) {
24219
+ for (const op of unit.ops()) {
24220
+ if (op.kind === OpKind.ElementStart || op.kind === OpKind.Template) {
24221
+ let tag = op.tag ?? '';
24222
+ switch (op.namespace) {
24223
+ case Namespace.SVG:
24224
+ tag = `:${SVG_NAMESPACE}:${tag}`;
24225
+ break;
24226
+ case Namespace.Math:
24227
+ tag = `:${MATH_ML_NAMESPACE}:${tag}`;
24228
+ break;
24229
+ }
24230
+ tagNamesByElement.set(op.xref, tag);
24231
+ }
24232
+ }
24233
+ }
24234
+ for (const unit of job.units) {
24235
+ for (const op of unit.create) {
24236
+ if (op.kind === OpKind.ExtractedAttribute &&
24237
+ op.i18nContext !== null &&
24238
+ op.expression !== null) {
24239
+ const tagName = tagNamesByElement.get(op.target) ?? '';
24240
+ let expr = op.expression;
24241
+ switch (op.securityContext) {
24242
+ case SecurityContext.HTML:
24243
+ expr = importExpr(Identifiers.sanitizeHtml).callFn([expr]);
24244
+ break;
24245
+ case SecurityContext.STYLE:
24246
+ expr = importExpr(Identifiers.sanitizeStyle).callFn([expr]);
24247
+ break;
24248
+ case SecurityContext.SCRIPT:
24249
+ expr = importExpr(Identifiers.sanitizeScript).callFn([expr]);
24250
+ break;
24251
+ case SecurityContext.URL:
24252
+ expr = importExpr(Identifiers.sanitizeUrl).callFn([expr]);
24253
+ break;
24254
+ case SecurityContext.RESOURCE_URL:
24255
+ expr = importExpr(Identifiers.sanitizeResourceUrl).callFn([expr]);
24256
+ break;
24257
+ case SecurityContext.ATTRIBUTE_NO_BINDING:
24258
+ expr = importExpr(Identifiers.validateAttribute)
24259
+ .callFn([expr, literal(tagName), literal(op.name)]);
24260
+ break;
24261
+ }
24262
+ op.expression = expr;
24263
+ }
24264
+ }
24265
+ }
24266
+ }
24267
+
24217
24268
  /**
24218
24269
  * Resolves lexical references in views (`ir.LexicalReadExpr`) to either a target variable or to
24219
24270
  * property reads on the top-level component context.
@@ -24379,15 +24430,14 @@ function resolveSanitizers(job) {
24379
24430
  case OpKind.Property:
24380
24431
  case OpKind.Attribute:
24381
24432
  case OpKind.DomProperty:
24433
+ case OpKind.TwoWayProperty:
24382
24434
  let sanitizerFn = null;
24383
24435
  if (Array.isArray(op.securityContext) &&
24384
- op.securityContext.length === 2 &&
24385
- op.securityContext.includes(SecurityContext.URL) &&
24386
- op.securityContext.includes(SecurityContext.RESOURCE_URL)) {
24387
- // When the host element isn't known, some URL attributes (such as "src" and "href") may
24388
- // be part of multiple different security contexts. In this case we use special
24389
- // sanitization function and select the actual sanitizer at runtime based on a tag name
24390
- // that is provided while invoking sanitization function.
24436
+ hasCompositeUrlSecurityContext(op.securityContext)) {
24437
+ // When the host element isn't known, attributes such as `href`, `src`, `data`,
24438
+ // `action`, and `codebase` may be part of multiple security contexts. In this case we
24439
+ // use a special sanitization function and select the actual behavior at runtime based
24440
+ // on the concrete host element.
24391
24441
  sanitizerFn = Identifiers.sanitizeUrlOrResourceUrl;
24392
24442
  }
24393
24443
  else {
@@ -24399,21 +24449,47 @@ function resolveSanitizers(job) {
24399
24449
  }
24400
24450
  }
24401
24451
  }
24452
+ function hasCompositeUrlSecurityContext(securityContext) {
24453
+ let hasUrlContext = false;
24454
+ let hasResourceUrlContext = false;
24455
+ let hasNoneContext = false;
24456
+ for (const context of securityContext) {
24457
+ switch (context) {
24458
+ case SecurityContext.URL:
24459
+ hasUrlContext = true;
24460
+ break;
24461
+ case SecurityContext.RESOURCE_URL:
24462
+ hasResourceUrlContext = true;
24463
+ break;
24464
+ case SecurityContext.NONE:
24465
+ hasNoneContext = true;
24466
+ break;
24467
+ default:
24468
+ return false;
24469
+ }
24470
+ }
24471
+ return (((hasUrlContext || hasResourceUrlContext) && hasNoneContext) ||
24472
+ (hasUrlContext && hasResourceUrlContext));
24473
+ }
24402
24474
  /**
24403
- * Asserts that there is only a single security context and returns it.
24475
+ * Asserts that there is only a single non-NONE security context and returns it.
24404
24476
  */
24405
24477
  function getOnlySecurityContext(securityContext) {
24406
- if (Array.isArray(securityContext)) {
24407
- if (securityContext.length > 1) {
24408
- // TODO: What should we do here? TDB just took the first one, but this feels like something we
24409
- // would want to know about and create a special case for like we did for Url/ResourceUrl. My
24410
- // guess is that, outside of the Url/ResourceUrl case, this never actually happens. If there
24411
- // do turn out to be other cases, throwing an error until we can address it feels safer.
24412
- throw Error(`AssertionError: Ambiguous security context`);
24413
- }
24414
- return securityContext[0] || SecurityContext.NONE;
24478
+ if (!Array.isArray(securityContext)) {
24479
+ return securityContext;
24480
+ }
24481
+ if (securityContext.length < 2) {
24482
+ return securityContext[0] ?? SecurityContext.NONE;
24415
24483
  }
24416
- return securityContext;
24484
+ const nonNoneSecurityContexts = securityContext.filter((context) => context !== SecurityContext.NONE);
24485
+ if (nonNoneSecurityContexts.length > 1) {
24486
+ // TODO: What should we do here? TDB just took the first one, but this feels like something we
24487
+ // would want to know about and create a special case for like we did for Url/ResourceUrl. My
24488
+ // guess is that, outside of the Url/ResourceUrl case, this never actually happens. If there
24489
+ // do turn out to be other cases, throwing an error until we can address it feels safer.
24490
+ throw Error(`AssertionError: Ambiguous security context`);
24491
+ }
24492
+ return nonNoneSecurityContexts[0] ?? SecurityContext.NONE;
24417
24493
  }
24418
24494
 
24419
24495
  /**
@@ -25613,6 +25689,7 @@ const phases = [
25613
25689
  { kind: CompilationJobKind.Tmpl, fn: resolveI18nExpressionPlaceholders },
25614
25690
  { kind: CompilationJobKind.Tmpl, fn: extractI18nMessages },
25615
25691
  { kind: CompilationJobKind.Tmpl, fn: collectI18nConsts },
25692
+ { kind: CompilationJobKind.Tmpl, fn: resolveI18nAttrSanitizers },
25616
25693
  { kind: CompilationJobKind.Tmpl, fn: collectConstExpressions },
25617
25694
  { kind: CompilationJobKind.Both, fn: collectElementConsts },
25618
25695
  { kind: CompilationJobKind.Tmpl, fn: removeI18nContexts },
@@ -25726,1756 +25803,1801 @@ function emitHostBindingFunction(job) {
25726
25803
  /* sourceSpan */ undefined, job.root.fnName);
25727
25804
  }
25728
25805
 
25729
- const compatibilityMode = CompatibilityMode.TemplateDefinitionBuilder;
25730
- // Schema containing DOM elements and their properties.
25731
- const domSchema = new DomElementSchemaRegistry();
25732
- // Tag name of the `ng-template` element.
25733
- const NG_TEMPLATE_TAG_NAME = 'ng-template';
25734
- // prefix for any animation binding
25735
- const ANIMATE_PREFIX$1 = 'animate.';
25736
- function isI18nRootNode(meta) {
25737
- return meta instanceof Message;
25738
- }
25739
- function isSingleI18nIcu(meta) {
25740
- return isI18nRootNode(meta) && meta.nodes.length === 1 && meta.nodes[0] instanceof Icu;
25741
- }
25742
- /**
25743
- * Process a template AST and convert it into a `ComponentCompilation` in the intermediate
25744
- * representation.
25745
- * TODO: Refactor more of the ingestion code into phases.
25746
- */
25747
- function ingestComponent(componentName, template, constantPool, compilationMode, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations) {
25748
- const job = new ComponentCompilationJob(componentName, constantPool, compatibilityMode, compilationMode, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations);
25749
- ingestNodes(job.root, template);
25750
- return job;
25751
- }
25806
+ const PROPERTY_PARTS_SEPARATOR = '.';
25807
+ const ATTRIBUTE_PREFIX = 'attr';
25808
+ const ANIMATE_PREFIX$1 = 'animate';
25809
+ const CLASS_PREFIX = 'class';
25810
+ const STYLE_PREFIX = 'style';
25811
+ const TEMPLATE_ATTR_PREFIX$1 = '*';
25812
+ const LEGACY_ANIMATE_PROP_PREFIX = 'animate-';
25752
25813
  /**
25753
- * Process a host binding AST and convert it into a `HostBindingCompilationJob` in the intermediate
25754
- * representation.
25814
+ * Parses bindings in templates and in the directive host area.
25755
25815
  */
25756
- function ingestHostBinding(input, bindingParser, constantPool) {
25757
- const job = new HostBindingCompilationJob(input.componentName, constantPool, compatibilityMode, TemplateCompilationMode.DomOnly);
25758
- for (const property of input.properties ?? []) {
25759
- let bindingKind = BindingKind.Property;
25760
- // TODO: this should really be handled in the parser.
25761
- if (property.name.startsWith('attr.')) {
25762
- property.name = property.name.substring('attr.'.length);
25763
- bindingKind = BindingKind.Attribute;
25764
- }
25765
- if (property.isLegacyAnimation) {
25766
- bindingKind = BindingKind.LegacyAnimation;
25767
- }
25768
- if (property.isAnimation) {
25769
- bindingKind = BindingKind.Animation;
25770
- }
25771
- const securityContexts = bindingParser
25772
- .calcPossibleSecurityContexts(input.componentSelector, property.name, bindingKind === BindingKind.Attribute)
25773
- .filter((context) => context !== SecurityContext.NONE);
25774
- ingestDomProperty(job, property, bindingKind, securityContexts);
25775
- }
25776
- for (const [name, expr] of Object.entries(input.attributes) ?? []) {
25777
- const securityContexts = bindingParser
25778
- .calcPossibleSecurityContexts(input.componentSelector, name, true)
25779
- .filter((context) => context !== SecurityContext.NONE);
25780
- ingestHostAttribute(job, name, expr, securityContexts);
25816
+ class BindingParser {
25817
+ _exprParser;
25818
+ _interpolationConfig;
25819
+ _schemaRegistry;
25820
+ errors;
25821
+ constructor(_exprParser, _interpolationConfig, _schemaRegistry, errors) {
25822
+ this._exprParser = _exprParser;
25823
+ this._interpolationConfig = _interpolationConfig;
25824
+ this._schemaRegistry = _schemaRegistry;
25825
+ this.errors = errors;
25781
25826
  }
25782
- for (const event of input.events ?? []) {
25783
- ingestHostEvent(job, event);
25827
+ get interpolationConfig() {
25828
+ return this._interpolationConfig;
25784
25829
  }
25785
- return job;
25786
- }
25787
- // TODO: We should refactor the parser to use the same types and structures for host bindings as
25788
- // with ordinary components. This would allow us to share a lot more ingestion code.
25789
- function ingestDomProperty(job, property, bindingKind, securityContexts) {
25790
- let expression;
25791
- const ast = property.expression.ast;
25792
- if (ast instanceof Interpolation$1) {
25793
- expression = new Interpolation(ast.strings, ast.expressions.map((expr) => convertAst(expr, job, property.sourceSpan)), []);
25830
+ createBoundHostProperties(properties, sourceSpan) {
25831
+ const boundProps = [];
25832
+ for (const propName of Object.keys(properties)) {
25833
+ const expression = properties[propName];
25834
+ if (typeof expression === 'string') {
25835
+ this.parsePropertyBinding(propName, expression, true, false, sourceSpan, sourceSpan.start.offset, undefined, [],
25836
+ // Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the
25837
+ // sourceSpan, as it represents the sourceSpan of the host itself rather than the
25838
+ // source of the host binding (which doesn't exist in the template). Regardless,
25839
+ // neither of these values are used in Ivy but are only here to satisfy the function
25840
+ // signature. This should likely be refactored in the future so that `sourceSpan`
25841
+ // isn't being used inaccurately.
25842
+ boundProps, sourceSpan);
25843
+ }
25844
+ else {
25845
+ this._reportError(`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
25846
+ }
25847
+ }
25848
+ return boundProps;
25794
25849
  }
25795
- else {
25796
- expression = convertAst(ast, job, property.sourceSpan);
25850
+ createDirectiveHostEventAsts(hostListeners, sourceSpan) {
25851
+ const targetEvents = [];
25852
+ for (const propName of Object.keys(hostListeners)) {
25853
+ const expression = hostListeners[propName];
25854
+ if (typeof expression === 'string') {
25855
+ // Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but
25856
+ // neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself
25857
+ // rather than the source of the host binding (which doesn't exist in the template).
25858
+ // Regardless, neither of these values are used in Ivy but are only here to satisfy the
25859
+ // function signature. This should likely be refactored in the future so that `sourceSpan`
25860
+ // isn't being used inaccurately.
25861
+ this.parseEvent(propName, expression,
25862
+ /* isAssignmentEvent */ false, sourceSpan, sourceSpan, [], targetEvents, sourceSpan);
25863
+ }
25864
+ else {
25865
+ this._reportError(`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
25866
+ }
25867
+ }
25868
+ return targetEvents;
25797
25869
  }
25798
- job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, securityContexts, false, false, null,
25799
- /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
25800
- }
25801
- function ingestHostAttribute(job, name, value, securityContexts) {
25802
- const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, securityContexts,
25803
- /* Host attributes should always be extracted to const hostAttrs, even if they are not
25804
- *strictly* text literals */
25805
- true, false, null,
25806
- /* TODO */ null,
25807
- /** TODO: May be null? */ value.sourceSpan);
25808
- job.root.update.push(attrBinding);
25809
- }
25810
- function ingestHostEvent(job, event) {
25811
- let eventBinding;
25812
- if (event.type === ParsedEventType.Animation) {
25813
- eventBinding = createAnimationListenerOp(job.root.xref, new SlotHandle(), event.name, null, makeListenerHandlerOps(job.root, event.handler, event.handlerSpan), event.name.endsWith('enter') ? "enter" /* ir.AnimationKind.ENTER */ : "leave" /* ir.AnimationKind.LEAVE */, event.targetOrPhase, true, event.sourceSpan);
25814
- }
25815
- else {
25816
- const [phase, target] = event.type !== ParsedEventType.LegacyAnimation
25817
- ? [null, event.targetOrPhase]
25818
- : [event.targetOrPhase, null];
25819
- eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, makeListenerHandlerOps(job.root, event.handler, event.handlerSpan), phase, target, true, event.sourceSpan);
25870
+ parseInterpolation(value, sourceSpan, interpolatedTokens) {
25871
+ const absoluteOffset = sourceSpan.fullStart.offset;
25872
+ try {
25873
+ const ast = this._exprParser.parseInterpolation(value, sourceSpan, absoluteOffset, interpolatedTokens, this._interpolationConfig);
25874
+ if (ast) {
25875
+ this.errors.push(...ast.errors);
25876
+ }
25877
+ return ast;
25878
+ }
25879
+ catch (e) {
25880
+ this._reportError(`${e}`, sourceSpan);
25881
+ return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
25882
+ }
25820
25883
  }
25821
- job.root.create.push(eventBinding);
25822
- }
25823
- /**
25824
- * Ingest the nodes of a template AST into the given `ViewCompilation`.
25825
- */
25826
- function ingestNodes(unit, template) {
25827
- for (const node of template) {
25828
- if (node instanceof Element$1) {
25829
- ingestElement(unit, node);
25884
+ /**
25885
+ * Similar to `parseInterpolation`, but treats the provided string as a single expression
25886
+ * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
25887
+ * This is used for parsing the switch expression in ICUs.
25888
+ */
25889
+ parseInterpolationExpression(expression, sourceSpan) {
25890
+ const absoluteOffset = sourceSpan.start.offset;
25891
+ try {
25892
+ const ast = this._exprParser.parseInterpolationExpression(expression, sourceSpan, absoluteOffset);
25893
+ if (ast) {
25894
+ this.errors.push(...ast.errors);
25895
+ }
25896
+ return ast;
25830
25897
  }
25831
- else if (node instanceof Template) {
25832
- ingestTemplate(unit, node);
25898
+ catch (e) {
25899
+ this._reportError(`${e}`, sourceSpan);
25900
+ return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
25833
25901
  }
25834
- else if (node instanceof Content) {
25835
- ingestContent(unit, node);
25902
+ }
25903
+ /**
25904
+ * Parses the bindings in a microsyntax expression, and converts them to
25905
+ * `ParsedProperty` or `ParsedVariable`.
25906
+ *
25907
+ * @param tplKey template binding name
25908
+ * @param tplValue template binding value
25909
+ * @param sourceSpan span of template binding relative to entire the template
25910
+ * @param absoluteValueOffset start of the tplValue relative to the entire template
25911
+ * @param targetMatchableAttrs potential attributes to match in the template
25912
+ * @param targetProps target property bindings in the template
25913
+ * @param targetVars target variables in the template
25914
+ */
25915
+ parseInlineTemplateBinding(tplKey, tplValue, sourceSpan, absoluteValueOffset, targetMatchableAttrs, targetProps, targetVars, isIvyAst) {
25916
+ const absoluteKeyOffset = sourceSpan.start.offset + TEMPLATE_ATTR_PREFIX$1.length;
25917
+ const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
25918
+ for (const binding of bindings) {
25919
+ // sourceSpan is for the entire HTML attribute. bindingSpan is for a particular
25920
+ // binding within the microsyntax expression so it's more narrow than sourceSpan.
25921
+ const bindingSpan = moveParseSourceSpan(sourceSpan, binding.sourceSpan);
25922
+ const key = binding.key.source;
25923
+ const keySpan = moveParseSourceSpan(sourceSpan, binding.key.span);
25924
+ if (binding instanceof VariableBinding) {
25925
+ const value = binding.value ? binding.value.source : '$implicit';
25926
+ const valueSpan = binding.value
25927
+ ? moveParseSourceSpan(sourceSpan, binding.value.span)
25928
+ : undefined;
25929
+ targetVars.push(new ParsedVariable(key, value, bindingSpan, keySpan, valueSpan));
25930
+ }
25931
+ else if (binding.value) {
25932
+ const srcSpan = isIvyAst ? bindingSpan : sourceSpan;
25933
+ const valueSpan = moveParseSourceSpan(sourceSpan, binding.value.ast.sourceSpan);
25934
+ this._parsePropertyAst(key, binding.value, false, srcSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
25935
+ }
25936
+ else {
25937
+ targetMatchableAttrs.push([key, '' /* value */]);
25938
+ // Since this is a literal attribute with no RHS, source span should be
25939
+ // just the key span.
25940
+ this.parseLiteralAttr(key, null /* value */, keySpan, absoluteValueOffset, undefined /* valueSpan */, targetMatchableAttrs, targetProps, keySpan);
25941
+ }
25836
25942
  }
25837
- else if (node instanceof Text$3) {
25838
- ingestText(unit, node, null);
25943
+ }
25944
+ /**
25945
+ * Parses the bindings in a microsyntax expression, e.g.
25946
+ * ```html
25947
+ * <tag *tplKey="let value1 = prop; let value2 = localVar">
25948
+ * ```
25949
+ *
25950
+ * @param tplKey template binding name
25951
+ * @param tplValue template binding value
25952
+ * @param sourceSpan span of template binding relative to entire the template
25953
+ * @param absoluteKeyOffset start of the `tplKey`
25954
+ * @param absoluteValueOffset start of the `tplValue`
25955
+ */
25956
+ _parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset) {
25957
+ try {
25958
+ const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
25959
+ bindingsResult.errors.forEach((e) => this.errors.push(e));
25960
+ bindingsResult.warnings.forEach((warning) => {
25961
+ this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING);
25962
+ });
25963
+ return bindingsResult.templateBindings;
25839
25964
  }
25840
- else if (node instanceof BoundText) {
25841
- ingestBoundText(unit, node, null);
25965
+ catch (e) {
25966
+ this._reportError(`${e}`, sourceSpan);
25967
+ return [];
25842
25968
  }
25843
- else if (node instanceof IfBlock) {
25844
- ingestIfBlock(unit, node);
25969
+ }
25970
+ parseLiteralAttr(name, value, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
25971
+ if (isLegacyAnimationLabel(name)) {
25972
+ name = name.substring(1);
25973
+ if (keySpan !== undefined) {
25974
+ keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
25975
+ }
25976
+ if (value) {
25977
+ this._reportError(`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
25978
+ ` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`, sourceSpan, ParseErrorLevel.ERROR);
25979
+ }
25980
+ this._parseLegacyAnimation(name, value, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
25845
25981
  }
25846
- else if (node instanceof SwitchBlock) {
25847
- ingestSwitchBlock(unit, node);
25982
+ else {
25983
+ targetProps.push(new ParsedProperty(name, this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset), ParsedPropertyType.LITERAL_ATTR, sourceSpan, keySpan, valueSpan));
25848
25984
  }
25849
- else if (node instanceof DeferredBlock) {
25850
- ingestDeferBlock(unit, node);
25985
+ }
25986
+ parsePropertyBinding(name, expression, isHost, isPartOfAssignmentBinding, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
25987
+ if (name.length === 0) {
25988
+ this._reportError(`Property name is missing in binding`, sourceSpan);
25851
25989
  }
25852
- else if (node instanceof Icu$1) {
25853
- ingestIcu(unit, node);
25990
+ let isLegacyAnimationProp = false;
25991
+ if (name.startsWith(LEGACY_ANIMATE_PROP_PREFIX)) {
25992
+ isLegacyAnimationProp = true;
25993
+ name = name.substring(LEGACY_ANIMATE_PROP_PREFIX.length);
25994
+ if (keySpan !== undefined) {
25995
+ keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + LEGACY_ANIMATE_PROP_PREFIX.length, keySpan.end.offset));
25996
+ }
25854
25997
  }
25855
- else if (node instanceof ForLoopBlock) {
25856
- ingestForBlock(unit, node);
25998
+ else if (isLegacyAnimationLabel(name)) {
25999
+ isLegacyAnimationProp = true;
26000
+ name = name.substring(1);
26001
+ if (keySpan !== undefined) {
26002
+ keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
26003
+ }
25857
26004
  }
25858
- else if (node instanceof LetDeclaration$1) {
25859
- ingestLetDeclaration(unit, node);
26005
+ if (isLegacyAnimationProp) {
26006
+ this._parseLegacyAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
26007
+ }
26008
+ else if (name.startsWith(`${ANIMATE_PREFIX$1}${PROPERTY_PARTS_SEPARATOR}`)) {
26009
+ this._parseAnimation(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
25860
26010
  }
25861
- else if (node instanceof Component$1) ;
25862
26011
  else {
25863
- throw new Error(`Unsupported template node: ${node.constructor.name}`);
26012
+ this._parsePropertyAst(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
25864
26013
  }
25865
26014
  }
25866
- }
25867
- /**
25868
- * Ingest an element AST from the template into the given `ViewCompilation`.
25869
- */
25870
- function ingestElement(unit, element) {
25871
- if (element.i18n !== undefined &&
25872
- !(element.i18n instanceof Message || element.i18n instanceof TagPlaceholder)) {
25873
- throw Error(`Unhandled i18n metadata type for element: ${element.i18n.constructor.name}`);
25874
- }
25875
- const id = unit.job.allocateXrefId();
25876
- const [namespaceKey, elementName] = splitNsName(element.name);
25877
- const startOp = createElementStartOp(elementName, id, namespaceForKey(namespaceKey), element.i18n instanceof TagPlaceholder ? element.i18n : undefined, element.startSourceSpan, element.sourceSpan);
25878
- unit.create.push(startOp);
25879
- ingestElementBindings(unit, startOp, element);
25880
- ingestReferences(startOp, element);
25881
- // Start i18n, if needed, goes after the element create and bindings, but before the nodes
25882
- let i18nBlockId = null;
25883
- if (element.i18n instanceof Message) {
25884
- i18nBlockId = unit.job.allocateXrefId();
25885
- unit.create.push(createI18nStartOp(i18nBlockId, element.i18n, undefined, element.startSourceSpan));
26015
+ parsePropertyInterpolation(name, value, sourceSpan, valueSpan, targetMatchableAttrs, targetProps, keySpan, interpolatedTokens) {
26016
+ const expr = this.parseInterpolation(value, valueSpan || sourceSpan, interpolatedTokens);
26017
+ if (expr) {
26018
+ this._parsePropertyAst(name, expr, false, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
26019
+ return true;
26020
+ }
26021
+ return false;
25886
26022
  }
25887
- ingestNodes(unit, element.children);
25888
- // The source span for the end op is typically the element closing tag. However, if no closing tag
25889
- // exists, such as in `<input>`, we use the start source span instead. Usually the start and end
25890
- // instructions will be collapsed into one `element` instruction, negating the purpose of this
25891
- // fallback, but in cases when it is not collapsed (such as an input with a binding), we still
25892
- // want to map the end instruction to the main element.
25893
- const endOp = createElementEndOp(id, element.endSourceSpan ?? element.startSourceSpan);
25894
- unit.create.push(endOp);
25895
- // If there is an i18n message associated with this element, insert i18n start and end ops.
25896
- if (i18nBlockId !== null) {
25897
- OpList.insertBefore(createI18nEndOp(i18nBlockId, element.endSourceSpan ?? element.startSourceSpan), endOp);
26023
+ _parsePropertyAst(name, ast, isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
26024
+ targetMatchableAttrs.push([name, ast.source]);
26025
+ targetProps.push(new ParsedProperty(name, ast, isPartOfAssignmentBinding ? ParsedPropertyType.TWO_WAY : ParsedPropertyType.DEFAULT, sourceSpan, keySpan, valueSpan));
25898
26026
  }
25899
- }
25900
- /**
25901
- * Ingest an `ng-template` node from the AST into the given `ViewCompilation`.
25902
- */
25903
- function ingestTemplate(unit, tmpl) {
25904
- if (tmpl.i18n !== undefined &&
25905
- !(tmpl.i18n instanceof Message || tmpl.i18n instanceof TagPlaceholder)) {
25906
- throw Error(`Unhandled i18n metadata type for template: ${tmpl.i18n.constructor.name}`);
26027
+ _parseAnimation(name, ast, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
26028
+ targetMatchableAttrs.push([name, ast.source]);
26029
+ targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan, keySpan, valueSpan));
25907
26030
  }
25908
- const childView = unit.job.allocateView(unit.xref);
25909
- let tagNameWithoutNamespace = tmpl.tagName;
25910
- let namespacePrefix = '';
25911
- if (tmpl.tagName) {
25912
- [namespacePrefix, tagNameWithoutNamespace] = splitNsName(tmpl.tagName);
26031
+ _parseLegacyAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
26032
+ if (name.length === 0) {
26033
+ this._reportError('Animation trigger is missing', sourceSpan);
26034
+ }
26035
+ // This will occur when a @trigger is not paired with an expression.
26036
+ // For animations it is valid to not have an expression since */void
26037
+ // states will be applied by angular when the element is attached/detached
26038
+ const ast = this.parseBinding(expression || 'undefined', false, valueSpan || sourceSpan, absoluteOffset);
26039
+ targetMatchableAttrs.push([name, ast.source]);
26040
+ targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.LEGACY_ANIMATION, sourceSpan, keySpan, valueSpan));
25913
26041
  }
25914
- const i18nPlaceholder = tmpl.i18n instanceof TagPlaceholder ? tmpl.i18n : undefined;
25915
- const namespace = namespaceForKey(namespacePrefix);
25916
- const functionNameSuffix = tagNameWithoutNamespace === null ? '' : prefixWithNamespace(tagNameWithoutNamespace, namespace);
25917
- const templateKind = isPlainTemplate(tmpl)
25918
- ? TemplateKind.NgTemplate
25919
- : TemplateKind.Structural;
25920
- const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan, tmpl.sourceSpan);
25921
- unit.create.push(templateOp);
25922
- ingestTemplateBindings(unit, templateOp, tmpl, templateKind);
25923
- ingestReferences(templateOp, tmpl);
25924
- ingestNodes(childView, tmpl.children);
25925
- for (const { name, value } of tmpl.variables) {
25926
- childView.contextVariables.set(name, value !== '' ? value : '$implicit');
25927
- }
25928
- // If this is a plain template and there is an i18n message associated with it, insert i18n start
25929
- // and end ops. For structural directive templates, the i18n ops will be added when ingesting the
25930
- // element/template the directive is placed on.
25931
- if (templateKind === TemplateKind.NgTemplate && tmpl.i18n instanceof Message) {
25932
- const id = unit.job.allocateXrefId();
25933
- OpList.insertAfter(createI18nStartOp(id, tmpl.i18n, undefined, tmpl.startSourceSpan), childView.create.head);
25934
- OpList.insertBefore(createI18nEndOp(id, tmpl.endSourceSpan ?? tmpl.startSourceSpan), childView.create.tail);
25935
- }
25936
- }
25937
- /**
25938
- * Ingest a content node from the AST into the given `ViewCompilation`.
25939
- */
25940
- function ingestContent(unit, content) {
25941
- if (content.i18n !== undefined && !(content.i18n instanceof TagPlaceholder)) {
25942
- throw Error(`Unhandled i18n metadata type for element: ${content.i18n.constructor.name}`);
25943
- }
25944
- let fallbackView = null;
25945
- // Don't capture default content that's only made up of empty text nodes and comments.
25946
- // Note that we process the default content before the projection in order to match the
25947
- // insertion order at runtime.
25948
- if (content.children.some((child) => !(child instanceof Comment$1) &&
25949
- (!(child instanceof Text$3) || child.value.trim().length > 0))) {
25950
- fallbackView = unit.job.allocateView(unit.xref);
25951
- ingestNodes(fallbackView, content.children);
26042
+ parseBinding(value, isHostBinding, sourceSpan, absoluteOffset) {
26043
+ try {
26044
+ const ast = isHostBinding
26045
+ ? this._exprParser.parseSimpleBinding(value, sourceSpan, absoluteOffset, this._interpolationConfig)
26046
+ : this._exprParser.parseBinding(value, sourceSpan, absoluteOffset, this._interpolationConfig);
26047
+ if (ast) {
26048
+ this.errors.push(...ast.errors);
26049
+ }
26050
+ return ast;
26051
+ }
26052
+ catch (e) {
26053
+ this._reportError(`${e}`, sourceSpan);
26054
+ return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
26055
+ }
25952
26056
  }
25953
- const id = unit.job.allocateXrefId();
25954
- const op = createProjectionOp(id, content.selector, content.i18n, fallbackView?.xref ?? null, content.sourceSpan);
25955
- for (const attr of content.attributes) {
25956
- const securityContext = domSchema.securityContext(content.name, attr.name, true);
25957
- unit.update.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
26057
+ createBoundElementProperty(elementSelector, boundProp, skipValidation = false, mapPropertyName = true) {
26058
+ if (boundProp.isLegacyAnimation) {
26059
+ return new BoundElementProperty(boundProp.name, BindingType.LegacyAnimation, SecurityContext.NONE, boundProp.expression, null, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
26060
+ }
26061
+ let unit = null;
26062
+ let bindingType = undefined;
26063
+ let boundPropertyName = null;
26064
+ const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
26065
+ let securityContexts = undefined;
26066
+ // Check for special cases (prefix style, attr, class)
26067
+ if (parts.length > 1) {
26068
+ if (parts[0] == ATTRIBUTE_PREFIX) {
26069
+ boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
26070
+ if (!skipValidation) {
26071
+ this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
26072
+ }
26073
+ securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, boundPropertyName, true);
26074
+ const nsSeparatorIdx = boundPropertyName.indexOf(':');
26075
+ if (nsSeparatorIdx > -1) {
26076
+ const ns = boundPropertyName.substring(0, nsSeparatorIdx);
26077
+ const name = boundPropertyName.substring(nsSeparatorIdx + 1);
26078
+ boundPropertyName = mergeNsAndName(ns, name);
26079
+ }
26080
+ bindingType = BindingType.Attribute;
26081
+ }
26082
+ else if (parts[0] == CLASS_PREFIX) {
26083
+ boundPropertyName = parts[1];
26084
+ bindingType = BindingType.Class;
26085
+ securityContexts = [SecurityContext.NONE];
26086
+ }
26087
+ else if (parts[0] == STYLE_PREFIX) {
26088
+ unit = parts.length > 2 ? parts[2] : null;
26089
+ boundPropertyName = parts[1];
26090
+ bindingType = BindingType.Style;
26091
+ securityContexts = [SecurityContext.STYLE];
26092
+ }
26093
+ else if (parts[0] == ANIMATE_PREFIX$1) {
26094
+ boundPropertyName = boundProp.name;
26095
+ bindingType = BindingType.Animation;
26096
+ securityContexts = [SecurityContext.NONE];
26097
+ }
26098
+ }
26099
+ // If not a special case, use the full property name
26100
+ if (boundPropertyName === null) {
26101
+ const mappedPropName = this._schemaRegistry.getMappedPropName(boundProp.name);
26102
+ boundPropertyName = mapPropertyName ? mappedPropName : boundProp.name;
26103
+ securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, mappedPropName, false);
26104
+ bindingType =
26105
+ boundProp.type === ParsedPropertyType.TWO_WAY ? BindingType.TwoWay : BindingType.Property;
26106
+ if (!skipValidation) {
26107
+ this._validatePropertyOrAttributeName(mappedPropName, boundProp.sourceSpan, false);
26108
+ }
26109
+ }
26110
+ return new BoundElementProperty(boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
25958
26111
  }
25959
- unit.create.push(op);
25960
- }
25961
- /**
25962
- * Ingest a literal text node from the AST into the given `ViewCompilation`.
25963
- */
25964
- function ingestText(unit, text, icuPlaceholder) {
25965
- unit.create.push(createTextOp(unit.job.allocateXrefId(), text.value, icuPlaceholder, text.sourceSpan));
25966
- }
25967
- /**
25968
- * Ingest an interpolated text node from the AST into the given `ViewCompilation`.
25969
- */
25970
- function ingestBoundText(unit, text, icuPlaceholder) {
25971
- let value = text.value;
25972
- if (value instanceof ASTWithSource) {
25973
- value = value.ast;
26112
+ parseEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
26113
+ if (name.length === 0) {
26114
+ this._reportError(`Event name is missing in binding`, sourceSpan);
26115
+ }
26116
+ if (isLegacyAnimationLabel(name)) {
26117
+ name = name.slice(1);
26118
+ if (keySpan !== undefined) {
26119
+ keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
26120
+ }
26121
+ this._parseLegacyAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan);
26122
+ }
26123
+ else {
26124
+ this._parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan);
26125
+ }
25974
26126
  }
25975
- if (!(value instanceof Interpolation$1)) {
25976
- throw new Error(`AssertionError: expected Interpolation for BoundText node, got ${value.constructor.name}`);
26127
+ calcPossibleSecurityContexts(selector, propName, isAttribute) {
26128
+ const prop = this._schemaRegistry.getMappedPropName(propName);
26129
+ return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
25977
26130
  }
25978
- if (text.i18n !== undefined && !(text.i18n instanceof Container)) {
25979
- throw Error(`Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`);
26131
+ parseEventListenerName(rawName) {
26132
+ const [target, eventName] = splitAtColon(rawName, [null, rawName]);
26133
+ return { eventName: eventName, target };
25980
26134
  }
25981
- const i18nPlaceholders = text.i18n instanceof Container
25982
- ? text.i18n.children
25983
- .filter((node) => node instanceof Placeholder)
25984
- .map((placeholder) => placeholder.name)
25985
- : [];
25986
- if (i18nPlaceholders.length > 0 && i18nPlaceholders.length !== value.expressions.length) {
25987
- throw Error(`Unexpected number of i18n placeholders (${value.expressions.length}) for BoundText with ${value.expressions.length} expressions`);
26135
+ parseLegacyAnimationEventName(rawName) {
26136
+ const matches = splitAtPeriod(rawName, [rawName, null]);
26137
+ return { eventName: matches[0], phase: matches[1] === null ? null : matches[1].toLowerCase() };
25988
26138
  }
25989
- const textXref = unit.job.allocateXrefId();
25990
- unit.create.push(createTextOp(textXref, '', icuPlaceholder, text.sourceSpan));
25991
- // TemplateDefinitionBuilder does not generate source maps for sub-expressions inside an
25992
- // interpolation. We copy that behavior in compatibility mode.
25993
- // TODO: is it actually correct to generate these extra maps in modern mode?
25994
- const baseSourceSpan = unit.job.compatibility ? null : text.sourceSpan;
25995
- unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map((expr) => convertAst(expr, unit.job, baseSourceSpan)), i18nPlaceholders), text.sourceSpan));
25996
- }
25997
- /**
25998
- * Ingest an `@if` block into the given `ViewCompilation`.
25999
- */
26000
- function ingestIfBlock(unit, ifBlock) {
26001
- let firstXref = null;
26002
- let conditions = [];
26003
- for (let i = 0; i < ifBlock.branches.length; i++) {
26004
- const ifCase = ifBlock.branches[i];
26005
- const cView = unit.job.allocateView(unit.xref);
26006
- const tagName = ingestControlFlowInsertionPoint(unit, cView.xref, ifCase);
26007
- if (ifCase.expressionAlias !== null) {
26008
- cView.contextVariables.set(ifCase.expressionAlias.name, CTX_REF);
26139
+ _parseLegacyAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan) {
26140
+ const { eventName, phase } = this.parseLegacyAnimationEventName(name);
26141
+ const ast = this._parseAction(expression, handlerSpan);
26142
+ targetEvents.push(new ParsedEvent(eventName, phase, ParsedEventType.LegacyAnimation, ast, sourceSpan, handlerSpan, keySpan));
26143
+ if (eventName.length === 0) {
26144
+ this._reportError(`Animation event name is missing in binding`, sourceSpan);
26009
26145
  }
26010
- let ifCaseI18nMeta = undefined;
26011
- if (ifCase.i18n !== undefined) {
26012
- if (!(ifCase.i18n instanceof BlockPlaceholder)) {
26013
- throw Error(`Unhandled i18n metadata type for if block: ${ifCase.i18n?.constructor.name}`);
26146
+ if (phase) {
26147
+ if (phase !== 'start' && phase !== 'done') {
26148
+ this._reportError(`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`, sourceSpan);
26014
26149
  }
26015
- ifCaseI18nMeta = ifCase.i18n;
26016
26150
  }
26017
- const createOp = i === 0 ? createConditionalCreateOp : createConditionalBranchCreateOp;
26018
- const conditionalCreateOp = createOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.startSourceSpan, ifCase.sourceSpan);
26019
- unit.create.push(conditionalCreateOp);
26020
- if (firstXref === null) {
26021
- firstXref = cView.xref;
26151
+ else {
26152
+ this._reportError(`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`, sourceSpan);
26022
26153
  }
26023
- const caseExpr = ifCase.expression ? convertAst(ifCase.expression, unit.job, null) : null;
26024
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, conditionalCreateOp.xref, conditionalCreateOp.handle, ifCase.expressionAlias);
26025
- conditions.push(conditionalCaseExpr);
26026
- ingestNodes(cView, ifCase.children);
26027
26154
  }
26028
- unit.update.push(createConditionalOp(firstXref, null, conditions, ifBlock.sourceSpan));
26029
- }
26030
- /**
26031
- * Ingest an `@switch` block into the given `ViewCompilation`.
26032
- */
26033
- function ingestSwitchBlock(unit, switchBlock) {
26034
- // Don't ingest empty switches since they won't render anything.
26035
- if (switchBlock.cases.length === 0) {
26036
- return;
26155
+ _parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
26156
+ // long format: 'target: eventName'
26157
+ const { eventName, target } = this.parseEventListenerName(name);
26158
+ const prevErrorCount = this.errors.length;
26159
+ const ast = this._parseAction(expression, handlerSpan);
26160
+ const isValid = this.errors.length === prevErrorCount;
26161
+ targetMatchableAttrs.push([name, ast.source]);
26162
+ // Don't try to validate assignment events if there were other
26163
+ // parsing errors to avoid adding more noise to the error logs.
26164
+ if (isAssignmentEvent && isValid && !this._isAllowedAssignmentEvent(ast)) {
26165
+ this._reportError('Unsupported expression in a two-way binding', sourceSpan);
26166
+ }
26167
+ let eventType = ParsedEventType.Regular;
26168
+ if (isAssignmentEvent) {
26169
+ eventType = ParsedEventType.TwoWay;
26170
+ }
26171
+ if (name.startsWith(`${ANIMATE_PREFIX$1}${PROPERTY_PARTS_SEPARATOR}`)) {
26172
+ eventType = ParsedEventType.Animation;
26173
+ }
26174
+ targetEvents.push(new ParsedEvent(eventName, target, eventType, ast, sourceSpan, handlerSpan, keySpan));
26175
+ // Don't detect directives for event names for now,
26176
+ // so don't add the event name to the matchableAttrs
26037
26177
  }
26038
- let firstXref = null;
26039
- let conditions = [];
26040
- for (let i = 0; i < switchBlock.cases.length; i++) {
26041
- const switchCase = switchBlock.cases[i];
26042
- const cView = unit.job.allocateView(unit.xref);
26043
- const tagName = ingestControlFlowInsertionPoint(unit, cView.xref, switchCase);
26044
- let switchCaseI18nMeta = undefined;
26045
- if (switchCase.i18n !== undefined) {
26046
- if (!(switchCase.i18n instanceof BlockPlaceholder)) {
26047
- throw Error(`Unhandled i18n metadata type for switch block: ${switchCase.i18n?.constructor.name}`);
26178
+ _parseAction(value, sourceSpan) {
26179
+ const absoluteOffset = sourceSpan && sourceSpan.start ? sourceSpan.start.offset : 0;
26180
+ try {
26181
+ const ast = this._exprParser.parseAction(value, sourceSpan, absoluteOffset, this._interpolationConfig);
26182
+ if (ast) {
26183
+ this.errors.push(...ast.errors);
26048
26184
  }
26049
- switchCaseI18nMeta = switchCase.i18n;
26185
+ if (!ast || ast.ast instanceof EmptyExpr$1) {
26186
+ this._reportError(`Empty expressions are not allowed`, sourceSpan);
26187
+ return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
26188
+ }
26189
+ return ast;
26050
26190
  }
26051
- const createOp = i === 0 ? createConditionalCreateOp : createConditionalBranchCreateOp;
26052
- const conditionalCreateOp = createOp(cView.xref, TemplateKind.Block, tagName, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.startSourceSpan, switchCase.sourceSpan);
26053
- unit.create.push(conditionalCreateOp);
26054
- if (firstXref === null) {
26055
- firstXref = cView.xref;
26191
+ catch (e) {
26192
+ this._reportError(`${e}`, sourceSpan);
26193
+ return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
26056
26194
  }
26057
- const caseExpr = switchCase.expression
26058
- ? convertAst(switchCase.expression, unit.job, switchBlock.startSourceSpan)
26059
- : null;
26060
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, conditionalCreateOp.xref, conditionalCreateOp.handle);
26061
- conditions.push(conditionalCaseExpr);
26062
- ingestNodes(cView, switchCase.children);
26063
- }
26064
- unit.update.push(createConditionalOp(firstXref, convertAst(switchBlock.expression, unit.job, null), conditions, switchBlock.sourceSpan));
26065
- }
26066
- function ingestDeferView(unit, suffix, i18nMeta, children, sourceSpan) {
26067
- if (i18nMeta !== undefined && !(i18nMeta instanceof BlockPlaceholder)) {
26068
- throw Error('Unhandled i18n metadata type for defer block');
26069
26195
  }
26070
- if (children === undefined) {
26071
- return null;
26196
+ _reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
26197
+ this.errors.push(new ParseError(sourceSpan, message, level));
26072
26198
  }
26073
- const secondaryView = unit.job.allocateView(unit.xref);
26074
- ingestNodes(secondaryView, children);
26075
- const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, i18nMeta, sourceSpan, sourceSpan);
26076
- unit.create.push(templateOp);
26077
- return templateOp;
26078
- }
26079
- function ingestDeferBlock(unit, deferBlock) {
26080
- let ownResolverFn = null;
26081
- if (unit.job.deferMeta.mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
26082
- if (!unit.job.deferMeta.blocks.has(deferBlock)) {
26083
- throw new Error(`AssertionError: unable to find a dependency function for this deferred block`);
26199
+ /**
26200
+ * @param propName the name of the property / attribute
26201
+ * @param sourceSpan
26202
+ * @param isAttr true when binding to an attribute
26203
+ */
26204
+ _validatePropertyOrAttributeName(propName, sourceSpan, isAttr) {
26205
+ const report = isAttr
26206
+ ? this._schemaRegistry.validateAttribute(propName)
26207
+ : this._schemaRegistry.validateProperty(propName);
26208
+ if (report.error) {
26209
+ this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
26084
26210
  }
26085
- ownResolverFn = unit.job.deferMeta.blocks.get(deferBlock) ?? null;
26086
- }
26087
- // Generate the defer main view and all secondary views.
26088
- const main = ingestDeferView(unit, '', deferBlock.i18n, deferBlock.children, deferBlock.sourceSpan);
26089
- const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.i18n, deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
26090
- const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.i18n, deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
26091
- const error = ingestDeferView(unit, 'Error', deferBlock.error?.i18n, deferBlock.error?.children, deferBlock.error?.sourceSpan);
26092
- // Create the main defer op, and ops for all secondary views.
26093
- const deferXref = unit.job.allocateXrefId();
26094
- const deferOp = createDeferOp(deferXref, main.xref, main.handle, ownResolverFn, unit.job.allDeferrableDepsFn, deferBlock.sourceSpan);
26095
- deferOp.placeholderView = placeholder?.xref ?? null;
26096
- deferOp.placeholderSlot = placeholder?.handle ?? null;
26097
- deferOp.loadingSlot = loading?.handle ?? null;
26098
- deferOp.errorSlot = error?.handle ?? null;
26099
- deferOp.placeholderMinimumTime = deferBlock.placeholder?.minimumTime ?? null;
26100
- deferOp.loadingMinimumTime = deferBlock.loading?.minimumTime ?? null;
26101
- deferOp.loadingAfterTime = deferBlock.loading?.afterTime ?? null;
26102
- deferOp.flags = calcDeferBlockFlags(deferBlock);
26103
- unit.create.push(deferOp);
26104
- // Configure all defer `on` conditions.
26105
- // TODO: refactor prefetch triggers to use a separate op type, with a shared superclass. This will
26106
- // make it easier to refactor prefetch behavior in the future.
26107
- const deferOnOps = [];
26108
- const deferWhenOps = [];
26109
- // Ingest the hydrate triggers first since they set up all the other triggers during SSR.
26110
- ingestDeferTriggers("hydrate" /* ir.DeferOpModifierKind.HYDRATE */, deferBlock.hydrateTriggers, deferOnOps, deferWhenOps, unit, deferXref);
26111
- ingestDeferTriggers("none" /* ir.DeferOpModifierKind.NONE */, deferBlock.triggers, deferOnOps, deferWhenOps, unit, deferXref);
26112
- ingestDeferTriggers("prefetch" /* ir.DeferOpModifierKind.PREFETCH */, deferBlock.prefetchTriggers, deferOnOps, deferWhenOps, unit, deferXref);
26113
- // If no (non-prefetching or hydrating) defer triggers were provided, default to `idle`.
26114
- const hasConcreteTrigger = deferOnOps.some((op) => op.modifier === "none" /* ir.DeferOpModifierKind.NONE */) ||
26115
- deferWhenOps.some((op) => op.modifier === "none" /* ir.DeferOpModifierKind.NONE */);
26116
- if (!hasConcreteTrigger) {
26117
- deferOnOps.push(createDeferOnOp(deferXref, { kind: DeferTriggerKind.Idle }, "none" /* ir.DeferOpModifierKind.NONE */, null));
26118
26211
  }
26119
- unit.create.push(deferOnOps);
26120
- unit.update.push(deferWhenOps);
26121
- }
26122
- function calcDeferBlockFlags(deferBlockDetails) {
26123
- if (Object.keys(deferBlockDetails.hydrateTriggers).length > 0) {
26124
- return 1 /* ir.TDeferDetailsFlags.HasHydrateTriggers */;
26212
+ /**
26213
+ * Returns whether a parsed AST is allowed to be used within the event side of a two-way binding.
26214
+ * @param ast Parsed AST to be checked.
26215
+ */
26216
+ _isAllowedAssignmentEvent(ast) {
26217
+ if (ast instanceof ASTWithSource) {
26218
+ return this._isAllowedAssignmentEvent(ast.ast);
26219
+ }
26220
+ if (ast instanceof NonNullAssert) {
26221
+ return this._isAllowedAssignmentEvent(ast.expression);
26222
+ }
26223
+ if (ast instanceof Call &&
26224
+ ast.args.length === 1 &&
26225
+ ast.receiver instanceof PropertyRead &&
26226
+ ast.receiver.name === '$any' &&
26227
+ ast.receiver.receiver instanceof ImplicitReceiver &&
26228
+ !(ast.receiver.receiver instanceof ThisReceiver)) {
26229
+ return this._isAllowedAssignmentEvent(ast.args[0]);
26230
+ }
26231
+ if (ast instanceof PropertyRead || ast instanceof KeyedRead) {
26232
+ if (!hasRecursiveSafeReceiver(ast)) {
26233
+ return true;
26234
+ }
26235
+ }
26236
+ return false;
26125
26237
  }
26126
- return null;
26127
26238
  }
26128
- function ingestDeferTriggers(modifier, triggers, onOps, whenOps, unit, deferXref) {
26129
- if (triggers.idle !== undefined) {
26130
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Idle }, modifier, triggers.idle.sourceSpan);
26131
- onOps.push(deferOnOp);
26239
+ function hasRecursiveSafeReceiver(ast) {
26240
+ if (ast instanceof SafePropertyRead || ast instanceof SafeKeyedRead) {
26241
+ return true;
26132
26242
  }
26133
- if (triggers.immediate !== undefined) {
26134
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Immediate }, modifier, triggers.immediate.sourceSpan);
26135
- onOps.push(deferOnOp);
26243
+ if (ast instanceof ParenthesizedExpression) {
26244
+ return hasRecursiveSafeReceiver(ast.expression);
26136
26245
  }
26137
- if (triggers.timer !== undefined) {
26138
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Timer, delay: triggers.timer.delay }, modifier, triggers.timer.sourceSpan);
26139
- onOps.push(deferOnOp);
26246
+ if (ast instanceof PropertyRead || ast instanceof KeyedRead || ast instanceof Call) {
26247
+ return hasRecursiveSafeReceiver(ast.receiver);
26140
26248
  }
26141
- if (triggers.hover !== undefined) {
26142
- const deferOnOp = createDeferOnOp(deferXref, {
26143
- kind: DeferTriggerKind.Hover,
26144
- targetName: triggers.hover.reference,
26145
- targetXref: null,
26146
- targetSlot: null,
26147
- targetView: null,
26148
- targetSlotViewSteps: null,
26149
- }, modifier, triggers.hover.sourceSpan);
26150
- onOps.push(deferOnOp);
26249
+ return false;
26250
+ }
26251
+ function isLegacyAnimationLabel(name) {
26252
+ return name[0] == '@';
26253
+ }
26254
+ function calcPossibleSecurityContexts(registry, selector, propName, isAttribute) {
26255
+ let ctxs;
26256
+ const [namespaceKey, baseSelector] = selector ? splitNsName(selector, false) : [null, selector];
26257
+ const nameToContext = (elName) => {
26258
+ const [nsStr, name] = splitNsName(elName, false);
26259
+ const ns = nsStr ?? namespaceKey;
26260
+ const fullName = ns ? `:${ns}:${name}` : name;
26261
+ return registry.securityContext(fullName, propName, isAttribute);
26262
+ };
26263
+ const allKnownElements = registry.allKnownElementNames();
26264
+ if (baseSelector === null) {
26265
+ ctxs = allKnownElements.map(nameToContext);
26151
26266
  }
26152
- if (triggers.interaction !== undefined) {
26153
- const deferOnOp = createDeferOnOp(deferXref, {
26154
- kind: DeferTriggerKind.Interaction,
26155
- targetName: triggers.interaction.reference,
26156
- targetXref: null,
26157
- targetSlot: null,
26158
- targetView: null,
26159
- targetSlotViewSteps: null,
26160
- }, modifier, triggers.interaction.sourceSpan);
26161
- onOps.push(deferOnOp);
26267
+ else {
26268
+ ctxs = [];
26269
+ CssSelector.parse(baseSelector).forEach((selector) => {
26270
+ let elementNames = selector.element ? [selector.element] : allKnownElements;
26271
+ if (selector.element && !registry.hasElement(selector.element, [])) {
26272
+ const svgElement = `:${SVG_NAMESPACE}:${selector.element}`;
26273
+ const mathElement = `:${MATH_ML_NAMESPACE}:${selector.element}`;
26274
+ if (registry.hasElement(svgElement, [])) {
26275
+ elementNames = [svgElement];
26276
+ }
26277
+ else if (registry.hasElement(mathElement, [])) {
26278
+ elementNames = [mathElement];
26279
+ }
26280
+ }
26281
+ const notElementNames = new Set(selector.notSelectors
26282
+ .filter((selector) => selector.isElementSelector())
26283
+ .map((selector) => selector.element?.toLowerCase()));
26284
+ const possibleElementNames = elementNames.filter((elName) => {
26285
+ const elNameLowerCase = elName.toLowerCase();
26286
+ return (!notElementNames.has(elNameLowerCase) &&
26287
+ !notElementNames.has(splitNsName(elNameLowerCase)[1]));
26288
+ });
26289
+ ctxs.push(...possibleElementNames.map(nameToContext));
26290
+ });
26162
26291
  }
26163
- if (triggers.viewport !== undefined) {
26164
- const deferOnOp = createDeferOnOp(deferXref, {
26165
- kind: DeferTriggerKind.Viewport,
26166
- targetName: triggers.viewport.reference,
26167
- targetXref: null,
26168
- targetSlot: null,
26169
- targetView: null,
26170
- targetSlotViewSteps: null,
26171
- }, modifier, triggers.viewport.sourceSpan);
26172
- onOps.push(deferOnOp);
26292
+ return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
26293
+ }
26294
+ /**
26295
+ * Compute a new ParseSourceSpan based off an original `sourceSpan` by using
26296
+ * absolute offsets from the specified `absoluteSpan`.
26297
+ *
26298
+ * @param sourceSpan original source span
26299
+ * @param absoluteSpan absolute source span to move to
26300
+ */
26301
+ function moveParseSourceSpan(sourceSpan, absoluteSpan) {
26302
+ // The difference of two absolute offsets provide the relative offset
26303
+ const startDiff = absoluteSpan.start - sourceSpan.start.offset;
26304
+ const endDiff = absoluteSpan.end - sourceSpan.end.offset;
26305
+ return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff), sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
26306
+ }
26307
+
26308
+ const compatibilityMode = CompatibilityMode.TemplateDefinitionBuilder;
26309
+ // Schema containing DOM elements and their properties.
26310
+ const domSchema = new DomElementSchemaRegistry();
26311
+ // Tag name of the `ng-template` element.
26312
+ const NG_TEMPLATE_TAG_NAME = 'ng-template';
26313
+ // prefix for any animation binding
26314
+ const ANIMATE_PREFIX = 'animate.';
26315
+ function isI18nRootNode(meta) {
26316
+ return meta instanceof Message;
26317
+ }
26318
+ function isSingleI18nIcu(meta) {
26319
+ return isI18nRootNode(meta) && meta.nodes.length === 1 && meta.nodes[0] instanceof Icu;
26320
+ }
26321
+ /**
26322
+ * Process a template AST and convert it into a `ComponentCompilation` in the intermediate
26323
+ * representation.
26324
+ * TODO: Refactor more of the ingestion code into phases.
26325
+ */
26326
+ function ingestComponent(componentName, template, constantPool, compilationMode, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations) {
26327
+ const job = new ComponentCompilationJob(componentName, constantPool, compatibilityMode, compilationMode, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations);
26328
+ ingestNodes(job.root, template);
26329
+ return job;
26330
+ }
26331
+ /**
26332
+ * Process a host binding AST and convert it into a `HostBindingCompilationJob` in the intermediate
26333
+ * representation.
26334
+ */
26335
+ function ingestHostBinding(input, bindingParser, constantPool) {
26336
+ const job = new HostBindingCompilationJob(input.componentName, constantPool, compatibilityMode, TemplateCompilationMode.DomOnly);
26337
+ for (const property of input.properties ?? []) {
26338
+ let bindingKind = BindingKind.Property;
26339
+ // TODO: this should really be handled in the parser.
26340
+ if (property.name.startsWith('attr.')) {
26341
+ property.name = property.name.substring('attr.'.length);
26342
+ bindingKind = BindingKind.Attribute;
26343
+ }
26344
+ if (property.isLegacyAnimation) {
26345
+ bindingKind = BindingKind.LegacyAnimation;
26346
+ }
26347
+ if (property.isAnimation) {
26348
+ bindingKind = BindingKind.Animation;
26349
+ }
26350
+ const securityContexts = calcHostBindingSecurityContexts(bindingParser, input.componentSelector, property.name, bindingKind === BindingKind.Attribute);
26351
+ ingestDomProperty(job, property, bindingKind, securityContexts);
26173
26352
  }
26174
- if (triggers.never !== undefined) {
26175
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Never }, modifier, triggers.never.sourceSpan);
26176
- onOps.push(deferOnOp);
26353
+ for (const [name, expr] of Object.entries(input.attributes) ?? []) {
26354
+ const securityContexts = calcHostBindingSecurityContexts(bindingParser, input.componentSelector, name, true);
26355
+ ingestHostAttribute(job, name, expr, securityContexts);
26177
26356
  }
26178
- if (triggers.when !== undefined) {
26179
- if (triggers.when.value instanceof Interpolation$1) {
26180
- // TemplateDefinitionBuilder supports this case, but it's very strange to me. What would it
26181
- // even mean?
26182
- throw new Error(`Unexpected interpolation in defer block when trigger`);
26183
- }
26184
- const deferOnOp = createDeferWhenOp(deferXref, convertAst(triggers.when.value, unit.job, triggers.when.sourceSpan), modifier, triggers.when.sourceSpan);
26185
- whenOps.push(deferOnOp);
26357
+ for (const event of input.events ?? []) {
26358
+ ingestHostEvent(job, event);
26186
26359
  }
26360
+ return job;
26187
26361
  }
26188
- function ingestIcu(unit, icu) {
26189
- if (icu.i18n instanceof Message && isSingleI18nIcu(icu.i18n)) {
26190
- const xref = unit.job.allocateXrefId();
26191
- unit.create.push(createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null));
26192
- for (const [placeholder, text] of Object.entries({ ...icu.vars, ...icu.placeholders })) {
26193
- if (text instanceof BoundText) {
26194
- ingestBoundText(unit, text, placeholder);
26195
- }
26196
- else {
26197
- ingestText(unit, text, placeholder);
26198
- }
26199
- }
26200
- unit.create.push(createIcuEndOp(xref));
26362
+ function calcHostBindingSecurityContexts(bindingParser, selector, name, isAttribute) {
26363
+ const declaringSelectorContexts = bindingParser.calcPossibleSecurityContexts(selector, name, isAttribute);
26364
+ const concreteHostContexts = calcPossibleSecurityContexts(domSchema, null, domSchema.getMappedPropName(name), isAttribute);
26365
+ const concreteHostNonNoneContexts = concreteHostContexts.filter((context) => context !== SecurityContext.NONE);
26366
+ const concreteHostNonNoneCount = concreteHostNonNoneContexts.length;
26367
+ const hasConcreteHostNoneContext = concreteHostNonNoneCount !== concreteHostContexts.length;
26368
+ // Host bindings can run against a concrete host whose element name differs from the declaring
26369
+ // selector, including dynamic root components whose TNode name is `#host`.
26370
+ if (hasConcreteHostNoneContext && concreteHostNonNoneCount > 0) {
26371
+ return concreteHostContexts;
26201
26372
  }
26202
- else {
26203
- throw Error(`Unhandled i18n metadata type for ICU: ${icu.i18n?.constructor.name}`);
26373
+ if (concreteHostNonNoneContexts.some((context) => !declaringSelectorContexts.includes(context))) {
26374
+ return concreteHostContexts;
26204
26375
  }
26376
+ return declaringSelectorContexts.filter((context) => context !== SecurityContext.NONE);
26205
26377
  }
26206
- /**
26207
- * Ingest an `@for` block into the given `ViewCompilation`.
26378
+ // TODO: We should refactor the parser to use the same types and structures for host bindings as
26379
+ // with ordinary components. This would allow us to share a lot more ingestion code.
26380
+ function ingestDomProperty(job, property, bindingKind, securityContexts) {
26381
+ let expression;
26382
+ const ast = property.expression.ast;
26383
+ if (ast instanceof Interpolation$1) {
26384
+ expression = new Interpolation(ast.strings, ast.expressions.map((expr) => convertAst(expr, job, property.sourceSpan)), []);
26385
+ }
26386
+ else {
26387
+ expression = convertAst(ast, job, property.sourceSpan);
26388
+ }
26389
+ job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, securityContexts, false, false, null,
26390
+ /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
26391
+ }
26392
+ function ingestHostAttribute(job, name, value, securityContexts) {
26393
+ const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, securityContexts,
26394
+ /* Host attributes should always be extracted to const hostAttrs, even if they are not
26395
+ *strictly* text literals */
26396
+ true, false, null,
26397
+ /* TODO */ null,
26398
+ /** TODO: May be null? */ value.sourceSpan);
26399
+ job.root.update.push(attrBinding);
26400
+ }
26401
+ function ingestHostEvent(job, event) {
26402
+ let eventBinding;
26403
+ if (event.type === ParsedEventType.Animation) {
26404
+ eventBinding = createAnimationListenerOp(job.root.xref, new SlotHandle(), event.name, null, makeListenerHandlerOps(job.root, event.handler, event.handlerSpan), event.name.endsWith('enter') ? "enter" /* ir.AnimationKind.ENTER */ : "leave" /* ir.AnimationKind.LEAVE */, event.targetOrPhase, true, event.sourceSpan);
26405
+ }
26406
+ else {
26407
+ const [phase, target] = event.type !== ParsedEventType.LegacyAnimation
26408
+ ? [null, event.targetOrPhase]
26409
+ : [event.targetOrPhase, null];
26410
+ eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, makeListenerHandlerOps(job.root, event.handler, event.handlerSpan), phase, target, true, event.sourceSpan);
26411
+ }
26412
+ job.root.create.push(eventBinding);
26413
+ }
26414
+ /**
26415
+ * Ingest the nodes of a template AST into the given `ViewCompilation`.
26208
26416
  */
26209
- function ingestForBlock(unit, forBlock) {
26210
- const repeaterView = unit.job.allocateView(unit.xref);
26211
- // We copy TemplateDefinitionBuilder's scheme of creating names for `$count` and `$index`
26212
- // that are suffixed with special information, to disambiguate which level of nested loop
26213
- // the below aliases refer to.
26214
- // TODO: We should refactor Template Pipeline's variable phases to gracefully handle
26215
- // shadowing, and arbitrarily many levels of variables depending on each other.
26216
- const indexName = `ɵ$index_${repeaterView.xref}`;
26217
- const countName = `ɵ$count_${repeaterView.xref}`;
26218
- const indexVarNames = new Set();
26219
- // Set all the context variables and aliases available in the repeater.
26220
- repeaterView.contextVariables.set(forBlock.item.name, forBlock.item.value);
26221
- for (const variable of forBlock.contextVariables) {
26222
- if (variable.value === '$index') {
26223
- indexVarNames.add(variable.name);
26417
+ function ingestNodes(unit, template) {
26418
+ for (const node of template) {
26419
+ if (node instanceof Element$1) {
26420
+ ingestElement(unit, node);
26224
26421
  }
26225
- if (variable.name === '$index') {
26226
- repeaterView.contextVariables.set('$index', variable.value).set(indexName, variable.value);
26422
+ else if (node instanceof Template) {
26423
+ ingestTemplate(unit, node);
26227
26424
  }
26228
- else if (variable.name === '$count') {
26229
- repeaterView.contextVariables.set('$count', variable.value).set(countName, variable.value);
26425
+ else if (node instanceof Content) {
26426
+ ingestContent(unit, node);
26427
+ }
26428
+ else if (node instanceof Text$3) {
26429
+ ingestText(unit, node, null);
26430
+ }
26431
+ else if (node instanceof BoundText) {
26432
+ ingestBoundText(unit, node, null);
26433
+ }
26434
+ else if (node instanceof IfBlock) {
26435
+ ingestIfBlock(unit, node);
26436
+ }
26437
+ else if (node instanceof SwitchBlock) {
26438
+ ingestSwitchBlock(unit, node);
26439
+ }
26440
+ else if (node instanceof DeferredBlock) {
26441
+ ingestDeferBlock(unit, node);
26230
26442
  }
26443
+ else if (node instanceof Icu$1) {
26444
+ ingestIcu(unit, node);
26445
+ }
26446
+ else if (node instanceof ForLoopBlock) {
26447
+ ingestForBlock(unit, node);
26448
+ }
26449
+ else if (node instanceof LetDeclaration$1) {
26450
+ ingestLetDeclaration(unit, node);
26451
+ }
26452
+ else if (node instanceof Component$1) ;
26231
26453
  else {
26232
- repeaterView.aliases.add({
26233
- kind: SemanticVariableKind.Alias,
26234
- name: null,
26235
- identifier: variable.name,
26236
- expression: getComputedForLoopVariableExpression(variable, indexName, countName),
26237
- });
26454
+ throw new Error(`Unsupported template node: ${node.constructor.name}`);
26238
26455
  }
26239
26456
  }
26240
- const sourceSpan = convertSourceSpan(forBlock.trackBy.span, forBlock.sourceSpan);
26241
- const track = convertAst(forBlock.trackBy, unit.job, sourceSpan);
26242
- ingestNodes(repeaterView, forBlock.children);
26243
- let emptyView = null;
26244
- let emptyTagName = null;
26245
- if (forBlock.empty !== null) {
26246
- emptyView = unit.job.allocateView(unit.xref);
26247
- ingestNodes(emptyView, forBlock.empty.children);
26248
- emptyTagName = ingestControlFlowInsertionPoint(unit, emptyView.xref, forBlock.empty);
26457
+ }
26458
+ /**
26459
+ * Ingest an element AST from the template into the given `ViewCompilation`.
26460
+ */
26461
+ function ingestElement(unit, element) {
26462
+ if (element.i18n !== undefined &&
26463
+ !(element.i18n instanceof Message || element.i18n instanceof TagPlaceholder)) {
26464
+ throw Error(`Unhandled i18n metadata type for element: ${element.i18n.constructor.name}`);
26249
26465
  }
26250
- const varNames = {
26251
- $index: indexVarNames,
26252
- $implicit: forBlock.item.name,
26253
- };
26254
- if (forBlock.i18n !== undefined && !(forBlock.i18n instanceof BlockPlaceholder)) {
26255
- throw Error('AssertionError: Unhandled i18n metadata type or @for');
26466
+ const id = unit.job.allocateXrefId();
26467
+ const [namespaceKey, elementName] = splitNsName(element.name);
26468
+ const startOp = createElementStartOp(elementName, id, namespaceForKey(namespaceKey), element.i18n instanceof TagPlaceholder ? element.i18n : undefined, element.startSourceSpan, element.sourceSpan);
26469
+ unit.create.push(startOp);
26470
+ ingestElementBindings(unit, startOp, element);
26471
+ ingestReferences(startOp, element);
26472
+ // Start i18n, if needed, goes after the element create and bindings, but before the nodes
26473
+ let i18nBlockId = null;
26474
+ if (element.i18n instanceof Message) {
26475
+ i18nBlockId = unit.job.allocateXrefId();
26476
+ unit.create.push(createI18nStartOp(i18nBlockId, element.i18n, undefined, element.startSourceSpan));
26256
26477
  }
26257
- if (forBlock.empty?.i18n !== undefined &&
26258
- !(forBlock.empty.i18n instanceof BlockPlaceholder)) {
26259
- throw Error('AssertionError: Unhandled i18n metadata type or @empty');
26478
+ ingestNodes(unit, element.children);
26479
+ // The source span for the end op is typically the element closing tag. However, if no closing tag
26480
+ // exists, such as in `<input>`, we use the start source span instead. Usually the start and end
26481
+ // instructions will be collapsed into one `element` instruction, negating the purpose of this
26482
+ // fallback, but in cases when it is not collapsed (such as an input with a binding), we still
26483
+ // want to map the end instruction to the main element.
26484
+ const endOp = createElementEndOp(id, element.endSourceSpan ?? element.startSourceSpan);
26485
+ unit.create.push(endOp);
26486
+ // If there is an i18n message associated with this element, insert i18n start and end ops.
26487
+ if (i18nBlockId !== null) {
26488
+ OpList.insertBefore(createI18nEndOp(i18nBlockId, element.endSourceSpan ?? element.startSourceSpan), endOp);
26260
26489
  }
26261
- const i18nPlaceholder = forBlock.i18n;
26262
- const emptyI18nPlaceholder = forBlock.empty?.i18n;
26263
- const tagName = ingestControlFlowInsertionPoint(unit, repeaterView.xref, forBlock);
26264
- const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, emptyTagName, i18nPlaceholder, emptyI18nPlaceholder, forBlock.startSourceSpan, forBlock.sourceSpan);
26265
- unit.create.push(repeaterCreate);
26266
- const expression = convertAst(forBlock.expression, unit.job, convertSourceSpan(forBlock.expression.span, forBlock.sourceSpan));
26267
- const repeater = createRepeaterOp(repeaterCreate.xref, repeaterCreate.handle, expression, forBlock.sourceSpan);
26268
- unit.update.push(repeater);
26269
26490
  }
26270
26491
  /**
26271
- * Gets an expression that represents a variable in an `@for` loop.
26272
- * @param variable AST representing the variable.
26273
- * @param indexName Loop-specific name for `$index`.
26274
- * @param countName Loop-specific name for `$count`.
26492
+ * Ingest an `ng-template` node from the AST into the given `ViewCompilation`.
26275
26493
  */
26276
- function getComputedForLoopVariableExpression(variable, indexName, countName) {
26277
- switch (variable.value) {
26278
- case '$index':
26279
- return new LexicalReadExpr(indexName);
26280
- case '$count':
26281
- return new LexicalReadExpr(countName);
26282
- case '$first':
26283
- return new LexicalReadExpr(indexName).identical(literal(0));
26284
- case '$last':
26285
- return new LexicalReadExpr(indexName).identical(new LexicalReadExpr(countName).minus(literal(1)));
26286
- case '$even':
26287
- return new LexicalReadExpr(indexName).modulo(literal(2)).identical(literal(0));
26288
- case '$odd':
26289
- return new LexicalReadExpr(indexName).modulo(literal(2)).notIdentical(literal(0));
26290
- default:
26291
- throw new Error(`AssertionError: unknown @for loop variable ${variable.value}`);
26494
+ function ingestTemplate(unit, tmpl) {
26495
+ if (tmpl.i18n !== undefined &&
26496
+ !(tmpl.i18n instanceof Message || tmpl.i18n instanceof TagPlaceholder)) {
26497
+ throw Error(`Unhandled i18n metadata type for template: ${tmpl.i18n.constructor.name}`);
26498
+ }
26499
+ const childView = unit.job.allocateView(unit.xref);
26500
+ let tagNameWithoutNamespace = tmpl.tagName;
26501
+ let namespacePrefix = '';
26502
+ if (tmpl.tagName) {
26503
+ [namespacePrefix, tagNameWithoutNamespace] = splitNsName(tmpl.tagName);
26504
+ }
26505
+ const i18nPlaceholder = tmpl.i18n instanceof TagPlaceholder ? tmpl.i18n : undefined;
26506
+ const namespace = namespaceForKey(namespacePrefix);
26507
+ const functionNameSuffix = tagNameWithoutNamespace === null ? '' : prefixWithNamespace(tagNameWithoutNamespace, namespace);
26508
+ const templateKind = isPlainTemplate(tmpl)
26509
+ ? TemplateKind.NgTemplate
26510
+ : TemplateKind.Structural;
26511
+ const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan, tmpl.sourceSpan);
26512
+ unit.create.push(templateOp);
26513
+ ingestTemplateBindings(unit, templateOp, tmpl, templateKind);
26514
+ ingestReferences(templateOp, tmpl);
26515
+ ingestNodes(childView, tmpl.children);
26516
+ for (const { name, value } of tmpl.variables) {
26517
+ childView.contextVariables.set(name, value !== '' ? value : '$implicit');
26518
+ }
26519
+ // If this is a plain template and there is an i18n message associated with it, insert i18n start
26520
+ // and end ops. For structural directive templates, the i18n ops will be added when ingesting the
26521
+ // element/template the directive is placed on.
26522
+ if (templateKind === TemplateKind.NgTemplate && tmpl.i18n instanceof Message) {
26523
+ const id = unit.job.allocateXrefId();
26524
+ OpList.insertAfter(createI18nStartOp(id, tmpl.i18n, undefined, tmpl.startSourceSpan), childView.create.head);
26525
+ OpList.insertBefore(createI18nEndOp(id, tmpl.endSourceSpan ?? tmpl.startSourceSpan), childView.create.tail);
26292
26526
  }
26293
- }
26294
- function ingestLetDeclaration(unit, node) {
26295
- const target = unit.job.allocateXrefId();
26296
- unit.create.push(createDeclareLetOp(target, node.name, node.sourceSpan));
26297
- unit.update.push(createStoreLetOp(target, node.name, convertAst(node.value, unit.job, node.valueSpan), node.sourceSpan));
26298
26527
  }
26299
26528
  /**
26300
- * Convert a template AST expression into an output AST expression.
26529
+ * Ingest a content node from the AST into the given `ViewCompilation`.
26301
26530
  */
26302
- function convertAst(ast, job, baseSourceSpan) {
26303
- if (ast instanceof ASTWithSource) {
26304
- return convertAst(ast.ast, job, baseSourceSpan);
26305
- }
26306
- else if (ast instanceof PropertyRead) {
26307
- // Whether this is an implicit receiver, *excluding* explicit reads of `this`.
26308
- const isImplicitReceiver = ast.receiver instanceof ImplicitReceiver && !(ast.receiver instanceof ThisReceiver);
26309
- if (isImplicitReceiver) {
26310
- return new LexicalReadExpr(ast.name);
26311
- }
26312
- else {
26313
- return new ReadPropExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name, null, convertSourceSpan(ast.span, baseSourceSpan));
26314
- }
26531
+ function ingestContent(unit, content) {
26532
+ if (content.i18n !== undefined && !(content.i18n instanceof TagPlaceholder)) {
26533
+ throw Error(`Unhandled i18n metadata type for element: ${content.i18n.constructor.name}`);
26315
26534
  }
26316
- else if (ast instanceof Call) {
26317
- if (ast.receiver instanceof ImplicitReceiver) {
26318
- throw new Error(`Unexpected ImplicitReceiver`);
26319
- }
26320
- else {
26321
- return new InvokeFunctionExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((arg) => convertAst(arg, job, baseSourceSpan)), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26322
- }
26535
+ let fallbackView = null;
26536
+ // Don't capture default content that's only made up of empty text nodes and comments.
26537
+ // Note that we process the default content before the projection in order to match the
26538
+ // insertion order at runtime.
26539
+ if (content.children.some((child) => !(child instanceof Comment$1) &&
26540
+ (!(child instanceof Text$3) || child.value.trim().length > 0))) {
26541
+ fallbackView = unit.job.allocateView(unit.xref);
26542
+ ingestNodes(fallbackView, content.children);
26323
26543
  }
26324
- else if (ast instanceof LiteralPrimitive) {
26325
- return literal(ast.value, undefined, convertSourceSpan(ast.span, baseSourceSpan));
26544
+ const id = unit.job.allocateXrefId();
26545
+ const op = createProjectionOp(id, content.selector, content.i18n, fallbackView?.xref ?? null, content.sourceSpan);
26546
+ for (const attr of content.attributes) {
26547
+ const securityContext = domSchema.securityContext(content.name, attr.name, true);
26548
+ unit.update.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
26326
26549
  }
26327
- else if (ast instanceof Unary) {
26328
- switch (ast.operator) {
26329
- case '+':
26330
- return new UnaryOperatorExpr(UnaryOperator.Plus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26331
- case '-':
26332
- return new UnaryOperatorExpr(UnaryOperator.Minus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26333
- default:
26334
- throw new Error(`AssertionError: unknown unary operator ${ast.operator}`);
26335
- }
26336
- }
26337
- else if (ast instanceof Binary) {
26338
- const operator = BINARY_OPERATORS.get(ast.operation);
26339
- if (operator === undefined) {
26340
- throw new Error(`AssertionError: unknown binary operator ${ast.operation}`);
26341
- }
26342
- return new BinaryOperatorExpr(operator, convertAst(ast.left, job, baseSourceSpan), convertAst(ast.right, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26343
- }
26344
- else if (ast instanceof ThisReceiver) {
26345
- // TODO: should context expressions have source maps?
26346
- return new ContextExpr(job.root.xref);
26347
- }
26348
- else if (ast instanceof KeyedRead) {
26349
- return new ReadKeyExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26350
- }
26351
- else if (ast instanceof Chain) {
26352
- throw new Error(`AssertionError: Chain in unknown context`);
26353
- }
26354
- else if (ast instanceof LiteralMap) {
26355
- const entries = ast.keys.map((key, idx) => {
26356
- const value = ast.values[idx];
26357
- // TODO: should literals have source maps, or do we just map the whole surrounding
26358
- // expression?
26359
- return new LiteralMapEntry(key.key, convertAst(value, job, baseSourceSpan), key.quoted);
26360
- });
26361
- return new LiteralMapExpr(entries, undefined, convertSourceSpan(ast.span, baseSourceSpan));
26362
- }
26363
- else if (ast instanceof LiteralArray) {
26364
- // TODO: should literals have source maps, or do we just map the whole surrounding expression?
26365
- return new LiteralArrayExpr(ast.expressions.map((expr) => convertAst(expr, job, baseSourceSpan)));
26366
- }
26367
- else if (ast instanceof Conditional) {
26368
- return new ConditionalExpr(convertAst(ast.condition, job, baseSourceSpan), convertAst(ast.trueExp, job, baseSourceSpan), convertAst(ast.falseExp, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26369
- }
26370
- else if (ast instanceof NonNullAssert) {
26371
- // A non-null assertion shouldn't impact generated instructions, so we can just drop it.
26372
- return convertAst(ast.expression, job, baseSourceSpan);
26373
- }
26374
- else if (ast instanceof BindingPipe) {
26375
- // TODO: pipes should probably have source maps; figure out details.
26376
- return new PipeBindingExpr(job.allocateXrefId(), new SlotHandle(), ast.name, [
26377
- convertAst(ast.exp, job, baseSourceSpan),
26378
- ...ast.args.map((arg) => convertAst(arg, job, baseSourceSpan)),
26379
- ]);
26380
- }
26381
- else if (ast instanceof SafeKeyedRead) {
26382
- return new SafeKeyedReadExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
26383
- }
26384
- else if (ast instanceof SafePropertyRead) {
26385
- // TODO: source span
26386
- return new SafePropertyReadExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name);
26387
- }
26388
- else if (ast instanceof SafeCall) {
26389
- // TODO: source span
26390
- return new SafeInvokeFunctionExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((a) => convertAst(a, job, baseSourceSpan)));
26391
- }
26392
- else if (ast instanceof EmptyExpr$1) {
26393
- return new EmptyExpr(convertSourceSpan(ast.span, baseSourceSpan));
26394
- }
26395
- else if (ast instanceof PrefixNot) {
26396
- return not(convertAst(ast.expression, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
26397
- }
26398
- else if (ast instanceof TypeofExpression) {
26399
- return typeofExpr(convertAst(ast.expression, job, baseSourceSpan));
26400
- }
26401
- else if (ast instanceof VoidExpression) {
26402
- return new VoidExpr(convertAst(ast.expression, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26403
- }
26404
- else if (ast instanceof TemplateLiteral) {
26405
- return convertTemplateLiteral(ast, job, baseSourceSpan);
26406
- }
26407
- else if (ast instanceof TaggedTemplateLiteral) {
26408
- return new TaggedTemplateLiteralExpr(convertAst(ast.tag, job, baseSourceSpan), convertTemplateLiteral(ast.template, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26409
- }
26410
- else if (ast instanceof ParenthesizedExpression) {
26411
- return new ParenthesizedExpr(convertAst(ast.expression, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26412
- }
26413
- else {
26414
- throw new Error(`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`);
26415
- }
26416
- }
26417
- function convertTemplateLiteral(ast, job, baseSourceSpan) {
26418
- return new TemplateLiteralExpr(ast.elements.map((el) => {
26419
- return new TemplateLiteralElementExpr(el.text, convertSourceSpan(el.span, baseSourceSpan));
26420
- }), ast.expressions.map((expr) => convertAst(expr, job, baseSourceSpan)), convertSourceSpan(ast.span, baseSourceSpan));
26421
- }
26422
- function convertAstWithInterpolation(job, value, i18nMeta, sourceSpan) {
26423
- let expression;
26424
- if (value instanceof Interpolation$1) {
26425
- expression = new Interpolation(value.strings, value.expressions.map((e) => convertAst(e, job, null)), Object.keys(asMessage(i18nMeta)?.placeholders ?? {}));
26426
- }
26427
- else if (value instanceof AST) {
26428
- expression = convertAst(value, job, null);
26429
- }
26430
- else {
26431
- expression = literal(value);
26432
- }
26433
- return expression;
26550
+ unit.create.push(op);
26434
26551
  }
26435
- // TODO: Can we populate Template binding kinds in ingest?
26436
- const BINDING_KINDS = new Map([
26437
- [BindingType.Property, BindingKind.Property],
26438
- [BindingType.TwoWay, BindingKind.TwoWayProperty],
26439
- [BindingType.Attribute, BindingKind.Attribute],
26440
- [BindingType.Class, BindingKind.ClassName],
26441
- [BindingType.Style, BindingKind.StyleProperty],
26442
- [BindingType.LegacyAnimation, BindingKind.LegacyAnimation],
26443
- [BindingType.Animation, BindingKind.Animation],
26444
- ]);
26445
26552
  /**
26446
- * Checks whether the given template is a plain ng-template (as opposed to another kind of template
26447
- * such as a structural directive template or control flow template). This is checked based on the
26448
- * tagName. We can expect that only plain ng-templates will come through with a tagName of
26449
- * 'ng-template'.
26450
- *
26451
- * Here are some of the cases we expect:
26452
- *
26453
- * | Angular HTML | Template tagName |
26454
- * | ---------------------------------- | ------------------ |
26455
- * | `<ng-template>` | 'ng-template' |
26456
- * | `<div *ngIf="true">` | 'div' |
26457
- * | `<svg><ng-template>` | 'svg:ng-template' |
26458
- * | `@if (true) {` | 'Conditional' |
26459
- * | `<ng-template *ngIf>` (plain) | 'ng-template' |
26460
- * | `<ng-template *ngIf>` (structural) | null |
26553
+ * Ingest a literal text node from the AST into the given `ViewCompilation`.
26461
26554
  */
26462
- function isPlainTemplate(tmpl) {
26463
- return splitNsName(tmpl.tagName ?? '')[1] === NG_TEMPLATE_TAG_NAME;
26555
+ function ingestText(unit, text, icuPlaceholder) {
26556
+ unit.create.push(createTextOp(unit.job.allocateXrefId(), text.value, icuPlaceholder, text.sourceSpan));
26464
26557
  }
26465
26558
  /**
26466
- * Ensures that the i18nMeta, if provided, is an i18n.Message.
26559
+ * Ingest an interpolated text node from the AST into the given `ViewCompilation`.
26467
26560
  */
26468
- function asMessage(i18nMeta) {
26469
- if (i18nMeta == null) {
26470
- return null;
26561
+ function ingestBoundText(unit, text, icuPlaceholder) {
26562
+ let value = text.value;
26563
+ if (value instanceof ASTWithSource) {
26564
+ value = value.ast;
26471
26565
  }
26472
- if (!(i18nMeta instanceof Message)) {
26473
- throw Error(`Expected i18n meta to be a Message, but got: ${i18nMeta.constructor.name}`);
26566
+ if (!(value instanceof Interpolation$1)) {
26567
+ throw new Error(`AssertionError: expected Interpolation for BoundText node, got ${value.constructor.name}`);
26474
26568
  }
26475
- return i18nMeta;
26569
+ if (text.i18n !== undefined && !(text.i18n instanceof Container)) {
26570
+ throw Error(`Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`);
26571
+ }
26572
+ const i18nPlaceholders = text.i18n instanceof Container
26573
+ ? text.i18n.children
26574
+ .filter((node) => node instanceof Placeholder)
26575
+ .map((placeholder) => placeholder.name)
26576
+ : [];
26577
+ if (i18nPlaceholders.length > 0 && i18nPlaceholders.length !== value.expressions.length) {
26578
+ throw Error(`Unexpected number of i18n placeholders (${value.expressions.length}) for BoundText with ${value.expressions.length} expressions`);
26579
+ }
26580
+ const textXref = unit.job.allocateXrefId();
26581
+ unit.create.push(createTextOp(textXref, '', icuPlaceholder, text.sourceSpan));
26582
+ // TemplateDefinitionBuilder does not generate source maps for sub-expressions inside an
26583
+ // interpolation. We copy that behavior in compatibility mode.
26584
+ // TODO: is it actually correct to generate these extra maps in modern mode?
26585
+ const baseSourceSpan = unit.job.compatibility ? null : text.sourceSpan;
26586
+ unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map((expr) => convertAst(expr, unit.job, baseSourceSpan)), i18nPlaceholders), text.sourceSpan));
26476
26587
  }
26477
26588
  /**
26478
- * Process all of the bindings on an element in the template AST and convert them to their IR
26479
- * representation.
26589
+ * Ingest an `@if` block into the given `ViewCompilation`.
26480
26590
  */
26481
- function ingestElementBindings(unit, op, element) {
26482
- let bindings = new Array();
26483
- let i18nAttributeBindingNames = new Set();
26484
- for (const attr of element.attributes) {
26485
- // Attribute literal bindings, such as `attr.foo="bar"`.
26486
- const securityContext = domSchema.securityContext(element.name, attr.name, true);
26487
- 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));
26488
- if (attr.i18n) {
26489
- i18nAttributeBindingNames.add(attr.name);
26490
- }
26491
- }
26492
- for (const input of element.inputs) {
26493
- if (i18nAttributeBindingNames.has(input.name)) {
26494
- console.error(`On component ${unit.job.componentName}, the binding ${input.name} is both an i18n attribute and a property. You may want to remove the property binding. This will become a compilation error in future versions of Angular.`);
26495
- }
26496
- // All dynamic bindings (both attribute and property bindings).
26497
- 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));
26498
- }
26499
- unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
26500
- unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
26501
- for (const output of element.outputs) {
26502
- if (output.type === ParsedEventType.LegacyAnimation && output.phase === null) {
26503
- throw Error('Animation listener should have a phase');
26504
- }
26505
- if (output.type === ParsedEventType.TwoWay) {
26506
- unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
26591
+ function ingestIfBlock(unit, ifBlock) {
26592
+ let firstXref = null;
26593
+ let conditions = [];
26594
+ for (let i = 0; i < ifBlock.branches.length; i++) {
26595
+ const ifCase = ifBlock.branches[i];
26596
+ const cView = unit.job.allocateView(unit.xref);
26597
+ const tagName = ingestControlFlowInsertionPoint(unit, cView.xref, ifCase);
26598
+ if (ifCase.expressionAlias !== null) {
26599
+ cView.contextVariables.set(ifCase.expressionAlias.name, CTX_REF);
26507
26600
  }
26508
- else if (output.type === ParsedEventType.Animation) {
26509
- unit.create.push(createAnimationListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.name.endsWith('enter') ? "enter" /* ir.AnimationKind.ENTER */ : "leave" /* ir.AnimationKind.LEAVE */, output.target, false, output.sourceSpan));
26601
+ let ifCaseI18nMeta = undefined;
26602
+ if (ifCase.i18n !== undefined) {
26603
+ if (!(ifCase.i18n instanceof BlockPlaceholder)) {
26604
+ throw Error(`Unhandled i18n metadata type for if block: ${ifCase.i18n?.constructor.name}`);
26605
+ }
26606
+ ifCaseI18nMeta = ifCase.i18n;
26510
26607
  }
26511
- else {
26512
- 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));
26608
+ const createOp = i === 0 ? createConditionalCreateOp : createConditionalBranchCreateOp;
26609
+ const conditionalCreateOp = createOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.startSourceSpan, ifCase.sourceSpan);
26610
+ unit.create.push(conditionalCreateOp);
26611
+ if (firstXref === null) {
26612
+ firstXref = cView.xref;
26513
26613
  }
26614
+ const caseExpr = ifCase.expression ? convertAst(ifCase.expression, unit.job, null) : null;
26615
+ const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, conditionalCreateOp.xref, conditionalCreateOp.handle, ifCase.expressionAlias);
26616
+ conditions.push(conditionalCaseExpr);
26617
+ ingestNodes(cView, ifCase.children);
26514
26618
  }
26515
- // If any of the bindings on this element have an i18n message, then an i18n attrs configuration
26516
- // op is also required.
26517
- if (bindings.some((b) => b?.i18nMessage) !== null) {
26518
- unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
26519
- }
26619
+ unit.update.push(createConditionalOp(firstXref, null, conditions, ifBlock.sourceSpan));
26520
26620
  }
26521
26621
  /**
26522
- * Process all of the bindings on a template in the template AST and convert them to their IR
26523
- * representation.
26622
+ * Ingest an `@switch` block into the given `ViewCompilation`.
26524
26623
  */
26525
- function ingestTemplateBindings(unit, op, template, templateKind) {
26526
- let bindings = new Array();
26527
- for (const attr of template.templateAttrs) {
26528
- if (attr instanceof TextAttribute) {
26529
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
26530
- bindings.push(createTemplateBinding(unit, op.xref, BindingType.Attribute, attr.name, attr.value, null, securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
26531
- }
26532
- else {
26533
- bindings.push(createTemplateBinding(unit, op.xref, attr.type, attr.name, astOf(attr.value), attr.unit, attr.securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
26534
- }
26535
- }
26536
- for (const attr of template.attributes) {
26537
- // Attribute literal bindings, such as `attr.foo="bar"`.
26538
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
26539
- bindings.push(createTemplateBinding(unit, op.xref, BindingType.Attribute, attr.name, attr.value, null, securityContext, false, templateKind, asMessage(attr.i18n), attr.sourceSpan));
26540
- }
26541
- for (const input of template.inputs) {
26542
- // Dynamic bindings (both attribute and property bindings).
26543
- bindings.push(createTemplateBinding(unit, op.xref, input.type, input.name, astOf(input.value), input.unit, input.securityContext, false, templateKind, asMessage(input.i18n), input.sourceSpan));
26624
+ function ingestSwitchBlock(unit, switchBlock) {
26625
+ // Don't ingest empty switches since they won't render anything.
26626
+ if (switchBlock.cases.length === 0) {
26627
+ return;
26544
26628
  }
26545
- unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
26546
- unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
26547
- for (const output of template.outputs) {
26548
- if (output.type === ParsedEventType.LegacyAnimation && output.phase === null) {
26549
- throw Error('Animation listener should have a phase');
26550
- }
26551
- if (templateKind === TemplateKind.NgTemplate) {
26552
- if (output.type === ParsedEventType.TwoWay) {
26553
- unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
26554
- }
26555
- else {
26556
- 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));
26629
+ let firstXref = null;
26630
+ let conditions = [];
26631
+ for (let i = 0; i < switchBlock.cases.length; i++) {
26632
+ const switchCase = switchBlock.cases[i];
26633
+ const cView = unit.job.allocateView(unit.xref);
26634
+ const tagName = ingestControlFlowInsertionPoint(unit, cView.xref, switchCase);
26635
+ let switchCaseI18nMeta = undefined;
26636
+ if (switchCase.i18n !== undefined) {
26637
+ if (!(switchCase.i18n instanceof BlockPlaceholder)) {
26638
+ throw Error(`Unhandled i18n metadata type for switch block: ${switchCase.i18n?.constructor.name}`);
26557
26639
  }
26640
+ switchCaseI18nMeta = switchCase.i18n;
26558
26641
  }
26559
- if (templateKind === TemplateKind.Structural &&
26560
- output.type !== ParsedEventType.LegacyAnimation) {
26561
- // Animation bindings are excluded from the structural template's const array.
26562
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, output.name, false);
26563
- unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, null, output.name, null, null, null, securityContext));
26642
+ const createOp = i === 0 ? createConditionalCreateOp : createConditionalBranchCreateOp;
26643
+ const conditionalCreateOp = createOp(cView.xref, TemplateKind.Block, tagName, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.startSourceSpan, switchCase.sourceSpan);
26644
+ unit.create.push(conditionalCreateOp);
26645
+ if (firstXref === null) {
26646
+ firstXref = cView.xref;
26564
26647
  }
26648
+ const caseExpr = switchCase.expression
26649
+ ? convertAst(switchCase.expression, unit.job, switchBlock.startSourceSpan)
26650
+ : null;
26651
+ const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, conditionalCreateOp.xref, conditionalCreateOp.handle);
26652
+ conditions.push(conditionalCaseExpr);
26653
+ ingestNodes(cView, switchCase.children);
26565
26654
  }
26566
- // TODO: Perhaps we could do this in a phase? (It likely wouldn't change the slot indices.)
26567
- if (bindings.some((b) => b?.i18nMessage) !== null) {
26568
- unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
26569
- }
26655
+ unit.update.push(createConditionalOp(firstXref, convertAst(switchBlock.expression, unit.job, null), conditions, switchBlock.sourceSpan));
26570
26656
  }
26571
- /**
26572
- * Helper to ingest an individual binding on a template, either an explicit `ng-template`, or an
26573
- * implicit template created via structural directive.
26574
- *
26575
- * Bindings on templates are *extremely* tricky. I have tried to isolate all of the confusing edge
26576
- * cases into this function, and to comment it well to document the behavior.
26577
- *
26578
- * Some of this behavior is intuitively incorrect, and we should consider changing it in the future.
26579
- *
26580
- * @param view The compilation unit for the view containing the template.
26581
- * @param xref The xref of the template op.
26582
- * @param type The binding type, according to the parser. This is fairly reasonable, e.g. both
26583
- * dynamic and static attributes have e.BindingType.Attribute.
26584
- * @param name The binding's name.
26585
- * @param value The bindings's value, which will either be an input AST expression, or a string
26586
- * literal. Note that the input AST expression may or may not be const -- it will only be a
26587
- * string literal if the parser considered it a text binding.
26588
- * @param unit If the binding has a unit (e.g. `px` for style bindings), then this is the unit.
26589
- * @param securityContext The security context of the binding.
26590
- * @param isStructuralTemplateAttribute Whether this binding actually applies to the structural
26591
- * ng-template. For example, an `ngFor` would actually apply to the structural template. (Most
26592
- * bindings on structural elements target the inner element, not the template.)
26593
- * @param templateKind Whether this is an explicit `ng-template` or an implicit template created by
26594
- * a structural directive. This should never be a block template.
26595
- * @param i18nMessage The i18n metadata for the binding, if any.
26596
- * @param sourceSpan The source span of the binding.
26597
- * @returns An IR binding op, or null if the binding should be skipped.
26598
- */
26599
- function createTemplateBinding(view, xref, type, name, value, unit, securityContext, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
26600
- const isTextBinding = typeof value === 'string';
26601
- // If this is a structural template, then several kinds of bindings should not result in an
26602
- // update instruction.
26603
- if (templateKind === TemplateKind.Structural) {
26604
- if (!isStructuralTemplateAttribute) {
26605
- switch (type) {
26606
- case BindingType.Property:
26607
- case BindingType.Class:
26608
- case BindingType.Style:
26609
- // Because this binding doesn't really target the ng-template, it must be a binding on an
26610
- // inner node of a structural template. We can't skip it entirely, because we still need
26611
- // it on the ng-template's consts (e.g. for the purposes of directive matching). However,
26612
- // we should not generate an update instruction for it.
26613
- return createExtractedAttributeOp(xref, BindingKind.Property, null, name, null, null, i18nMessage, securityContext);
26614
- case BindingType.TwoWay:
26615
- return createExtractedAttributeOp(xref, BindingKind.TwoWayProperty, null, name, null, null, i18nMessage, securityContext);
26616
- }
26617
- }
26618
- if (!isTextBinding &&
26619
- (type === BindingType.Attribute ||
26620
- type === BindingType.LegacyAnimation ||
26621
- type === BindingType.Animation)) {
26622
- // Again, this binding doesn't really target the ng-template; it actually targets the element
26623
- // inside the structural template. In the case of non-text attribute or animation bindings,
26624
- // the binding doesn't even show up on the ng-template const array, so we just skip it
26625
- // entirely.
26626
- return null;
26627
- }
26628
- }
26629
- let bindingType = BINDING_KINDS.get(type);
26630
- if (templateKind === TemplateKind.NgTemplate) {
26631
- // We know we are dealing with bindings directly on an explicit ng-template.
26632
- // Static attribute bindings should be collected into the const array as k/v pairs. Property
26633
- // bindings should result in a `property` instruction, and `AttributeMarker.Bindings` const
26634
- // entries.
26635
- //
26636
- // The difficulty is with dynamic attribute, style, and class bindings. These don't really make
26637
- // sense on an `ng-template` and should probably be parser errors. However,
26638
- // TemplateDefinitionBuilder generates `property` instructions for them, and so we do that as
26639
- // well.
26640
- //
26641
- // Note that we do have a slight behavior difference with TemplateDefinitionBuilder: although
26642
- // TDB emits `property` instructions for dynamic attributes, styles, and classes, only styles
26643
- // and classes also get const collected into the `AttributeMarker.Bindings` field. Dynamic
26644
- // attribute bindings are missing from the consts entirely. We choose to emit them into the
26645
- // consts field anyway, to avoid creating special cases for something so arcane and nonsensical.
26646
- if (type === BindingType.Class ||
26647
- type === BindingType.Style ||
26648
- (type === BindingType.Attribute && !isTextBinding)) {
26649
- // TODO: These cases should be parse errors.
26650
- bindingType = BindingKind.Property;
26651
- }
26657
+ function ingestDeferView(unit, suffix, i18nMeta, children, sourceSpan) {
26658
+ if (i18nMeta !== undefined && !(i18nMeta instanceof BlockPlaceholder)) {
26659
+ throw Error('Unhandled i18n metadata type for defer block');
26652
26660
  }
26653
- return createBindingOp(xref, bindingType, name, convertAstWithInterpolation(view.job, value, i18nMessage), unit, securityContext, isTextBinding, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan);
26654
- }
26655
- function makeListenerHandlerOps(unit, handler, handlerSpan) {
26656
- handler = astOf(handler);
26657
- const handlerOps = new Array();
26658
- let handlerExprs = handler instanceof Chain ? handler.expressions : [handler];
26659
- if (handlerExprs.length === 0) {
26660
- throw new Error('Expected listener to have non-empty expression list.');
26661
+ if (children === undefined) {
26662
+ return null;
26661
26663
  }
26662
- const expressions = handlerExprs.map((expr) => convertAst(expr, unit.job, handlerSpan));
26663
- const returnExpr = expressions.pop();
26664
- handlerOps.push(...expressions.map((e) => createStatementOp(new ExpressionStatement(e, e.sourceSpan))));
26665
- handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
26666
- return handlerOps;
26664
+ const secondaryView = unit.job.allocateView(unit.xref);
26665
+ ingestNodes(secondaryView, children);
26666
+ const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, i18nMeta, sourceSpan, sourceSpan);
26667
+ unit.create.push(templateOp);
26668
+ return templateOp;
26667
26669
  }
26668
- function makeTwoWayListenerHandlerOps(unit, handler, handlerSpan) {
26669
- handler = astOf(handler);
26670
- const handlerOps = new Array();
26671
- if (handler instanceof Chain) {
26672
- if (handler.expressions.length === 1) {
26673
- handler = handler.expressions[0];
26674
- }
26675
- else {
26676
- // This is validated during parsing already, but we do it here just in case.
26677
- throw new Error('Expected two-way listener to have a single expression.');
26670
+ function ingestDeferBlock(unit, deferBlock) {
26671
+ let ownResolverFn = null;
26672
+ if (unit.job.deferMeta.mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
26673
+ if (!unit.job.deferMeta.blocks.has(deferBlock)) {
26674
+ throw new Error(`AssertionError: unable to find a dependency function for this deferred block`);
26678
26675
  }
26676
+ ownResolverFn = unit.job.deferMeta.blocks.get(deferBlock) ?? null;
26679
26677
  }
26680
- const handlerExpr = convertAst(handler, unit.job, handlerSpan);
26681
- const eventReference = new LexicalReadExpr('$event');
26682
- const twoWaySetExpr = new TwoWayBindingSetExpr(handlerExpr, eventReference);
26683
- handlerOps.push(createStatementOp(new ExpressionStatement(twoWaySetExpr)));
26684
- handlerOps.push(createStatementOp(new ReturnStatement(eventReference)));
26685
- return handlerOps;
26686
- }
26687
- function astOf(ast) {
26688
- return ast instanceof ASTWithSource ? ast.ast : ast;
26689
- }
26690
- /**
26691
- * Process all of the local references on an element-like structure in the template AST and
26692
- * convert them to their IR representation.
26693
- */
26694
- function ingestReferences(op, element) {
26695
- assertIsArray(op.localRefs);
26696
- for (const { name, value } of element.references) {
26697
- op.localRefs.push({
26698
- name,
26699
- target: value,
26700
- });
26678
+ // Generate the defer main view and all secondary views.
26679
+ const main = ingestDeferView(unit, '', deferBlock.i18n, deferBlock.children, deferBlock.sourceSpan);
26680
+ const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.i18n, deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
26681
+ const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.i18n, deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
26682
+ const error = ingestDeferView(unit, 'Error', deferBlock.error?.i18n, deferBlock.error?.children, deferBlock.error?.sourceSpan);
26683
+ // Create the main defer op, and ops for all secondary views.
26684
+ const deferXref = unit.job.allocateXrefId();
26685
+ const deferOp = createDeferOp(deferXref, main.xref, main.handle, ownResolverFn, unit.job.allDeferrableDepsFn, deferBlock.sourceSpan);
26686
+ deferOp.placeholderView = placeholder?.xref ?? null;
26687
+ deferOp.placeholderSlot = placeholder?.handle ?? null;
26688
+ deferOp.loadingSlot = loading?.handle ?? null;
26689
+ deferOp.errorSlot = error?.handle ?? null;
26690
+ deferOp.placeholderMinimumTime = deferBlock.placeholder?.minimumTime ?? null;
26691
+ deferOp.loadingMinimumTime = deferBlock.loading?.minimumTime ?? null;
26692
+ deferOp.loadingAfterTime = deferBlock.loading?.afterTime ?? null;
26693
+ deferOp.flags = calcDeferBlockFlags(deferBlock);
26694
+ unit.create.push(deferOp);
26695
+ // Configure all defer `on` conditions.
26696
+ // TODO: refactor prefetch triggers to use a separate op type, with a shared superclass. This will
26697
+ // make it easier to refactor prefetch behavior in the future.
26698
+ const deferOnOps = [];
26699
+ const deferWhenOps = [];
26700
+ // Ingest the hydrate triggers first since they set up all the other triggers during SSR.
26701
+ ingestDeferTriggers("hydrate" /* ir.DeferOpModifierKind.HYDRATE */, deferBlock.hydrateTriggers, deferOnOps, deferWhenOps, unit, deferXref);
26702
+ ingestDeferTriggers("none" /* ir.DeferOpModifierKind.NONE */, deferBlock.triggers, deferOnOps, deferWhenOps, unit, deferXref);
26703
+ ingestDeferTriggers("prefetch" /* ir.DeferOpModifierKind.PREFETCH */, deferBlock.prefetchTriggers, deferOnOps, deferWhenOps, unit, deferXref);
26704
+ // If no (non-prefetching or hydrating) defer triggers were provided, default to `idle`.
26705
+ const hasConcreteTrigger = deferOnOps.some((op) => op.modifier === "none" /* ir.DeferOpModifierKind.NONE */) ||
26706
+ deferWhenOps.some((op) => op.modifier === "none" /* ir.DeferOpModifierKind.NONE */);
26707
+ if (!hasConcreteTrigger) {
26708
+ deferOnOps.push(createDeferOnOp(deferXref, { kind: DeferTriggerKind.Idle }, "none" /* ir.DeferOpModifierKind.NONE */, null));
26701
26709
  }
26710
+ unit.create.push(deferOnOps);
26711
+ unit.update.push(deferWhenOps);
26702
26712
  }
26703
- /**
26704
- * Assert that the given value is an array.
26705
- */
26706
- function assertIsArray(value) {
26707
- if (!Array.isArray(value)) {
26708
- throw new Error(`AssertionError: expected an array`);
26713
+ function calcDeferBlockFlags(deferBlockDetails) {
26714
+ if (Object.keys(deferBlockDetails.hydrateTriggers).length > 0) {
26715
+ return 1 /* ir.TDeferDetailsFlags.HasHydrateTriggers */;
26709
26716
  }
26717
+ return null;
26710
26718
  }
26711
- /**
26712
- * Creates an absolute `ParseSourceSpan` from the relative `ParseSpan`.
26713
- *
26714
- * `ParseSpan` objects are relative to the start of the expression.
26715
- * This method converts these to full `ParseSourceSpan` objects that
26716
- * show where the span is within the overall source file.
26717
- *
26718
- * @param span the relative span to convert.
26719
- * @param baseSourceSpan a span corresponding to the base of the expression tree.
26720
- * @returns a `ParseSourceSpan` for the given span or null if no `baseSourceSpan` was provided.
26721
- */
26722
- function convertSourceSpan(span, baseSourceSpan) {
26723
- if (baseSourceSpan === null) {
26724
- return null;
26719
+ function ingestDeferTriggers(modifier, triggers, onOps, whenOps, unit, deferXref) {
26720
+ if (triggers.idle !== undefined) {
26721
+ const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Idle }, modifier, triggers.idle.sourceSpan);
26722
+ onOps.push(deferOnOp);
26725
26723
  }
26726
- const start = baseSourceSpan.start.moveBy(span.start);
26727
- const end = baseSourceSpan.start.moveBy(span.end);
26728
- const fullStart = baseSourceSpan.fullStart.moveBy(span.start);
26729
- return new ParseSourceSpan(start, end, fullStart);
26730
- }
26731
- /**
26732
- * With the directive-based control flow users were able to conditionally project content using
26733
- * the `*` syntax. E.g. `<div *ngIf="expr" projectMe></div>` will be projected into
26734
- * `<ng-content select="[projectMe]"/>`, because the attributes and tag name from the `div` are
26735
- * copied to the template via the template creation instruction. With `@if` and `@for` that is
26736
- * not the case, because the conditional is placed *around* elements, rather than *on* them.
26737
- * The result is that content projection won't work in the same way if a user converts from
26738
- * `*ngIf` to `@if`.
26739
- *
26740
- * This function aims to cover the most common case by doing the same copying when a control flow
26741
- * node has *one and only one* root element or template node.
26742
- *
26743
- * This approach comes with some caveats:
26744
- * 1. As soon as any other node is added to the root, the copying behavior won't work anymore.
26745
- * A diagnostic will be added to flag cases like this and to explain how to work around it.
26746
- * 2. If `preserveWhitespaces` is enabled, it's very likely that indentation will break this
26747
- * workaround, because it'll include an additional text node as the first child. We can work
26748
- * around it here, but in a discussion it was decided not to, because the user explicitly opted
26749
- * into preserving the whitespace and we would have to drop it from the generated code.
26750
- * The diagnostic mentioned point in #1 will flag such cases to users.
26751
- *
26752
- * @returns Tag name to be used for the control flow template.
26753
- */
26754
- function ingestControlFlowInsertionPoint(unit, xref, node) {
26755
- let root = null;
26756
- for (const child of node.children) {
26757
- // Skip over comment nodes and @let declarations since
26758
- // it doesn't matter where they end up in the DOM.
26759
- if (child instanceof Comment$1 || child instanceof LetDeclaration$1) {
26760
- continue;
26761
- }
26762
- // We can only infer the tag name/attributes if there's a single root node.
26763
- if (root !== null) {
26764
- return null;
26765
- }
26766
- // Root nodes can only elements or templates with a tag name (e.g. `<div *foo></div>`).
26767
- if (child instanceof Element$1 || (child instanceof Template && child.tagName !== null)) {
26768
- root = child;
26769
- }
26770
- else {
26771
- return null;
26772
- }
26724
+ if (triggers.immediate !== undefined) {
26725
+ const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Immediate }, modifier, triggers.immediate.sourceSpan);
26726
+ onOps.push(deferOnOp);
26773
26727
  }
26774
- // If we've found a single root node, its tag name and attributes can be
26775
- // copied to the surrounding template to be used for content projection.
26776
- if (root !== null) {
26777
- // Collect the static attributes for content projection purposes.
26778
- for (const attr of root.attributes) {
26779
- if (!attr.name.startsWith(ANIMATE_PREFIX$1)) {
26780
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
26781
- unit.update.push(createBindingOp(xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
26782
- }
26783
- }
26784
- // Also collect the inputs since they participate in content projection as well.
26785
- // Note that TDB used to collect the outputs as well, but it wasn't passing them into
26786
- // the template instruction. Here we just don't collect them.
26787
- for (const attr of root.inputs) {
26788
- if (attr.type !== BindingType.LegacyAnimation &&
26789
- attr.type !== BindingType.Animation &&
26790
- attr.type !== BindingType.Attribute) {
26791
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
26792
- unit.create.push(createExtractedAttributeOp(xref, BindingKind.Property, null, attr.name, null, null, null, securityContext));
26793
- }
26794
- }
26795
- const tagName = root instanceof Element$1 ? root.name : root.tagName;
26796
- // Don't pass along `ng-template` tag name since it enables directive matching.
26797
- return tagName === NG_TEMPLATE_TAG_NAME ? null : tagName;
26728
+ if (triggers.timer !== undefined) {
26729
+ const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Timer, delay: triggers.timer.delay }, modifier, triggers.timer.sourceSpan);
26730
+ onOps.push(deferOnOp);
26798
26731
  }
26799
- return null;
26800
- }
26801
-
26802
- /*!
26803
- * @license
26804
- * Copyright Google LLC All Rights Reserved.
26805
- *
26806
- * Use of this source code is governed by an MIT-style license that can be
26807
- * found in the LICENSE file at https://angular.dev/license
26808
- */
26809
- /**
26810
- * Whether to produce instructions that will attach the source location to each DOM node.
26811
- *
26812
- * !!!Important!!! at the time of writing this flag isn't exposed externally, but internal debug
26813
- * tools enable it via a local change. Any modifications to this flag need to update the
26814
- * internal tooling as well.
26815
- */
26816
- let ENABLE_TEMPLATE_SOURCE_LOCATIONS = false;
26817
- /** Gets whether template source locations are enabled. */
26818
- function getTemplateSourceLocationsEnabled() {
26819
- return ENABLE_TEMPLATE_SOURCE_LOCATIONS;
26820
- }
26821
-
26822
- // if (rf & flags) { .. }
26823
- function renderFlagCheckIfStmt(flags, statements) {
26824
- return ifStmt(variable(RENDER_FLAGS).bitwiseAnd(literal(flags), null), statements);
26825
- }
26826
- /**
26827
- * Translates query flags into `TQueryFlags` type in
26828
- * packages/core/src/render3/interfaces/query.ts
26829
- * @param query
26830
- */
26831
- function toQueryFlags(query) {
26832
- return ((query.descendants ? 1 /* QueryFlags.descendants */ : 0 /* QueryFlags.none */) |
26833
- (query.static ? 2 /* QueryFlags.isStatic */ : 0 /* QueryFlags.none */) |
26834
- (query.emitDistinctChangesOnly ? 4 /* QueryFlags.emitDistinctChangesOnly */ : 0 /* QueryFlags.none */));
26835
- }
26836
- function getQueryPredicate(query, constantPool) {
26837
- if (Array.isArray(query.predicate)) {
26838
- let predicate = [];
26839
- query.predicate.forEach((selector) => {
26840
- // Each item in predicates array may contain strings with comma-separated refs
26841
- // (for ex. 'ref, ref1, ..., refN'), thus we extract individual refs and store them
26842
- // as separate array entities
26843
- const selectors = selector.split(',').map((token) => literal(token.trim()));
26844
- predicate.push(...selectors);
26845
- });
26846
- return constantPool.getConstLiteral(literalArr(predicate), true);
26732
+ if (triggers.hover !== undefined) {
26733
+ const deferOnOp = createDeferOnOp(deferXref, {
26734
+ kind: DeferTriggerKind.Hover,
26735
+ targetName: triggers.hover.reference,
26736
+ targetXref: null,
26737
+ targetSlot: null,
26738
+ targetView: null,
26739
+ targetSlotViewSteps: null,
26740
+ }, modifier, triggers.hover.sourceSpan);
26741
+ onOps.push(deferOnOp);
26847
26742
  }
26848
- else {
26849
- // The original predicate may have been wrapped in a `forwardRef()` call.
26850
- switch (query.predicate.forwardRef) {
26851
- case 0 /* ForwardRefHandling.None */:
26852
- case 2 /* ForwardRefHandling.Unwrapped */:
26853
- return query.predicate.expression;
26854
- case 1 /* ForwardRefHandling.Wrapped */:
26855
- return importExpr(Identifiers.resolveForwardRef).callFn([query.predicate.expression]);
26743
+ if (triggers.interaction !== undefined) {
26744
+ const deferOnOp = createDeferOnOp(deferXref, {
26745
+ kind: DeferTriggerKind.Interaction,
26746
+ targetName: triggers.interaction.reference,
26747
+ targetXref: null,
26748
+ targetSlot: null,
26749
+ targetView: null,
26750
+ targetSlotViewSteps: null,
26751
+ }, modifier, triggers.interaction.sourceSpan);
26752
+ onOps.push(deferOnOp);
26753
+ }
26754
+ if (triggers.viewport !== undefined) {
26755
+ const deferOnOp = createDeferOnOp(deferXref, {
26756
+ kind: DeferTriggerKind.Viewport,
26757
+ targetName: triggers.viewport.reference,
26758
+ targetXref: null,
26759
+ targetSlot: null,
26760
+ targetView: null,
26761
+ targetSlotViewSteps: null,
26762
+ }, modifier, triggers.viewport.sourceSpan);
26763
+ onOps.push(deferOnOp);
26764
+ }
26765
+ if (triggers.never !== undefined) {
26766
+ const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Never }, modifier, triggers.never.sourceSpan);
26767
+ onOps.push(deferOnOp);
26768
+ }
26769
+ if (triggers.when !== undefined) {
26770
+ if (triggers.when.value instanceof Interpolation$1) {
26771
+ // TemplateDefinitionBuilder supports this case, but it's very strange to me. What would it
26772
+ // even mean?
26773
+ throw new Error(`Unexpected interpolation in defer block when trigger`);
26856
26774
  }
26775
+ const deferOnOp = createDeferWhenOp(deferXref, convertAst(triggers.when.value, unit.job, triggers.when.sourceSpan), modifier, triggers.when.sourceSpan);
26776
+ whenOps.push(deferOnOp);
26857
26777
  }
26858
26778
  }
26859
- function createQueryCreateCall(query, constantPool, queryTypeFns, prependParams) {
26860
- const parameters = [];
26861
- if (prependParams !== undefined) {
26862
- parameters.push(...prependParams);
26863
- }
26864
- if (query.isSignal) {
26865
- parameters.push(new ReadPropExpr(variable(CONTEXT_NAME), query.propertyName));
26779
+ function ingestIcu(unit, icu) {
26780
+ if (icu.i18n instanceof Message && isSingleI18nIcu(icu.i18n)) {
26781
+ const xref = unit.job.allocateXrefId();
26782
+ unit.create.push(createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null));
26783
+ for (const [placeholder, text] of Object.entries({ ...icu.vars, ...icu.placeholders })) {
26784
+ if (text instanceof BoundText) {
26785
+ ingestBoundText(unit, text, placeholder);
26786
+ }
26787
+ else {
26788
+ ingestText(unit, text, placeholder);
26789
+ }
26790
+ }
26791
+ unit.create.push(createIcuEndOp(xref));
26866
26792
  }
26867
- parameters.push(getQueryPredicate(query, constantPool), literal(toQueryFlags(query)));
26868
- if (query.read) {
26869
- parameters.push(query.read);
26793
+ else {
26794
+ throw Error(`Unhandled i18n metadata type for ICU: ${icu.i18n?.constructor.name}`);
26870
26795
  }
26871
- const queryCreateFn = query.isSignal ? queryTypeFns.signalBased : queryTypeFns.nonSignal;
26872
- return importExpr(queryCreateFn).callFn(parameters);
26873
26796
  }
26874
- const queryAdvancePlaceholder = Symbol('queryAdvancePlaceholder');
26875
26797
  /**
26876
- * Collapses query advance placeholders in a list of statements.
26877
- *
26878
- * This allows for less generated code because multiple sibling query advance
26879
- * statements can be collapsed into a single call with the count as argument.
26880
- *
26881
- * e.g.
26882
- *
26883
- * ```ts
26884
- * bla();
26885
- * queryAdvance();
26886
- * queryAdvance();
26887
- * bla();
26888
- * ```
26889
- *
26890
- * --> will turn into
26891
- *
26892
- * ```ts
26893
- * bla();
26894
- * queryAdvance(2);
26895
- * bla();
26896
- * ```
26798
+ * Ingest an `@for` block into the given `ViewCompilation`.
26897
26799
  */
26898
- function collapseAdvanceStatements(statements) {
26899
- const result = [];
26900
- let advanceCollapseCount = 0;
26901
- const flushAdvanceCount = () => {
26902
- if (advanceCollapseCount > 0) {
26903
- result.unshift(importExpr(Identifiers.queryAdvance)
26904
- .callFn(advanceCollapseCount === 1 ? [] : [literal(advanceCollapseCount)])
26905
- .toStmt());
26906
- advanceCollapseCount = 0;
26800
+ function ingestForBlock(unit, forBlock) {
26801
+ const repeaterView = unit.job.allocateView(unit.xref);
26802
+ // We copy TemplateDefinitionBuilder's scheme of creating names for `$count` and `$index`
26803
+ // that are suffixed with special information, to disambiguate which level of nested loop
26804
+ // the below aliases refer to.
26805
+ // TODO: We should refactor Template Pipeline's variable phases to gracefully handle
26806
+ // shadowing, and arbitrarily many levels of variables depending on each other.
26807
+ const indexName = `ɵ$index_${repeaterView.xref}`;
26808
+ const countName = `ɵ$count_${repeaterView.xref}`;
26809
+ const indexVarNames = new Set();
26810
+ // Set all the context variables and aliases available in the repeater.
26811
+ repeaterView.contextVariables.set(forBlock.item.name, forBlock.item.value);
26812
+ for (const variable of forBlock.contextVariables) {
26813
+ if (variable.value === '$index') {
26814
+ indexVarNames.add(variable.name);
26907
26815
  }
26908
- };
26909
- // Iterate through statements in reverse and collapse advance placeholders.
26910
- for (let i = statements.length - 1; i >= 0; i--) {
26911
- const st = statements[i];
26912
- if (st === queryAdvancePlaceholder) {
26913
- advanceCollapseCount++;
26816
+ if (variable.name === '$index') {
26817
+ repeaterView.contextVariables.set('$index', variable.value).set(indexName, variable.value);
26818
+ }
26819
+ else if (variable.name === '$count') {
26820
+ repeaterView.contextVariables.set('$count', variable.value).set(countName, variable.value);
26914
26821
  }
26915
26822
  else {
26916
- flushAdvanceCount();
26917
- result.unshift(st);
26823
+ repeaterView.aliases.add({
26824
+ kind: SemanticVariableKind.Alias,
26825
+ name: null,
26826
+ identifier: variable.name,
26827
+ expression: getComputedForLoopVariableExpression(variable, indexName, countName),
26828
+ });
26918
26829
  }
26919
26830
  }
26920
- flushAdvanceCount();
26921
- return result;
26922
- }
26923
- // Define and update any view queries
26924
- function createViewQueriesFunction(viewQueries, constantPool, name) {
26925
- const createStatements = [];
26926
- const updateStatements = [];
26927
- const tempAllocator = temporaryAllocator((st) => updateStatements.push(st), TEMPORARY_NAME);
26928
- viewQueries.forEach((query) => {
26929
- // creation call, e.g. r3.viewQuery(somePredicate, true) or
26930
- // r3.viewQuerySignal(ctx.prop, somePredicate, true);
26931
- const queryDefinitionCall = createQueryCreateCall(query, constantPool, {
26932
- signalBased: Identifiers.viewQuerySignal,
26933
- nonSignal: Identifiers.viewQuery,
26934
- });
26935
- createStatements.push(queryDefinitionCall.toStmt());
26936
- // Signal queries update lazily and we just advance the index.
26937
- if (query.isSignal) {
26938
- updateStatements.push(queryAdvancePlaceholder);
26939
- return;
26940
- }
26941
- // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
26942
- const temporary = tempAllocator();
26943
- const getQueryList = importExpr(Identifiers.loadQuery).callFn([]);
26944
- const refresh = importExpr(Identifiers.queryRefresh).callFn([temporary.set(getQueryList)]);
26945
- const updateDirective = variable(CONTEXT_NAME)
26946
- .prop(query.propertyName)
26947
- .set(query.first ? temporary.prop('first') : temporary);
26948
- updateStatements.push(refresh.and(updateDirective).toStmt());
26949
- });
26950
- const viewQueryFnName = name ? `${name}_Query` : null;
26951
- return fn([new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null)], [
26952
- renderFlagCheckIfStmt(1 /* core.RenderFlags.Create */, createStatements),
26953
- renderFlagCheckIfStmt(2 /* core.RenderFlags.Update */, collapseAdvanceStatements(updateStatements)),
26954
- ], INFERRED_TYPE, null, viewQueryFnName);
26955
- }
26956
- // Define and update any content queries
26957
- function createContentQueriesFunction(queries, constantPool, name) {
26958
- const createStatements = [];
26959
- const updateStatements = [];
26960
- const tempAllocator = temporaryAllocator((st) => updateStatements.push(st), TEMPORARY_NAME);
26961
- for (const query of queries) {
26962
- // creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null) or
26963
- // r3.contentQuerySignal(dirIndex, propName, somePredicate, <flags>, <read>).
26964
- createStatements.push(createQueryCreateCall(query, constantPool, { nonSignal: Identifiers.contentQuery, signalBased: Identifiers.contentQuerySignal },
26965
- /* prependParams */ [variable('dirIndex')]).toStmt());
26966
- // Signal queries update lazily and we just advance the index.
26967
- if (query.isSignal) {
26968
- updateStatements.push(queryAdvancePlaceholder);
26969
- continue;
26970
- }
26971
- // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
26972
- const temporary = tempAllocator();
26973
- const getQueryList = importExpr(Identifiers.loadQuery).callFn([]);
26974
- const refresh = importExpr(Identifiers.queryRefresh).callFn([temporary.set(getQueryList)]);
26975
- const updateDirective = variable(CONTEXT_NAME)
26976
- .prop(query.propertyName)
26977
- .set(query.first ? temporary.prop('first') : temporary);
26978
- updateStatements.push(refresh.and(updateDirective).toStmt());
26831
+ const sourceSpan = convertSourceSpan(forBlock.trackBy.span, forBlock.sourceSpan);
26832
+ const track = convertAst(forBlock.trackBy, unit.job, sourceSpan);
26833
+ ingestNodes(repeaterView, forBlock.children);
26834
+ let emptyView = null;
26835
+ let emptyTagName = null;
26836
+ if (forBlock.empty !== null) {
26837
+ emptyView = unit.job.allocateView(unit.xref);
26838
+ ingestNodes(emptyView, forBlock.empty.children);
26839
+ emptyTagName = ingestControlFlowInsertionPoint(unit, emptyView.xref, forBlock.empty);
26979
26840
  }
26980
- const contentQueriesFnName = name ? `${name}_ContentQueries` : null;
26981
- return fn([
26982
- new FnParam(RENDER_FLAGS, NUMBER_TYPE),
26983
- new FnParam(CONTEXT_NAME, null),
26984
- new FnParam('dirIndex', null),
26985
- ], [
26986
- renderFlagCheckIfStmt(1 /* core.RenderFlags.Create */, createStatements),
26987
- renderFlagCheckIfStmt(2 /* core.RenderFlags.Update */, collapseAdvanceStatements(updateStatements)),
26988
- ], INFERRED_TYPE, null, contentQueriesFnName);
26989
- }
26990
-
26991
- class HtmlParser extends Parser$1 {
26992
- constructor() {
26993
- super(getHtmlTagDefinition);
26841
+ const varNames = {
26842
+ $index: indexVarNames,
26843
+ $implicit: forBlock.item.name,
26844
+ };
26845
+ if (forBlock.i18n !== undefined && !(forBlock.i18n instanceof BlockPlaceholder)) {
26846
+ throw Error('AssertionError: Unhandled i18n metadata type or @for');
26994
26847
  }
26995
- parse(source, url, options) {
26996
- return super.parse(source, url, options);
26848
+ if (forBlock.empty?.i18n !== undefined &&
26849
+ !(forBlock.empty.i18n instanceof BlockPlaceholder)) {
26850
+ throw Error('AssertionError: Unhandled i18n metadata type or @empty');
26997
26851
  }
26852
+ const i18nPlaceholder = forBlock.i18n;
26853
+ const emptyI18nPlaceholder = forBlock.empty?.i18n;
26854
+ const tagName = ingestControlFlowInsertionPoint(unit, repeaterView.xref, forBlock);
26855
+ const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, emptyTagName, i18nPlaceholder, emptyI18nPlaceholder, forBlock.startSourceSpan, forBlock.sourceSpan);
26856
+ unit.create.push(repeaterCreate);
26857
+ const expression = convertAst(forBlock.expression, unit.job, convertSourceSpan(forBlock.expression.span, forBlock.sourceSpan));
26858
+ const repeater = createRepeaterOp(repeaterCreate.xref, repeaterCreate.handle, expression, forBlock.sourceSpan);
26859
+ unit.update.push(repeater);
26998
26860
  }
26999
-
27000
- const PROPERTY_PARTS_SEPARATOR = '.';
27001
- const ATTRIBUTE_PREFIX = 'attr';
27002
- const ANIMATE_PREFIX = 'animate';
27003
- const CLASS_PREFIX = 'class';
27004
- const STYLE_PREFIX = 'style';
27005
- const TEMPLATE_ATTR_PREFIX$1 = '*';
27006
- const LEGACY_ANIMATE_PROP_PREFIX = 'animate-';
27007
26861
  /**
27008
- * Parses bindings in templates and in the directive host area.
26862
+ * Gets an expression that represents a variable in an `@for` loop.
26863
+ * @param variable AST representing the variable.
26864
+ * @param indexName Loop-specific name for `$index`.
26865
+ * @param countName Loop-specific name for `$count`.
27009
26866
  */
27010
- class BindingParser {
27011
- _exprParser;
27012
- _interpolationConfig;
27013
- _schemaRegistry;
27014
- errors;
27015
- constructor(_exprParser, _interpolationConfig, _schemaRegistry, errors) {
27016
- this._exprParser = _exprParser;
27017
- this._interpolationConfig = _interpolationConfig;
27018
- this._schemaRegistry = _schemaRegistry;
27019
- this.errors = errors;
26867
+ function getComputedForLoopVariableExpression(variable, indexName, countName) {
26868
+ switch (variable.value) {
26869
+ case '$index':
26870
+ return new LexicalReadExpr(indexName);
26871
+ case '$count':
26872
+ return new LexicalReadExpr(countName);
26873
+ case '$first':
26874
+ return new LexicalReadExpr(indexName).identical(literal(0));
26875
+ case '$last':
26876
+ return new LexicalReadExpr(indexName).identical(new LexicalReadExpr(countName).minus(literal(1)));
26877
+ case '$even':
26878
+ return new LexicalReadExpr(indexName).modulo(literal(2)).identical(literal(0));
26879
+ case '$odd':
26880
+ return new LexicalReadExpr(indexName).modulo(literal(2)).notIdentical(literal(0));
26881
+ default:
26882
+ throw new Error(`AssertionError: unknown @for loop variable ${variable.value}`);
27020
26883
  }
27021
- get interpolationConfig() {
27022
- return this._interpolationConfig;
26884
+ }
26885
+ function ingestLetDeclaration(unit, node) {
26886
+ const target = unit.job.allocateXrefId();
26887
+ unit.create.push(createDeclareLetOp(target, node.name, node.sourceSpan));
26888
+ unit.update.push(createStoreLetOp(target, node.name, convertAst(node.value, unit.job, node.valueSpan), node.sourceSpan));
26889
+ }
26890
+ /**
26891
+ * Convert a template AST expression into an output AST expression.
26892
+ */
26893
+ function convertAst(ast, job, baseSourceSpan) {
26894
+ if (ast instanceof ASTWithSource) {
26895
+ return convertAst(ast.ast, job, baseSourceSpan);
27023
26896
  }
27024
- createBoundHostProperties(properties, sourceSpan) {
27025
- const boundProps = [];
27026
- for (const propName of Object.keys(properties)) {
27027
- const expression = properties[propName];
27028
- if (typeof expression === 'string') {
27029
- this.parsePropertyBinding(propName, expression, true, false, sourceSpan, sourceSpan.start.offset, undefined, [],
27030
- // Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the
27031
- // sourceSpan, as it represents the sourceSpan of the host itself rather than the
27032
- // source of the host binding (which doesn't exist in the template). Regardless,
27033
- // neither of these values are used in Ivy but are only here to satisfy the function
27034
- // signature. This should likely be refactored in the future so that `sourceSpan`
27035
- // isn't being used inaccurately.
27036
- boundProps, sourceSpan);
27037
- }
27038
- else {
27039
- this._reportError(`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
27040
- }
26897
+ else if (ast instanceof PropertyRead) {
26898
+ // Whether this is an implicit receiver, *excluding* explicit reads of `this`.
26899
+ const isImplicitReceiver = ast.receiver instanceof ImplicitReceiver && !(ast.receiver instanceof ThisReceiver);
26900
+ if (isImplicitReceiver) {
26901
+ return new LexicalReadExpr(ast.name);
27041
26902
  }
27042
- return boundProps;
27043
- }
27044
- createDirectiveHostEventAsts(hostListeners, sourceSpan) {
27045
- const targetEvents = [];
27046
- for (const propName of Object.keys(hostListeners)) {
27047
- const expression = hostListeners[propName];
27048
- if (typeof expression === 'string') {
27049
- // Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but
27050
- // neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself
27051
- // rather than the source of the host binding (which doesn't exist in the template).
27052
- // Regardless, neither of these values are used in Ivy but are only here to satisfy the
27053
- // function signature. This should likely be refactored in the future so that `sourceSpan`
27054
- // isn't being used inaccurately.
27055
- this.parseEvent(propName, expression,
27056
- /* isAssignmentEvent */ false, sourceSpan, sourceSpan, [], targetEvents, sourceSpan);
27057
- }
27058
- else {
27059
- this._reportError(`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
27060
- }
26903
+ else {
26904
+ return new ReadPropExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name, null, convertSourceSpan(ast.span, baseSourceSpan));
27061
26905
  }
27062
- return targetEvents;
27063
26906
  }
27064
- parseInterpolation(value, sourceSpan, interpolatedTokens) {
27065
- const absoluteOffset = sourceSpan.fullStart.offset;
27066
- try {
27067
- const ast = this._exprParser.parseInterpolation(value, sourceSpan, absoluteOffset, interpolatedTokens, this._interpolationConfig);
27068
- if (ast) {
27069
- this.errors.push(...ast.errors);
27070
- }
27071
- return ast;
26907
+ else if (ast instanceof Call) {
26908
+ if (ast.receiver instanceof ImplicitReceiver) {
26909
+ throw new Error(`Unexpected ImplicitReceiver`);
27072
26910
  }
27073
- catch (e) {
27074
- this._reportError(`${e}`, sourceSpan);
27075
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
26911
+ else {
26912
+ return new InvokeFunctionExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((arg) => convertAst(arg, job, baseSourceSpan)), undefined, convertSourceSpan(ast.span, baseSourceSpan));
27076
26913
  }
27077
26914
  }
27078
- /**
27079
- * Similar to `parseInterpolation`, but treats the provided string as a single expression
27080
- * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
27081
- * This is used for parsing the switch expression in ICUs.
27082
- */
27083
- parseInterpolationExpression(expression, sourceSpan) {
27084
- const absoluteOffset = sourceSpan.start.offset;
27085
- try {
27086
- const ast = this._exprParser.parseInterpolationExpression(expression, sourceSpan, absoluteOffset);
27087
- if (ast) {
27088
- this.errors.push(...ast.errors);
27089
- }
27090
- return ast;
27091
- }
27092
- catch (e) {
27093
- this._reportError(`${e}`, sourceSpan);
27094
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
27095
- }
26915
+ else if (ast instanceof LiteralPrimitive) {
26916
+ return literal(ast.value, undefined, convertSourceSpan(ast.span, baseSourceSpan));
27096
26917
  }
27097
- /**
27098
- * Parses the bindings in a microsyntax expression, and converts them to
27099
- * `ParsedProperty` or `ParsedVariable`.
27100
- *
27101
- * @param tplKey template binding name
27102
- * @param tplValue template binding value
27103
- * @param sourceSpan span of template binding relative to entire the template
27104
- * @param absoluteValueOffset start of the tplValue relative to the entire template
27105
- * @param targetMatchableAttrs potential attributes to match in the template
27106
- * @param targetProps target property bindings in the template
27107
- * @param targetVars target variables in the template
27108
- */
27109
- parseInlineTemplateBinding(tplKey, tplValue, sourceSpan, absoluteValueOffset, targetMatchableAttrs, targetProps, targetVars, isIvyAst) {
27110
- const absoluteKeyOffset = sourceSpan.start.offset + TEMPLATE_ATTR_PREFIX$1.length;
27111
- const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
27112
- for (const binding of bindings) {
27113
- // sourceSpan is for the entire HTML attribute. bindingSpan is for a particular
27114
- // binding within the microsyntax expression so it's more narrow than sourceSpan.
27115
- const bindingSpan = moveParseSourceSpan(sourceSpan, binding.sourceSpan);
27116
- const key = binding.key.source;
27117
- const keySpan = moveParseSourceSpan(sourceSpan, binding.key.span);
27118
- if (binding instanceof VariableBinding) {
27119
- const value = binding.value ? binding.value.source : '$implicit';
27120
- const valueSpan = binding.value
27121
- ? moveParseSourceSpan(sourceSpan, binding.value.span)
27122
- : undefined;
27123
- targetVars.push(new ParsedVariable(key, value, bindingSpan, keySpan, valueSpan));
27124
- }
27125
- else if (binding.value) {
27126
- const srcSpan = isIvyAst ? bindingSpan : sourceSpan;
27127
- const valueSpan = moveParseSourceSpan(sourceSpan, binding.value.ast.sourceSpan);
27128
- this._parsePropertyAst(key, binding.value, false, srcSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
27129
- }
27130
- else {
27131
- targetMatchableAttrs.push([key, '' /* value */]);
27132
- // Since this is a literal attribute with no RHS, source span should be
27133
- // just the key span.
27134
- this.parseLiteralAttr(key, null /* value */, keySpan, absoluteValueOffset, undefined /* valueSpan */, targetMatchableAttrs, targetProps, keySpan);
27135
- }
26918
+ else if (ast instanceof Unary) {
26919
+ switch (ast.operator) {
26920
+ case '+':
26921
+ return new UnaryOperatorExpr(UnaryOperator.Plus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26922
+ case '-':
26923
+ return new UnaryOperatorExpr(UnaryOperator.Minus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26924
+ default:
26925
+ throw new Error(`AssertionError: unknown unary operator ${ast.operator}`);
27136
26926
  }
27137
26927
  }
27138
- /**
27139
- * Parses the bindings in a microsyntax expression, e.g.
27140
- * ```html
27141
- * <tag *tplKey="let value1 = prop; let value2 = localVar">
27142
- * ```
27143
- *
27144
- * @param tplKey template binding name
27145
- * @param tplValue template binding value
27146
- * @param sourceSpan span of template binding relative to entire the template
27147
- * @param absoluteKeyOffset start of the `tplKey`
27148
- * @param absoluteValueOffset start of the `tplValue`
27149
- */
27150
- _parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset) {
27151
- try {
27152
- const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
27153
- bindingsResult.errors.forEach((e) => this.errors.push(e));
27154
- bindingsResult.warnings.forEach((warning) => {
27155
- this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING);
27156
- });
27157
- return bindingsResult.templateBindings;
27158
- }
27159
- catch (e) {
27160
- this._reportError(`${e}`, sourceSpan);
27161
- return [];
26928
+ else if (ast instanceof Binary) {
26929
+ const operator = BINARY_OPERATORS.get(ast.operation);
26930
+ if (operator === undefined) {
26931
+ throw new Error(`AssertionError: unknown binary operator ${ast.operation}`);
27162
26932
  }
26933
+ return new BinaryOperatorExpr(operator, convertAst(ast.left, job, baseSourceSpan), convertAst(ast.right, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26934
+ }
26935
+ else if (ast instanceof ThisReceiver) {
26936
+ // TODO: should context expressions have source maps?
26937
+ return new ContextExpr(job.root.xref);
26938
+ }
26939
+ else if (ast instanceof KeyedRead) {
26940
+ return new ReadKeyExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26941
+ }
26942
+ else if (ast instanceof Chain) {
26943
+ throw new Error(`AssertionError: Chain in unknown context`);
26944
+ }
26945
+ else if (ast instanceof LiteralMap) {
26946
+ const entries = ast.keys.map((key, idx) => {
26947
+ const value = ast.values[idx];
26948
+ // TODO: should literals have source maps, or do we just map the whole surrounding
26949
+ // expression?
26950
+ return new LiteralMapEntry(key.key, convertAst(value, job, baseSourceSpan), key.quoted);
26951
+ });
26952
+ return new LiteralMapExpr(entries, undefined, convertSourceSpan(ast.span, baseSourceSpan));
26953
+ }
26954
+ else if (ast instanceof LiteralArray) {
26955
+ // TODO: should literals have source maps, or do we just map the whole surrounding expression?
26956
+ return new LiteralArrayExpr(ast.expressions.map((expr) => convertAst(expr, job, baseSourceSpan)));
26957
+ }
26958
+ else if (ast instanceof Conditional) {
26959
+ return new ConditionalExpr(convertAst(ast.condition, job, baseSourceSpan), convertAst(ast.trueExp, job, baseSourceSpan), convertAst(ast.falseExp, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26960
+ }
26961
+ else if (ast instanceof NonNullAssert) {
26962
+ // A non-null assertion shouldn't impact generated instructions, so we can just drop it.
26963
+ return convertAst(ast.expression, job, baseSourceSpan);
26964
+ }
26965
+ else if (ast instanceof BindingPipe) {
26966
+ // TODO: pipes should probably have source maps; figure out details.
26967
+ return new PipeBindingExpr(job.allocateXrefId(), new SlotHandle(), ast.name, [
26968
+ convertAst(ast.exp, job, baseSourceSpan),
26969
+ ...ast.args.map((arg) => convertAst(arg, job, baseSourceSpan)),
26970
+ ]);
26971
+ }
26972
+ else if (ast instanceof SafeKeyedRead) {
26973
+ return new SafeKeyedReadExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
26974
+ }
26975
+ else if (ast instanceof SafePropertyRead) {
26976
+ // TODO: source span
26977
+ return new SafePropertyReadExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name);
26978
+ }
26979
+ else if (ast instanceof SafeCall) {
26980
+ // TODO: source span
26981
+ return new SafeInvokeFunctionExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((a) => convertAst(a, job, baseSourceSpan)));
26982
+ }
26983
+ else if (ast instanceof EmptyExpr$1) {
26984
+ return new EmptyExpr(convertSourceSpan(ast.span, baseSourceSpan));
26985
+ }
26986
+ else if (ast instanceof PrefixNot) {
26987
+ return not(convertAst(ast.expression, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
26988
+ }
26989
+ else if (ast instanceof TypeofExpression) {
26990
+ return typeofExpr(convertAst(ast.expression, job, baseSourceSpan));
26991
+ }
26992
+ else if (ast instanceof VoidExpression) {
26993
+ return new VoidExpr(convertAst(ast.expression, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
26994
+ }
26995
+ else if (ast instanceof TemplateLiteral) {
26996
+ return convertTemplateLiteral(ast, job, baseSourceSpan);
26997
+ }
26998
+ else if (ast instanceof TaggedTemplateLiteral) {
26999
+ return new TaggedTemplateLiteralExpr(convertAst(ast.tag, job, baseSourceSpan), convertTemplateLiteral(ast.template, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
27000
+ }
27001
+ else if (ast instanceof ParenthesizedExpression) {
27002
+ return new ParenthesizedExpr(convertAst(ast.expression, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
27003
+ }
27004
+ else {
27005
+ throw new Error(`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`);
27006
+ }
27007
+ }
27008
+ function convertTemplateLiteral(ast, job, baseSourceSpan) {
27009
+ return new TemplateLiteralExpr(ast.elements.map((el) => {
27010
+ return new TemplateLiteralElementExpr(el.text, convertSourceSpan(el.span, baseSourceSpan));
27011
+ }), ast.expressions.map((expr) => convertAst(expr, job, baseSourceSpan)), convertSourceSpan(ast.span, baseSourceSpan));
27012
+ }
27013
+ function convertAstWithInterpolation(job, value, i18nMeta, sourceSpan) {
27014
+ let expression;
27015
+ if (value instanceof Interpolation$1) {
27016
+ expression = new Interpolation(value.strings, value.expressions.map((e) => convertAst(e, job, null)), Object.keys(asMessage(i18nMeta)?.placeholders ?? {}));
27017
+ }
27018
+ else if (value instanceof AST) {
27019
+ expression = convertAst(value, job, null);
27020
+ }
27021
+ else {
27022
+ expression = literal(value);
27023
+ }
27024
+ return expression;
27025
+ }
27026
+ // TODO: Can we populate Template binding kinds in ingest?
27027
+ const BINDING_KINDS = new Map([
27028
+ [BindingType.Property, BindingKind.Property],
27029
+ [BindingType.TwoWay, BindingKind.TwoWayProperty],
27030
+ [BindingType.Attribute, BindingKind.Attribute],
27031
+ [BindingType.Class, BindingKind.ClassName],
27032
+ [BindingType.Style, BindingKind.StyleProperty],
27033
+ [BindingType.LegacyAnimation, BindingKind.LegacyAnimation],
27034
+ [BindingType.Animation, BindingKind.Animation],
27035
+ ]);
27036
+ /**
27037
+ * Checks whether the given template is a plain ng-template (as opposed to another kind of template
27038
+ * such as a structural directive template or control flow template). This is checked based on the
27039
+ * tagName. We can expect that only plain ng-templates will come through with a tagName of
27040
+ * 'ng-template'.
27041
+ *
27042
+ * Here are some of the cases we expect:
27043
+ *
27044
+ * | Angular HTML | Template tagName |
27045
+ * | ---------------------------------- | ------------------ |
27046
+ * | `<ng-template>` | 'ng-template' |
27047
+ * | `<div *ngIf="true">` | 'div' |
27048
+ * | `<svg><ng-template>` | 'svg:ng-template' |
27049
+ * | `@if (true) {` | 'Conditional' |
27050
+ * | `<ng-template *ngIf>` (plain) | 'ng-template' |
27051
+ * | `<ng-template *ngIf>` (structural) | null |
27052
+ */
27053
+ function isPlainTemplate(tmpl) {
27054
+ return splitNsName(tmpl.tagName ?? '')[1] === NG_TEMPLATE_TAG_NAME;
27055
+ }
27056
+ /**
27057
+ * Ensures that the i18nMeta, if provided, is an i18n.Message.
27058
+ */
27059
+ function asMessage(i18nMeta) {
27060
+ if (i18nMeta == null) {
27061
+ return null;
27163
27062
  }
27164
- parseLiteralAttr(name, value, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
27165
- if (isLegacyAnimationLabel(name)) {
27166
- name = name.substring(1);
27167
- if (keySpan !== undefined) {
27168
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
27169
- }
27170
- if (value) {
27171
- this._reportError(`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
27172
- ` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`, sourceSpan, ParseErrorLevel.ERROR);
27063
+ if (!(i18nMeta instanceof Message)) {
27064
+ throw Error(`Expected i18n meta to be a Message, but got: ${i18nMeta.constructor.name}`);
27065
+ }
27066
+ return i18nMeta;
27067
+ }
27068
+ /**
27069
+ * Process all of the bindings on an element in the template AST and convert them to their IR
27070
+ * representation.
27071
+ */
27072
+ function ingestElementBindings(unit, op, element) {
27073
+ let bindings = new Array();
27074
+ let i18nAttributeBindingNames = new Set();
27075
+ for (const attr of element.attributes) {
27076
+ // Attribute literal bindings, such as `attr.foo="bar"`.
27077
+ const [ns, elementName] = splitNsName(element.name);
27078
+ let namespace = ns;
27079
+ if (!ns) {
27080
+ switch (op.namespace) {
27081
+ case Namespace.SVG:
27082
+ namespace = SVG_NAMESPACE;
27083
+ break;
27084
+ case Namespace.Math:
27085
+ namespace = MATH_ML_NAMESPACE;
27086
+ break;
27173
27087
  }
27174
- this._parseLegacyAnimation(name, value, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
27175
27088
  }
27176
- else {
27177
- targetProps.push(new ParsedProperty(name, this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset), ParsedPropertyType.LITERAL_ATTR, sourceSpan, keySpan, valueSpan));
27089
+ const securityContext = domSchema.securityContext(namespace ? `:${namespace}:${elementName}` : elementName, attr.name, true);
27090
+ 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));
27091
+ if (attr.i18n) {
27092
+ i18nAttributeBindingNames.add(attr.name);
27178
27093
  }
27179
27094
  }
27180
- parsePropertyBinding(name, expression, isHost, isPartOfAssignmentBinding, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
27181
- if (name.length === 0) {
27182
- this._reportError(`Property name is missing in binding`, sourceSpan);
27183
- }
27184
- let isLegacyAnimationProp = false;
27185
- if (name.startsWith(LEGACY_ANIMATE_PROP_PREFIX)) {
27186
- isLegacyAnimationProp = true;
27187
- name = name.substring(LEGACY_ANIMATE_PROP_PREFIX.length);
27188
- if (keySpan !== undefined) {
27189
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + LEGACY_ANIMATE_PROP_PREFIX.length, keySpan.end.offset));
27190
- }
27095
+ for (const input of element.inputs) {
27096
+ if (i18nAttributeBindingNames.has(input.name)) {
27097
+ console.error(`On component ${unit.job.componentName}, the binding ${input.name} is both an i18n attribute and a property. You may want to remove the property binding. This will become a compilation error in future versions of Angular.`);
27191
27098
  }
27192
- else if (isLegacyAnimationLabel(name)) {
27193
- isLegacyAnimationProp = true;
27194
- name = name.substring(1);
27195
- if (keySpan !== undefined) {
27196
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
27197
- }
27099
+ // All dynamic bindings (both attribute and property bindings).
27100
+ 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));
27101
+ }
27102
+ unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
27103
+ unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
27104
+ for (const output of element.outputs) {
27105
+ if (output.type === ParsedEventType.LegacyAnimation && output.phase === null) {
27106
+ throw Error('Animation listener should have a phase');
27198
27107
  }
27199
- if (isLegacyAnimationProp) {
27200
- this._parseLegacyAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
27108
+ if (output.type === ParsedEventType.TwoWay) {
27109
+ unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
27201
27110
  }
27202
- else if (name.startsWith(`${ANIMATE_PREFIX}${PROPERTY_PARTS_SEPARATOR}`)) {
27203
- this._parseAnimation(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
27111
+ else if (output.type === ParsedEventType.Animation) {
27112
+ unit.create.push(createAnimationListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.name.endsWith('enter') ? "enter" /* ir.AnimationKind.ENTER */ : "leave" /* ir.AnimationKind.LEAVE */, output.target, false, output.sourceSpan));
27204
27113
  }
27205
27114
  else {
27206
- this._parsePropertyAst(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
27207
- }
27208
- }
27209
- parsePropertyInterpolation(name, value, sourceSpan, valueSpan, targetMatchableAttrs, targetProps, keySpan, interpolatedTokens) {
27210
- const expr = this.parseInterpolation(value, valueSpan || sourceSpan, interpolatedTokens);
27211
- if (expr) {
27212
- this._parsePropertyAst(name, expr, false, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
27213
- return true;
27115
+ 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));
27214
27116
  }
27215
- return false;
27216
- }
27217
- _parsePropertyAst(name, ast, isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
27218
- targetMatchableAttrs.push([name, ast.source]);
27219
- targetProps.push(new ParsedProperty(name, ast, isPartOfAssignmentBinding ? ParsedPropertyType.TWO_WAY : ParsedPropertyType.DEFAULT, sourceSpan, keySpan, valueSpan));
27220
- }
27221
- _parseAnimation(name, ast, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
27222
- targetMatchableAttrs.push([name, ast.source]);
27223
- targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan, keySpan, valueSpan));
27224
27117
  }
27225
- _parseLegacyAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
27226
- if (name.length === 0) {
27227
- this._reportError('Animation trigger is missing', sourceSpan);
27228
- }
27229
- // This will occur when a @trigger is not paired with an expression.
27230
- // For animations it is valid to not have an expression since */void
27231
- // states will be applied by angular when the element is attached/detached
27232
- const ast = this.parseBinding(expression || 'undefined', false, valueSpan || sourceSpan, absoluteOffset);
27233
- targetMatchableAttrs.push([name, ast.source]);
27234
- targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.LEGACY_ANIMATION, sourceSpan, keySpan, valueSpan));
27118
+ // If any of the bindings on this element have an i18n message, then an i18n attrs configuration
27119
+ // op is also required.
27120
+ if (bindings.some((b) => b?.i18nMessage) !== null) {
27121
+ unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
27235
27122
  }
27236
- parseBinding(value, isHostBinding, sourceSpan, absoluteOffset) {
27237
- try {
27238
- const ast = isHostBinding
27239
- ? this._exprParser.parseSimpleBinding(value, sourceSpan, absoluteOffset, this._interpolationConfig)
27240
- : this._exprParser.parseBinding(value, sourceSpan, absoluteOffset, this._interpolationConfig);
27241
- if (ast) {
27242
- this.errors.push(...ast.errors);
27243
- }
27244
- return ast;
27123
+ }
27124
+ /**
27125
+ * Process all of the bindings on a template in the template AST and convert them to their IR
27126
+ * representation.
27127
+ */
27128
+ function ingestTemplateBindings(unit, op, template, templateKind) {
27129
+ let bindings = new Array();
27130
+ for (const attr of template.templateAttrs) {
27131
+ if (attr instanceof TextAttribute) {
27132
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
27133
+ bindings.push(createTemplateBinding(unit, op.xref, BindingType.Attribute, attr.name, attr.value, null, securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
27245
27134
  }
27246
- catch (e) {
27247
- this._reportError(`${e}`, sourceSpan);
27248
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
27135
+ else {
27136
+ bindings.push(createTemplateBinding(unit, op.xref, attr.type, attr.name, astOf(attr.value), attr.unit, attr.securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
27249
27137
  }
27250
27138
  }
27251
- createBoundElementProperty(elementSelector, boundProp, skipValidation = false, mapPropertyName = true) {
27252
- if (boundProp.isLegacyAnimation) {
27253
- return new BoundElementProperty(boundProp.name, BindingType.LegacyAnimation, SecurityContext.NONE, boundProp.expression, null, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
27139
+ for (const attr of template.attributes) {
27140
+ // Attribute literal bindings, such as `attr.foo="bar"`.
27141
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
27142
+ bindings.push(createTemplateBinding(unit, op.xref, BindingType.Attribute, attr.name, attr.value, null, securityContext, false, templateKind, asMessage(attr.i18n), attr.sourceSpan));
27143
+ }
27144
+ for (const input of template.inputs) {
27145
+ // Dynamic bindings (both attribute and property bindings).
27146
+ bindings.push(createTemplateBinding(unit, op.xref, input.type, input.name, astOf(input.value), input.unit, input.securityContext, false, templateKind, asMessage(input.i18n), input.sourceSpan));
27147
+ }
27148
+ unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
27149
+ unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
27150
+ for (const output of template.outputs) {
27151
+ if (output.type === ParsedEventType.LegacyAnimation && output.phase === null) {
27152
+ throw Error('Animation listener should have a phase');
27254
27153
  }
27255
- let unit = null;
27256
- let bindingType = undefined;
27257
- let boundPropertyName = null;
27258
- const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
27259
- let securityContexts = undefined;
27260
- // Check for special cases (prefix style, attr, class)
27261
- if (parts.length > 1) {
27262
- if (parts[0] == ATTRIBUTE_PREFIX) {
27263
- boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
27264
- if (!skipValidation) {
27265
- this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
27266
- }
27267
- securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, boundPropertyName, true);
27268
- const nsSeparatorIdx = boundPropertyName.indexOf(':');
27269
- if (nsSeparatorIdx > -1) {
27270
- const ns = boundPropertyName.substring(0, nsSeparatorIdx);
27271
- const name = boundPropertyName.substring(nsSeparatorIdx + 1);
27272
- boundPropertyName = mergeNsAndName(ns, name);
27273
- }
27274
- bindingType = BindingType.Attribute;
27275
- }
27276
- else if (parts[0] == CLASS_PREFIX) {
27277
- boundPropertyName = parts[1];
27278
- bindingType = BindingType.Class;
27279
- securityContexts = [SecurityContext.NONE];
27280
- }
27281
- else if (parts[0] == STYLE_PREFIX) {
27282
- unit = parts.length > 2 ? parts[2] : null;
27283
- boundPropertyName = parts[1];
27284
- bindingType = BindingType.Style;
27285
- securityContexts = [SecurityContext.STYLE];
27154
+ if (templateKind === TemplateKind.NgTemplate) {
27155
+ if (output.type === ParsedEventType.TwoWay) {
27156
+ unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
27286
27157
  }
27287
- else if (parts[0] == ANIMATE_PREFIX) {
27288
- boundPropertyName = boundProp.name;
27289
- bindingType = BindingType.Animation;
27290
- securityContexts = [SecurityContext.NONE];
27158
+ else {
27159
+ 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));
27291
27160
  }
27292
27161
  }
27293
- // If not a special case, use the full property name
27294
- if (boundPropertyName === null) {
27295
- const mappedPropName = this._schemaRegistry.getMappedPropName(boundProp.name);
27296
- boundPropertyName = mapPropertyName ? mappedPropName : boundProp.name;
27297
- securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, mappedPropName, false);
27298
- bindingType =
27299
- boundProp.type === ParsedPropertyType.TWO_WAY ? BindingType.TwoWay : BindingType.Property;
27300
- if (!skipValidation) {
27301
- this._validatePropertyOrAttributeName(mappedPropName, boundProp.sourceSpan, false);
27302
- }
27162
+ if (templateKind === TemplateKind.Structural &&
27163
+ output.type !== ParsedEventType.LegacyAnimation) {
27164
+ // Animation bindings are excluded from the structural template's const array.
27165
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, output.name, false);
27166
+ unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, null, output.name, null, null, null, securityContext));
27303
27167
  }
27304
- return new BoundElementProperty(boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
27305
27168
  }
27306
- parseEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
27307
- if (name.length === 0) {
27308
- this._reportError(`Event name is missing in binding`, sourceSpan);
27309
- }
27310
- if (isLegacyAnimationLabel(name)) {
27311
- name = name.slice(1);
27312
- if (keySpan !== undefined) {
27313
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
27169
+ // TODO: Perhaps we could do this in a phase? (It likely wouldn't change the slot indices.)
27170
+ if (bindings.some((b) => b?.i18nMessage) !== null) {
27171
+ unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
27172
+ }
27173
+ }
27174
+ /**
27175
+ * Helper to ingest an individual binding on a template, either an explicit `ng-template`, or an
27176
+ * implicit template created via structural directive.
27177
+ *
27178
+ * Bindings on templates are *extremely* tricky. I have tried to isolate all of the confusing edge
27179
+ * cases into this function, and to comment it well to document the behavior.
27180
+ *
27181
+ * Some of this behavior is intuitively incorrect, and we should consider changing it in the future.
27182
+ *
27183
+ * @param view The compilation unit for the view containing the template.
27184
+ * @param xref The xref of the template op.
27185
+ * @param type The binding type, according to the parser. This is fairly reasonable, e.g. both
27186
+ * dynamic and static attributes have e.BindingType.Attribute.
27187
+ * @param name The binding's name.
27188
+ * @param value The bindings's value, which will either be an input AST expression, or a string
27189
+ * literal. Note that the input AST expression may or may not be const -- it will only be a
27190
+ * string literal if the parser considered it a text binding.
27191
+ * @param unit If the binding has a unit (e.g. `px` for style bindings), then this is the unit.
27192
+ * @param securityContext The security context of the binding.
27193
+ * @param isStructuralTemplateAttribute Whether this binding actually applies to the structural
27194
+ * ng-template. For example, an `ngFor` would actually apply to the structural template. (Most
27195
+ * bindings on structural elements target the inner element, not the template.)
27196
+ * @param templateKind Whether this is an explicit `ng-template` or an implicit template created by
27197
+ * a structural directive. This should never be a block template.
27198
+ * @param i18nMessage The i18n metadata for the binding, if any.
27199
+ * @param sourceSpan The source span of the binding.
27200
+ * @returns An IR binding op, or null if the binding should be skipped.
27201
+ */
27202
+ function createTemplateBinding(view, xref, type, name, value, unit, securityContext, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
27203
+ const isTextBinding = typeof value === 'string';
27204
+ // If this is a structural template, then several kinds of bindings should not result in an
27205
+ // update instruction.
27206
+ if (templateKind === TemplateKind.Structural) {
27207
+ if (!isStructuralTemplateAttribute) {
27208
+ switch (type) {
27209
+ case BindingType.Property:
27210
+ case BindingType.Class:
27211
+ case BindingType.Style:
27212
+ // Because this binding doesn't really target the ng-template, it must be a binding on an
27213
+ // inner node of a structural template. We can't skip it entirely, because we still need
27214
+ // it on the ng-template's consts (e.g. for the purposes of directive matching). However,
27215
+ // we should not generate an update instruction for it.
27216
+ return createExtractedAttributeOp(xref, BindingKind.Property, null, name, null, null, i18nMessage, securityContext);
27217
+ case BindingType.TwoWay:
27218
+ return createExtractedAttributeOp(xref, BindingKind.TwoWayProperty, null, name, null, null, i18nMessage, securityContext);
27314
27219
  }
27315
- this._parseLegacyAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan);
27316
27220
  }
27317
- else {
27318
- this._parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan);
27221
+ if (!isTextBinding &&
27222
+ (type === BindingType.Attribute ||
27223
+ type === BindingType.LegacyAnimation ||
27224
+ type === BindingType.Animation)) {
27225
+ // Again, this binding doesn't really target the ng-template; it actually targets the element
27226
+ // inside the structural template. In the case of non-text attribute or animation bindings,
27227
+ // the binding doesn't even show up on the ng-template const array, so we just skip it
27228
+ // entirely.
27229
+ return null;
27319
27230
  }
27320
27231
  }
27321
- calcPossibleSecurityContexts(selector, propName, isAttribute) {
27322
- const prop = this._schemaRegistry.getMappedPropName(propName);
27323
- return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
27324
- }
27325
- parseEventListenerName(rawName) {
27326
- const [target, eventName] = splitAtColon(rawName, [null, rawName]);
27327
- return { eventName: eventName, target };
27232
+ let bindingType = BINDING_KINDS.get(type);
27233
+ if (templateKind === TemplateKind.NgTemplate) {
27234
+ // We know we are dealing with bindings directly on an explicit ng-template.
27235
+ // Static attribute bindings should be collected into the const array as k/v pairs. Property
27236
+ // bindings should result in a `property` instruction, and `AttributeMarker.Bindings` const
27237
+ // entries.
27238
+ //
27239
+ // The difficulty is with dynamic attribute, style, and class bindings. These don't really make
27240
+ // sense on an `ng-template` and should probably be parser errors. However,
27241
+ // TemplateDefinitionBuilder generates `property` instructions for them, and so we do that as
27242
+ // well.
27243
+ //
27244
+ // Note that we do have a slight behavior difference with TemplateDefinitionBuilder: although
27245
+ // TDB emits `property` instructions for dynamic attributes, styles, and classes, only styles
27246
+ // and classes also get const collected into the `AttributeMarker.Bindings` field. Dynamic
27247
+ // attribute bindings are missing from the consts entirely. We choose to emit them into the
27248
+ // consts field anyway, to avoid creating special cases for something so arcane and nonsensical.
27249
+ if (type === BindingType.Class ||
27250
+ type === BindingType.Style ||
27251
+ (type === BindingType.Attribute && !isTextBinding)) {
27252
+ // TODO: These cases should be parse errors.
27253
+ bindingType = BindingKind.Property;
27254
+ }
27328
27255
  }
27329
- parseLegacyAnimationEventName(rawName) {
27330
- const matches = splitAtPeriod(rawName, [rawName, null]);
27331
- return { eventName: matches[0], phase: matches[1] === null ? null : matches[1].toLowerCase() };
27256
+ return createBindingOp(xref, bindingType, name, convertAstWithInterpolation(view.job, value, i18nMessage), unit, securityContext, isTextBinding, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan);
27257
+ }
27258
+ function makeListenerHandlerOps(unit, handler, handlerSpan) {
27259
+ handler = astOf(handler);
27260
+ const handlerOps = new Array();
27261
+ let handlerExprs = handler instanceof Chain ? handler.expressions : [handler];
27262
+ if (handlerExprs.length === 0) {
27263
+ throw new Error('Expected listener to have non-empty expression list.');
27332
27264
  }
27333
- _parseLegacyAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan) {
27334
- const { eventName, phase } = this.parseLegacyAnimationEventName(name);
27335
- const ast = this._parseAction(expression, handlerSpan);
27336
- targetEvents.push(new ParsedEvent(eventName, phase, ParsedEventType.LegacyAnimation, ast, sourceSpan, handlerSpan, keySpan));
27337
- if (eventName.length === 0) {
27338
- this._reportError(`Animation event name is missing in binding`, sourceSpan);
27339
- }
27340
- if (phase) {
27341
- if (phase !== 'start' && phase !== 'done') {
27342
- this._reportError(`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`, sourceSpan);
27343
- }
27265
+ const expressions = handlerExprs.map((expr) => convertAst(expr, unit.job, handlerSpan));
27266
+ const returnExpr = expressions.pop();
27267
+ handlerOps.push(...expressions.map((e) => createStatementOp(new ExpressionStatement(e, e.sourceSpan))));
27268
+ handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
27269
+ return handlerOps;
27270
+ }
27271
+ function makeTwoWayListenerHandlerOps(unit, handler, handlerSpan) {
27272
+ handler = astOf(handler);
27273
+ const handlerOps = new Array();
27274
+ if (handler instanceof Chain) {
27275
+ if (handler.expressions.length === 1) {
27276
+ handler = handler.expressions[0];
27344
27277
  }
27345
27278
  else {
27346
- this._reportError(`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`, sourceSpan);
27279
+ // This is validated during parsing already, but we do it here just in case.
27280
+ throw new Error('Expected two-way listener to have a single expression.');
27347
27281
  }
27348
27282
  }
27349
- _parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
27350
- // long format: 'target: eventName'
27351
- const { eventName, target } = this.parseEventListenerName(name);
27352
- const prevErrorCount = this.errors.length;
27353
- const ast = this._parseAction(expression, handlerSpan);
27354
- const isValid = this.errors.length === prevErrorCount;
27355
- targetMatchableAttrs.push([name, ast.source]);
27356
- // Don't try to validate assignment events if there were other
27357
- // parsing errors to avoid adding more noise to the error logs.
27358
- if (isAssignmentEvent && isValid && !this._isAllowedAssignmentEvent(ast)) {
27359
- this._reportError('Unsupported expression in a two-way binding', sourceSpan);
27360
- }
27361
- let eventType = ParsedEventType.Regular;
27362
- if (isAssignmentEvent) {
27363
- eventType = ParsedEventType.TwoWay;
27364
- }
27365
- if (name.startsWith(`${ANIMATE_PREFIX}${PROPERTY_PARTS_SEPARATOR}`)) {
27366
- eventType = ParsedEventType.Animation;
27367
- }
27368
- targetEvents.push(new ParsedEvent(eventName, target, eventType, ast, sourceSpan, handlerSpan, keySpan));
27369
- // Don't detect directives for event names for now,
27370
- // so don't add the event name to the matchableAttrs
27283
+ const handlerExpr = convertAst(handler, unit.job, handlerSpan);
27284
+ const eventReference = new LexicalReadExpr('$event');
27285
+ const twoWaySetExpr = new TwoWayBindingSetExpr(handlerExpr, eventReference);
27286
+ handlerOps.push(createStatementOp(new ExpressionStatement(twoWaySetExpr)));
27287
+ handlerOps.push(createStatementOp(new ReturnStatement(eventReference)));
27288
+ return handlerOps;
27289
+ }
27290
+ function astOf(ast) {
27291
+ return ast instanceof ASTWithSource ? ast.ast : ast;
27292
+ }
27293
+ /**
27294
+ * Process all of the local references on an element-like structure in the template AST and
27295
+ * convert them to their IR representation.
27296
+ */
27297
+ function ingestReferences(op, element) {
27298
+ assertIsArray(op.localRefs);
27299
+ for (const { name, value } of element.references) {
27300
+ op.localRefs.push({
27301
+ name,
27302
+ target: value,
27303
+ });
27371
27304
  }
27372
- _parseAction(value, sourceSpan) {
27373
- const absoluteOffset = sourceSpan && sourceSpan.start ? sourceSpan.start.offset : 0;
27374
- try {
27375
- const ast = this._exprParser.parseAction(value, sourceSpan, absoluteOffset, this._interpolationConfig);
27376
- if (ast) {
27377
- this.errors.push(...ast.errors);
27378
- }
27379
- if (!ast || ast.ast instanceof EmptyExpr$1) {
27380
- this._reportError(`Empty expressions are not allowed`, sourceSpan);
27381
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
27382
- }
27383
- return ast;
27384
- }
27385
- catch (e) {
27386
- this._reportError(`${e}`, sourceSpan);
27387
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
27388
- }
27305
+ }
27306
+ /**
27307
+ * Assert that the given value is an array.
27308
+ */
27309
+ function assertIsArray(value) {
27310
+ if (!Array.isArray(value)) {
27311
+ throw new Error(`AssertionError: expected an array`);
27389
27312
  }
27390
- _reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
27391
- this.errors.push(new ParseError(sourceSpan, message, level));
27313
+ }
27314
+ /**
27315
+ * Creates an absolute `ParseSourceSpan` from the relative `ParseSpan`.
27316
+ *
27317
+ * `ParseSpan` objects are relative to the start of the expression.
27318
+ * This method converts these to full `ParseSourceSpan` objects that
27319
+ * show where the span is within the overall source file.
27320
+ *
27321
+ * @param span the relative span to convert.
27322
+ * @param baseSourceSpan a span corresponding to the base of the expression tree.
27323
+ * @returns a `ParseSourceSpan` for the given span or null if no `baseSourceSpan` was provided.
27324
+ */
27325
+ function convertSourceSpan(span, baseSourceSpan) {
27326
+ if (baseSourceSpan === null) {
27327
+ return null;
27392
27328
  }
27393
- /**
27394
- * @param propName the name of the property / attribute
27395
- * @param sourceSpan
27396
- * @param isAttr true when binding to an attribute
27397
- */
27398
- _validatePropertyOrAttributeName(propName, sourceSpan, isAttr) {
27399
- const report = isAttr
27400
- ? this._schemaRegistry.validateAttribute(propName)
27401
- : this._schemaRegistry.validateProperty(propName);
27402
- if (report.error) {
27403
- this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
27329
+ const start = baseSourceSpan.start.moveBy(span.start);
27330
+ const end = baseSourceSpan.start.moveBy(span.end);
27331
+ const fullStart = baseSourceSpan.fullStart.moveBy(span.start);
27332
+ return new ParseSourceSpan(start, end, fullStart);
27333
+ }
27334
+ /**
27335
+ * With the directive-based control flow users were able to conditionally project content using
27336
+ * the `*` syntax. E.g. `<div *ngIf="expr" projectMe></div>` will be projected into
27337
+ * `<ng-content select="[projectMe]"/>`, because the attributes and tag name from the `div` are
27338
+ * copied to the template via the template creation instruction. With `@if` and `@for` that is
27339
+ * not the case, because the conditional is placed *around* elements, rather than *on* them.
27340
+ * The result is that content projection won't work in the same way if a user converts from
27341
+ * `*ngIf` to `@if`.
27342
+ *
27343
+ * This function aims to cover the most common case by doing the same copying when a control flow
27344
+ * node has *one and only one* root element or template node.
27345
+ *
27346
+ * This approach comes with some caveats:
27347
+ * 1. As soon as any other node is added to the root, the copying behavior won't work anymore.
27348
+ * A diagnostic will be added to flag cases like this and to explain how to work around it.
27349
+ * 2. If `preserveWhitespaces` is enabled, it's very likely that indentation will break this
27350
+ * workaround, because it'll include an additional text node as the first child. We can work
27351
+ * around it here, but in a discussion it was decided not to, because the user explicitly opted
27352
+ * into preserving the whitespace and we would have to drop it from the generated code.
27353
+ * The diagnostic mentioned point in #1 will flag such cases to users.
27354
+ *
27355
+ * @returns Tag name to be used for the control flow template.
27356
+ */
27357
+ function ingestControlFlowInsertionPoint(unit, xref, node) {
27358
+ let root = null;
27359
+ for (const child of node.children) {
27360
+ // Skip over comment nodes and @let declarations since
27361
+ // it doesn't matter where they end up in the DOM.
27362
+ if (child instanceof Comment$1 || child instanceof LetDeclaration$1) {
27363
+ continue;
27404
27364
  }
27405
- }
27406
- /**
27407
- * Returns whether a parsed AST is allowed to be used within the event side of a two-way binding.
27408
- * @param ast Parsed AST to be checked.
27409
- */
27410
- _isAllowedAssignmentEvent(ast) {
27411
- if (ast instanceof ASTWithSource) {
27412
- return this._isAllowedAssignmentEvent(ast.ast);
27365
+ // We can only infer the tag name/attributes if there's a single root node.
27366
+ if (root !== null) {
27367
+ return null;
27413
27368
  }
27414
- if (ast instanceof NonNullAssert) {
27415
- return this._isAllowedAssignmentEvent(ast.expression);
27369
+ // Root nodes can only elements or templates with a tag name (e.g. `<div *foo></div>`).
27370
+ if (child instanceof Element$1 || (child instanceof Template && child.tagName !== null)) {
27371
+ root = child;
27416
27372
  }
27417
- if (ast instanceof Call &&
27418
- ast.args.length === 1 &&
27419
- ast.receiver instanceof PropertyRead &&
27420
- ast.receiver.name === '$any' &&
27421
- ast.receiver.receiver instanceof ImplicitReceiver &&
27422
- !(ast.receiver.receiver instanceof ThisReceiver)) {
27423
- return this._isAllowedAssignmentEvent(ast.args[0]);
27373
+ else {
27374
+ return null;
27375
+ }
27376
+ }
27377
+ // If we've found a single root node, its tag name and attributes can be
27378
+ // copied to the surrounding template to be used for content projection.
27379
+ if (root !== null) {
27380
+ // Collect the static attributes for content projection purposes.
27381
+ for (const attr of root.attributes) {
27382
+ if (!attr.name.startsWith(ANIMATE_PREFIX)) {
27383
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
27384
+ unit.update.push(createBindingOp(xref, BindingKind.Attribute, attr.name, literal(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
27385
+ }
27424
27386
  }
27425
- if (ast instanceof PropertyRead || ast instanceof KeyedRead) {
27426
- if (!hasRecursiveSafeReceiver(ast)) {
27427
- return true;
27387
+ // Also collect the inputs since they participate in content projection as well.
27388
+ // Note that TDB used to collect the outputs as well, but it wasn't passing them into
27389
+ // the template instruction. Here we just don't collect them.
27390
+ for (const attr of root.inputs) {
27391
+ if (attr.type !== BindingType.LegacyAnimation &&
27392
+ attr.type !== BindingType.Animation &&
27393
+ attr.type !== BindingType.Attribute) {
27394
+ const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
27395
+ unit.create.push(createExtractedAttributeOp(xref, BindingKind.Property, null, attr.name, null, null, null, securityContext));
27428
27396
  }
27429
27397
  }
27430
- return false;
27398
+ const tagName = root instanceof Element$1 ? root.name : root.tagName;
27399
+ // Don't pass along `ng-template` tag name since it enables directive matching.
27400
+ return tagName === NG_TEMPLATE_TAG_NAME ? null : tagName;
27431
27401
  }
27402
+ return null;
27432
27403
  }
27433
- function hasRecursiveSafeReceiver(ast) {
27434
- if (ast instanceof SafePropertyRead || ast instanceof SafeKeyedRead) {
27435
- return true;
27436
- }
27437
- if (ast instanceof ParenthesizedExpression) {
27438
- return hasRecursiveSafeReceiver(ast.expression);
27439
- }
27440
- if (ast instanceof PropertyRead || ast instanceof KeyedRead || ast instanceof Call) {
27441
- return hasRecursiveSafeReceiver(ast.receiver);
27442
- }
27443
- return false;
27404
+
27405
+ /*!
27406
+ * @license
27407
+ * Copyright Google LLC All Rights Reserved.
27408
+ *
27409
+ * Use of this source code is governed by an MIT-style license that can be
27410
+ * found in the LICENSE file at https://angular.dev/license
27411
+ */
27412
+ /**
27413
+ * Whether to produce instructions that will attach the source location to each DOM node.
27414
+ *
27415
+ * !!!Important!!! at the time of writing this flag isn't exposed externally, but internal debug
27416
+ * tools enable it via a local change. Any modifications to this flag need to update the
27417
+ * internal tooling as well.
27418
+ */
27419
+ let ENABLE_TEMPLATE_SOURCE_LOCATIONS = false;
27420
+ /** Gets whether template source locations are enabled. */
27421
+ function getTemplateSourceLocationsEnabled() {
27422
+ return ENABLE_TEMPLATE_SOURCE_LOCATIONS;
27444
27423
  }
27445
- function isLegacyAnimationLabel(name) {
27446
- return name[0] == '@';
27424
+
27425
+ // if (rf & flags) { .. }
27426
+ function renderFlagCheckIfStmt(flags, statements) {
27427
+ return ifStmt(variable(RENDER_FLAGS).bitwiseAnd(literal(flags), null), statements);
27447
27428
  }
27448
- function calcPossibleSecurityContexts(registry, selector, propName, isAttribute) {
27449
- let ctxs;
27450
- const nameToContext = (elName) => registry.securityContext(elName, propName, isAttribute);
27451
- if (selector === null) {
27452
- ctxs = registry.allKnownElementNames().map(nameToContext);
27429
+ /**
27430
+ * Translates query flags into `TQueryFlags` type in
27431
+ * packages/core/src/render3/interfaces/query.ts
27432
+ * @param query
27433
+ */
27434
+ function toQueryFlags(query) {
27435
+ return ((query.descendants ? 1 /* QueryFlags.descendants */ : 0 /* QueryFlags.none */) |
27436
+ (query.static ? 2 /* QueryFlags.isStatic */ : 0 /* QueryFlags.none */) |
27437
+ (query.emitDistinctChangesOnly ? 4 /* QueryFlags.emitDistinctChangesOnly */ : 0 /* QueryFlags.none */));
27438
+ }
27439
+ function getQueryPredicate(query, constantPool) {
27440
+ if (Array.isArray(query.predicate)) {
27441
+ let predicate = [];
27442
+ query.predicate.forEach((selector) => {
27443
+ // Each item in predicates array may contain strings with comma-separated refs
27444
+ // (for ex. 'ref, ref1, ..., refN'), thus we extract individual refs and store them
27445
+ // as separate array entities
27446
+ const selectors = selector.split(',').map((token) => literal(token.trim()));
27447
+ predicate.push(...selectors);
27448
+ });
27449
+ return constantPool.getConstLiteral(literalArr(predicate), true);
27453
27450
  }
27454
27451
  else {
27455
- ctxs = [];
27456
- CssSelector.parse(selector).forEach((selector) => {
27457
- const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
27458
- const notElementNames = new Set(selector.notSelectors
27459
- .filter((selector) => selector.isElementSelector())
27460
- .map((selector) => selector.element));
27461
- const possibleElementNames = elementNames.filter((elName) => !notElementNames.has(elName));
27462
- ctxs.push(...possibleElementNames.map(nameToContext));
27463
- });
27452
+ // The original predicate may have been wrapped in a `forwardRef()` call.
27453
+ switch (query.predicate.forwardRef) {
27454
+ case 0 /* ForwardRefHandling.None */:
27455
+ case 2 /* ForwardRefHandling.Unwrapped */:
27456
+ return query.predicate.expression;
27457
+ case 1 /* ForwardRefHandling.Wrapped */:
27458
+ return importExpr(Identifiers.resolveForwardRef).callFn([query.predicate.expression]);
27459
+ }
27464
27460
  }
27465
- return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
27466
27461
  }
27462
+ function createQueryCreateCall(query, constantPool, queryTypeFns, prependParams) {
27463
+ const parameters = [];
27464
+ if (prependParams !== undefined) {
27465
+ parameters.push(...prependParams);
27466
+ }
27467
+ if (query.isSignal) {
27468
+ parameters.push(new ReadPropExpr(variable(CONTEXT_NAME), query.propertyName));
27469
+ }
27470
+ parameters.push(getQueryPredicate(query, constantPool), literal(toQueryFlags(query)));
27471
+ if (query.read) {
27472
+ parameters.push(query.read);
27473
+ }
27474
+ const queryCreateFn = query.isSignal ? queryTypeFns.signalBased : queryTypeFns.nonSignal;
27475
+ return importExpr(queryCreateFn).callFn(parameters);
27476
+ }
27477
+ const queryAdvancePlaceholder = Symbol('queryAdvancePlaceholder');
27467
27478
  /**
27468
- * Compute a new ParseSourceSpan based off an original `sourceSpan` by using
27469
- * absolute offsets from the specified `absoluteSpan`.
27479
+ * Collapses query advance placeholders in a list of statements.
27470
27480
  *
27471
- * @param sourceSpan original source span
27472
- * @param absoluteSpan absolute source span to move to
27481
+ * This allows for less generated code because multiple sibling query advance
27482
+ * statements can be collapsed into a single call with the count as argument.
27483
+ *
27484
+ * e.g.
27485
+ *
27486
+ * ```ts
27487
+ * bla();
27488
+ * queryAdvance();
27489
+ * queryAdvance();
27490
+ * bla();
27491
+ * ```
27492
+ *
27493
+ * --> will turn into
27494
+ *
27495
+ * ```ts
27496
+ * bla();
27497
+ * queryAdvance(2);
27498
+ * bla();
27499
+ * ```
27473
27500
  */
27474
- function moveParseSourceSpan(sourceSpan, absoluteSpan) {
27475
- // The difference of two absolute offsets provide the relative offset
27476
- const startDiff = absoluteSpan.start - sourceSpan.start.offset;
27477
- const endDiff = absoluteSpan.end - sourceSpan.end.offset;
27478
- return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff), sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
27501
+ function collapseAdvanceStatements(statements) {
27502
+ const result = [];
27503
+ let advanceCollapseCount = 0;
27504
+ const flushAdvanceCount = () => {
27505
+ if (advanceCollapseCount > 0) {
27506
+ result.unshift(importExpr(Identifiers.queryAdvance)
27507
+ .callFn(advanceCollapseCount === 1 ? [] : [literal(advanceCollapseCount)])
27508
+ .toStmt());
27509
+ advanceCollapseCount = 0;
27510
+ }
27511
+ };
27512
+ // Iterate through statements in reverse and collapse advance placeholders.
27513
+ for (let i = statements.length - 1; i >= 0; i--) {
27514
+ const st = statements[i];
27515
+ if (st === queryAdvancePlaceholder) {
27516
+ advanceCollapseCount++;
27517
+ }
27518
+ else {
27519
+ flushAdvanceCount();
27520
+ result.unshift(st);
27521
+ }
27522
+ }
27523
+ flushAdvanceCount();
27524
+ return result;
27525
+ }
27526
+ // Define and update any view queries
27527
+ function createViewQueriesFunction(viewQueries, constantPool, name) {
27528
+ const createStatements = [];
27529
+ const updateStatements = [];
27530
+ const tempAllocator = temporaryAllocator((st) => updateStatements.push(st), TEMPORARY_NAME);
27531
+ viewQueries.forEach((query) => {
27532
+ // creation call, e.g. r3.viewQuery(somePredicate, true) or
27533
+ // r3.viewQuerySignal(ctx.prop, somePredicate, true);
27534
+ const queryDefinitionCall = createQueryCreateCall(query, constantPool, {
27535
+ signalBased: Identifiers.viewQuerySignal,
27536
+ nonSignal: Identifiers.viewQuery,
27537
+ });
27538
+ createStatements.push(queryDefinitionCall.toStmt());
27539
+ // Signal queries update lazily and we just advance the index.
27540
+ if (query.isSignal) {
27541
+ updateStatements.push(queryAdvancePlaceholder);
27542
+ return;
27543
+ }
27544
+ // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
27545
+ const temporary = tempAllocator();
27546
+ const getQueryList = importExpr(Identifiers.loadQuery).callFn([]);
27547
+ const refresh = importExpr(Identifiers.queryRefresh).callFn([temporary.set(getQueryList)]);
27548
+ const updateDirective = variable(CONTEXT_NAME)
27549
+ .prop(query.propertyName)
27550
+ .set(query.first ? temporary.prop('first') : temporary);
27551
+ updateStatements.push(refresh.and(updateDirective).toStmt());
27552
+ });
27553
+ const viewQueryFnName = name ? `${name}_Query` : null;
27554
+ return fn([new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null)], [
27555
+ renderFlagCheckIfStmt(1 /* core.RenderFlags.Create */, createStatements),
27556
+ renderFlagCheckIfStmt(2 /* core.RenderFlags.Update */, collapseAdvanceStatements(updateStatements)),
27557
+ ], INFERRED_TYPE, null, viewQueryFnName);
27558
+ }
27559
+ // Define and update any content queries
27560
+ function createContentQueriesFunction(queries, constantPool, name) {
27561
+ const createStatements = [];
27562
+ const updateStatements = [];
27563
+ const tempAllocator = temporaryAllocator((st) => updateStatements.push(st), TEMPORARY_NAME);
27564
+ for (const query of queries) {
27565
+ // creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null) or
27566
+ // r3.contentQuerySignal(dirIndex, propName, somePredicate, <flags>, <read>).
27567
+ createStatements.push(createQueryCreateCall(query, constantPool, { nonSignal: Identifiers.contentQuery, signalBased: Identifiers.contentQuerySignal },
27568
+ /* prependParams */ [variable('dirIndex')]).toStmt());
27569
+ // Signal queries update lazily and we just advance the index.
27570
+ if (query.isSignal) {
27571
+ updateStatements.push(queryAdvancePlaceholder);
27572
+ continue;
27573
+ }
27574
+ // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
27575
+ const temporary = tempAllocator();
27576
+ const getQueryList = importExpr(Identifiers.loadQuery).callFn([]);
27577
+ const refresh = importExpr(Identifiers.queryRefresh).callFn([temporary.set(getQueryList)]);
27578
+ const updateDirective = variable(CONTEXT_NAME)
27579
+ .prop(query.propertyName)
27580
+ .set(query.first ? temporary.prop('first') : temporary);
27581
+ updateStatements.push(refresh.and(updateDirective).toStmt());
27582
+ }
27583
+ const contentQueriesFnName = name ? `${name}_ContentQueries` : null;
27584
+ return fn([
27585
+ new FnParam(RENDER_FLAGS, NUMBER_TYPE),
27586
+ new FnParam(CONTEXT_NAME, null),
27587
+ new FnParam('dirIndex', null),
27588
+ ], [
27589
+ renderFlagCheckIfStmt(1 /* core.RenderFlags.Create */, createStatements),
27590
+ renderFlagCheckIfStmt(2 /* core.RenderFlags.Update */, collapseAdvanceStatements(updateStatements)),
27591
+ ], INFERRED_TYPE, null, contentQueriesFnName);
27592
+ }
27593
+
27594
+ class HtmlParser extends Parser$1 {
27595
+ constructor() {
27596
+ super(getHtmlTagDefinition);
27597
+ }
27598
+ parse(source, url, options) {
27599
+ return super.parse(source, url, options);
27600
+ }
27479
27601
  }
27480
27602
 
27481
27603
  // Some of the code comes from WebComponents.JS
@@ -27494,7 +27616,7 @@ const LINK_STYLE_REL_ATTR = 'rel';
27494
27616
  const LINK_STYLE_HREF_ATTR = 'href';
27495
27617
  const LINK_STYLE_REL_VALUE = 'stylesheet';
27496
27618
  const STYLE_ELEMENT = 'style';
27497
- const SCRIPT_ELEMENT = 'script';
27619
+ const SCRIPT_ELEMENTS = new Set([':svg:script', 'script']);
27498
27620
  const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
27499
27621
  const NG_PROJECT_AS = 'ngProjectAs';
27500
27622
  function preparseElement(ast) {
@@ -27503,7 +27625,7 @@ function preparseElement(ast) {
27503
27625
  let relAttr = null;
27504
27626
  let nonBindable = false;
27505
27627
  let projectAs = '';
27506
- ast.attrs.forEach((attr) => {
27628
+ for (const attr of ast.attrs) {
27507
27629
  const lcAttrName = attr.name.toLowerCase();
27508
27630
  if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
27509
27631
  selectAttr = attr.value;
@@ -27522,17 +27644,18 @@ function preparseElement(ast) {
27522
27644
  projectAs = attr.value;
27523
27645
  }
27524
27646
  }
27525
- });
27526
- selectAttr = normalizeNgContentSelect(selectAttr);
27647
+ }
27648
+ // Normalize selector to '*' if empty
27649
+ selectAttr ||= '*';
27527
27650
  const nodeName = ast.name.toLowerCase();
27528
27651
  let type = PreparsedElementType.OTHER;
27529
27652
  if (isNgContent(nodeName)) {
27530
27653
  type = PreparsedElementType.NG_CONTENT;
27531
27654
  }
27532
- else if (nodeName == STYLE_ELEMENT) {
27655
+ else if (STYLE_ELEMENT === nodeName) {
27533
27656
  type = PreparsedElementType.STYLE;
27534
27657
  }
27535
- else if (nodeName == SCRIPT_ELEMENT) {
27658
+ else if (SCRIPT_ELEMENTS.has(nodeName)) {
27536
27659
  type = PreparsedElementType.SCRIPT;
27537
27660
  }
27538
27661
  else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
@@ -27562,12 +27685,6 @@ class PreparsedElement {
27562
27685
  this.projectAs = projectAs;
27563
27686
  }
27564
27687
  }
27565
- function normalizeNgContentSelect(selectAttr) {
27566
- if (selectAttr === null || selectAttr.length === 0) {
27567
- return '*';
27568
- }
27569
- return selectAttr;
27570
- }
27571
27688
 
27572
27689
  /** Pattern for the expression in a for loop block. */
27573
27690
  const FOR_LOOP_EXPRESSION_PATTERN = /^\s*([0-9A-Za-z_$]*)\s+of\s+([\S\s]*)/;
@@ -28855,8 +28972,9 @@ class HtmlAstToIvyAst {
28855
28972
  // Note that validation is skipped and property mapping is disabled
28856
28973
  // due to the fact that we need to make sure a given prop is not an
28857
28974
  // input of a directive and directive matching happens at runtime.
28975
+ const isAttrOn = prop.name.toLowerCase().startsWith('attr.on');
28858
28976
  const bep = this.bindingParser.createBoundElementProperty(elementName, prop,
28859
- /* skipValidation */ true,
28977
+ /* skipValidation */ !isAttrOn,
28860
28978
  /* mapPropertyName */ false);
28861
28979
  bound.push(BoundAttribute.fromBoundElementProperty(bep, i18n));
28862
28980
  }
@@ -29752,8 +29870,30 @@ function verifyHostBindings(bindings, sourceSpan) {
29752
29870
  const bindingParser = makeBindingParser();
29753
29871
  bindingParser.createDirectiveHostEventAsts(bindings.listeners, sourceSpan);
29754
29872
  bindingParser.createBoundHostProperties(bindings.properties, sourceSpan);
29873
+ validateNoEventBindings(bindings, bindingParser, sourceSpan);
29755
29874
  return bindingParser.errors;
29756
29875
  }
29876
+ /**
29877
+ * Validates that there are no event attribute bindings in the host bindings.
29878
+ * @param bindings - Map of host bindings for the component.
29879
+ * @param bindingParser - Binding parser used to create the binding expression.
29880
+ * @param sourceSpan - Source span where the host bindings were defined.
29881
+ */
29882
+ function validateNoEventBindings(bindings, bindingParser, sourceSpan) {
29883
+ for (const prop in bindings.properties) {
29884
+ const isAttr = prop.startsWith('attr.');
29885
+ const boundName = isAttr ? prop.slice(5) : prop;
29886
+ if (boundName.toLowerCase().startsWith('on')) {
29887
+ const errorType = isAttr ? 'attribute' : 'property';
29888
+ const suggestion = `(${boundName.slice(2)})=...`;
29889
+ let msg = `Binding to event ${errorType} '${boundName}' is disallowed for security reasons, please use ${suggestion}`;
29890
+ if (!isAttr) {
29891
+ msg += `\nIf '${prop}' is a directive input, make sure the directive is imported by the current module.`;
29892
+ }
29893
+ bindingParser.errors.push(new ParseError(sourceSpan, msg));
29894
+ }
29895
+ }
29896
+ }
29757
29897
  function compileStyles(styles, selector, hostSelector) {
29758
29898
  const shadowCss = new ShadowCss();
29759
29899
  return styles.map((style) => {
@@ -31357,11 +31497,6 @@ function createR3ComponentDeferMetadata(boundTarget, deferBlockDependencies) {
31357
31497
  function extractHostBindings(propMetadata, sourceSpan, host) {
31358
31498
  // First parse the declarations from the metadata.
31359
31499
  const bindings = parseHostBindings(host || {});
31360
- // After that check host bindings for errors
31361
- const errors = verifyHostBindings(bindings, sourceSpan);
31362
- if (errors.length) {
31363
- throw new Error(errors.map((error) => error.msg).join('\n'));
31364
- }
31365
31500
  // Next, loop over the properties of the object, looking for @HostBinding and @HostListener.
31366
31501
  for (const field in propMetadata) {
31367
31502
  if (propMetadata.hasOwnProperty(field)) {
@@ -31378,6 +31513,11 @@ function extractHostBindings(propMetadata, sourceSpan, host) {
31378
31513
  });
31379
31514
  }
31380
31515
  }
31516
+ // After that check host bindings for errors
31517
+ const errors = verifyHostBindings(bindings, sourceSpan);
31518
+ if (errors.length) {
31519
+ throw new Error(errors.map((error) => error.msg).join('\n'));
31520
+ }
31381
31521
  return bindings;
31382
31522
  }
31383
31523
  function isHostBinding(value) {
@@ -31512,7 +31652,7 @@ var _VisitorMode;
31512
31652
  * @description
31513
31653
  * Entry point for all public APIs of the compiler package.
31514
31654
  */
31515
- new Version('20.3.19');
31655
+ new Version('20.3.28');
31516
31656
 
31517
31657
  //////////////////////////////////////
31518
31658
  // THIS FILE HAS GLOBAL SIDE EFFECT //