@wavemaker-ai/angular-app 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.
- package/dependencies/expression-parser.cjs.js +1918 -1778
- package/dependencies/pipe-provider.cjs.js +3172 -1972
- package/dependencies/transpilation-web.cjs.js +1874 -1290
- package/dependency-report.html +1 -1
- package/npm-shrinkwrap.json +4155 -4123
- package/package-lock.json +4155 -4123
- package/package.json +21 -15
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @license Angular v20.3.
|
|
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
|
-
|
|
19922
|
-
|
|
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(
|
|
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
|
-
|
|
19939
|
-
|
|
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(
|
|
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
|
-
|
|
19965
|
-
// property names do not have a security impact.
|
|
19966
|
-
tagName = tagName.toLowerCase();
|
|
19949
|
+
const normalizedTag = normalizeTagName(tagName);
|
|
19967
19950
|
propName = propName.toLowerCase();
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
24385
|
-
|
|
24386
|
-
|
|
24387
|
-
//
|
|
24388
|
-
//
|
|
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
|
-
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
|
|
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
|
-
|
|
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
|
|
25730
|
-
|
|
25731
|
-
const
|
|
25732
|
-
|
|
25733
|
-
const
|
|
25734
|
-
|
|
25735
|
-
const
|
|
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
|
-
*
|
|
25754
|
-
* representation.
|
|
25814
|
+
* Parses bindings in templates and in the directive host area.
|
|
25755
25815
|
*/
|
|
25756
|
-
|
|
25757
|
-
|
|
25758
|
-
|
|
25759
|
-
|
|
25760
|
-
|
|
25761
|
-
|
|
25762
|
-
|
|
25763
|
-
|
|
25764
|
-
|
|
25765
|
-
|
|
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
|
-
|
|
25783
|
-
|
|
25827
|
+
get interpolationConfig() {
|
|
25828
|
+
return this._interpolationConfig;
|
|
25784
25829
|
}
|
|
25785
|
-
|
|
25786
|
-
|
|
25787
|
-
|
|
25788
|
-
|
|
25789
|
-
|
|
25790
|
-
|
|
25791
|
-
|
|
25792
|
-
|
|
25793
|
-
|
|
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
|
-
|
|
25796
|
-
|
|
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
|
-
|
|
25799
|
-
|
|
25800
|
-
|
|
25801
|
-
|
|
25802
|
-
|
|
25803
|
-
|
|
25804
|
-
|
|
25805
|
-
|
|
25806
|
-
|
|
25807
|
-
|
|
25808
|
-
|
|
25809
|
-
|
|
25810
|
-
|
|
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
|
-
|
|
25822
|
-
|
|
25823
|
-
|
|
25824
|
-
|
|
25825
|
-
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
25829
|
-
|
|
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
|
-
|
|
25832
|
-
|
|
25898
|
+
catch (e) {
|
|
25899
|
+
this._reportError(`${e}`, sourceSpan);
|
|
25900
|
+
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceSpan, absoluteOffset);
|
|
25833
25901
|
}
|
|
25834
|
-
|
|
25835
|
-
|
|
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
|
-
|
|
25838
|
-
|
|
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
|
-
|
|
25841
|
-
|
|
25965
|
+
catch (e) {
|
|
25966
|
+
this._reportError(`${e}`, sourceSpan);
|
|
25967
|
+
return [];
|
|
25842
25968
|
}
|
|
25843
|
-
|
|
25844
|
-
|
|
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
|
|
25847
|
-
|
|
25982
|
+
else {
|
|
25983
|
+
targetProps.push(new ParsedProperty(name, this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset), ParsedPropertyType.LITERAL_ATTR, sourceSpan, keySpan, valueSpan));
|
|
25848
25984
|
}
|
|
25849
|
-
|
|
25850
|
-
|
|
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
|
-
|
|
25853
|
-
|
|
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 (
|
|
25856
|
-
|
|
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
|
-
|
|
25859
|
-
|
|
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
|
-
|
|
26012
|
+
this._parsePropertyAst(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
|
|
25864
26013
|
}
|
|
25865
26014
|
}
|
|
25866
|
-
|
|
25867
|
-
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
|
|
25871
|
-
|
|
25872
|
-
|
|
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
|
-
|
|
25888
|
-
|
|
25889
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
|
|
25912
|
-
|
|
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
|
-
|
|
25915
|
-
|
|
25916
|
-
|
|
25917
|
-
|
|
25918
|
-
|
|
25919
|
-
|
|
25920
|
-
|
|
25921
|
-
|
|
25922
|
-
|
|
25923
|
-
|
|
25924
|
-
|
|
25925
|
-
|
|
25926
|
-
|
|
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
|
-
|
|
25954
|
-
|
|
25955
|
-
|
|
25956
|
-
|
|
25957
|
-
unit
|
|
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
|
-
|
|
25960
|
-
|
|
25961
|
-
|
|
25962
|
-
|
|
25963
|
-
|
|
25964
|
-
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
25972
|
-
|
|
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
|
-
|
|
25976
|
-
|
|
26127
|
+
calcPossibleSecurityContexts(selector, propName, isAttribute) {
|
|
26128
|
+
const prop = this._schemaRegistry.getMappedPropName(propName);
|
|
26129
|
+
return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
|
|
25977
26130
|
}
|
|
25978
|
-
|
|
25979
|
-
|
|
26131
|
+
parseEventListenerName(rawName) {
|
|
26132
|
+
const [target, eventName] = splitAtColon(rawName, [null, rawName]);
|
|
26133
|
+
return { eventName: eventName, target };
|
|
25980
26134
|
}
|
|
25981
|
-
|
|
25982
|
-
|
|
25983
|
-
|
|
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
|
-
|
|
25990
|
-
|
|
25991
|
-
|
|
25992
|
-
|
|
25993
|
-
|
|
25994
|
-
|
|
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
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
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
|
-
|
|
26018
|
-
|
|
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
|
-
|
|
26029
|
-
|
|
26030
|
-
|
|
26031
|
-
|
|
26032
|
-
|
|
26033
|
-
|
|
26034
|
-
|
|
26035
|
-
|
|
26036
|
-
|
|
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
|
-
|
|
26039
|
-
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26052
|
-
|
|
26053
|
-
|
|
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
|
-
|
|
26071
|
-
|
|
26196
|
+
_reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
|
|
26197
|
+
this.errors.push(new ParseError(sourceSpan, message, level));
|
|
26072
26198
|
}
|
|
26073
|
-
|
|
26074
|
-
|
|
26075
|
-
|
|
26076
|
-
|
|
26077
|
-
|
|
26078
|
-
|
|
26079
|
-
|
|
26080
|
-
|
|
26081
|
-
|
|
26082
|
-
if (
|
|
26083
|
-
|
|
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
|
-
|
|
26120
|
-
|
|
26121
|
-
|
|
26122
|
-
|
|
26123
|
-
|
|
26124
|
-
|
|
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
|
|
26129
|
-
if (
|
|
26130
|
-
|
|
26131
|
-
onOps.push(deferOnOp);
|
|
26239
|
+
function hasRecursiveSafeReceiver(ast) {
|
|
26240
|
+
if (ast instanceof SafePropertyRead || ast instanceof SafeKeyedRead) {
|
|
26241
|
+
return true;
|
|
26132
26242
|
}
|
|
26133
|
-
if (
|
|
26134
|
-
|
|
26135
|
-
onOps.push(deferOnOp);
|
|
26243
|
+
if (ast instanceof ParenthesizedExpression) {
|
|
26244
|
+
return hasRecursiveSafeReceiver(ast.expression);
|
|
26136
26245
|
}
|
|
26137
|
-
if (
|
|
26138
|
-
|
|
26139
|
-
onOps.push(deferOnOp);
|
|
26246
|
+
if (ast instanceof PropertyRead || ast instanceof KeyedRead || ast instanceof Call) {
|
|
26247
|
+
return hasRecursiveSafeReceiver(ast.receiver);
|
|
26140
26248
|
}
|
|
26141
|
-
|
|
26142
|
-
|
|
26143
|
-
|
|
26144
|
-
|
|
26145
|
-
|
|
26146
|
-
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
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
|
-
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
|
|
26156
|
-
|
|
26157
|
-
|
|
26158
|
-
|
|
26159
|
-
|
|
26160
|
-
|
|
26161
|
-
|
|
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
|
-
|
|
26164
|
-
|
|
26165
|
-
|
|
26166
|
-
|
|
26167
|
-
|
|
26168
|
-
|
|
26169
|
-
|
|
26170
|
-
|
|
26171
|
-
|
|
26172
|
-
|
|
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
|
-
|
|
26175
|
-
const
|
|
26176
|
-
|
|
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
|
-
|
|
26179
|
-
|
|
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
|
|
26189
|
-
|
|
26190
|
-
|
|
26191
|
-
|
|
26192
|
-
|
|
26193
|
-
|
|
26194
|
-
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
|
|
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
|
-
|
|
26203
|
-
|
|
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
|
-
|
|
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
|
|
26210
|
-
const
|
|
26211
|
-
|
|
26212
|
-
|
|
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 (
|
|
26226
|
-
|
|
26422
|
+
else if (node instanceof Template) {
|
|
26423
|
+
ingestTemplate(unit, node);
|
|
26227
26424
|
}
|
|
26228
|
-
else if (
|
|
26229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26241
|
-
|
|
26242
|
-
|
|
26243
|
-
|
|
26244
|
-
|
|
26245
|
-
if (
|
|
26246
|
-
|
|
26247
|
-
|
|
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
|
|
26251
|
-
|
|
26252
|
-
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
|
|
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
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
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
|
-
*
|
|
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
|
|
26277
|
-
|
|
26278
|
-
|
|
26279
|
-
|
|
26280
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
|
|
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
|
-
*
|
|
26529
|
+
* Ingest a content node from the AST into the given `ViewCompilation`.
|
|
26301
26530
|
*/
|
|
26302
|
-
function
|
|
26303
|
-
if (
|
|
26304
|
-
|
|
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
|
-
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
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
|
-
|
|
26325
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
26463
|
-
|
|
26555
|
+
function ingestText(unit, text, icuPlaceholder) {
|
|
26556
|
+
unit.create.push(createTextOp(unit.job.allocateXrefId(), text.value, icuPlaceholder, text.sourceSpan));
|
|
26464
26557
|
}
|
|
26465
26558
|
/**
|
|
26466
|
-
*
|
|
26559
|
+
* Ingest an interpolated text node from the AST into the given `ViewCompilation`.
|
|
26467
26560
|
*/
|
|
26468
|
-
function
|
|
26469
|
-
|
|
26470
|
-
|
|
26561
|
+
function ingestBoundText(unit, text, icuPlaceholder) {
|
|
26562
|
+
let value = text.value;
|
|
26563
|
+
if (value instanceof ASTWithSource) {
|
|
26564
|
+
value = value.ast;
|
|
26471
26565
|
}
|
|
26472
|
-
if (!(
|
|
26473
|
-
throw Error(`
|
|
26566
|
+
if (!(value instanceof Interpolation$1)) {
|
|
26567
|
+
throw new Error(`AssertionError: expected Interpolation for BoundText node, got ${value.constructor.name}`);
|
|
26474
26568
|
}
|
|
26475
|
-
|
|
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
|
-
*
|
|
26479
|
-
* representation.
|
|
26589
|
+
* Ingest an `@if` block into the given `ViewCompilation`.
|
|
26480
26590
|
*/
|
|
26481
|
-
function
|
|
26482
|
-
let
|
|
26483
|
-
let
|
|
26484
|
-
for (
|
|
26485
|
-
|
|
26486
|
-
const
|
|
26487
|
-
|
|
26488
|
-
if (
|
|
26489
|
-
|
|
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
|
-
|
|
26509
|
-
|
|
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
|
-
|
|
26512
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
26523
|
-
* representation.
|
|
26622
|
+
* Ingest an `@switch` block into the given `ViewCompilation`.
|
|
26524
26623
|
*/
|
|
26525
|
-
function
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
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
|
-
|
|
26546
|
-
|
|
26547
|
-
for (
|
|
26548
|
-
|
|
26549
|
-
|
|
26550
|
-
|
|
26551
|
-
|
|
26552
|
-
|
|
26553
|
-
|
|
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
|
-
|
|
26560
|
-
|
|
26561
|
-
|
|
26562
|
-
|
|
26563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26573
|
-
|
|
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
|
-
|
|
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
|
|
26663
|
-
|
|
26664
|
-
|
|
26665
|
-
|
|
26666
|
-
return
|
|
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
|
|
26669
|
-
|
|
26670
|
-
|
|
26671
|
-
|
|
26672
|
-
|
|
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
|
-
|
|
26681
|
-
const
|
|
26682
|
-
const
|
|
26683
|
-
|
|
26684
|
-
|
|
26685
|
-
|
|
26686
|
-
|
|
26687
|
-
|
|
26688
|
-
|
|
26689
|
-
|
|
26690
|
-
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
|
|
26694
|
-
|
|
26695
|
-
|
|
26696
|
-
|
|
26697
|
-
|
|
26698
|
-
|
|
26699
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26713
|
-
|
|
26714
|
-
|
|
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
|
-
|
|
26727
|
-
|
|
26728
|
-
|
|
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
|
-
|
|
26775
|
-
|
|
26776
|
-
|
|
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
|
-
|
|
26800
|
-
|
|
26801
|
-
|
|
26802
|
-
|
|
26803
|
-
|
|
26804
|
-
|
|
26805
|
-
|
|
26806
|
-
|
|
26807
|
-
|
|
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
|
-
|
|
26849
|
-
|
|
26850
|
-
|
|
26851
|
-
|
|
26852
|
-
|
|
26853
|
-
|
|
26854
|
-
|
|
26855
|
-
|
|
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
|
|
26860
|
-
|
|
26861
|
-
|
|
26862
|
-
|
|
26863
|
-
|
|
26864
|
-
|
|
26865
|
-
|
|
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
|
-
|
|
26868
|
-
|
|
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
|
-
*
|
|
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
|
|
26899
|
-
const
|
|
26900
|
-
|
|
26901
|
-
|
|
26902
|
-
|
|
26903
|
-
|
|
26904
|
-
|
|
26905
|
-
|
|
26906
|
-
|
|
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
|
-
|
|
26910
|
-
|
|
26911
|
-
|
|
26912
|
-
|
|
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
|
-
|
|
26917
|
-
|
|
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
|
-
|
|
26921
|
-
|
|
26922
|
-
|
|
26923
|
-
|
|
26924
|
-
|
|
26925
|
-
|
|
26926
|
-
|
|
26927
|
-
|
|
26928
|
-
|
|
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
|
|
26981
|
-
|
|
26982
|
-
|
|
26983
|
-
|
|
26984
|
-
|
|
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
|
-
|
|
26996
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
27011
|
-
|
|
27012
|
-
|
|
27013
|
-
|
|
27014
|
-
|
|
27015
|
-
|
|
27016
|
-
|
|
27017
|
-
|
|
27018
|
-
|
|
27019
|
-
|
|
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
|
-
|
|
27022
|
-
|
|
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
|
-
|
|
27025
|
-
|
|
27026
|
-
|
|
27027
|
-
|
|
27028
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27065
|
-
|
|
27066
|
-
|
|
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
|
-
|
|
27074
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27099
|
-
|
|
27100
|
-
|
|
27101
|
-
|
|
27102
|
-
|
|
27103
|
-
|
|
27104
|
-
|
|
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
|
-
|
|
27140
|
-
|
|
27141
|
-
|
|
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
|
-
|
|
27165
|
-
|
|
27166
|
-
|
|
27167
|
-
|
|
27168
|
-
|
|
27169
|
-
|
|
27170
|
-
|
|
27171
|
-
|
|
27172
|
-
|
|
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
|
-
|
|
27177
|
-
|
|
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
|
-
|
|
27181
|
-
if (name
|
|
27182
|
-
|
|
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
|
-
|
|
27193
|
-
|
|
27194
|
-
|
|
27195
|
-
|
|
27196
|
-
|
|
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 (
|
|
27200
|
-
|
|
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 (
|
|
27203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27226
|
-
|
|
27227
|
-
|
|
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
|
-
|
|
27237
|
-
|
|
27238
|
-
|
|
27239
|
-
|
|
27240
|
-
|
|
27241
|
-
|
|
27242
|
-
|
|
27243
|
-
|
|
27244
|
-
|
|
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
|
-
|
|
27247
|
-
|
|
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
|
-
|
|
27252
|
-
|
|
27253
|
-
|
|
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
|
-
|
|
27256
|
-
|
|
27257
|
-
|
|
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
|
|
27288
|
-
|
|
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
|
-
|
|
27294
|
-
|
|
27295
|
-
|
|
27296
|
-
|
|
27297
|
-
|
|
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
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
|
|
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
|
-
|
|
27318
|
-
|
|
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
|
-
|
|
27322
|
-
|
|
27323
|
-
|
|
27324
|
-
|
|
27325
|
-
|
|
27326
|
-
|
|
27327
|
-
|
|
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
|
-
|
|
27330
|
-
|
|
27331
|
-
|
|
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
|
-
|
|
27334
|
-
|
|
27335
|
-
|
|
27336
|
-
|
|
27337
|
-
|
|
27338
|
-
|
|
27339
|
-
|
|
27340
|
-
|
|
27341
|
-
|
|
27342
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
27354
|
-
|
|
27355
|
-
|
|
27356
|
-
|
|
27357
|
-
|
|
27358
|
-
|
|
27359
|
-
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27369
|
-
|
|
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
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
|
|
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
|
-
|
|
27391
|
-
|
|
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
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
|
|
27398
|
-
|
|
27399
|
-
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
|
|
27403
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27415
|
-
|
|
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
|
-
|
|
27418
|
-
|
|
27419
|
-
|
|
27420
|
-
|
|
27421
|
-
|
|
27422
|
-
|
|
27423
|
-
|
|
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
|
-
|
|
27426
|
-
|
|
27427
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27434
|
-
|
|
27435
|
-
|
|
27436
|
-
|
|
27437
|
-
|
|
27438
|
-
|
|
27439
|
-
|
|
27440
|
-
|
|
27441
|
-
|
|
27442
|
-
|
|
27443
|
-
|
|
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
|
-
|
|
27446
|
-
|
|
27424
|
+
|
|
27425
|
+
// if (rf & flags) { .. }
|
|
27426
|
+
function renderFlagCheckIfStmt(flags, statements) {
|
|
27427
|
+
return ifStmt(variable(RENDER_FLAGS).bitwiseAnd(literal(flags), null), statements);
|
|
27447
27428
|
}
|
|
27448
|
-
|
|
27449
|
-
|
|
27450
|
-
|
|
27451
|
-
|
|
27452
|
-
|
|
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
|
-
|
|
27456
|
-
|
|
27457
|
-
|
|
27458
|
-
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
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
|
-
*
|
|
27469
|
-
* absolute offsets from the specified `absoluteSpan`.
|
|
27479
|
+
* Collapses query advance placeholders in a list of statements.
|
|
27470
27480
|
*
|
|
27471
|
-
*
|
|
27472
|
-
*
|
|
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
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
const
|
|
27478
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
27655
|
+
else if (STYLE_ELEMENT === nodeName) {
|
|
27533
27656
|
type = PreparsedElementType.STYLE;
|
|
27534
27657
|
}
|
|
27535
|
-
else if (nodeName
|
|
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 */
|
|
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.
|
|
31655
|
+
new Version('20.3.28');
|
|
31516
31656
|
|
|
31517
31657
|
//////////////////////////////////////
|
|
31518
31658
|
// THIS FILE HAS GLOBAL SIDE EFFECT //
|