From e89ebf3dc72c296d736f32201aa4bb3ceab4d0a8 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 23 Apr 2025 11:59:15 +0200 Subject: [PATCH] refactor(compiler-cli): add infrastructure for new diagnostics (#60977) We need a couple of custom diagnostics for selectorless. These changes add the infrastructure so they can be reported. PR Close #60977 --- .../public-api/compiler-cli/error_code.api.md | 3 + .../src/ngtsc/diagnostics/src/error_code.ts | 17 ++++ .../src/ngtsc/typecheck/src/oob.ts | 87 ++++++++++++++++++- .../src/ngtsc/typecheck/testing/index.ts | 18 ++++ 4 files changed, 123 insertions(+), 2 deletions(-) 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 {} }