@angular/core 20.0.0-next.2 → 20.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/fesm2022/core.mjs +413 -365
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/di.mjs +3 -2
  4. package/fesm2022/primitives/di.mjs.map +1 -1
  5. package/fesm2022/primitives/event-dispatch.mjs +1 -1
  6. package/fesm2022/primitives/signals.mjs +28 -7
  7. package/fesm2022/primitives/signals.mjs.map +1 -1
  8. package/fesm2022/rxjs-interop.mjs +1 -8
  9. package/fesm2022/rxjs-interop.mjs.map +1 -1
  10. package/fesm2022/testing.mjs +2 -10
  11. package/fesm2022/testing.mjs.map +1 -1
  12. package/fesm2022/weak_ref-DrMdAIDh.mjs +1 -1
  13. package/index.d.ts +7523 -7489
  14. package/navigation_types.d-u4EOrrdZ.d.ts +1 -1
  15. package/package.json +2 -2
  16. package/primitives/di/index.d.ts +25 -10
  17. package/primitives/event-dispatch/index.d.ts +1 -1
  18. package/primitives/signals/index.d.ts +9 -6
  19. package/rxjs-interop/index.d.ts +1 -10
  20. package/schematics/bundles/{apply_import_manager-CyRT0UvU.js → apply_import_manager-BXQEjo09.js} +6 -6
  21. package/schematics/bundles/{checker-DF8ZaFW5.js → checker-BHb19MHt.js} +629 -71
  22. package/schematics/bundles/cleanup-unused-imports.js +42 -69
  23. package/schematics/bundles/{compiler_host-Da636uJ8.js → compiler_host-Bk3repE2.js} +2 -2
  24. package/schematics/bundles/control-flow-migration.js +3 -3
  25. package/schematics/bundles/imports-CIX-JgAN.js +1 -1
  26. package/schematics/bundles/{index-DnkWgagp.js → index-BL9kAIe5.js} +11 -11
  27. package/schematics/bundles/{program-BZk27Ndu.js → index-I8VbxQcO.js} +2234 -2097
  28. package/schematics/bundles/inject-flags.js +18 -52
  29. package/schematics/bundles/inject-migration.js +3 -3
  30. package/schematics/bundles/leading_space-D9nQ8UQC.js +1 -1
  31. package/schematics/bundles/{migrate_ts_type_references-DtkOnnv0.js → migrate_ts_type_references-KlOTWeDl.js} +20 -20
  32. package/schematics/bundles/ng_decorators-DznZ5jMl.js +1 -1
  33. package/schematics/bundles/nodes-B16H9JUd.js +1 -1
  34. package/schematics/bundles/output-migration.js +62 -90
  35. package/schematics/bundles/project_tsconfig_paths-CDVxT6Ov.js +1 -1
  36. package/schematics/bundles/property_name-BBwFuqMe.js +1 -1
  37. package/schematics/bundles/route-lazy-loading.js +3 -3
  38. package/schematics/bundles/{project_paths-Jtbi76Bs.js → run_in_devkit-C0JPtK2u.js} +262 -197
  39. package/schematics/bundles/self-closing-tags-migration.js +41 -71
  40. package/schematics/bundles/signal-input-migration.js +69 -97
  41. package/schematics/bundles/signal-queries-migration.js +80 -108
  42. package/schematics/bundles/signals.js +11 -11
  43. package/schematics/bundles/standalone-migration.js +8 -22
  44. package/schematics/bundles/symbol-VPWguRxr.js +25 -0
  45. package/schematics/bundles/test-bed-get.js +98 -0
  46. package/schematics/migrations.json +5 -0
  47. package/testing/index.d.ts +1 -3
  48. package/weak_ref.d-ttyj86RV.d.ts +1 -1
  49. package/schematics/bundles/index-vGJcp5M7.js +0 -30
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  /**
3
- * @license Angular v20.0.0-next.2
3
+ * @license Angular v20.0.0-next.3
4
4
  * (c) 2010-2025 Google LLC. https://angular.io/
5
5
  * License: MIT
6
6
  */
@@ -5350,6 +5350,28 @@ let Icu$1 = class Icu {
5350
5350
  return visitor.visitIcu(this);
5351
5351
  }
5352
5352
  };
5353
+ /**
5354
+ * AST node that represents the host element of a directive.
5355
+ * This node is used only for type checking purposes and cannot be produced from a user's template.
5356
+ */
5357
+ class HostElement {
5358
+ tagNames;
5359
+ bindings;
5360
+ listeners;
5361
+ sourceSpan;
5362
+ constructor(tagNames, bindings, listeners, sourceSpan) {
5363
+ this.tagNames = tagNames;
5364
+ this.bindings = bindings;
5365
+ this.listeners = listeners;
5366
+ this.sourceSpan = sourceSpan;
5367
+ if (tagNames.length === 0) {
5368
+ throw new Error('HostElement must have at least one tag name.');
5369
+ }
5370
+ }
5371
+ visit() {
5372
+ throw new Error(`HostElement cannot be visited`);
5373
+ }
5374
+ }
5353
5375
  let RecursiveVisitor$1 = class RecursiveVisitor {
5354
5376
  visitElement(element) {
5355
5377
  visitAll$1(this, element.attributes);
@@ -22548,7 +22570,6 @@ function enableBindings() {
22548
22570
  function listener(name, handlerFn, eventTargetResolver, syntheticHost, sourceSpan) {
22549
22571
  const args = [literal$1(name), handlerFn];
22550
22572
  if (eventTargetResolver !== null) {
22551
- args.push(literal$1(false)); // `useCapture` flag, defaults to `false`
22552
22573
  args.push(importExpr(eventTargetResolver));
22553
22574
  }
22554
22575
  return call(syntheticHost ? Identifiers.syntheticHostListener : Identifiers.listener, args, sourceSpan);
@@ -29463,7 +29484,7 @@ class R3TargetBinder {
29463
29484
  * metadata about the types referenced in the template.
29464
29485
  */
29465
29486
  bind(target) {
29466
- if (!target.template) {
29487
+ if (!target.template && !target.host) {
29467
29488
  throw new Error('Empty bound targets are not supported');
29468
29489
  }
29469
29490
  const directives = new Map();
@@ -29493,6 +29514,11 @@ class R3TargetBinder {
29493
29514
  // template. This extracts all the metadata that doesn't depend on directive matching.
29494
29515
  TemplateBinder.applyWithScope(target.template, scope, expressions, symbols, nestingLevel, usedPipes, eagerPipes, deferBlocks);
29495
29516
  }
29517
+ // Bind the host element in a separate scope. Note that it only uses the
29518
+ // `TemplateBinder` since directives don't apply inside a host context.
29519
+ if (target.host) {
29520
+ TemplateBinder.applyWithScope(target.host, Scope$1.apply(target.host), expressions, symbols, nestingLevel, usedPipes, eagerPipes, deferBlocks);
29521
+ }
29496
29522
  return new R3BoundTarget(target, directives, eagerDirectives, bindings, references, expressions, symbols, nestingLevel, scopedNodeEntities, usedPipes, eagerPipes, deferBlocks);
29497
29523
  }
29498
29524
  }
@@ -29568,7 +29594,7 @@ let Scope$1 = class Scope {
29568
29594
  nodeOrNodes instanceof Content) {
29569
29595
  nodeOrNodes.children.forEach((node) => node.visit(this));
29570
29596
  }
29571
- else {
29597
+ else if (!(nodeOrNodes instanceof HostElement)) {
29572
29598
  // No overarching `Template` instance, so process the nodes directly.
29573
29599
  nodeOrNodes.forEach((node) => node.visit(this));
29574
29600
  }
@@ -29897,7 +29923,7 @@ class TemplateBinder extends RecursiveAstVisitor {
29897
29923
  /**
29898
29924
  * Process a template and extract metadata about expressions and symbols within.
29899
29925
  *
29900
- * @param nodes the nodes of the template to process
29926
+ * @param nodeOrNodes the nodes of the template to process
29901
29927
  * @param scope the `Scope` of the template being processed.
29902
29928
  * @returns three maps which contain metadata about the template: `expressions` which interprets
29903
29929
  * special `AST` nodes in expressions as pointing to references or variables declared within the
@@ -29906,11 +29932,11 @@ class TemplateBinder extends RecursiveAstVisitor {
29906
29932
  * nesting level (how many levels deep within the template structure the `Template` is), starting
29907
29933
  * at 1.
29908
29934
  */
29909
- static applyWithScope(nodes, scope, expressions, symbols, nestingLevel, usedPipes, eagerPipes, deferBlocks) {
29910
- const template = nodes instanceof Template ? nodes : null;
29935
+ static applyWithScope(nodeOrNodes, scope, expressions, symbols, nestingLevel, usedPipes, eagerPipes, deferBlocks) {
29936
+ const template = nodeOrNodes instanceof Template ? nodeOrNodes : null;
29911
29937
  // The top-level template has nesting level 0.
29912
29938
  const binder = new TemplateBinder(expressions, symbols, usedPipes, eagerPipes, deferBlocks, nestingLevel, scope, template, 0);
29913
- binder.ingest(nodes);
29939
+ binder.ingest(nodeOrNodes);
29914
29940
  }
29915
29941
  ingest(nodeOrNodes) {
29916
29942
  if (nodeOrNodes instanceof Template) {
@@ -29952,6 +29978,10 @@ class TemplateBinder extends RecursiveAstVisitor {
29952
29978
  nodeOrNodes.children.forEach((node) => node.visit(this));
29953
29979
  this.nestingLevel.set(nodeOrNodes, this.level);
29954
29980
  }
29981
+ else if (nodeOrNodes instanceof HostElement) {
29982
+ // Host elements are always at the top level.
29983
+ this.nestingLevel.set(nodeOrNodes, 0);
29984
+ }
29955
29985
  else {
29956
29986
  // Visit each node from the top-level template.
29957
29987
  nodeOrNodes.forEach(this.visitNode);
@@ -31437,7 +31467,7 @@ var FactoryTarget;
31437
31467
  * @description
31438
31468
  * Entry point for all public APIs of the compiler package.
31439
31469
  */
31440
- new Version('20.0.0-next.2');
31470
+ new Version('20.0.0-next.3');
31441
31471
 
31442
31472
  //////////////////////////////////////
31443
31473
  // THIS FILE HAS GLOBAL SIDE EFFECT //
@@ -31903,6 +31933,10 @@ exports.ErrorCode = void 0;
31903
31933
  * A symbol referenced in `@Component.imports` isn't being used within the template.
31904
31934
  */
31905
31935
  ErrorCode[ErrorCode["UNUSED_STANDALONE_IMPORTS"] = 8113] = "UNUSED_STANDALONE_IMPORTS";
31936
+ /**
31937
+ * An expression mixes nullish coalescing and logical and/or without parentheses.
31938
+ */
31939
+ ErrorCode[ErrorCode["UNPARENTHESIZED_NULLISH_COALESCING"] = 8114] = "UNPARENTHESIZED_NULLISH_COALESCING";
31906
31940
  /**
31907
31941
  * The template type-checking engine would need to generate an inline type check block for a
31908
31942
  * component, but the current type-checking environment doesn't support it.
@@ -32061,6 +32095,7 @@ exports.ExtendedTemplateDiagnosticName = void 0;
32061
32095
  ExtendedTemplateDiagnosticName["CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION"] = "controlFlowPreventingContentProjection";
32062
32096
  ExtendedTemplateDiagnosticName["UNUSED_LET_DECLARATION"] = "unusedLetDeclaration";
32063
32097
  ExtendedTemplateDiagnosticName["UNUSED_STANDALONE_IMPORTS"] = "unusedStandaloneImports";
32098
+ ExtendedTemplateDiagnosticName["UNPARENTHESIZED_NULLISH_COALESCING"] = "unparenthesizedNullishCoalescing";
32064
32099
  })(exports.ExtendedTemplateDiagnosticName || (exports.ExtendedTemplateDiagnosticName = {}));
32065
32100
 
32066
32101
  /**
@@ -32387,7 +32422,7 @@ class NodeJSPathManipulation {
32387
32422
  // G3-ESM-MARKER: G3 uses CommonJS, but externally everything in ESM.
32388
32423
  // CommonJS/ESM interop for determining the current file name and containing dir.
32389
32424
  const isCommonJS = typeof __filename !== 'undefined';
32390
- const currentFileUrl = isCommonJS ? null : (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('checker-DF8ZaFW5.js', document.baseURI).href));
32425
+ const currentFileUrl = isCommonJS ? null : (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('checker-BHb19MHt.js', document.baseURI).href));
32391
32426
  const currentFileName = isCommonJS ? __filename : url.fileURLToPath(currentFileUrl);
32392
32427
  /**
32393
32428
  * A wrapper around the Node.js file-system that supports readonly operations and path manipulation.
@@ -39209,7 +39244,12 @@ function extractDirectiveMetadata(clazz, decorator, reflector, importTracker, ev
39209
39244
  throw new FatalDiagnosticError(exports.ErrorCode.DIRECTIVE_MISSING_SELECTOR, expr, `Directive ${clazz.name.text} has no selector, please add it!`);
39210
39245
  }
39211
39246
  }
39212
- const host = extractHostBindings(decoratedElements, evaluator, coreModule, compilationMode, directive);
39247
+ const hostBindingNodes = {
39248
+ literal: null,
39249
+ bindingDecorators: new Set(),
39250
+ listenerDecorators: new Set(),
39251
+ };
39252
+ const host = extractHostBindings(decoratedElements, evaluator, coreModule, compilationMode, hostBindingNodes, directive);
39213
39253
  const providers = directive.has('providers')
39214
39254
  ? new WrappedNodeExpr(annotateForClosureCompiler
39215
39255
  ? wrapFunctionExpressionsInParens(directive.get('providers'))
@@ -39321,6 +39361,7 @@ function extractDirectiveMetadata(clazz, decorator, reflector, importTracker, ev
39321
39361
  isStructural,
39322
39362
  hostDirectives,
39323
39363
  rawHostDirectives,
39364
+ hostBindingNodes,
39324
39365
  // Track inputs from class metadata. This is useful for migration efforts.
39325
39366
  inputFieldNamesFromMetadataArray: new Set(Object.values(inputsFromMeta).map((i) => i.classPropertyName)),
39326
39367
  };
@@ -39403,10 +39444,14 @@ function extractDecoratorQueryMetadata(exprNode, name, args, propertyName, refle
39403
39444
  emitDistinctChangesOnly,
39404
39445
  };
39405
39446
  }
39406
- function extractHostBindings(members, evaluator, coreModule, compilationMode, metadata) {
39447
+ function extractHostBindings(members, evaluator, coreModule, compilationMode, hostBindingNodes, metadata) {
39407
39448
  let bindings;
39408
39449
  if (metadata && metadata.has('host')) {
39409
- bindings = evaluateHostExpressionBindings(metadata.get('host'), evaluator);
39450
+ const hostExpression = metadata.get('host');
39451
+ bindings = evaluateHostExpressionBindings(hostExpression, evaluator);
39452
+ if (ts.isObjectLiteralExpression(hostExpression)) {
39453
+ hostBindingNodes.literal = hostExpression;
39454
+ }
39410
39455
  }
39411
39456
  else {
39412
39457
  bindings = parseHostBindings({});
@@ -39430,6 +39475,9 @@ function extractHostBindings(members, evaluator, coreModule, compilationMode, me
39430
39475
  }
39431
39476
  hostPropertyName = resolved;
39432
39477
  }
39478
+ if (ts.isDecorator(decorator.node)) {
39479
+ hostBindingNodes.bindingDecorators.add(decorator.node);
39480
+ }
39433
39481
  // Since this is a decorator, we know that the value is a class member. Always access it
39434
39482
  // through `this` so that further down the line it can't be confused for a literal value
39435
39483
  // (e.g. if there's a property called `true`). There is no size penalty, because all
@@ -39465,6 +39513,9 @@ function extractHostBindings(members, evaluator, coreModule, compilationMode, me
39465
39513
  args = resolvedArgs;
39466
39514
  }
39467
39515
  }
39516
+ if (ts.isDecorator(decorator.node)) {
39517
+ hostBindingNodes.listenerDecorators.add(decorator.node);
39518
+ }
39468
39519
  bindings.listeners[eventName] = `${member.name}(${args.join(',')})`;
39469
39520
  });
39470
39521
  });
@@ -40134,6 +40185,19 @@ function toR3InputMetadata(mapping) {
40134
40185
  isSignal: mapping.isSignal,
40135
40186
  };
40136
40187
  }
40188
+ function extractHostBindingResources(nodes) {
40189
+ const result = new Set();
40190
+ if (nodes.literal !== null) {
40191
+ result.add({ path: null, node: nodes.literal });
40192
+ }
40193
+ for (const current of nodes.bindingDecorators) {
40194
+ result.add({ path: null, node: current.expression });
40195
+ }
40196
+ for (const current of nodes.listenerDecorators) {
40197
+ result.add({ path: null, node: current.expression });
40198
+ }
40199
+ return result;
40200
+ }
40137
40201
 
40138
40202
  const NgOriginalFile = Symbol('NgOriginalFile');
40139
40203
  exports.UpdateMode = void 0;
@@ -40441,7 +40505,7 @@ function parseTemplateAsSourceFile(fileName, template) {
40441
40505
  }
40442
40506
 
40443
40507
  const TYPE_CHECK_ID_MAP = Symbol('TypeCheckId');
40444
- function getTypeCheckId(clazz) {
40508
+ function getTypeCheckId$1(clazz) {
40445
40509
  const sf = clazz.getSourceFile();
40446
40510
  if (sf[TYPE_CHECK_ID_MAP] === undefined) {
40447
40511
  sf[TYPE_CHECK_ID_MAP] = new Map();
@@ -42212,7 +42276,7 @@ class RegistryDomSchemaChecker {
42212
42276
  this._diagnostics.push(diag);
42213
42277
  }
42214
42278
  }
42215
- checkProperty(id, element, name, span, schemas, hostIsStandalone) {
42279
+ checkTemplateElementProperty(id, element, name, span, schemas, hostIsStandalone) {
42216
42280
  if (!REGISTRY$1.hasProperty(element.name, name, schemas)) {
42217
42281
  const mapping = this.resolver.getTemplateSourceMapping(id);
42218
42282
  const decorator = hostIsStandalone ? '@Component' : '@NgModule';
@@ -42235,6 +42299,18 @@ class RegistryDomSchemaChecker {
42235
42299
  this._diagnostics.push(diag);
42236
42300
  }
42237
42301
  }
42302
+ checkHostElementProperty(id, element, name, span, schemas) {
42303
+ for (const tagName of element.tagNames) {
42304
+ if (REGISTRY$1.hasProperty(tagName, name, schemas)) {
42305
+ continue;
42306
+ }
42307
+ const errorMessage = `Can't bind to '${name}' since it isn't a known property of '${tagName}'.`;
42308
+ const mapping = this.resolver.getHostBindingsMapping(id);
42309
+ const diag = makeTemplateDiagnostic(id, mapping, span, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.SCHEMA_INVALID_ATTRIBUTE), errorMessage);
42310
+ this._diagnostics.push(diag);
42311
+ break;
42312
+ }
42313
+ }
42238
42314
  }
42239
42315
 
42240
42316
  /**
@@ -42346,13 +42422,26 @@ function tsCastToAny(expr) {
42346
42422
  * Thanks to narrowing of `document.createElement()`, this expression will have its type inferred
42347
42423
  * based on the tag name, including for custom elements that have appropriate .d.ts definitions.
42348
42424
  */
42349
- function tsCreateElement(tagName) {
42425
+ function tsCreateElement(...tagNames) {
42350
42426
  const createElement = ts.factory.createPropertyAccessExpression(
42351
42427
  /* expression */ ts.factory.createIdentifier('document'), 'createElement');
42428
+ let arg;
42429
+ if (tagNames.length === 1) {
42430
+ // If there's only one tag name, we can pass it in directly.
42431
+ arg = ts.factory.createStringLiteral(tagNames[0]);
42432
+ }
42433
+ else {
42434
+ // If there's more than one name, we have to generate a union of all the tag names. To do so,
42435
+ // create an expression in the form of `null! as 'tag-1' | 'tag-2' | 'tag-3'`. This allows
42436
+ // TypeScript to infer the type as a union of the differnet tags.
42437
+ const assertedNullExpression = ts.factory.createNonNullExpression(ts.factory.createNull());
42438
+ const type = ts.factory.createUnionTypeNode(tagNames.map((tag) => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(tag))));
42439
+ arg = ts.factory.createAsExpression(assertedNullExpression, type);
42440
+ }
42352
42441
  return ts.factory.createCallExpression(
42353
42442
  /* expression */ createElement,
42354
42443
  /* typeArguments */ undefined,
42355
- /* argumentsArray */ [ts.factory.createStringLiteral(tagName)]);
42444
+ /* argumentsArray */ [arg]);
42356
42445
  }
42357
42446
  /**
42358
42447
  * Create a `ts.VariableStatement` which declares a variable without explicit initialization.
@@ -42528,6 +42617,353 @@ class TypeParameterEmitter {
42528
42617
  }
42529
42618
  }
42530
42619
 
42620
+ /*!
42621
+ * @license
42622
+ * Copyright Google LLC All Rights Reserved.
42623
+ *
42624
+ * Use of this source code is governed by an MIT-style license that can be
42625
+ * found in the LICENSE file at https://angular.dev/license
42626
+ */
42627
+ /**
42628
+ * Comment attached to an AST node that serves as a guard to distinguish nodes
42629
+ * used for type checking host bindings from ones used for templates.
42630
+ */
42631
+ const GUARD_COMMENT_TEXT = 'hostBindingsBlockGuard';
42632
+ /**
42633
+ * Creates an AST node that represents the host element of a directive.
42634
+ * Can return null if there are no valid bindings to be checked.
42635
+ * @param type Whether the host element is for a directive or a component.
42636
+ * @param selector Selector of the directive.
42637
+ * @param sourceNode Class declaration for the directive.
42638
+ * @param literal `host` object literal from the decorator.
42639
+ * @param bindingDecorators `HostBinding` decorators discovered on the node.
42640
+ * @param listenerDecorators `HostListener` decorators discovered on the node.
42641
+ */
42642
+ function createHostElement(type, selector, sourceNode, literal, bindingDecorators, listenerDecorators) {
42643
+ const bindings = [];
42644
+ const listeners = [];
42645
+ let parser = null;
42646
+ if (literal !== null) {
42647
+ for (const prop of literal.properties) {
42648
+ // We only support type checking of static bindings.
42649
+ if (ts.isPropertyAssignment(prop) &&
42650
+ ts.isStringLiteralLike(prop.initializer) &&
42651
+ isStaticName(prop.name)) {
42652
+ parser ??= makeBindingParser();
42653
+ createNodeFromHostLiteralProperty(prop, parser, bindings, listeners);
42654
+ }
42655
+ }
42656
+ }
42657
+ for (const decorator of bindingDecorators) {
42658
+ createNodeFromBindingDecorator(decorator, bindings);
42659
+ }
42660
+ for (const decorator of listenerDecorators) {
42661
+ parser ??= makeBindingParser();
42662
+ createNodeFromListenerDecorator(decorator, parser, listeners);
42663
+ }
42664
+ // The element will be a no-op if there are no bindings.
42665
+ if (bindings.length === 0 && listeners.length === 0) {
42666
+ return null;
42667
+ }
42668
+ const tagNames = [];
42669
+ if (selector !== null) {
42670
+ const parts = CssSelector.parse(selector);
42671
+ for (const part of parts) {
42672
+ if (part.element !== null) {
42673
+ tagNames.push(part.element);
42674
+ }
42675
+ }
42676
+ }
42677
+ // If none of the selectors have a tag name, fall back to `ng-component`/`ng-directive`.
42678
+ // This is how the runtime handles components without tag names as well.
42679
+ if (tagNames.length === 0) {
42680
+ tagNames.push(`ng-${type}`);
42681
+ }
42682
+ return new HostElement(tagNames, bindings, listeners, createSourceSpan(sourceNode.name));
42683
+ }
42684
+ /**
42685
+ * Creates an AST node that can be used as a guard in `if` statements to distinguish TypeScript
42686
+ * nodes used for checking host bindings from ones used for checking templates.
42687
+ */
42688
+ function createHostBindingsBlockGuard() {
42689
+ // Note that the comment text is quite generic. This doesn't really matter, because it is
42690
+ // used only inside a TCB and there's no way for users to produce a comment there.
42691
+ // `true /*hostBindings*/`.
42692
+ const trueExpr = ts.addSyntheticTrailingComment(ts.factory.createTrue(), ts.SyntaxKind.MultiLineCommentTrivia, GUARD_COMMENT_TEXT);
42693
+ // Wrap the expression in parentheses to ensure that the comment is attached to the correct node.
42694
+ return ts.factory.createParenthesizedExpression(trueExpr);
42695
+ }
42696
+ /**
42697
+ * Determines if a given node is a guard that indicates that descendant nodes are used to check
42698
+ * host bindings.
42699
+ */
42700
+ function isHostBindingsBlockGuard(node) {
42701
+ if (!ts.isIfStatement(node)) {
42702
+ return false;
42703
+ }
42704
+ // Needs to be kept in sync with `createHostBindingsMarker`.
42705
+ const expr = node.expression;
42706
+ if (!ts.isParenthesizedExpression(expr) || expr.expression.kind !== ts.SyntaxKind.TrueKeyword) {
42707
+ return false;
42708
+ }
42709
+ const text = expr.getSourceFile().text;
42710
+ return (ts.forEachTrailingCommentRange(text, expr.expression.getEnd(), (pos, end, kind) => kind === ts.SyntaxKind.MultiLineCommentTrivia &&
42711
+ text.substring(pos + 2, end - 2) === GUARD_COMMENT_TEXT) || false);
42712
+ }
42713
+ /**
42714
+ * If possible, creates and tracks the relevant AST node for a binding declared
42715
+ * through a property on the `host` literal.
42716
+ * @param prop Property to parse.
42717
+ * @param parser Binding parser used to parse the expressions.
42718
+ * @param bindings Array tracking the bound attributes of the host element.
42719
+ * @param listeners Array tracking the event listeners of the host element.
42720
+ */
42721
+ function createNodeFromHostLiteralProperty(property, parser, bindings, listeners) {
42722
+ // TODO(crisbeto): surface parsing errors here, because currently they just get ignored.
42723
+ // They'll still get reported when the handler tries to parse the bindings, but here we
42724
+ // can highlight the nodes more accurately.
42725
+ const { name, initializer } = property;
42726
+ if (name.text.startsWith('[') && name.text.endsWith(']')) {
42727
+ const { attrName, type } = inferBoundAttribute(name.text.slice(1, -1));
42728
+ const valueSpan = createStaticExpressionSpan(initializer);
42729
+ const ast = parser.parseBinding(initializer.text, true, valueSpan, valueSpan.start.offset);
42730
+ if (ast.errors.length > 0) {
42731
+ return; // See TODO above.
42732
+ }
42733
+ fixupSpans(ast, initializer);
42734
+ bindings.push(new BoundAttribute(attrName, type, 0, ast, null, createSourceSpan(property), createStaticExpressionSpan(name), valueSpan, undefined));
42735
+ }
42736
+ else if (name.text.startsWith('(') && name.text.endsWith(')')) {
42737
+ const events = [];
42738
+ parser.parseEvent(name.text.slice(1, -1), initializer.text, false, createSourceSpan(property), createStaticExpressionSpan(initializer), [], events, createStaticExpressionSpan(name));
42739
+ if (events.length === 0 || events[0].handler.errors.length > 0) {
42740
+ return; // See TODO above.
42741
+ }
42742
+ fixupSpans(events[0].handler, initializer);
42743
+ listeners.push(BoundEvent.fromParsedEvent(events[0]));
42744
+ }
42745
+ }
42746
+ /**
42747
+ * If possible, creates and tracks a bound attribute node from a `HostBinding` decorator.
42748
+ * @param decorator Decorator from which to create the node.
42749
+ * @param bindings Array tracking the bound attributes of the host element.
42750
+ */
42751
+ function createNodeFromBindingDecorator(decorator, bindings) {
42752
+ // We only support decorators that are being called.
42753
+ if (!ts.isCallExpression(decorator.expression)) {
42754
+ return;
42755
+ }
42756
+ const args = decorator.expression.arguments;
42757
+ const property = decorator.parent;
42758
+ let nameNode = null;
42759
+ let propertyName = null;
42760
+ if (property && ts.isPropertyDeclaration(property) && isStaticName(property.name)) {
42761
+ propertyName = property.name;
42762
+ }
42763
+ // The first parameter is optional. If omitted, the name
42764
+ // of the class member is used as the property.
42765
+ if (args.length === 0) {
42766
+ nameNode = propertyName;
42767
+ }
42768
+ else if (ts.isStringLiteralLike(args[0])) {
42769
+ nameNode = args[0];
42770
+ }
42771
+ else {
42772
+ return;
42773
+ }
42774
+ if (nameNode === null || propertyName === null) {
42775
+ return;
42776
+ }
42777
+ // We can't synthesize a fake expression here and pass it through the binding parser, because
42778
+ // it constructs all the spans based on the source code origin and they aren't easily mappable
42779
+ // back to the source. E.g. `@HostBinding('foo') id = '123'` in source code would look
42780
+ // something like `[foo]="this.id"` in the AST. Instead we construct the expressions
42781
+ // manually here. Note that we use a dummy span with -1/-1 as offsets, because it isn't
42782
+ // used for type checking and constructing it accurately would take some effort.
42783
+ const span = new ParseSpan(-1, -1);
42784
+ const propertyStart = property.getStart();
42785
+ const receiver = new ThisReceiver(span, new AbsoluteSourceSpan(propertyStart, propertyStart));
42786
+ const nameSpan = new AbsoluteSourceSpan(propertyName.getStart(), propertyName.getEnd());
42787
+ const read = ts.isIdentifier(propertyName)
42788
+ ? new PropertyRead(span, nameSpan, nameSpan, receiver, propertyName.text)
42789
+ : new KeyedRead(span, nameSpan, receiver, new LiteralPrimitive(span, nameSpan, propertyName.text));
42790
+ const { attrName, type } = inferBoundAttribute(nameNode.text);
42791
+ bindings.push(new BoundAttribute(attrName, type, 0, read, null, createSourceSpan(decorator), createStaticExpressionSpan(nameNode), createSourceSpan(decorator), undefined));
42792
+ }
42793
+ /**
42794
+ * If possible, creates and tracks a bound event node from a `HostBinding` decorator.
42795
+ * @param decorator Decorator from which to create the node.
42796
+ * @param parser Binding parser used to parse the expressions.
42797
+ * @param bindings Array tracking the bound events of the host element.
42798
+ */
42799
+ function createNodeFromListenerDecorator(decorator, parser, listeners) {
42800
+ // We only support decorators that are being called with at least one argument.
42801
+ if (!ts.isCallExpression(decorator.expression) || decorator.expression.arguments.length === 0) {
42802
+ return;
42803
+ }
42804
+ const args = decorator.expression.arguments;
42805
+ const method = decorator.parent;
42806
+ // Only handle decorators that are statically analyzable.
42807
+ if (!method ||
42808
+ !ts.isMethodDeclaration(method) ||
42809
+ !isStaticName(method.name) ||
42810
+ !ts.isStringLiteralLike(args[0])) {
42811
+ return;
42812
+ }
42813
+ // We can't synthesize a fake expression here and pass it through the binding parser, because
42814
+ // it constructs all the spans based on the source code origin and they aren't easily mappable
42815
+ // back to the source. E.g. `@HostListener('foo') handleFoo() {}` in source code would look
42816
+ // something like `(foo)="handleFoo()"` in the AST. Instead we construct the expressions
42817
+ // manually here. Note that we use a dummy span with -1/-1 as offsets, because it isn't
42818
+ // used for type checking and constructing it accurately would take some effort.
42819
+ const span = new ParseSpan(-1, -1);
42820
+ const argNodes = [];
42821
+ const methodStart = method.getStart();
42822
+ const methodReceiver = new ThisReceiver(span, new AbsoluteSourceSpan(methodStart, methodStart));
42823
+ const nameSpan = new AbsoluteSourceSpan(method.name.getStart(), method.name.getEnd());
42824
+ const receiver = ts.isIdentifier(method.name)
42825
+ ? new PropertyRead(span, nameSpan, nameSpan, methodReceiver, method.name.text)
42826
+ : new KeyedRead(span, nameSpan, methodReceiver, new LiteralPrimitive(span, nameSpan, method.name.text));
42827
+ if (args.length > 1 && ts.isArrayLiteralExpression(args[1])) {
42828
+ for (const expr of args[1].elements) {
42829
+ // If the parameter is a static string, parse it using the binding parser since it can be any
42830
+ // expression, otherwise treat it as `any` so the rest of the parameters can be checked.
42831
+ if (ts.isStringLiteralLike(expr)) {
42832
+ const span = createStaticExpressionSpan(expr);
42833
+ const ast = parser.parseBinding(expr.text, true, span, span.start.offset);
42834
+ fixupSpans(ast, expr);
42835
+ argNodes.push(ast);
42836
+ }
42837
+ else {
42838
+ // Represents `$any(0)`. We need to construct it manually in order to set the right spans.
42839
+ const expressionSpan = new AbsoluteSourceSpan(expr.getStart(), expr.getEnd());
42840
+ const anyRead = new PropertyRead(span, expressionSpan, expressionSpan, new ImplicitReceiver(span, expressionSpan), '$any');
42841
+ const anyCall = new Call(span, expressionSpan, anyRead, [new LiteralPrimitive(span, expressionSpan, 0)], expressionSpan);
42842
+ argNodes.push(anyCall);
42843
+ }
42844
+ }
42845
+ }
42846
+ const callNode = new Call(span, nameSpan, receiver, argNodes, span);
42847
+ const eventNameNode = args[0];
42848
+ const [eventName, phase] = eventNameNode.text.split('.');
42849
+ listeners.push(new BoundEvent(eventName, eventName.startsWith('@') ? exports.ParsedEventType.Animation : exports.ParsedEventType.Regular, callNode, null, phase, createSourceSpan(decorator), createSourceSpan(decorator), createStaticExpressionSpan(eventNameNode)));
42850
+ }
42851
+ /**
42852
+ * Infers the attribute name and binding type of a bound attribute based on its raw name.
42853
+ * @param name Name from which to infer the information.
42854
+ */
42855
+ function inferBoundAttribute(name) {
42856
+ const attrPrefix = 'attr.';
42857
+ const classPrefix = 'class.';
42858
+ const stylePrefix = 'style.';
42859
+ const animationPrefix = '@';
42860
+ let attrName;
42861
+ let type;
42862
+ // Infer the binding type based on the prefix.
42863
+ if (name.startsWith(attrPrefix)) {
42864
+ attrName = name.slice(attrPrefix.length);
42865
+ type = exports.BindingType.Attribute;
42866
+ }
42867
+ else if (name.startsWith(classPrefix)) {
42868
+ attrName = name.slice(classPrefix.length);
42869
+ type = exports.BindingType.Class;
42870
+ }
42871
+ else if (name.startsWith(stylePrefix)) {
42872
+ attrName = name.slice(stylePrefix.length);
42873
+ type = exports.BindingType.Style;
42874
+ }
42875
+ else if (name.startsWith(animationPrefix)) {
42876
+ attrName = name.slice(animationPrefix.length);
42877
+ type = exports.BindingType.Animation;
42878
+ }
42879
+ else {
42880
+ attrName = name;
42881
+ type = exports.BindingType.Property;
42882
+ }
42883
+ return { attrName, type };
42884
+ }
42885
+ /** Checks whether the specified node is a static name node. */
42886
+ function isStaticName(node) {
42887
+ return ts.isIdentifier(node) || ts.isStringLiteralLike(node);
42888
+ }
42889
+ /** Creates a `ParseSourceSpan` pointing to a static expression AST node's source. */
42890
+ function createStaticExpressionSpan(node) {
42891
+ const span = createSourceSpan(node);
42892
+ // Offset by one on both sides to skip over the quotes.
42893
+ if (ts.isStringLiteralLike(node)) {
42894
+ span.fullStart = span.fullStart.moveBy(1);
42895
+ span.start = span.start.moveBy(1);
42896
+ span.end = span.end.moveBy(-1);
42897
+ }
42898
+ return span;
42899
+ }
42900
+ /**
42901
+ * Adjusts the spans of a parsed AST so that they're appropriate for a host bindings context.
42902
+ * @param ast The parsed AST that may need to be adjusted.
42903
+ * @param initializer TypeScript node from which the source of the AST was extracted.
42904
+ */
42905
+ function fixupSpans(ast, initializer) {
42906
+ // When parsing the initializer as a property/event binding, we use `.text` which excludes escaped
42907
+ // quotes and is generally what we want, because preserving them would result in a parser error,
42908
+ // however it has the downside in that the spans of the expressions following the escaped
42909
+ // characters are no longer accurate relative to the source code. The more escaped characters
42910
+ // there are before a node, the more inaccurate its span will be. If we detect cases like that,
42911
+ // we override the spans of all nodes following the escaped string to point to the entire
42912
+ // initializer string so that we don't surface diagnostics with mangled spans. This isn't ideal,
42913
+ // but is likely rare in user code. Some workarounds that were attempted and ultimately discarded:
42914
+ // 1. Counting the number of escaped strings before a node and adjusting its span accordingly -
42915
+ // There's a prototype of this approach in https://github.com/crisbeto/angular/commit/1eb92353784a609f6be7e6653ae5a9faef549e6a
42916
+ // It works for the most part, but is complex and somewhat brittle, because it's not just the
42917
+ // escaped literals that need to be updated, but also any nodes _after_ them and any nodes
42918
+ // _containing_ them which gets increasingly complex with more complicated ASTs.
42919
+ // 2. Replacing escape characters with whitespaces, for example `\'foo\' + 123` would become
42920
+ // ` 'foo ' + 123` - this appears to produce accurate ASTs for some simpler use cases, but has
42921
+ // the potential of either changing the user's code into something that's no longer parseable or
42922
+ // causing type checking errors (e.g. the typings might require the exact string 'foo').
42923
+ // 3. Passing the raw text (e.g. `initializer.getText().slice(1, -1)`) into the binding parser -
42924
+ // This will preserve the right mappings, but can lead to parsing errors, because some of the
42925
+ // strings won't have to be escaped anymore. We could add a mode to the parser that allows it to
42926
+ // recover from such cases, but it'll introduce more complexity that we may not want to take on.
42927
+ // 4. Constructing some sort of string like `<host ${name.getText()}=${initializer.getText()}/>`,
42928
+ // passing it through the HTML parser and extracting the first attribute from it - wasn't explored
42929
+ // much, but likely has the same issues as approach #3.
42930
+ const escapeIndex = initializer.getText().indexOf('\\', 1);
42931
+ if (escapeIndex > -1) {
42932
+ const newSpan = new ParseSpan(0, initializer.getWidth());
42933
+ const newSourceSpan = new AbsoluteSourceSpan(initializer.getStart(), initializer.getEnd());
42934
+ ast.visit(new ReplaceSpanVisitor(escapeIndex, newSpan, newSourceSpan));
42935
+ }
42936
+ }
42937
+ /**
42938
+ * Visitor that replaces the spans of all nodes with new ones,
42939
+ * if they're defined after a specific index.
42940
+ */
42941
+ class ReplaceSpanVisitor extends RecursiveAstVisitor {
42942
+ afterIndex;
42943
+ overrideSpan;
42944
+ overrideSourceSpan;
42945
+ constructor(afterIndex, overrideSpan, overrideSourceSpan) {
42946
+ super();
42947
+ this.afterIndex = afterIndex;
42948
+ this.overrideSpan = overrideSpan;
42949
+ this.overrideSourceSpan = overrideSourceSpan;
42950
+ }
42951
+ visit(ast) {
42952
+ // Only nodes after the index need to be adjusted, but all nodes should be visited.
42953
+ if (ast.span.start >= this.afterIndex || ast.span.end >= this.afterIndex) {
42954
+ ast.span = this.overrideSpan;
42955
+ ast.sourceSpan = this.overrideSourceSpan;
42956
+ if (ast instanceof ASTWithName) {
42957
+ ast.nameSpan = this.overrideSourceSpan;
42958
+ }
42959
+ if (ast instanceof Call || ast instanceof SafeCall) {
42960
+ ast.argumentSpan = this.overrideSourceSpan;
42961
+ }
42962
+ }
42963
+ super.visit(ast);
42964
+ }
42965
+ }
42966
+
42531
42967
  /**
42532
42968
  * External modules/identifiers that always should exist for type check
42533
42969
  * block files.
@@ -42596,18 +43032,39 @@ function getSourceMapping(shimSf, position, resolver, isDiagnosticRequest) {
42596
43032
  if (sourceLocation === null) {
42597
43033
  return null;
42598
43034
  }
42599
- const mapping = resolver.getTemplateSourceMapping(sourceLocation.id);
43035
+ if (isInHostBindingTcb(node)) {
43036
+ const hostSourceMapping = resolver.getHostBindingsMapping(sourceLocation.id);
43037
+ const span = resolver.toHostParseSourceSpan(sourceLocation.id, sourceLocation.span);
43038
+ if (span === null) {
43039
+ return null;
43040
+ }
43041
+ return { sourceLocation, sourceMapping: hostSourceMapping, span };
43042
+ }
42600
43043
  const span = resolver.toTemplateParseSourceSpan(sourceLocation.id, sourceLocation.span);
42601
43044
  if (span === null) {
42602
43045
  return null;
42603
43046
  }
42604
43047
  // TODO(atscott): Consider adding a context span by walking up from `node` until we get a
42605
43048
  // different span.
42606
- return { sourceLocation, sourceMapping: mapping, span };
43049
+ return {
43050
+ sourceLocation,
43051
+ sourceMapping: resolver.getTemplateSourceMapping(sourceLocation.id),
43052
+ span,
43053
+ };
43054
+ }
43055
+ function isInHostBindingTcb(node) {
43056
+ let current = node;
43057
+ while (current && !ts.isFunctionDeclaration(current)) {
43058
+ if (isHostBindingsBlockGuard(current)) {
43059
+ return true;
43060
+ }
43061
+ current = current.parent;
43062
+ }
43063
+ return false;
42607
43064
  }
42608
43065
  function findTypeCheckBlock(file, id, isDiagnosticRequest) {
42609
43066
  for (const stmt of file.statements) {
42610
- if (ts.isFunctionDeclaration(stmt) && getTemplateId(stmt, file, isDiagnosticRequest) === id) {
43067
+ if (ts.isFunctionDeclaration(stmt) && getTypeCheckId(stmt, file, isDiagnosticRequest) === id) {
42611
43068
  return stmt;
42612
43069
  }
42613
43070
  }
@@ -42630,7 +43087,7 @@ function findSourceLocation(node, sourceFile, isDiagnosticsRequest) {
42630
43087
  if (span !== null) {
42631
43088
  // Once the positional information has been extracted, search further up the TCB to extract
42632
43089
  // the unique id that is attached with the TCB's function declaration.
42633
- const id = getTemplateId(node, sourceFile, isDiagnosticsRequest);
43090
+ const id = getTypeCheckId(node, sourceFile, isDiagnosticsRequest);
42634
43091
  if (id === null) {
42635
43092
  return null;
42636
43093
  }
@@ -42640,7 +43097,7 @@ function findSourceLocation(node, sourceFile, isDiagnosticsRequest) {
42640
43097
  }
42641
43098
  return null;
42642
43099
  }
42643
- function getTemplateId(node, sourceFile, isDiagnosticRequest) {
43100
+ function getTypeCheckId(node, sourceFile, isDiagnosticRequest) {
42644
43101
  // Walk up to the function declaration of the TCB, the file information is attached there.
42645
43102
  while (!ts.isFunctionDeclaration(node)) {
42646
43103
  if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticRequest) {
@@ -43896,7 +44353,6 @@ var TcbGenericContextBehavior;
43896
44353
  */
43897
44354
  function generateTypeCheckBlock(env, ref, name, meta, domSchemaChecker, oobRecorder, genericContextBehavior) {
43898
44355
  const tcb = new Context(env, domSchemaChecker, oobRecorder, meta.id, meta.boundTarget, meta.pipes, meta.schemas, meta.isStandalone, meta.preserveWhitespaces);
43899
- const scope = Scope.forNodes(tcb, null, null, tcb.boundTarget.target.template, /* guard */ null);
43900
44356
  const ctxRawType = env.referenceType(ref);
43901
44357
  if (!ts.isTypeReferenceNode(ctxRawType)) {
43902
44358
  throw new Error(`Expected TypeReferenceNode when referencing the ctx param for ${ref.debugName}`);
@@ -43923,13 +44379,19 @@ function generateTypeCheckBlock(env, ref, name, meta, domSchemaChecker, oobRecor
43923
44379
  }
43924
44380
  }
43925
44381
  const paramList = [tcbThisParam(ctxRawType.typeName, typeArguments)];
43926
- const scopeStatements = scope.render();
43927
- const innerBody = ts.factory.createBlock([...env.getPreludeStatements(), ...scopeStatements]);
43928
- // Wrap the body in an "if (true)" expression. This is unnecessary but has the effect of causing
43929
- // the `ts.Printer` to format the type-check block nicely.
43930
- const body = ts.factory.createBlock([
43931
- ts.factory.createIfStatement(ts.factory.createTrue(), innerBody, undefined),
43932
- ]);
44382
+ const statements = [];
44383
+ // Add the template type checking code.
44384
+ if (tcb.boundTarget.target.template !== undefined) {
44385
+ const templateScope = Scope.forNodes(tcb, null, null, tcb.boundTarget.target.template,
44386
+ /* guard */ null);
44387
+ statements.push(renderBlockStatements(env, templateScope, ts.factory.createTrue()));
44388
+ }
44389
+ // Add the host bindings type checking code.
44390
+ if (tcb.boundTarget.target.host !== undefined) {
44391
+ const hostScope = Scope.forNodes(tcb, null, tcb.boundTarget.target.host, null, null);
44392
+ statements.push(renderBlockStatements(env, hostScope, createHostBindingsBlockGuard()));
44393
+ }
44394
+ const body = ts.factory.createBlock(statements);
43933
44395
  const fnDecl = ts.factory.createFunctionDeclaration(
43934
44396
  /* modifiers */ undefined,
43935
44397
  /* asteriskToken */ undefined,
@@ -43941,6 +44403,14 @@ function generateTypeCheckBlock(env, ref, name, meta, domSchemaChecker, oobRecor
43941
44403
  addTypeCheckId(fnDecl, meta.id);
43942
44404
  return fnDecl;
43943
44405
  }
44406
+ function renderBlockStatements(env, scope, wrapperExpression) {
44407
+ const scopeStatements = scope.render();
44408
+ const innerBody = ts.factory.createBlock([...env.getPreludeStatements(), ...scopeStatements]);
44409
+ // Wrap the body in an if statement. This serves two purposes:
44410
+ // 1. It allows us to distinguish between the sections of the block (e.g. host or template).
44411
+ // 2. It allows the `ts.Printer` to produce better-looking output.
44412
+ return ts.factory.createIfStatement(wrapperExpression, innerBody);
44413
+ }
43944
44414
  /**
43945
44415
  * A code generation operation that's involved in the construction of a Type Check Block.
43946
44416
  *
@@ -44693,20 +45163,28 @@ class TcbDomSchemaCheckerOp extends TcbOp {
44693
45163
  return false;
44694
45164
  }
44695
45165
  execute() {
44696
- if (this.checkElement) {
44697
- this.tcb.domSchemaChecker.checkElement(this.tcb.id, this.element, this.tcb.schemas, this.tcb.hostIsStandalone);
45166
+ const element = this.element;
45167
+ const isTemplateElement = element instanceof Element$1;
45168
+ const bindings = isTemplateElement ? element.inputs : element.bindings;
45169
+ if (this.checkElement && isTemplateElement) {
45170
+ this.tcb.domSchemaChecker.checkElement(this.tcb.id, element, this.tcb.schemas, this.tcb.hostIsStandalone);
44698
45171
  }
44699
45172
  // TODO(alxhub): this could be more efficient.
44700
- for (const binding of this.element.inputs) {
45173
+ for (const binding of bindings) {
44701
45174
  const isPropertyBinding = binding.type === exports.BindingType.Property || binding.type === exports.BindingType.TwoWay;
44702
- if (isPropertyBinding && this.claimedInputs.has(binding.name)) {
45175
+ if (isPropertyBinding && this.claimedInputs?.has(binding.name)) {
44703
45176
  // Skip this binding as it was claimed by a directive.
44704
45177
  continue;
44705
45178
  }
44706
45179
  if (isPropertyBinding && binding.name !== 'style' && binding.name !== 'class') {
44707
45180
  // A direct binding to a property.
44708
45181
  const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name;
44709
- this.tcb.domSchemaChecker.checkProperty(this.tcb.id, this.element, propertyName, binding.sourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone);
45182
+ if (isTemplateElement) {
45183
+ this.tcb.domSchemaChecker.checkTemplateElementProperty(this.tcb.id, element, propertyName, binding.sourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone);
45184
+ }
45185
+ else {
45186
+ this.tcb.domSchemaChecker.checkHostElementProperty(this.tcb.id, element, propertyName, binding.keySpan, this.tcb.schemas);
45187
+ }
44710
45188
  }
44711
45189
  }
44712
45190
  return null;
@@ -44821,6 +45299,30 @@ class TcbControlFlowContentProjectionOp extends TcbOp {
44821
45299
  return false;
44822
45300
  }
44823
45301
  }
45302
+ /**
45303
+ * A `TcbOp` which creates an expression for a the host element of a directive.
45304
+ *
45305
+ * Executing this operation returns a reference to the element variable.
45306
+ */
45307
+ class TcbHostElementOp extends TcbOp {
45308
+ tcb;
45309
+ scope;
45310
+ element;
45311
+ optional = true;
45312
+ constructor(tcb, scope, element) {
45313
+ super();
45314
+ this.tcb = tcb;
45315
+ this.scope = scope;
45316
+ this.element = element;
45317
+ }
45318
+ execute() {
45319
+ const id = this.tcb.allocateId();
45320
+ const initializer = tsCreateElement(...this.element.tagNames);
45321
+ addParseSpanInfo(initializer, this.element.sourceSpan);
45322
+ this.scope.addStatement(tsCreateVariable(id, initializer));
45323
+ return id;
45324
+ }
45325
+ }
44824
45326
  /**
44825
45327
  * Mapping between attributes names that don't correspond to their element property names.
44826
45328
  * Note: this mapping has to be kept in sync with the equally named mapping in the runtime.
@@ -44846,13 +45348,15 @@ const ATTR_TO_PROP = new Map(Object.entries({
44846
45348
  class TcbUnclaimedInputsOp extends TcbOp {
44847
45349
  tcb;
44848
45350
  scope;
44849
- element;
45351
+ inputs;
45352
+ target;
44850
45353
  claimedInputs;
44851
- constructor(tcb, scope, element, claimedInputs) {
45354
+ constructor(tcb, scope, inputs, target, claimedInputs) {
44852
45355
  super();
44853
45356
  this.tcb = tcb;
44854
45357
  this.scope = scope;
44855
- this.element = element;
45358
+ this.inputs = inputs;
45359
+ this.target = target;
44856
45360
  this.claimedInputs = claimedInputs;
44857
45361
  }
44858
45362
  get optional() {
@@ -44863,9 +45367,9 @@ class TcbUnclaimedInputsOp extends TcbOp {
44863
45367
  // the element itself.
44864
45368
  let elId = null;
44865
45369
  // TODO(alxhub): this could be more efficient.
44866
- for (const binding of this.element.inputs) {
45370
+ for (const binding of this.inputs) {
44867
45371
  const isPropertyBinding = binding.type === exports.BindingType.Property || binding.type === exports.BindingType.TwoWay;
44868
- if (isPropertyBinding && this.claimedInputs.has(binding.name)) {
45372
+ if (isPropertyBinding && this.claimedInputs?.has(binding.name)) {
44869
45373
  // Skip this binding as it was claimed by a directive.
44870
45374
  continue;
44871
45375
  }
@@ -44873,7 +45377,7 @@ class TcbUnclaimedInputsOp extends TcbOp {
44873
45377
  if (this.tcb.env.config.checkTypeOfDomBindings && isPropertyBinding) {
44874
45378
  if (binding.name !== 'style' && binding.name !== 'class') {
44875
45379
  if (elId === null) {
44876
- elId = this.scope.resolve(this.element);
45380
+ elId = this.scope.resolve(this.target);
44877
45381
  }
44878
45382
  // A direct binding to a property.
44879
45383
  const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name;
@@ -44973,13 +45477,17 @@ class TcbDirectiveOutputsOp extends TcbOp {
44973
45477
  class TcbUnclaimedOutputsOp extends TcbOp {
44974
45478
  tcb;
44975
45479
  scope;
44976
- element;
45480
+ target;
45481
+ outputs;
45482
+ inputs;
44977
45483
  claimedOutputs;
44978
- constructor(tcb, scope, element, claimedOutputs) {
45484
+ constructor(tcb, scope, target, outputs, inputs, claimedOutputs) {
44979
45485
  super();
44980
45486
  this.tcb = tcb;
44981
45487
  this.scope = scope;
44982
- this.element = element;
45488
+ this.target = target;
45489
+ this.outputs = outputs;
45490
+ this.inputs = inputs;
44983
45491
  this.claimedOutputs = claimedOutputs;
44984
45492
  }
44985
45493
  get optional() {
@@ -44988,14 +45496,16 @@ class TcbUnclaimedOutputsOp extends TcbOp {
44988
45496
  execute() {
44989
45497
  let elId = null;
44990
45498
  // TODO(alxhub): this could be more efficient.
44991
- for (const output of this.element.outputs) {
44992
- if (this.claimedOutputs.has(output.name)) {
45499
+ for (const output of this.outputs) {
45500
+ if (this.claimedOutputs?.has(output.name)) {
44993
45501
  // Skip this event handler as it was claimed by a directive.
44994
45502
  continue;
44995
45503
  }
44996
- if (this.tcb.env.config.checkTypeOfOutputEvents && output.name.endsWith('Change')) {
45504
+ if (this.tcb.env.config.checkTypeOfOutputEvents &&
45505
+ this.inputs !== null &&
45506
+ output.name.endsWith('Change')) {
44997
45507
  const inputName = output.name.slice(0, -6);
44998
- if (checkSplitTwoWayBinding(inputName, output, this.element.inputs, this.tcb)) {
45508
+ if (checkSplitTwoWayBinding(inputName, output, this.inputs, this.tcb)) {
44999
45509
  // Skip this event handler as the error was already handled.
45000
45510
  continue;
45001
45511
  }
@@ -45016,7 +45526,7 @@ class TcbUnclaimedOutputsOp extends TcbOp {
45016
45526
  // base `Event` type.
45017
45527
  const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 0 /* EventParamType.Infer */);
45018
45528
  if (elId === null) {
45019
- elId = this.scope.resolve(this.element);
45529
+ elId = this.scope.resolve(this.target);
45020
45530
  }
45021
45531
  const propertyAccess = ts.factory.createPropertyAccessExpression(elId, 'addEventListener');
45022
45532
  addParseSpanInfo(propertyAccess, output.keySpan);
@@ -45406,6 +45916,10 @@ class Scope {
45406
45916
  * A map of `TmplAstElement`s to the index of their `TcbElementOp` in the `opQueue`
45407
45917
  */
45408
45918
  elementOpMap = new Map();
45919
+ /**
45920
+ * A map of `TmplAstHostElement`s to the index of their `TcbHostElementOp` in the `opQueue`
45921
+ */
45922
+ hostElementOpMap = new Map();
45409
45923
  /**
45410
45924
  * A map of maps which tracks the index of `TcbDirectiveCtorOp`s in the `opQueue` for each
45411
45925
  * directive on a `TmplAstElement` or `TmplAstTemplate` node.
@@ -45508,8 +46022,13 @@ class Scope {
45508
46022
  this.registerVariable(scope, variable, new TcbBlockImplicitVariableOp(tcb, scope, type, variable));
45509
46023
  }
45510
46024
  }
45511
- for (const node of children) {
45512
- scope.appendNode(node);
46025
+ else if (scopedNode instanceof HostElement) {
46026
+ scope.appendNode(scopedNode);
46027
+ }
46028
+ if (children !== null) {
46029
+ for (const node of children) {
46030
+ scope.appendNode(node);
46031
+ }
45513
46032
  }
45514
46033
  // Once everything is registered, we need to check if there are `@let`
45515
46034
  // declarations that conflict with other local symbols defined after them.
@@ -45669,6 +46188,9 @@ class Scope {
45669
46188
  // Resolving the DOM node of an element in this template.
45670
46189
  return this.resolveOp(this.elementOpMap.get(ref));
45671
46190
  }
46191
+ else if (ref instanceof HostElement && this.hostElementOpMap.has(ref)) {
46192
+ return this.resolveOp(this.hostElementOpMap.get(ref));
46193
+ }
45672
46194
  else {
45673
46195
  return null;
45674
46196
  }
@@ -45764,6 +46286,11 @@ class Scope {
45764
46286
  this.letDeclOpMap.set(node.name, { opIndex, node });
45765
46287
  }
45766
46288
  }
46289
+ else if (node instanceof HostElement) {
46290
+ const opIndex = this.opQueue.push(new TcbHostElementOp(this.tcb, this, node)) - 1;
46291
+ this.hostElementOpMap.set(node, opIndex);
46292
+ this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node.bindings, node, null), new TcbUnclaimedOutputsOp(this.tcb, this, node, node.listeners, null, null), new TcbDomSchemaCheckerOp(this.tcb, node, false, null));
46293
+ }
45767
46294
  }
45768
46295
  appendChildren(node) {
45769
46296
  for (const child of node.children) {
@@ -45798,8 +46325,7 @@ class Scope {
45798
46325
  // If there are no directives, then all inputs are unclaimed inputs, so queue an operation
45799
46326
  // to add them if needed.
45800
46327
  if (node instanceof Element$1) {
45801
- this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node, claimedInputs));
45802
- this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, /* checkElement */ true, claimedInputs));
46328
+ this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node.inputs, node, claimedInputs), new TcbDomSchemaCheckerOp(this.tcb, node, /* checkElement */ true, claimedInputs));
45803
46329
  }
45804
46330
  return;
45805
46331
  }
@@ -45850,7 +46376,7 @@ class Scope {
45850
46376
  claimedInputs.add(propertyName);
45851
46377
  }
45852
46378
  }
45853
- this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node, claimedInputs));
46379
+ this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node.inputs, node, claimedInputs));
45854
46380
  // If there are no directives which match this element, then it's a "plain" DOM element (or a
45855
46381
  // web component), and should be checked against the DOM schema. If any directives match,
45856
46382
  // we must assume that the element could be custom (either a component, or a directive like
@@ -45867,7 +46393,7 @@ class Scope {
45867
46393
  // If there are no directives, then all outputs are unclaimed outputs, so queue an operation
45868
46394
  // to add them if needed.
45869
46395
  if (node instanceof Element$1) {
45870
- this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, claimedOutputs));
46396
+ this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, node.outputs, node.inputs, claimedOutputs));
45871
46397
  }
45872
46398
  return;
45873
46399
  }
@@ -45884,7 +46410,7 @@ class Scope {
45884
46410
  claimedOutputs.add(outputProperty);
45885
46411
  }
45886
46412
  }
45887
- this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, claimedOutputs));
46413
+ this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, node.outputs, node.inputs, claimedOutputs));
45888
46414
  }
45889
46415
  }
45890
46416
  appendDeepSchemaChecks(nodes) {
@@ -46535,18 +47061,22 @@ class TypeCheckContextImpl {
46535
47061
  *
46536
47062
  * Implements `TypeCheckContext.addTemplate`.
46537
47063
  */
46538
- addDirective(ref, binder, schemas, templateContext, isStandalone) {
47064
+ addDirective(ref, binder, schemas, templateContext, hostBindingContext, isStandalone) {
46539
47065
  if (!this.host.shouldCheckClass(ref.node)) {
46540
47066
  return;
46541
47067
  }
46542
- const fileData = this.dataForFile(ref.node.getSourceFile());
47068
+ const sourceFile = ref.node.getSourceFile();
47069
+ const fileData = this.dataForFile(sourceFile);
46543
47070
  const shimData = this.pendingShimForClass(ref.node);
46544
47071
  const id = fileData.sourceManager.getTypeCheckId(ref.node);
46545
47072
  const templateParsingDiagnostics = [];
46546
47073
  if (templateContext !== null && templateContext.parseErrors !== null) {
46547
47074
  templateParsingDiagnostics.push(...getTemplateDiagnostics(templateContext.parseErrors, id, templateContext.sourceMapping));
46548
47075
  }
46549
- const boundTarget = binder.bind({ template: templateContext?.nodes });
47076
+ const boundTarget = binder.bind({
47077
+ template: templateContext?.nodes,
47078
+ host: hostBindingContext?.node,
47079
+ });
46550
47080
  if (this.inlining === InliningMode.InlineOps) {
46551
47081
  // Get all of the directives used in the template and record inline type constructors when
46552
47082
  // required.
@@ -46576,6 +47106,7 @@ class TypeCheckContextImpl {
46576
47106
  template: templateContext?.nodes || null,
46577
47107
  boundTarget,
46578
47108
  templateParsingDiagnostics,
47109
+ hostElement: hostBindingContext?.node ?? null,
46579
47110
  });
46580
47111
  const usedPipes = [];
46581
47112
  if (templateContext !== null) {
@@ -46601,6 +47132,12 @@ class TypeCheckContextImpl {
46601
47132
  if (templateContext !== null) {
46602
47133
  fileData.sourceManager.captureTemplateSource(id, templateContext.sourceMapping, templateContext.file);
46603
47134
  }
47135
+ if (hostBindingContext !== null) {
47136
+ fileData.sourceManager.captureHostBindingsMapping(id, hostBindingContext.sourceMapping,
47137
+ // We only support host bindings in the same file as the directive
47138
+ // so we can get the source file from here.
47139
+ new ParseSourceFile(sourceFile.text, sourceFile.fileName));
47140
+ }
46604
47141
  const meta = {
46605
47142
  id,
46606
47143
  boundTarget,
@@ -46904,10 +47441,10 @@ function findClosestLineStartPosition(linesMap, position, low = 0, high = linesM
46904
47441
  }
46905
47442
 
46906
47443
  /**
46907
- * Represents the source of a template that was processed during type-checking. This information is
46908
- * used when translating parse offsets in diagnostics back to their original line/column location.
47444
+ * Represents the source of code processed during type-checking. This information is used when
47445
+ * translating parse offsets in diagnostics back to their original line/column location.
46909
47446
  */
46910
- class TemplateSource {
47447
+ class Source {
46911
47448
  mapping;
46912
47449
  file;
46913
47450
  lineStarts = null;
@@ -46944,11 +47481,16 @@ class DirectiveSourceManager {
46944
47481
  * diagnostics produced for TCB code to their source location in the template.
46945
47482
  */
46946
47483
  templateSources = new Map();
47484
+ /** Keeps track of type check IDs and the source location of their host bindings. */
47485
+ hostBindingSources = new Map();
46947
47486
  getTypeCheckId(node) {
46948
- return getTypeCheckId(node);
47487
+ return getTypeCheckId$1(node);
46949
47488
  }
46950
47489
  captureTemplateSource(id, mapping, file) {
46951
- this.templateSources.set(id, new TemplateSource(mapping, file));
47490
+ this.templateSources.set(id, new Source(mapping, file));
47491
+ }
47492
+ captureHostBindingsMapping(id, mapping, file) {
47493
+ this.hostBindingSources.set(id, new Source(mapping, file));
46952
47494
  }
46953
47495
  getTemplateSourceMapping(id) {
46954
47496
  if (!this.templateSources.has(id)) {
@@ -46956,6 +47498,12 @@ class DirectiveSourceManager {
46956
47498
  }
46957
47499
  return this.templateSources.get(id).mapping;
46958
47500
  }
47501
+ getHostBindingsMapping(id) {
47502
+ if (!this.hostBindingSources.has(id)) {
47503
+ throw new Error(`Unexpected unknown type check ID: ${id}`);
47504
+ }
47505
+ return this.hostBindingSources.get(id).mapping;
47506
+ }
46959
47507
  toTemplateParseSourceSpan(id, span) {
46960
47508
  if (!this.templateSources.has(id)) {
46961
47509
  return null;
@@ -46963,6 +47511,13 @@ class DirectiveSourceManager {
46963
47511
  const templateSource = this.templateSources.get(id);
46964
47512
  return templateSource.toParseSourceSpan(span.start, span.end);
46965
47513
  }
47514
+ toHostParseSourceSpan(id, span) {
47515
+ if (!this.hostBindingSources.has(id)) {
47516
+ return null;
47517
+ }
47518
+ const source = this.hostBindingSources.get(id);
47519
+ return source.toParseSourceSpan(span.start, span.end);
47520
+ }
46966
47521
  }
46967
47522
 
46968
47523
  /**
@@ -47727,16 +48282,17 @@ class TemplateTypeCheckerImpl {
47727
48282
  }
47728
48283
  getTemplate(component, optimizeFor) {
47729
48284
  const { data } = this.getLatestComponentState(component, optimizeFor);
47730
- if (data === null) {
47731
- return null;
47732
- }
47733
- return data.template;
48285
+ return data?.template ?? null;
48286
+ }
48287
+ getHostElement(directive, optimizeFor) {
48288
+ const { data } = this.getLatestComponentState(directive, optimizeFor);
48289
+ return data?.hostElement ?? null;
47734
48290
  }
47735
48291
  getUsedDirectives(component) {
47736
- return this.getLatestComponentState(component).data?.boundTarget.getUsedDirectives() || null;
48292
+ return this.getLatestComponentState(component).data?.boundTarget.getUsedDirectives() ?? null;
47737
48293
  }
47738
48294
  getUsedPipes(component) {
47739
- return this.getLatestComponentState(component).data?.boundTarget.getUsedPipes() || null;
48295
+ return this.getLatestComponentState(component).data?.boundTarget.getUsedPipes() ?? null;
47740
48296
  }
47741
48297
  getLatestComponentState(component, optimizeFor = exports.OptimizeFor.SingleFile) {
47742
48298
  switch (optimizeFor) {
@@ -47931,7 +48487,7 @@ class TemplateTypeCheckerImpl {
47931
48487
  this.isComplete = false;
47932
48488
  }
47933
48489
  getExpressionTarget(expression, clazz) {
47934
- return (this.getLatestComponentState(clazz).data?.boundTarget.getExpressionTarget(expression) || null);
48490
+ return (this.getLatestComponentState(clazz).data?.boundTarget.getExpressionTarget(expression) ?? null);
47935
48491
  }
47936
48492
  makeTemplateDiagnostic(clazz, sourceSpan, category, errorCode, message, relatedInformation) {
47937
48493
  const sfPath = absoluteFromSourceFile(clazz.getSourceFile());
@@ -48607,6 +49163,7 @@ exports.createDirectiveType = createDirectiveType;
48607
49163
  exports.createFactoryType = createFactoryType;
48608
49164
  exports.createForwardRefResolver = createForwardRefResolver;
48609
49165
  exports.createHostDirectivesMappingArray = createHostDirectivesMappingArray;
49166
+ exports.createHostElement = createHostElement;
48610
49167
  exports.createInjectableType = createInjectableType;
48611
49168
  exports.createInjectorType = createInjectorType;
48612
49169
  exports.createMayBeForwardRefExpression = createMayBeForwardRefExpression;
@@ -48622,6 +49179,7 @@ exports.extraReferenceFromTypeQuery = extraReferenceFromTypeQuery;
48622
49179
  exports.extractDecoratorQueryMetadata = extractDecoratorQueryMetadata;
48623
49180
  exports.extractDirectiveMetadata = extractDirectiveMetadata;
48624
49181
  exports.extractDirectiveTypeCheckMeta = extractDirectiveTypeCheckMeta;
49182
+ exports.extractHostBindingResources = extractHostBindingResources;
48625
49183
  exports.extractMessages = extractMessages;
48626
49184
  exports.extractReferencesFromType = extractReferencesFromType;
48627
49185
  exports.findAngularDecorator = findAngularDecorator;