@angular/core 20.0.0-next.8 → 20.0.0-rc.0
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/{api.d-KjtSQajV.d.ts → api.d-CRxC7NlU.d.ts} +28 -43
- package/{chrome_dev_tools_performance.d-qv7drdAl.d.ts → chrome_dev_tools_performance.d-B0FzTuRf.d.ts} +7 -1
- package/{discovery.d-vJaEafe4.d.ts → discovery.d-CBxzK1ay.d.ts} +30 -6
- package/event_dispatcher.d-DlbccpYq.d.ts +1 -1
- package/fesm2022/attribute-BWp59EjE.mjs +1 -1
- package/fesm2022/core.mjs +80 -166
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/{debug_node-DEfPCixm.mjs → debug_node-3mmnD06K.mjs} +138 -54
- package/fesm2022/debug_node-3mmnD06K.mjs.map +1 -0
- package/fesm2022/primitives/di.mjs +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +2 -2
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +7 -5
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/{resource-DhKtse7l.mjs → resource-nrAenwIA.mjs} +31 -76
- package/fesm2022/resource-nrAenwIA.mjs.map +1 -0
- package/fesm2022/{root_effect_scheduler-BZMWiScf.mjs → root_effect_scheduler-B_EWGyLU.mjs} +20 -12
- package/fesm2022/root_effect_scheduler-B_EWGyLU.mjs.map +1 -0
- package/fesm2022/rxjs-interop.mjs +16 -11
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/{signal-B6pMq7KS.mjs → signal-ePSl6jXn.mjs} +14 -4
- package/fesm2022/{signal-B6pMq7KS.mjs.map → signal-ePSl6jXn.mjs.map} +1 -1
- package/fesm2022/testing.mjs +61 -63
- package/fesm2022/testing.mjs.map +1 -1
- package/fesm2022/{untracked-Bz5WMeU1.mjs → untracked-2ouAFbCz.mjs} +4 -4
- package/fesm2022/untracked-2ouAFbCz.mjs.map +1 -0
- package/fesm2022/weak_ref-BaIq-pgY.mjs +1 -1
- package/graph.d-BcIOep_B.d.ts +1 -1
- package/index.d.ts +154 -136
- package/package.json +3 -3
- package/primitives/di/index.d.ts +1 -1
- package/primitives/event-dispatch/index.d.ts +1 -1
- package/primitives/signals/index.d.ts +3 -3
- package/rxjs-interop/index.d.ts +11 -11
- package/schematics/bundles/{apply_import_manager-CaG-_cEq.js → apply_import_manager-Coc7Hewu.js} +3 -3
- package/schematics/bundles/{change_tracker-ISzWfEHN.js → change_tracker-CDJPOAni.js} +3 -3
- package/schematics/bundles/{checker-DV96LHWz.js → checker-BAl7CJ0l.js} +386 -122
- package/schematics/bundles/cleanup-unused-imports.js +6 -6
- package/schematics/bundles/{compiler-BEZ6sUQS.js → compiler-BSv6JWRF.js} +58 -65
- package/schematics/bundles/compiler_host-CAfDJO3W.js +1 -1
- package/schematics/bundles/control-flow-migration.js +2 -2
- package/schematics/bundles/document-core.js +6 -6
- package/schematics/bundles/imports-CIX-JgAN.js +1 -1
- package/schematics/bundles/{index-6wv04ZFQ.js → index-CnKffIJ6.js} +6 -5
- package/schematics/bundles/{index-B1R5GL-k.js → index-CxuDmkeg.js} +421 -317
- package/schematics/bundles/inject-flags.js +6 -6
- package/schematics/bundles/inject-migration.js +4 -4
- package/schematics/bundles/leading_space-D9nQ8UQC.js +1 -1
- package/schematics/bundles/{migrate_ts_type_references-Dh9TZgTr.js → migrate_ts_type_references-DE1AlxIs.js} +6 -6
- package/schematics/bundles/ng_decorators-DznZ5jMl.js +1 -1
- package/schematics/bundles/nodes-B16H9JUd.js +1 -1
- package/schematics/bundles/output-migration.js +7 -7
- package/schematics/bundles/{project_paths-B-Nevd-p.js → project_paths-Bl-H7Vlb.js} +4 -4
- package/schematics/bundles/project_tsconfig_paths-CDVxT6Ov.js +1 -1
- package/schematics/bundles/property_name-BBwFuqMe.js +1 -1
- package/schematics/bundles/route-lazy-loading.js +4 -4
- package/schematics/bundles/self-closing-tags-migration.js +5 -5
- package/schematics/bundles/signal-input-migration.js +8 -8
- package/schematics/bundles/signal-queries-migration.js +8 -8
- package/schematics/bundles/signals.js +8 -8
- package/schematics/bundles/standalone-migration.js +5 -5
- package/schematics/bundles/symbol-VPWguRxr.js +1 -1
- package/schematics/bundles/test-bed-get.js +5 -5
- package/{signal.d-E0e5nW1p.d.ts → signal.d-D6VJ67xi.d.ts} +8 -2
- package/testing/index.d.ts +12 -24
- package/weak_ref.d-eGOEP9S1.d.ts +1 -1
- package/fesm2022/debug_node-DEfPCixm.mjs.map +0 -1
- package/fesm2022/resource-DhKtse7l.mjs.map +0 -1
- package/fesm2022/root_effect_scheduler-BZMWiScf.mjs.map +0 -1
- package/fesm2022/untracked-Bz5WMeU1.mjs.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v20.0.0-
|
|
3
|
+
* @license Angular v20.0.0-rc.0
|
|
4
4
|
* (c) 2010-2025 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
var compiler = require('./compiler-
|
|
9
|
+
var compiler = require('./compiler-BSv6JWRF.js');
|
|
10
10
|
var ts = require('typescript');
|
|
11
11
|
require('os');
|
|
12
12
|
var fs$1 = require('fs');
|
|
@@ -162,6 +162,15 @@ exports.ErrorCode = void 0;
|
|
|
162
162
|
* Raised when a `standalone: false` component is declared but `strictStandalone` is set.
|
|
163
163
|
*/
|
|
164
164
|
ErrorCode[ErrorCode["NON_STANDALONE_NOT_ALLOWED"] = 2023] = "NON_STANDALONE_NOT_ALLOWED";
|
|
165
|
+
/**
|
|
166
|
+
* Raised when a named template dependency isn't defined in the component's source file.
|
|
167
|
+
*/
|
|
168
|
+
ErrorCode[ErrorCode["MISSING_NAMED_TEMPLATE_DEPENDENCY"] = 2024] = "MISSING_NAMED_TEMPLATE_DEPENDENCY";
|
|
169
|
+
/**
|
|
170
|
+
* Raised if an incorrect type is used for a named template dependency (e.g. directive
|
|
171
|
+
* class used as a component).
|
|
172
|
+
*/
|
|
173
|
+
ErrorCode[ErrorCode["INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE"] = 2025] = "INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE";
|
|
165
174
|
ErrorCode[ErrorCode["SYMBOL_NOT_EXPORTED"] = 3001] = "SYMBOL_NOT_EXPORTED";
|
|
166
175
|
/**
|
|
167
176
|
* Raised when a relationship between directives and/or pipes would cause a cyclic import to be
|
|
@@ -341,6 +350,11 @@ exports.ErrorCode = void 0;
|
|
|
341
350
|
ErrorCode[ErrorCode["LET_USED_BEFORE_DEFINITION"] = 8016] = "LET_USED_BEFORE_DEFINITION";
|
|
342
351
|
/** A `@let` declaration conflicts with another symbol in the same scope. */
|
|
343
352
|
ErrorCode[ErrorCode["CONFLICTING_LET_DECLARATION"] = 8017] = "CONFLICTING_LET_DECLARATION";
|
|
353
|
+
/**
|
|
354
|
+
* A binding inside selectorless directive syntax did
|
|
355
|
+
* not match any inputs/outputs of the directive.
|
|
356
|
+
*/
|
|
357
|
+
ErrorCode[ErrorCode["UNCLAIMED_DIRECTIVE_BINDING"] = 8018] = "UNCLAIMED_DIRECTIVE_BINDING";
|
|
344
358
|
/**
|
|
345
359
|
* A two way binding in a template has an incorrect syntax,
|
|
346
360
|
* parentheses outside brackets. For example:
|
|
@@ -495,6 +509,10 @@ exports.ErrorCode = void 0;
|
|
|
495
509
|
* ```
|
|
496
510
|
*/
|
|
497
511
|
ErrorCode[ErrorCode["UNINVOKED_TRACK_FUNCTION"] = 8115] = "UNINVOKED_TRACK_FUNCTION";
|
|
512
|
+
/**
|
|
513
|
+
* A structural directive is used in a template, but the directive is not imported.
|
|
514
|
+
*/
|
|
515
|
+
ErrorCode[ErrorCode["MISSING_STRUCTURAL_DIRECTIVE"] = 8116] = "MISSING_STRUCTURAL_DIRECTIVE";
|
|
498
516
|
/**
|
|
499
517
|
* The template type-checking engine would need to generate an inline type check block for a
|
|
500
518
|
* component, but the current type-checking environment doesn't support it.
|
|
@@ -644,6 +662,7 @@ exports.ExtendedTemplateDiagnosticName = void 0;
|
|
|
644
662
|
ExtendedTemplateDiagnosticName["NULLISH_COALESCING_NOT_NULLABLE"] = "nullishCoalescingNotNullable";
|
|
645
663
|
ExtendedTemplateDiagnosticName["OPTIONAL_CHAIN_NOT_NULLABLE"] = "optionalChainNotNullable";
|
|
646
664
|
ExtendedTemplateDiagnosticName["MISSING_CONTROL_FLOW_DIRECTIVE"] = "missingControlFlowDirective";
|
|
665
|
+
ExtendedTemplateDiagnosticName["MISSING_STRUCTURAL_DIRECTIVE"] = "missingStructuralDirective";
|
|
647
666
|
ExtendedTemplateDiagnosticName["TEXT_ATTRIBUTE_NOT_BINDING"] = "textAttributeNotBinding";
|
|
648
667
|
ExtendedTemplateDiagnosticName["UNINVOKED_FUNCTION_IN_EVENT_BINDING"] = "uninvokedFunctionInEventBinding";
|
|
649
668
|
ExtendedTemplateDiagnosticName["MISSING_NGFOROF_LET"] = "missingNgForOfLet";
|
|
@@ -981,7 +1000,7 @@ class NodeJSPathManipulation {
|
|
|
981
1000
|
// G3-ESM-MARKER: G3 uses CommonJS, but externally everything in ESM.
|
|
982
1001
|
// CommonJS/ESM interop for determining the current file name and containing dir.
|
|
983
1002
|
const isCommonJS = typeof __filename !== 'undefined';
|
|
984
|
-
const currentFileUrl = isCommonJS ? null : (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('checker-
|
|
1003
|
+
const currentFileUrl = isCommonJS ? null : (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('checker-BAl7CJ0l.js', document.baseURI).href));
|
|
985
1004
|
const currentFileName = isCommonJS ? __filename : url.fileURLToPath(currentFileUrl);
|
|
986
1005
|
/**
|
|
987
1006
|
* A wrapper around the Node.js file-system that supports readonly operations and path manipulation.
|
|
@@ -10789,11 +10808,11 @@ class RegistryDomSchemaChecker {
|
|
|
10789
10808
|
constructor(resolver) {
|
|
10790
10809
|
this.resolver = resolver;
|
|
10791
10810
|
}
|
|
10792
|
-
checkElement(id,
|
|
10811
|
+
checkElement(id, tagName, sourceSpanForDiagnostics, schemas, hostIsStandalone) {
|
|
10793
10812
|
// HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
|
|
10794
10813
|
// We need to strip it before handing it over to the registry because all HTML tag names
|
|
10795
10814
|
// in the registry are without a namespace.
|
|
10796
|
-
const name =
|
|
10815
|
+
const name = tagName.replace(REMOVE_XHTML_REGEX, '');
|
|
10797
10816
|
if (!REGISTRY$1.hasElement(name, schemas)) {
|
|
10798
10817
|
const mapping = this.resolver.getTemplateSourceMapping(id);
|
|
10799
10818
|
const schemas = `'${hostIsStandalone ? '@Component' : '@NgModule'}.schemas'`;
|
|
@@ -10807,27 +10826,27 @@ class RegistryDomSchemaChecker {
|
|
|
10807
10826
|
else {
|
|
10808
10827
|
errorMsg += `2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
|
|
10809
10828
|
}
|
|
10810
|
-
const diag = makeTemplateDiagnostic(id, mapping,
|
|
10829
|
+
const diag = makeTemplateDiagnostic(id, mapping, sourceSpanForDiagnostics, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.SCHEMA_INVALID_ELEMENT), errorMsg);
|
|
10811
10830
|
this._diagnostics.push(diag);
|
|
10812
10831
|
}
|
|
10813
10832
|
}
|
|
10814
|
-
checkTemplateElementProperty(id,
|
|
10815
|
-
if (!REGISTRY$1.hasProperty(
|
|
10833
|
+
checkTemplateElementProperty(id, tagName, name, span, schemas, hostIsStandalone) {
|
|
10834
|
+
if (!REGISTRY$1.hasProperty(tagName, name, schemas)) {
|
|
10816
10835
|
const mapping = this.resolver.getTemplateSourceMapping(id);
|
|
10817
10836
|
const decorator = hostIsStandalone ? '@Component' : '@NgModule';
|
|
10818
10837
|
const schemas = `'${decorator}.schemas'`;
|
|
10819
|
-
let errorMsg = `Can't bind to '${name}' since it isn't a known property of '${
|
|
10820
|
-
if (
|
|
10838
|
+
let errorMsg = `Can't bind to '${name}' since it isn't a known property of '${tagName}'.`;
|
|
10839
|
+
if (tagName.startsWith('ng-')) {
|
|
10821
10840
|
errorMsg +=
|
|
10822
10841
|
`\n1. If '${name}' is an Angular directive, then add 'CommonModule' to the '${decorator}.imports' of this component.` +
|
|
10823
10842
|
`\n2. To allow any property add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
|
|
10824
10843
|
}
|
|
10825
|
-
else if (
|
|
10844
|
+
else if (tagName.indexOf('-') > -1) {
|
|
10826
10845
|
errorMsg +=
|
|
10827
|
-
`\n1. If '${
|
|
10846
|
+
`\n1. If '${tagName}' is an Angular component and it has '${name}' input, then verify that it is ${hostIsStandalone
|
|
10828
10847
|
? "included in the '@Component.imports' of this component"
|
|
10829
10848
|
: 'part of this module'}.` +
|
|
10830
|
-
`\n2. If '${
|
|
10849
|
+
`\n2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.` +
|
|
10831
10850
|
`\n3. To allow any property add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
|
|
10832
10851
|
}
|
|
10833
10852
|
const diag = makeTemplateDiagnostic(id, mapping, span, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.SCHEMA_INVALID_ATTRIBUTE), errorMsg);
|
|
@@ -11615,12 +11634,16 @@ function isInHostBindingTcb(node) {
|
|
|
11615
11634
|
return false;
|
|
11616
11635
|
}
|
|
11617
11636
|
function findTypeCheckBlock(file, id, isDiagnosticRequest) {
|
|
11637
|
+
// This prioritised-level statements using a breadth-first search
|
|
11638
|
+
// This is usually sufficient to find the TCB we're looking for
|
|
11618
11639
|
for (const stmt of file.statements) {
|
|
11619
11640
|
if (ts.isFunctionDeclaration(stmt) && getTypeCheckId(stmt, file, isDiagnosticRequest) === id) {
|
|
11620
11641
|
return stmt;
|
|
11621
11642
|
}
|
|
11622
11643
|
}
|
|
11623
|
-
|
|
11644
|
+
// In case the TCB we're looking for is nested (which is not common)
|
|
11645
|
+
// eg: when a directive is declared inside a function, as it can happen in test files
|
|
11646
|
+
return findNodeInFile(file, (node) => ts.isFunctionDeclaration(node) && getTypeCheckId(node, file, isDiagnosticRequest) === id);
|
|
11624
11647
|
}
|
|
11625
11648
|
/**
|
|
11626
11649
|
* Traverses up the AST starting from the given node to extract the source location from comments
|
|
@@ -11690,6 +11713,15 @@ function checkIfGenericTypeBoundsCanBeEmitted(node, reflector, env) {
|
|
|
11690
11713
|
const emitter = new TypeParameterEmitter(node.typeParameters, reflector);
|
|
11691
11714
|
return emitter.canEmit((ref) => env.canReferenceType(ref));
|
|
11692
11715
|
}
|
|
11716
|
+
function findNodeInFile(file, predicate) {
|
|
11717
|
+
const visit = (node) => {
|
|
11718
|
+
if (predicate(node)) {
|
|
11719
|
+
return node;
|
|
11720
|
+
}
|
|
11721
|
+
return ts.forEachChild(node, visit) ?? null;
|
|
11722
|
+
};
|
|
11723
|
+
return ts.forEachChild(file, visit) ?? null;
|
|
11724
|
+
}
|
|
11693
11725
|
|
|
11694
11726
|
function generateTypeCtorDeclarationFn(env, meta, nodeTypeRef, typeParams) {
|
|
11695
11727
|
const rawTypeArgs = typeParams !== undefined ? generateGenericArgs(typeParams) : undefined;
|
|
@@ -12246,6 +12278,20 @@ class OutOfBandDiagnosticRecorderImpl {
|
|
|
12246
12278
|
const errorMsg = `Cannot declare @let called '${decl.name}' as there is another symbol in the template with the same name.`;
|
|
12247
12279
|
this._diagnostics.push(makeTemplateDiagnostic(id, mapping, decl.sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.CONFLICTING_LET_DECLARATION), errorMsg));
|
|
12248
12280
|
}
|
|
12281
|
+
missingNamedTemplateDependency(id, node) {
|
|
12282
|
+
this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), node.startSourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.MISSING_NAMED_TEMPLATE_DEPENDENCY),
|
|
12283
|
+
// Wording is meant to mimic the wording TS uses in their diagnostic for missing symbols.
|
|
12284
|
+
`Cannot find name "${node instanceof compiler.Directive ? node.name : node.componentName}".`));
|
|
12285
|
+
}
|
|
12286
|
+
incorrectTemplateDependencyType(id, node) {
|
|
12287
|
+
this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), node.startSourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE), `Incorrect reference type. Type must be an ${node instanceof compiler.Component ? '@Component' : '@Directive'}.`));
|
|
12288
|
+
}
|
|
12289
|
+
unclaimedDirectiveBinding(id, directive, node) {
|
|
12290
|
+
const errorMsg = `Directive ${directive.name} does not have an ` +
|
|
12291
|
+
`${node instanceof compiler.BoundEvent ? 'output' : 'input'} named "${node.name}". ` +
|
|
12292
|
+
`Bindings to directives must target existing inputs or outputs.`;
|
|
12293
|
+
this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), node.keySpan || node.sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.UNCLAIMED_DIRECTIVE_BINDING), errorMsg));
|
|
12294
|
+
}
|
|
12249
12295
|
}
|
|
12250
12296
|
function makeInlineDiagnostic(id, code, node, messageText, relatedInformation) {
|
|
12251
12297
|
return {
|
|
@@ -13156,62 +13202,13 @@ class TcbTemplateBodyOp extends TcbOp {
|
|
|
13156
13202
|
// on the template can trigger extra guard expressions that serve to narrow types within the
|
|
13157
13203
|
// `if`. `guard` is calculated by starting with `true` and adding other conditions as needed.
|
|
13158
13204
|
// Collect these into `guards` by processing the directives.
|
|
13159
|
-
const directiveGuards = [];
|
|
13160
|
-
const directives = this.tcb.boundTarget.getDirectivesOfNode(this.template);
|
|
13161
|
-
if (directives !== null) {
|
|
13162
|
-
for (const dir of directives) {
|
|
13163
|
-
const dirInstId = this.scope.resolve(this.template, dir);
|
|
13164
|
-
const dirId = this.tcb.env.reference(dir.ref);
|
|
13165
|
-
// There are two kinds of guards. Template guards (ngTemplateGuards) allow type narrowing of
|
|
13166
|
-
// the expression passed to an @Input of the directive. Scan the directive to see if it has
|
|
13167
|
-
// any template guards, and generate them if needed.
|
|
13168
|
-
dir.ngTemplateGuards.forEach((guard) => {
|
|
13169
|
-
// For each template guard function on the directive, look for a binding to that input.
|
|
13170
|
-
const boundInput = this.template.inputs.find((i) => i.name === guard.inputName) ||
|
|
13171
|
-
this.template.templateAttrs.find((i) => i instanceof compiler.BoundAttribute && i.name === guard.inputName);
|
|
13172
|
-
if (boundInput !== undefined) {
|
|
13173
|
-
// If there is such a binding, generate an expression for it.
|
|
13174
|
-
const expr = tcbExpression(boundInput.value, this.tcb, this.scope);
|
|
13175
|
-
// The expression has already been checked in the type constructor invocation, so
|
|
13176
|
-
// it should be ignored when used within a template guard.
|
|
13177
|
-
markIgnoreDiagnostics(expr);
|
|
13178
|
-
if (guard.type === 'binding') {
|
|
13179
|
-
// Use the binding expression itself as guard.
|
|
13180
|
-
directiveGuards.push(expr);
|
|
13181
|
-
}
|
|
13182
|
-
else {
|
|
13183
|
-
// Call the guard function on the directive with the directive instance and that
|
|
13184
|
-
// expression.
|
|
13185
|
-
const guardInvoke = tsCallMethod(dirId, `ngTemplateGuard_${guard.inputName}`, [
|
|
13186
|
-
dirInstId,
|
|
13187
|
-
expr,
|
|
13188
|
-
]);
|
|
13189
|
-
addParseSpanInfo(guardInvoke, boundInput.value.sourceSpan);
|
|
13190
|
-
directiveGuards.push(guardInvoke);
|
|
13191
|
-
}
|
|
13192
|
-
}
|
|
13193
|
-
});
|
|
13194
|
-
// The second kind of guard is a template context guard. This guard narrows the template
|
|
13195
|
-
// rendering context variable `ctx`.
|
|
13196
|
-
if (dir.hasNgTemplateContextGuard) {
|
|
13197
|
-
if (this.tcb.env.config.applyTemplateContextGuards) {
|
|
13198
|
-
const ctx = this.scope.resolve(this.template);
|
|
13199
|
-
const guardInvoke = tsCallMethod(dirId, 'ngTemplateContextGuard', [dirInstId, ctx]);
|
|
13200
|
-
addParseSpanInfo(guardInvoke, this.template.sourceSpan);
|
|
13201
|
-
directiveGuards.push(guardInvoke);
|
|
13202
|
-
}
|
|
13203
|
-
else if (this.template.variables.length > 0 &&
|
|
13204
|
-
this.tcb.env.config.suggestionsForSuboptimalTypeInference) {
|
|
13205
|
-
// The compiler could have inferred a better type for the variables in this template,
|
|
13206
|
-
// but was prevented from doing so by the type-checking configuration. Issue a warning
|
|
13207
|
-
// diagnostic.
|
|
13208
|
-
this.tcb.oobRecorder.suboptimalTypeInference(this.tcb.id, this.template.variables);
|
|
13209
|
-
}
|
|
13210
|
-
}
|
|
13211
|
-
}
|
|
13212
|
-
}
|
|
13213
13205
|
// By default the guard is simply `true`.
|
|
13214
13206
|
let guard = null;
|
|
13207
|
+
const directiveGuards = [];
|
|
13208
|
+
this.addDirectiveGuards(directiveGuards, this.template, this.tcb.boundTarget.getDirectivesOfNode(this.template));
|
|
13209
|
+
for (const directive of this.template.directives) {
|
|
13210
|
+
this.addDirectiveGuards(directiveGuards, directive, this.tcb.boundTarget.getDirectivesOfNode(directive));
|
|
13211
|
+
}
|
|
13215
13212
|
// If there are any guards from directives, use them instead.
|
|
13216
13213
|
if (directiveGuards.length > 0) {
|
|
13217
13214
|
// Pop the first value and use it as the initializer to reduce(). This way, a single guard
|
|
@@ -13243,6 +13240,67 @@ class TcbTemplateBodyOp extends TcbOp {
|
|
|
13243
13240
|
this.scope.addStatement(tmplBlock);
|
|
13244
13241
|
return null;
|
|
13245
13242
|
}
|
|
13243
|
+
addDirectiveGuards(guards, hostNode, directives) {
|
|
13244
|
+
if (directives === null || directives.length === 0) {
|
|
13245
|
+
return;
|
|
13246
|
+
}
|
|
13247
|
+
const isTemplate = hostNode instanceof compiler.Template;
|
|
13248
|
+
for (const dir of directives) {
|
|
13249
|
+
const dirInstId = this.scope.resolve(hostNode, dir);
|
|
13250
|
+
const dirId = this.tcb.env.reference(dir.ref);
|
|
13251
|
+
// There are two kinds of guards. Template guards (ngTemplateGuards) allow type narrowing of
|
|
13252
|
+
// the expression passed to an @Input of the directive. Scan the directive to see if it has
|
|
13253
|
+
// any template guards, and generate them if needed.
|
|
13254
|
+
dir.ngTemplateGuards.forEach((guard) => {
|
|
13255
|
+
// For each template guard function on the directive, look for a binding to that input.
|
|
13256
|
+
const boundInput = hostNode.inputs.find((i) => i.name === guard.inputName) ||
|
|
13257
|
+
(isTemplate
|
|
13258
|
+
? hostNode.templateAttrs.find((input) => {
|
|
13259
|
+
return input instanceof compiler.BoundAttribute && input.name === guard.inputName;
|
|
13260
|
+
})
|
|
13261
|
+
: undefined);
|
|
13262
|
+
if (boundInput !== undefined) {
|
|
13263
|
+
// If there is such a binding, generate an expression for it.
|
|
13264
|
+
const expr = tcbExpression(boundInput.value, this.tcb, this.scope);
|
|
13265
|
+
// The expression has already been checked in the type constructor invocation, so
|
|
13266
|
+
// it should be ignored when used within a template guard.
|
|
13267
|
+
markIgnoreDiagnostics(expr);
|
|
13268
|
+
if (guard.type === 'binding') {
|
|
13269
|
+
// Use the binding expression itself as guard.
|
|
13270
|
+
guards.push(expr);
|
|
13271
|
+
}
|
|
13272
|
+
else {
|
|
13273
|
+
// Call the guard function on the directive with the directive instance and that
|
|
13274
|
+
// expression.
|
|
13275
|
+
const guardInvoke = tsCallMethod(dirId, `ngTemplateGuard_${guard.inputName}`, [
|
|
13276
|
+
dirInstId,
|
|
13277
|
+
expr,
|
|
13278
|
+
]);
|
|
13279
|
+
addParseSpanInfo(guardInvoke, boundInput.value.sourceSpan);
|
|
13280
|
+
guards.push(guardInvoke);
|
|
13281
|
+
}
|
|
13282
|
+
}
|
|
13283
|
+
});
|
|
13284
|
+
// The second kind of guard is a template context guard. This guard narrows the template
|
|
13285
|
+
// rendering context variable `ctx`.
|
|
13286
|
+
if (dir.hasNgTemplateContextGuard) {
|
|
13287
|
+
if (this.tcb.env.config.applyTemplateContextGuards) {
|
|
13288
|
+
const ctx = this.scope.resolve(hostNode);
|
|
13289
|
+
const guardInvoke = tsCallMethod(dirId, 'ngTemplateContextGuard', [dirInstId, ctx]);
|
|
13290
|
+
addParseSpanInfo(guardInvoke, hostNode.sourceSpan);
|
|
13291
|
+
guards.push(guardInvoke);
|
|
13292
|
+
}
|
|
13293
|
+
else if (isTemplate &&
|
|
13294
|
+
hostNode.variables.length > 0 &&
|
|
13295
|
+
this.tcb.env.config.suggestionsForSuboptimalTypeInference) {
|
|
13296
|
+
// The compiler could have inferred a better type for the variables in this template,
|
|
13297
|
+
// but was prevented from doing so by the type-checking configuration. Issue a warning
|
|
13298
|
+
// diagnostic.
|
|
13299
|
+
this.tcb.oobRecorder.suboptimalTypeInference(this.tcb.id, hostNode.variables);
|
|
13300
|
+
}
|
|
13301
|
+
}
|
|
13302
|
+
}
|
|
13303
|
+
}
|
|
13246
13304
|
}
|
|
13247
13305
|
/**
|
|
13248
13306
|
* A `TcbOp` which renders an Angular expression (e.g. `{{foo() && bar.baz}}`).
|
|
@@ -13507,7 +13565,7 @@ class TcbDirectiveCtorOp extends TcbOp {
|
|
|
13507
13565
|
return id;
|
|
13508
13566
|
}
|
|
13509
13567
|
circularFallback() {
|
|
13510
|
-
return new TcbDirectiveCtorCircularFallbackOp(this.tcb, this.scope, this.
|
|
13568
|
+
return new TcbDirectiveCtorCircularFallbackOp(this.tcb, this.scope, this.dir);
|
|
13511
13569
|
}
|
|
13512
13570
|
}
|
|
13513
13571
|
/**
|
|
@@ -13669,13 +13727,11 @@ class TcbDirectiveInputsOp extends TcbOp {
|
|
|
13669
13727
|
class TcbDirectiveCtorCircularFallbackOp extends TcbOp {
|
|
13670
13728
|
tcb;
|
|
13671
13729
|
scope;
|
|
13672
|
-
node;
|
|
13673
13730
|
dir;
|
|
13674
|
-
constructor(tcb, scope,
|
|
13731
|
+
constructor(tcb, scope, dir) {
|
|
13675
13732
|
super();
|
|
13676
13733
|
this.tcb = tcb;
|
|
13677
13734
|
this.scope = scope;
|
|
13678
|
-
this.node = node;
|
|
13679
13735
|
this.dir = dir;
|
|
13680
13736
|
}
|
|
13681
13737
|
get optional() {
|
|
@@ -13717,10 +13773,10 @@ class TcbDomSchemaCheckerOp extends TcbOp {
|
|
|
13717
13773
|
}
|
|
13718
13774
|
execute() {
|
|
13719
13775
|
const element = this.element;
|
|
13720
|
-
const isTemplateElement = element instanceof compiler.Element;
|
|
13776
|
+
const isTemplateElement = element instanceof compiler.Element || element instanceof compiler.Component;
|
|
13721
13777
|
const bindings = isTemplateElement ? element.inputs : element.bindings;
|
|
13722
13778
|
if (this.checkElement && isTemplateElement) {
|
|
13723
|
-
this.tcb.domSchemaChecker.checkElement(this.tcb.id, element, this.tcb.schemas, this.tcb.hostIsStandalone);
|
|
13779
|
+
this.tcb.domSchemaChecker.checkElement(this.tcb.id, this.getTagName(element), element.startSourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone);
|
|
13724
13780
|
}
|
|
13725
13781
|
// TODO(alxhub): this could be more efficient.
|
|
13726
13782
|
for (const binding of bindings) {
|
|
@@ -13733,7 +13789,7 @@ class TcbDomSchemaCheckerOp extends TcbOp {
|
|
|
13733
13789
|
// A direct binding to a property.
|
|
13734
13790
|
const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name;
|
|
13735
13791
|
if (isTemplateElement) {
|
|
13736
|
-
this.tcb.domSchemaChecker.checkTemplateElementProperty(this.tcb.id, element, propertyName, binding.sourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone);
|
|
13792
|
+
this.tcb.domSchemaChecker.checkTemplateElementProperty(this.tcb.id, this.getTagName(element), propertyName, binding.sourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone);
|
|
13737
13793
|
}
|
|
13738
13794
|
else {
|
|
13739
13795
|
this.tcb.domSchemaChecker.checkHostElementProperty(this.tcb.id, element, propertyName, binding.keySpan, this.tcb.schemas);
|
|
@@ -13742,6 +13798,9 @@ class TcbDomSchemaCheckerOp extends TcbOp {
|
|
|
13742
13798
|
}
|
|
13743
13799
|
return null;
|
|
13744
13800
|
}
|
|
13801
|
+
getTagName(node) {
|
|
13802
|
+
return node instanceof compiler.Element ? node.name : getComponentTagName(node);
|
|
13803
|
+
}
|
|
13745
13804
|
}
|
|
13746
13805
|
/**
|
|
13747
13806
|
* A `TcbOp` that finds and flags control flow nodes that interfere with content projection.
|
|
@@ -13876,6 +13935,30 @@ class TcbHostElementOp extends TcbOp {
|
|
|
13876
13935
|
return id;
|
|
13877
13936
|
}
|
|
13878
13937
|
}
|
|
13938
|
+
/**
|
|
13939
|
+
* A `TcbOp` which creates an expression for a native DOM element from a `TmplAstComponent`.
|
|
13940
|
+
*
|
|
13941
|
+
* Executing this operation returns a reference to the element variable.
|
|
13942
|
+
*/
|
|
13943
|
+
class TcbComponentNodeOp extends TcbOp {
|
|
13944
|
+
tcb;
|
|
13945
|
+
scope;
|
|
13946
|
+
component;
|
|
13947
|
+
optional = true;
|
|
13948
|
+
constructor(tcb, scope, component) {
|
|
13949
|
+
super();
|
|
13950
|
+
this.tcb = tcb;
|
|
13951
|
+
this.scope = scope;
|
|
13952
|
+
this.component = component;
|
|
13953
|
+
}
|
|
13954
|
+
execute() {
|
|
13955
|
+
const id = this.tcb.allocateId();
|
|
13956
|
+
const initializer = tsCreateElement(getComponentTagName(this.component));
|
|
13957
|
+
addParseSpanInfo(initializer, this.component.startSourceSpan || this.component.sourceSpan);
|
|
13958
|
+
this.scope.addStatement(tsCreateVariable(id, initializer));
|
|
13959
|
+
return id;
|
|
13960
|
+
}
|
|
13961
|
+
}
|
|
13879
13962
|
/**
|
|
13880
13963
|
* Mapping between attributes names that don't correspond to their element property names.
|
|
13881
13964
|
* Note: this mapping has to be kept in sync with the equally named mapping in the runtime.
|
|
@@ -14481,6 +14564,10 @@ class Scope {
|
|
|
14481
14564
|
* A map of `TmplAstHostElement`s to the index of their `TcbHostElementOp` in the `opQueue`
|
|
14482
14565
|
*/
|
|
14483
14566
|
hostElementOpMap = new Map();
|
|
14567
|
+
/**
|
|
14568
|
+
* A map of `TmplAstComponent`s to the index of their `TcbComponentNodeOp` in the `opQueue`
|
|
14569
|
+
*/
|
|
14570
|
+
componentNodeOpMap = new Map();
|
|
14484
14571
|
/**
|
|
14485
14572
|
* A map of maps which tracks the index of `TcbDirectiveCtorOp`s in the `opQueue` for each
|
|
14486
14573
|
* directive on a `TmplAstElement` or `TmplAstTemplate` node.
|
|
@@ -14733,22 +14820,23 @@ class Scope {
|
|
|
14733
14820
|
// Execute the `TcbTemplateContextOp` for the template.
|
|
14734
14821
|
return this.resolveOp(this.templateCtxOpMap.get(ref));
|
|
14735
14822
|
}
|
|
14736
|
-
else if ((ref instanceof compiler.Element ||
|
|
14823
|
+
else if ((ref instanceof compiler.Element ||
|
|
14824
|
+
ref instanceof compiler.Template ||
|
|
14825
|
+
ref instanceof compiler.Component ||
|
|
14826
|
+
ref instanceof compiler.Directive) &&
|
|
14737
14827
|
directive !== undefined &&
|
|
14738
14828
|
this.directiveOpMap.has(ref)) {
|
|
14739
14829
|
// Resolving a directive on an element or sub-template.
|
|
14740
14830
|
const dirMap = this.directiveOpMap.get(ref);
|
|
14741
|
-
|
|
14742
|
-
return this.resolveOp(dirMap.get(directive));
|
|
14743
|
-
}
|
|
14744
|
-
else {
|
|
14745
|
-
return null;
|
|
14746
|
-
}
|
|
14831
|
+
return dirMap.has(directive) ? this.resolveOp(dirMap.get(directive)) : null;
|
|
14747
14832
|
}
|
|
14748
14833
|
else if (ref instanceof compiler.Element && this.elementOpMap.has(ref)) {
|
|
14749
14834
|
// Resolving the DOM node of an element in this template.
|
|
14750
14835
|
return this.resolveOp(this.elementOpMap.get(ref));
|
|
14751
14836
|
}
|
|
14837
|
+
else if (ref instanceof compiler.Component && this.componentNodeOpMap.has(ref)) {
|
|
14838
|
+
return this.resolveOp(this.componentNodeOpMap.get(ref));
|
|
14839
|
+
}
|
|
14752
14840
|
else if (ref instanceof compiler.HostElement && this.hostElementOpMap.has(ref)) {
|
|
14753
14841
|
return this.resolveOp(this.hostElementOpMap.get(ref));
|
|
14754
14842
|
}
|
|
@@ -14797,15 +14885,17 @@ class Scope {
|
|
|
14797
14885
|
if (this.tcb.env.config.controlFlowPreventingContentProjection !== 'suppress') {
|
|
14798
14886
|
this.appendContentProjectionCheckOp(node);
|
|
14799
14887
|
}
|
|
14800
|
-
this.
|
|
14801
|
-
this.
|
|
14888
|
+
this.appendDirectivesAndInputsOfElementLikeNode(node);
|
|
14889
|
+
this.appendOutputsOfElementLikeNode(node);
|
|
14890
|
+
this.appendSelectorlessDirectives(node);
|
|
14802
14891
|
this.appendChildren(node);
|
|
14803
14892
|
this.checkAndAppendReferencesOfNode(node);
|
|
14804
14893
|
}
|
|
14805
14894
|
else if (node instanceof compiler.Template) {
|
|
14806
14895
|
// Template children are rendered in a child scope.
|
|
14807
|
-
this.
|
|
14808
|
-
this.
|
|
14896
|
+
this.appendDirectivesAndInputsOfElementLikeNode(node);
|
|
14897
|
+
this.appendOutputsOfElementLikeNode(node);
|
|
14898
|
+
this.appendSelectorlessDirectives(node);
|
|
14809
14899
|
const ctxIndex = this.opQueue.push(new TcbTemplateContextOp(this.tcb, this)) - 1;
|
|
14810
14900
|
this.templateCtxOpMap.set(node, ctxIndex);
|
|
14811
14901
|
if (this.tcb.env.config.checkTemplateBodies) {
|
|
@@ -14816,6 +14906,9 @@ class Scope {
|
|
|
14816
14906
|
}
|
|
14817
14907
|
this.checkAndAppendReferencesOfNode(node);
|
|
14818
14908
|
}
|
|
14909
|
+
else if (node instanceof compiler.Component) {
|
|
14910
|
+
this.appendComponentNode(node);
|
|
14911
|
+
}
|
|
14819
14912
|
else if (node instanceof compiler.DeferredBlock) {
|
|
14820
14913
|
this.appendDeferredBlock(node);
|
|
14821
14914
|
}
|
|
@@ -14878,9 +14971,11 @@ class Scope {
|
|
|
14878
14971
|
this.referenceOpMap.set(ref, ctxIndex);
|
|
14879
14972
|
}
|
|
14880
14973
|
}
|
|
14881
|
-
|
|
14974
|
+
appendDirectivesAndInputsOfElementLikeNode(node) {
|
|
14882
14975
|
// Collect all the inputs on the element.
|
|
14883
14976
|
const claimedInputs = new Set();
|
|
14977
|
+
// Don't resolve directives when selectorless is enabled and treat all the inputs on the element
|
|
14978
|
+
// as unclaimed. In selectorless the inputs are defined either in component or directive nodes.
|
|
14884
14979
|
const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
|
14885
14980
|
if (directives === null || directives.length === 0) {
|
|
14886
14981
|
// If there are no directives, then all inputs are unclaimed inputs, so queue an operation
|
|
@@ -14890,42 +14985,18 @@ class Scope {
|
|
|
14890
14985
|
}
|
|
14891
14986
|
return;
|
|
14892
14987
|
}
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
this.tcb.oobRecorder.deferredComponentUsedEagerly(this.tcb.id, node);
|
|
14901
|
-
}
|
|
14988
|
+
if (node instanceof compiler.Element) {
|
|
14989
|
+
const isDeferred = this.tcb.boundTarget.isDeferred(node);
|
|
14990
|
+
if (!isDeferred && directives.some((dirMeta) => dirMeta.isExplicitlyDeferred)) {
|
|
14991
|
+
// This node has directives/components that were defer-loaded (included into
|
|
14992
|
+
// `@Component.deferredImports`), but the node itself was used outside of a
|
|
14993
|
+
// `@defer` block, which is the error.
|
|
14994
|
+
this.tcb.oobRecorder.deferredComponentUsedEagerly(this.tcb.id, node);
|
|
14902
14995
|
}
|
|
14903
14996
|
}
|
|
14904
14997
|
const dirMap = new Map();
|
|
14905
14998
|
for (const dir of directives) {
|
|
14906
|
-
|
|
14907
|
-
const host = this.tcb.env.reflector;
|
|
14908
|
-
const dirRef = dir.ref;
|
|
14909
|
-
if (!dir.isGeneric) {
|
|
14910
|
-
// The most common case is that when a directive is not generic, we use the normal
|
|
14911
|
-
// `TcbNonDirectiveTypeOp`.
|
|
14912
|
-
directiveOp = new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir);
|
|
14913
|
-
}
|
|
14914
|
-
else if (!requiresInlineTypeCtor(dirRef.node, host, this.tcb.env) ||
|
|
14915
|
-
this.tcb.env.config.useInlineTypeConstructors) {
|
|
14916
|
-
// For generic directives, we use a type constructor to infer types. If a directive requires
|
|
14917
|
-
// an inline type constructor, then inlining must be available to use the
|
|
14918
|
-
// `TcbDirectiveCtorOp`. If not we, we fallback to using `any` – see below.
|
|
14919
|
-
directiveOp = new TcbDirectiveCtorOp(this.tcb, this, node, dir);
|
|
14920
|
-
}
|
|
14921
|
-
else {
|
|
14922
|
-
// If inlining is not available, then we give up on inferring the generic params, and use
|
|
14923
|
-
// `any` type for the directive's generic parameters.
|
|
14924
|
-
directiveOp = new TcbGenericDirectiveTypeWithAnyParamsOp(this.tcb, this, node, dir);
|
|
14925
|
-
}
|
|
14926
|
-
const dirIndex = this.opQueue.push(directiveOp) - 1;
|
|
14927
|
-
dirMap.set(dir, dirIndex);
|
|
14928
|
-
this.opQueue.push(new TcbDirectiveInputsOp(this.tcb, this, node, dir));
|
|
14999
|
+
this.appendDirectiveInputs(dir, node, dirMap);
|
|
14929
15000
|
}
|
|
14930
15001
|
this.directiveOpMap.set(node, dirMap);
|
|
14931
15002
|
// After expanding the directives, we might need to queue an operation to check any unclaimed
|
|
@@ -14946,9 +15017,12 @@ class Scope {
|
|
|
14946
15017
|
this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, checkElement, claimedInputs));
|
|
14947
15018
|
}
|
|
14948
15019
|
}
|
|
14949
|
-
|
|
15020
|
+
appendOutputsOfElementLikeNode(node) {
|
|
14950
15021
|
// Collect all the outputs on the element.
|
|
14951
15022
|
const claimedOutputs = new Set();
|
|
15023
|
+
// Don't resolve directives when selectorless is enabled and treat all the outputs on the
|
|
15024
|
+
// element as unclaimed. In selectorless the outputs are defined either in component or
|
|
15025
|
+
// directive nodes.
|
|
14952
15026
|
const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
|
14953
15027
|
if (directives === null || directives.length === 0) {
|
|
14954
15028
|
// If there are no directives, then all outputs are unclaimed outputs, so queue an operation
|
|
@@ -14974,6 +15048,107 @@ class Scope {
|
|
|
14974
15048
|
this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, node.outputs, node.inputs, claimedOutputs));
|
|
14975
15049
|
}
|
|
14976
15050
|
}
|
|
15051
|
+
appendInputsOfSelectorlessNode(node) {
|
|
15052
|
+
// Only resolve the directives that were brought in by this specific directive.
|
|
15053
|
+
const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
|
15054
|
+
const claimedInputs = new Set();
|
|
15055
|
+
if (directives !== null && directives.length > 0) {
|
|
15056
|
+
const dirMap = new Map();
|
|
15057
|
+
for (const dir of directives) {
|
|
15058
|
+
this.appendDirectiveInputs(dir, node, dirMap);
|
|
15059
|
+
for (const propertyName of dir.inputs.propertyNames) {
|
|
15060
|
+
claimedInputs.add(propertyName);
|
|
15061
|
+
}
|
|
15062
|
+
}
|
|
15063
|
+
this.directiveOpMap.set(node, dirMap);
|
|
15064
|
+
}
|
|
15065
|
+
// In selectorless all directive inputs have to be claimed.
|
|
15066
|
+
if (node instanceof compiler.Directive) {
|
|
15067
|
+
for (const input of node.inputs) {
|
|
15068
|
+
if (!claimedInputs.has(input.name)) {
|
|
15069
|
+
this.tcb.oobRecorder.unclaimedDirectiveBinding(this.tcb.id, node, input);
|
|
15070
|
+
}
|
|
15071
|
+
}
|
|
15072
|
+
for (const attr of node.attributes) {
|
|
15073
|
+
if (!claimedInputs.has(attr.name)) {
|
|
15074
|
+
this.tcb.oobRecorder.unclaimedDirectiveBinding(this.tcb.id, node, attr);
|
|
15075
|
+
}
|
|
15076
|
+
}
|
|
15077
|
+
}
|
|
15078
|
+
else {
|
|
15079
|
+
const checkElement = node.tagName !== null;
|
|
15080
|
+
this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node.inputs, node, claimedInputs), new TcbDomSchemaCheckerOp(this.tcb, node, checkElement, claimedInputs));
|
|
15081
|
+
}
|
|
15082
|
+
}
|
|
15083
|
+
appendOutputsOfSelectorlessNode(node) {
|
|
15084
|
+
// Only resolve the directives that were brought in by this specific directive.
|
|
15085
|
+
const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
|
15086
|
+
const claimedOutputs = new Set();
|
|
15087
|
+
if (directives !== null && directives.length > 0) {
|
|
15088
|
+
for (const dir of directives) {
|
|
15089
|
+
this.opQueue.push(new TcbDirectiveOutputsOp(this.tcb, this, node, dir));
|
|
15090
|
+
for (const outputProperty of dir.outputs.propertyNames) {
|
|
15091
|
+
claimedOutputs.add(outputProperty);
|
|
15092
|
+
}
|
|
15093
|
+
}
|
|
15094
|
+
}
|
|
15095
|
+
// In selectorless all directive outputs have to be claimed.
|
|
15096
|
+
if (node instanceof compiler.Directive) {
|
|
15097
|
+
for (const output of node.outputs) {
|
|
15098
|
+
if (!claimedOutputs.has(output.name)) {
|
|
15099
|
+
this.tcb.oobRecorder.unclaimedDirectiveBinding(this.tcb.id, node, output);
|
|
15100
|
+
}
|
|
15101
|
+
}
|
|
15102
|
+
}
|
|
15103
|
+
else {
|
|
15104
|
+
this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, node.outputs, node.inputs, claimedOutputs));
|
|
15105
|
+
}
|
|
15106
|
+
}
|
|
15107
|
+
appendDirectiveInputs(dir, node, dirMap) {
|
|
15108
|
+
let directiveOp;
|
|
15109
|
+
const host = this.tcb.env.reflector;
|
|
15110
|
+
const dirRef = dir.ref;
|
|
15111
|
+
if (!dir.isGeneric) {
|
|
15112
|
+
// The most common case is that when a directive is not generic, we use the normal
|
|
15113
|
+
// `TcbNonDirectiveTypeOp`.
|
|
15114
|
+
directiveOp = new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir);
|
|
15115
|
+
}
|
|
15116
|
+
else if (!requiresInlineTypeCtor(dirRef.node, host, this.tcb.env) ||
|
|
15117
|
+
this.tcb.env.config.useInlineTypeConstructors) {
|
|
15118
|
+
// For generic directives, we use a type constructor to infer types. If a directive requires
|
|
15119
|
+
// an inline type constructor, then inlining must be available to use the
|
|
15120
|
+
// `TcbDirectiveCtorOp`. If not we, we fallback to using `any` – see below.
|
|
15121
|
+
directiveOp = new TcbDirectiveCtorOp(this.tcb, this, node, dir);
|
|
15122
|
+
}
|
|
15123
|
+
else {
|
|
15124
|
+
// If inlining is not available, then we give up on inferring the generic params, and use
|
|
15125
|
+
// `any` type for the directive's generic parameters.
|
|
15126
|
+
directiveOp = new TcbGenericDirectiveTypeWithAnyParamsOp(this.tcb, this, node, dir);
|
|
15127
|
+
}
|
|
15128
|
+
const dirIndex = this.opQueue.push(directiveOp) - 1;
|
|
15129
|
+
dirMap.set(dir, dirIndex);
|
|
15130
|
+
this.opQueue.push(new TcbDirectiveInputsOp(this.tcb, this, node, dir));
|
|
15131
|
+
}
|
|
15132
|
+
appendSelectorlessDirectives(node) {
|
|
15133
|
+
for (const directive of node.directives) {
|
|
15134
|
+
// Check that the directive exists.
|
|
15135
|
+
if (!this.tcb.boundTarget.referencedDirectiveExists(directive.name)) {
|
|
15136
|
+
this.tcb.oobRecorder.missingNamedTemplateDependency(this.tcb.id, directive);
|
|
15137
|
+
continue;
|
|
15138
|
+
}
|
|
15139
|
+
// Check that the class is a directive class.
|
|
15140
|
+
const directives = this.tcb.boundTarget.getDirectivesOfNode(directive);
|
|
15141
|
+
if (directives === null ||
|
|
15142
|
+
directives.length === 0 ||
|
|
15143
|
+
directives.some((dir) => dir.isComponent)) {
|
|
15144
|
+
this.tcb.oobRecorder.incorrectTemplateDependencyType(this.tcb.id, directive);
|
|
15145
|
+
continue;
|
|
15146
|
+
}
|
|
15147
|
+
this.appendInputsOfSelectorlessNode(directive);
|
|
15148
|
+
this.appendOutputsOfSelectorlessNode(directive);
|
|
15149
|
+
this.checkAndAppendReferencesOfNode(directive);
|
|
15150
|
+
}
|
|
15151
|
+
}
|
|
14977
15152
|
appendDeepSchemaChecks(nodes) {
|
|
14978
15153
|
for (const node of nodes) {
|
|
14979
15154
|
if (!(node instanceof compiler.Element || node instanceof compiler.Template)) {
|
|
@@ -14981,7 +15156,14 @@ class Scope {
|
|
|
14981
15156
|
}
|
|
14982
15157
|
if (node instanceof compiler.Element) {
|
|
14983
15158
|
const claimedInputs = new Set();
|
|
14984
|
-
|
|
15159
|
+
let directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
|
15160
|
+
for (const dirNode of node.directives) {
|
|
15161
|
+
const directiveResults = this.tcb.boundTarget.getDirectivesOfNode(dirNode);
|
|
15162
|
+
if (directiveResults !== null && directiveResults.length > 0) {
|
|
15163
|
+
directives ??= [];
|
|
15164
|
+
directives.push(...directiveResults);
|
|
15165
|
+
}
|
|
15166
|
+
}
|
|
14985
15167
|
let hasDirectives;
|
|
14986
15168
|
if (directives === null || directives.length === 0) {
|
|
14987
15169
|
hasDirectives = false;
|
|
@@ -15020,6 +15202,32 @@ class Scope {
|
|
|
15020
15202
|
}
|
|
15021
15203
|
}
|
|
15022
15204
|
}
|
|
15205
|
+
appendComponentNode(node) {
|
|
15206
|
+
// TODO(crisbeto): should we still append the children if the component is invalid?
|
|
15207
|
+
// Check that the referenced class exists.
|
|
15208
|
+
if (!this.tcb.boundTarget.referencedDirectiveExists(node.componentName)) {
|
|
15209
|
+
this.tcb.oobRecorder.missingNamedTemplateDependency(this.tcb.id, node);
|
|
15210
|
+
return;
|
|
15211
|
+
}
|
|
15212
|
+
// Check that the class is a component.
|
|
15213
|
+
const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
|
15214
|
+
if (directives === null ||
|
|
15215
|
+
directives.length === 0 ||
|
|
15216
|
+
directives.every((dir) => !dir.isComponent)) {
|
|
15217
|
+
this.tcb.oobRecorder.incorrectTemplateDependencyType(this.tcb.id, node);
|
|
15218
|
+
return;
|
|
15219
|
+
}
|
|
15220
|
+
const opIndex = this.opQueue.push(new TcbComponentNodeOp(this.tcb, this, node)) - 1;
|
|
15221
|
+
this.componentNodeOpMap.set(node, opIndex);
|
|
15222
|
+
if (this.tcb.env.config.controlFlowPreventingContentProjection !== 'suppress') {
|
|
15223
|
+
this.appendContentProjectionCheckOp(node);
|
|
15224
|
+
}
|
|
15225
|
+
this.appendInputsOfSelectorlessNode(node);
|
|
15226
|
+
this.appendOutputsOfSelectorlessNode(node);
|
|
15227
|
+
this.appendSelectorlessDirectives(node);
|
|
15228
|
+
this.appendChildren(node);
|
|
15229
|
+
this.checkAndAppendReferencesOfNode(node);
|
|
15230
|
+
}
|
|
15023
15231
|
appendDeferredBlock(block) {
|
|
15024
15232
|
this.appendDeferredTriggers(block, block.triggers);
|
|
15025
15233
|
this.appendDeferredTriggers(block, block.prefetchTriggers);
|
|
@@ -15498,6 +15706,12 @@ class TcbForLoopTrackTranslator extends TcbExpressionTranslator {
|
|
|
15498
15706
|
return super.resolve(ast);
|
|
15499
15707
|
}
|
|
15500
15708
|
}
|
|
15709
|
+
// TODO(crisbeto): the logic for determining the fallback tag name of a Component node is
|
|
15710
|
+
// still being designed. For now fall back to `ng-component`, but this will have to be
|
|
15711
|
+
// revisited once the design is finalized.
|
|
15712
|
+
function getComponentTagName(node) {
|
|
15713
|
+
return node.tagName || 'ng-component';
|
|
15714
|
+
}
|
|
15501
15715
|
|
|
15502
15716
|
/**
|
|
15503
15717
|
* An `Environment` representing the single type-checking file into which most (if not all) Type
|
|
@@ -16269,7 +16483,26 @@ class SymbolBuilder {
|
|
|
16269
16483
|
if (directives === null) {
|
|
16270
16484
|
return null;
|
|
16271
16485
|
}
|
|
16272
|
-
|
|
16486
|
+
const directive = directives.find((m) => m.ref.node === directiveDeclaration);
|
|
16487
|
+
if (directive) {
|
|
16488
|
+
return directive;
|
|
16489
|
+
}
|
|
16490
|
+
const originalFile = directiveDeclaration.getSourceFile()[NgOriginalFile];
|
|
16491
|
+
if (originalFile !== undefined) {
|
|
16492
|
+
// This is a preliminary check ahead of a more expensive search
|
|
16493
|
+
const hasPotentialCandidate = directives.find((m) => m.ref.node.name.text === directiveDeclaration.name?.text);
|
|
16494
|
+
if (hasPotentialCandidate) {
|
|
16495
|
+
// In case the TCB has been inlined,
|
|
16496
|
+
// We will look for a matching class
|
|
16497
|
+
// If we find one, we look for it in the directives array
|
|
16498
|
+
const classWithSameName = findMatchingDirective(originalFile, directiveDeclaration);
|
|
16499
|
+
if (classWithSameName !== null) {
|
|
16500
|
+
return directives.find((m) => m.ref.node === classWithSameName) ?? null;
|
|
16501
|
+
}
|
|
16502
|
+
}
|
|
16503
|
+
}
|
|
16504
|
+
// Really nothing was found
|
|
16505
|
+
return null;
|
|
16273
16506
|
}
|
|
16274
16507
|
getDirectiveModule(declaration) {
|
|
16275
16508
|
const scope = this.componentScopeReader.getScopeForComponent(declaration);
|
|
@@ -16768,6 +17001,37 @@ function unwrapSignalInputWriteTAccessor(expr) {
|
|
|
16768
17001
|
typeExpr: expr,
|
|
16769
17002
|
};
|
|
16770
17003
|
}
|
|
17004
|
+
/**
|
|
17005
|
+
* Looks for a class declaration in the original source file that matches a given directive
|
|
17006
|
+
* from the type check source file.
|
|
17007
|
+
*
|
|
17008
|
+
* @param originalSourceFile The original source where the runtime code resides
|
|
17009
|
+
* @param directiveDeclarationInTypeCheckSourceFile The directive from the type check source file
|
|
17010
|
+
*/
|
|
17011
|
+
function findMatchingDirective(originalSourceFile, directiveDeclarationInTypeCheckSourceFile) {
|
|
17012
|
+
const className = directiveDeclarationInTypeCheckSourceFile.name?.text ?? '';
|
|
17013
|
+
// We build an index of the class declarations with the same name
|
|
17014
|
+
// To then compare the indexes to confirm we found the right class declaration
|
|
17015
|
+
const ogClasses = collectClassesWithName(originalSourceFile, className);
|
|
17016
|
+
const typecheckClasses = collectClassesWithName(directiveDeclarationInTypeCheckSourceFile.getSourceFile(), className);
|
|
17017
|
+
return ogClasses[typecheckClasses.indexOf(directiveDeclarationInTypeCheckSourceFile)] ?? null;
|
|
17018
|
+
}
|
|
17019
|
+
/**
|
|
17020
|
+
* Builds a list of class declarations of a given name
|
|
17021
|
+
* Is used as a index based reference to compare class declarations
|
|
17022
|
+
* between the typecheck source file and the original source file
|
|
17023
|
+
*/
|
|
17024
|
+
function collectClassesWithName(sourceFile, className) {
|
|
17025
|
+
const classes = [];
|
|
17026
|
+
function visit(node) {
|
|
17027
|
+
if (ts.isClassDeclaration(node) && node.name?.text === className) {
|
|
17028
|
+
classes.push(node);
|
|
17029
|
+
}
|
|
17030
|
+
ts.forEachChild(node, visit);
|
|
17031
|
+
}
|
|
17032
|
+
sourceFile.forEachChild(visit);
|
|
17033
|
+
return classes;
|
|
17034
|
+
}
|
|
16771
17035
|
|
|
16772
17036
|
const REGISTRY = new compiler.DomElementSchemaRegistry();
|
|
16773
17037
|
/**
|