diff --git a/goldens/public-api/compiler-cli/error_code.api.md b/goldens/public-api/compiler-cli/error_code.api.md
index 2b6ca2149aa..b5cb5c508ca 100644
--- a/goldens/public-api/compiler-cli/error_code.api.md
+++ b/goldens/public-api/compiler-cli/error_code.api.md
@@ -58,6 +58,7 @@ export enum ErrorCode {
IMPORT_CYCLE_DETECTED = 3003,
IMPORT_GENERATION_FAILURE = 3004,
INACCESSIBLE_DEFERRED_TRIGGER_ELEMENT = 8010,
+ INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE = 2025,
INCORRECTLY_DECLARED_ON_STATIC_MEMBER = 1100,
INITIALIZER_API_DECORATOR_METADATA_COLLISION = 1051,
INITIALIZER_API_DISALLOWED_MEMBER_VISIBILITY = 1053,
@@ -73,6 +74,7 @@ export enum ErrorCode {
LOCAL_COMPILATION_UNRESOLVED_CONST = 11001,
LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION = 11003,
MISSING_CONTROL_FLOW_DIRECTIVE = 8103,
+ MISSING_NAMED_TEMPLATE_DEPENDENCY = 2024,
MISSING_NGFOROF_LET = 8105,
MISSING_PIPE = 8004,
MISSING_REFERENCE_TARGET = 8003,
@@ -104,6 +106,7 @@ export enum ErrorCode {
SYMBOL_NOT_EXPORTED = 3001,
TEMPLATE_PARSE_ERROR = 5002,
TEXT_ATTRIBUTE_NOT_BINDING = 8104,
+ UNCLAIMED_DIRECTIVE_BINDING = 8018,
UNDECORATED_CLASS_USING_ANGULAR_FEATURES = 2007,
UNDECORATED_PROVIDER = 2005,
UNINVOKED_FUNCTION_IN_EVENT_BINDING = 8111,
diff --git a/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts b/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
index 5e7c58b7517..07a8c3dd3d3 100644
--- a/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
+++ b/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
@@ -162,6 +162,17 @@ export enum ErrorCode {
*/
NON_STANDALONE_NOT_ALLOWED = 2023,
+ /**
+ * Raised when a named template dependency isn't defined in the component's source file.
+ */
+ MISSING_NAMED_TEMPLATE_DEPENDENCY = 2024,
+
+ /**
+ * Raised if an incorrect type is used for a named template dependency (e.g. directive
+ * class used as a component).
+ */
+ INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE = 2025,
+
SYMBOL_NOT_EXPORTED = 3001,
/**
* Raised when a relationship between directives and/or pipes would cause a cyclic import to be
@@ -374,6 +385,12 @@ export enum ErrorCode {
/** A `@let` declaration conflicts with another symbol in the same scope. */
CONFLICTING_LET_DECLARATION = 8017,
+ /**
+ * A binding inside selectorless directive syntax did
+ * not match any inputs/outputs of the directive.
+ */
+ UNCLAIMED_DIRECTIVE_BINDING = 8018,
+
/**
* A two way binding in a template has an incorrect syntax,
* parentheses outside brackets. For example:
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts
index 85ac845f6c4..07b64756ab8 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts
@@ -13,6 +13,8 @@ import {
PropertyWrite,
TmplAstBoundAttribute,
TmplAstBoundEvent,
+ TmplAstComponent,
+ TmplAstDirective,
TmplAstElement,
TmplAstForLoopBlock,
TmplAstForLoopBlockEmpty,
@@ -23,6 +25,7 @@ import {
TmplAstReference,
TmplAstSwitchBlockCase,
TmplAstTemplate,
+ TmplAstTextAttribute,
TmplAstVariable,
TmplAstViewportDeferredTrigger,
} from '@angular/compiler';
@@ -125,7 +128,7 @@ export interface OutOfBandDiagnosticRecorder {
/** Reports required inputs that haven't been bound. */
missingRequiredInputs(
id: TypeCheckId,
- element: TmplAstElement | TmplAstTemplate,
+ element: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
directiveName: string,
isComponent: boolean,
inputAliases: string[],
@@ -185,6 +188,34 @@ export interface OutOfBandDiagnosticRecorder {
* @param current the `TmplAstLetDeclaration` which is invalid.
*/
conflictingDeclaration(id: TypeCheckId, current: TmplAstLetDeclaration): void;
+
+ /**
+ * Reports that a named template dependency (e.g. ``) is not available.
+ * @param id Type checking ID of the template in which the dependency is declared.
+ * @param node Node that declares the dependency.
+ */
+ missingNamedTemplateDependency(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void;
+
+ /**
+ * Reports that a templace dependency of the wrong kind has been referenced at a specific position
+ * (e.g. ``).
+ * @param id Type checking ID of the template in which the dependency is declared.
+ * @param node Node that declares the dependency.
+ */
+ incorrectTemplateDependencyType(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void;
+
+ /**
+ * Reports a binding inside directive syntax that does not match any of the inputs/outputs of
+ * the directive.
+ * @param id Type checking ID of the template in which the directive was defined.
+ * @param directive Directive that contains the binding.
+ * @param node Node declaring the binding.
+ */
+ unclaimedDirectiveBinding(
+ id: TypeCheckId,
+ directive: TmplAstDirective,
+ node: TmplAstBoundAttribute | TmplAstTextAttribute | TmplAstBoundEvent,
+ ): void;
}
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
@@ -467,7 +498,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
missingRequiredInputs(
id: TypeCheckId,
- element: TmplAstElement | TmplAstTemplate,
+ element: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
directiveName: string,
isComponent: boolean,
inputAliases: string[],
@@ -656,6 +687,58 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
),
);
}
+
+ missingNamedTemplateDependency(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void {
+ this._diagnostics.push(
+ makeTemplateDiagnostic(
+ id,
+ this.resolver.getTemplateSourceMapping(id),
+ node.startSourceSpan,
+ ts.DiagnosticCategory.Error,
+ ngErrorCode(ErrorCode.MISSING_NAMED_TEMPLATE_DEPENDENCY),
+ // Wording is meant to mimic the wording TS uses in their diagnostic for missing symbols.
+ `Cannot find name "${node instanceof TmplAstDirective ? node.name : node.componentName}".`,
+ ),
+ );
+ }
+
+ incorrectTemplateDependencyType(
+ id: TypeCheckId,
+ node: TmplAstComponent | TmplAstDirective,
+ ): void {
+ this._diagnostics.push(
+ makeTemplateDiagnostic(
+ id,
+ this.resolver.getTemplateSourceMapping(id),
+ node.startSourceSpan,
+ ts.DiagnosticCategory.Error,
+ ngErrorCode(ErrorCode.INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE),
+ `Incorrect reference type. Type must be an ${node instanceof TmplAstComponent ? '@Component' : '@Directive'}.`,
+ ),
+ );
+ }
+
+ unclaimedDirectiveBinding(
+ id: TypeCheckId,
+ directive: TmplAstDirective,
+ node: TmplAstBoundAttribute | TmplAstTextAttribute | TmplAstBoundEvent,
+ ): void {
+ const errorMsg =
+ `Directive ${directive.name} does not have an ` +
+ `${node instanceof TmplAstBoundEvent ? 'output' : 'input'} named "${node.name}". ` +
+ `Bindings to directives must target existing inputs or outputs.`;
+
+ this._diagnostics.push(
+ makeTemplateDiagnostic(
+ id,
+ this.resolver.getTemplateSourceMapping(id),
+ node.keySpan || node.sourceSpan,
+ ts.DiagnosticCategory.Error,
+ ngErrorCode(ErrorCode.UNCLAIMED_DIRECTIVE_BINDING),
+ errorMsg,
+ ),
+ );
+ }
}
function makeInlineDiagnostic(
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
index 7b875a7d7ad..cd1546a354b 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
@@ -17,8 +17,13 @@ import {
R3TargetBinder,
SelectorlessMatcher,
SelectorMatcher,
+ TmplAstBoundAttribute,
+ TmplAstBoundEvent,
+ TmplAstComponent,
+ TmplAstDirective,
TmplAstElement,
TmplAstLetDeclaration,
+ TmplAstTextAttribute,
} from '@angular/compiler';
import {readFileSync} from 'fs';
import path from 'path';
@@ -1023,4 +1028,17 @@ export class NoopOobRecorder implements OutOfBandDiagnosticRecorder {
target: TmplAstLetDeclaration,
): void {}
conflictingDeclaration(id: TypeCheckId, current: TmplAstLetDeclaration): void {}
+ missingNamedTemplateDependency(
+ id: TypeCheckId,
+ node: TmplAstComponent | TmplAstDirective,
+ ): void {}
+ unclaimedDirectiveBinding(
+ id: TypeCheckId,
+ directive: TmplAstDirective,
+ node: TmplAstBoundAttribute | TmplAstTextAttribute | TmplAstBoundEvent,
+ ): void {}
+ incorrectTemplateDependencyType(
+ id: TypeCheckId,
+ node: TmplAstComponent | TmplAstDirective,
+ ): void {}
}