Skip to content

Commit 167bc0d

Browse files
atscottAndrewKushnir
authored andcommitted
fix(compiler-cli): Produce diagnostic rather than crash when using invalid hostDirective (#48314)
Because the language service uses the compiler, we try to produce as much useful information as possible rather than throwing hard errors. Hard errors cause the compiler to crash. While this can be acceptable when compiling a program as part of a regular build, this is undesirable for the language service. PR Close #48314
1 parent b620d9b commit 167bc0d

File tree

4 files changed

+29
-8
lines changed

4 files changed

+29
-8
lines changed

packages/compiler-cli/src/ngtsc/metadata/src/host_directives_resolver.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,9 @@ export class HostDirectivesResolver {
4343
for (const current of directives) {
4444
const hostMeta = flattenInheritedDirectiveMetadata(this.metaReader, current.directive);
4545

46-
// This case has been checked for already, but we keep the assertion here so that the
47-
// user gets a better error message than "Cannot read property foo of null" in case
48-
// something slipped through.
46+
// This case has been checked for already and produces a diagnostic
4947
if (hostMeta === null) {
50-
throw new Error(
51-
`Could not resolve host directive metadata of ${current.directive.debugName}`);
48+
continue;
5249
}
5350

5451
if (hostMeta.hostDirectives) {

packages/compiler-cli/src/ngtsc/metadata/src/inheritance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
2121
* followed.
2222
*/
2323
export function flattenInheritedDirectiveMetadata(
24-
reader: MetadataReader, dir: Reference<ClassDeclaration>): DirectiveMeta {
24+
reader: MetadataReader, dir: Reference<ClassDeclaration>): DirectiveMeta|null {
2525
const topMeta = reader.getDirectiveMetadata(dir);
2626
if (topMeta === null) {
27-
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
27+
return null;
2828
}
2929
if (topMeta.baseClass === null) {
3030
return topMeta;

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ export class TypeCheckScopeRegistry {
9999
for (const meta of dependencies) {
100100
if (meta.kind === MetaKind.Directive && meta.selector !== null) {
101101
const extMeta = this.getTypeCheckDirectiveMetadata(meta.ref);
102+
if (extMeta === null) {
103+
continue;
104+
}
102105
matcher.addSelectables(
103106
CssSelector.parse(meta.selector),
104107
[...this.hostDirectivesResolver.resolve(extMeta), extMeta]);
@@ -125,13 +128,16 @@ export class TypeCheckScopeRegistry {
125128
return typeCheckScope;
126129
}
127130

128-
getTypeCheckDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta {
131+
getTypeCheckDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
129132
const clazz = ref.node;
130133
if (this.flattenedDirectiveMetaCache.has(clazz)) {
131134
return this.flattenedDirectiveMetaCache.get(clazz)!;
132135
}
133136

134137
const meta = flattenInheritedDirectiveMetadata(this.metaReader, ref);
138+
if (meta === null) {
139+
return null;
140+
}
135141
this.flattenedDirectiveMetaCache.set(clazz, meta);
136142
return meta;
137143
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,24 @@ runInEachFileSystem(() => {
451451
expect(messages).toEqual(['Host directive HostDir must be standalone']);
452452
});
453453

454+
it('should produce a diagnostic if a host directive is not a directive', () => {
455+
env.write('test.ts', `
456+
import {Directive, Pipe, Component, NgModule} from '@angular/core';
457+
458+
@Pipe({name: 'hostDir'})
459+
export class HostDir {}
460+
461+
@Directive({
462+
hostDirectives: [HostDir],
463+
})
464+
export class Dir {}
465+
`);
466+
467+
const messages = env.driveDiagnostics().map(extractMessage);
468+
expect(messages).toEqual(
469+
['HostDir must be a standalone directive to be used as a host directive']);
470+
});
471+
454472
it('should produce a diagnostic if a host directive is a component', () => {
455473
env.write('test.ts', `
456474
import {Directive, Component, NgModule} from '@angular/core';

0 commit comments

Comments
 (0)