Skip to content

Commit 18b0ee0

Browse files
dario-piotrowiczalxhub
authored andcommitted
fix(compiler-cli): update unknown tag error for aot standalone components (#45919)
update the error message presented during aot compilation when an unrecognized tag/element is found in a standalone component so that it does not mention the ngModule anymore Note: the jit variant is present in PR #45920 resolves #45818 PR Close #45919
1 parent e235c70 commit 18b0ee0

8 files changed

Lines changed: 81 additions & 17 deletions

File tree

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,8 @@ export class ComponentDecoratorHandler implements
542542
const binder = new R3TargetBinder(scope.matcher);
543543
ctx.addTemplate(
544544
new Reference(node), binder, meta.template.diagNodes, scope.pipes, scope.schemas,
545-
meta.template.sourceMapping, meta.template.file, meta.template.errors);
545+
meta.template.sourceMapping, meta.template.file, meta.template.errors,
546+
meta.meta.isStandalone);
546547
}
547548

548549
extendedTemplateCheck(

packages/compiler-cli/src/ngtsc/typecheck/api/api.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
import {AbsoluteSourceSpan, BoundTarget, DirectiveMeta, ParseSourceSpan, SchemaMetadata} from '@angular/compiler';
1010
import ts from 'typescript';
11-
import {ErrorCode} from '../../diagnostics';
1211

12+
import {ErrorCode} from '../../diagnostics';
1313
import {AbsoluteFsPath} from '../../file_system';
1414
import {Reference} from '../../imports';
1515
import {ClassPropertyMapping, DirectiveTypeCheckMeta} from '../../metadata';
@@ -76,6 +76,11 @@ export interface TypeCheckBlockMetadata {
7676
* Schemas that apply to this template.
7777
*/
7878
schemas: SchemaMetadata[];
79+
80+
/*
81+
* A boolean indicating whether the component is standalone.
82+
*/
83+
isStandalone: boolean;
7984
}
8085

8186
export interface TypeCtorMetadata {

packages/compiler-cli/src/ngtsc/typecheck/api/context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ export interface TypeCheckContext {
3636
* template text described by the AST.
3737
* @param file the `ParseSourceFile` associated with the template.
3838
* @param parseErrors the `ParseError`'s associated with the template.
39+
* @param isStandalone a boolean indicating whether the component is standalone.
3940
*/
4041
addTemplate(
4142
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
4243
binder: R3TargetBinder<TypeCheckableDirectiveMeta>, template: TmplAstNode[],
4344
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>,
4445
schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping, file: ParseSourceFile,
45-
parseErrors: ParseError[]|null): void;
46+
parseErrors: ParseError[]|null, isStandalone: boolean): void;
4647
}
4748

4849
/**

packages/compiler-cli/src/ngtsc/typecheck/src/context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
210210
binder: R3TargetBinder<TypeCheckableDirectiveMeta>, template: TmplAstNode[],
211211
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>,
212212
schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping, file: ParseSourceFile,
213-
parseErrors: ParseError[]|null): void {
213+
parseErrors: ParseError[]|null, isStandalone: boolean): void {
214214
if (!this.host.shouldCheckComponent(ref.node)) {
215215
return;
216216
}
@@ -286,6 +286,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
286286
boundTarget,
287287
pipes,
288288
schemas,
289+
isStandalone
289290
};
290291
this.perf.eventCount(PerfEvent.GenerateTcb);
291292
if (inliningRequirement !== TcbInliningRequirement.None &&

packages/compiler-cli/src/ngtsc/typecheck/src/dom.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ export interface DomSchemaChecker {
4141
* @param element the element node in question.
4242
* @param schemas any active schemas for the template, which might affect the validity of the
4343
* element.
44+
* @param hostIsStandalone boolean indicating whether the element's host is a standalone
45+
* component.
4446
*/
45-
checkElement(id: string, element: TmplAstElement, schemas: SchemaMetadata[]): void;
47+
checkElement(
48+
id: string, element: TmplAstElement, schemas: SchemaMetadata[],
49+
hostIsStandalone: boolean): void;
4650

4751
/**
4852
* Check a property binding on an element and record any diagnostics about it.
@@ -73,7 +77,9 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
7377

7478
constructor(private resolver: TemplateSourceResolver) {}
7579

76-
checkElement(id: TemplateId, element: TmplAstElement, schemas: SchemaMetadata[]): void {
80+
checkElement(
81+
id: TemplateId, element: TmplAstElement, schemas: SchemaMetadata[],
82+
hostIsStandalone: boolean): void {
7783
// HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
7884
// We need to strip it before handing it over to the registry because all HTML tag names
7985
// in the registry are without a namespace.
@@ -82,15 +88,17 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
8288
if (!REGISTRY.hasElement(name, schemas)) {
8389
const mapping = this.resolver.getSourceMapping(id);
8490

91+
const schemas = `'${hostIsStandalone ? '@Component' : '@NgModule'}.schemas'`;
8592
let errorMsg = `'${name}' is not a known element:\n`;
86-
errorMsg +=
87-
`1. If '${name}' is an Angular component, then verify that it is part of this module.\n`;
93+
errorMsg += `1. If '${name}' is an Angular component, then verify that it is ${
94+
hostIsStandalone ? 'included in the \'@Component.imports\' of this component' :
95+
'part of this module'}.\n`;
8896
if (name.indexOf('-') > -1) {
89-
errorMsg += `2. If '${
90-
name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.`;
97+
errorMsg += `2. If '${name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${
98+
schemas} of this component to suppress this message.`;
9199
} else {
92100
errorMsg +=
93-
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
101+
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
94102
}
95103

96104
const diag = makeTemplateDiagnostic(

packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export function generateTypeCheckBlock(
8383
oobRecorder: OutOfBandDiagnosticRecorder,
8484
genericContextBehavior: TcbGenericContextBehavior): ts.FunctionDeclaration {
8585
const tcb = new Context(
86-
env, domSchemaChecker, oobRecorder, meta.id, meta.boundTarget, meta.pipes, meta.schemas);
86+
env, domSchemaChecker, oobRecorder, meta.id, meta.boundTarget, meta.pipes, meta.schemas,
87+
meta.isStandalone);
8788
const scope = Scope.forNodes(tcb, null, tcb.boundTarget.target.template!, /* guard */ null);
8889
const ctxRawType = env.referenceType(ref);
8990
if (!ts.isTypeReferenceNode(ctxRawType)) {
@@ -849,7 +850,8 @@ class TcbDomSchemaCheckerOp extends TcbOp {
849850

850851
override execute(): ts.Expression|null {
851852
if (this.checkElement) {
852-
this.tcb.domSchemaChecker.checkElement(this.tcb.id, this.element, this.tcb.schemas);
853+
this.tcb.domSchemaChecker.checkElement(
854+
this.tcb.id, this.element, this.tcb.schemas, this.tcb.hostIsStandalone);
853855
}
854856

855857
// TODO(alxhub): this could be more efficient.
@@ -1144,7 +1146,7 @@ export class Context {
11441146
readonly oobRecorder: OutOfBandDiagnosticRecorder, readonly id: TemplateId,
11451147
readonly boundTarget: BoundTarget<TypeCheckableDirectiveMeta>,
11461148
private pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>,
1147-
readonly schemas: SchemaMetadata[]) {}
1149+
readonly schemas: SchemaMetadata[], readonly hostIsStandalone: boolean) {}
11481150

11491151
/**
11501152
* Allocate a new variable name for use within the `Context`.

packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export function tcb(
281281
const boundTarget = binder.bind({template: nodes});
282282

283283
const id = 'tcb' as TemplateId;
284-
const meta: TypeCheckBlockMetadata = {id, boundTarget, pipes, schemas: []};
284+
const meta: TypeCheckBlockMetadata = {id, boundTarget, pipes, schemas: [], isStandalone: false};
285285

286286
const fullConfig: TypeCheckingConfig = {
287287
applyTemplateContextGuards: true,
@@ -483,7 +483,8 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
483483
node: classRef.node.name,
484484
};
485485

486-
ctx.addTemplate(classRef, binder, nodes, pipes, [], sourceMapping, templateFile, errors);
486+
ctx.addTemplate(
487+
classRef, binder, nodes, pipes, [], sourceMapping, templateFile, errors, false);
487488
}
488489
}
489490
});
@@ -713,7 +714,9 @@ export class NoopSchemaChecker implements DomSchemaChecker {
713714
return [];
714715
}
715716

716-
checkElement(id: string, element: TmplAstElement, schemas: SchemaMetadata[]): void {}
717+
checkElement(
718+
id: string, element: TmplAstElement, schemas: SchemaMetadata[],
719+
hostIsStandalone: boolean): void {}
717720
checkProperty(
718721
id: string, element: TmplAstElement, name: string, span: ParseSourceSpan,
719722
schemas: SchemaMetadata[]): void {}

packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,6 +2096,27 @@ export declare class AnimationEvent {
20962096
2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`);
20972097
});
20982098

2099+
it('should check for unknown elements in standalone components', () => {
2100+
env.write('test.ts', `
2101+
import {Component, NgModule} from '@angular/core';
2102+
@Component({
2103+
selector: 'blah',
2104+
template: '<foo>test</foo>',
2105+
standalone: true,
2106+
})
2107+
export class FooCmp {}
2108+
@NgModule({
2109+
imports: [FooCmp],
2110+
})
2111+
export class FooModule {}
2112+
`);
2113+
const diags = env.driveDiagnostics();
2114+
expect(diags.length).toBe(1);
2115+
expect(diags[0].messageText).toBe(`'foo' is not a known element:
2116+
1. If 'foo' is an Angular component, then verify that it is included in the '@Component.imports' of this component.
2117+
2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@Component.schemas' of this component.`);
2118+
});
2119+
20992120
it('should have a descriptive error for unknown elements that contain a dash', () => {
21002121
env.write('test.ts', `
21012122
import {Component, NgModule} from '@angular/core';
@@ -2116,6 +2137,28 @@ export declare class AnimationEvent {
21162137
2. If 'my-foo' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.`);
21172138
});
21182139

2140+
it('should have a descriptive error for unknown elements that contain a dash in standalone components',
2141+
() => {
2142+
env.write('test.ts', `
2143+
import {Component, NgModule} from '@angular/core';
2144+
@Component({
2145+
selector: 'blah',
2146+
template: '<my-foo>test</my-foo>',
2147+
standalone: true,
2148+
})
2149+
export class FooCmp {}
2150+
@NgModule({
2151+
imports: [FooCmp],
2152+
})
2153+
export class FooModule {}
2154+
`);
2155+
const diags = env.driveDiagnostics();
2156+
expect(diags.length).toBe(1);
2157+
expect(diags[0].messageText).toBe(`'my-foo' is not a known element:
2158+
1. If 'my-foo' is an Angular component, then verify that it is included in the '@Component.imports' of this component.
2159+
2. If 'my-foo' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.`);
2160+
});
2161+
21192162
it('should check for unknown properties', () => {
21202163
env.write('test.ts', `
21212164
import {Component, NgModule} from '@angular/core';

0 commit comments

Comments
 (0)