diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index 4f567a20f4..e103aadd31 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -19,7 +19,7 @@ import {getSourceFile, isExported} from '../../util/src/typescript'; import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api'; import {DtsTransformRegistry} from './declaration'; -import {Trait, TraitState} from './trait'; +import {PendingTrait, Trait, TraitState} from './trait'; /** @@ -198,7 +198,8 @@ export class TraitCompiler { this.fileToClasses.get(sf) !.add(record.node); } - private scanClassForTraits(clazz: ClassDeclaration): Trait[]|null { + private scanClassForTraits(clazz: ClassDeclaration): + PendingTrait[]|null { if (!this.compileNonExportedClasses && !isExported(clazz)) { return null; } @@ -209,9 +210,9 @@ export class TraitCompiler { } protected detectTraits(clazz: ClassDeclaration, decorators: Decorator[]|null): - Trait[]|null { + PendingTrait[]|null { let record: ClassRecord|null = this.recordFor(clazz); - let foundTraits: Trait[] = []; + let foundTraits: PendingTrait[] = []; for (const handler of this.handlers) { const result = handler.detect(clazz, decorators); @@ -302,7 +303,18 @@ export class TraitCompiler { let preanalysis: Promise|null = null; if (preanalyzeQueue !== null && trait.handler.preanalyze !== undefined) { - preanalysis = trait.handler.preanalyze(clazz, trait.detected.metadata) || null; + // Attempt to run preanalysis. This could fail with a `FatalDiagnosticError`; catch it if it + // does. + try { + preanalysis = trait.handler.preanalyze(clazz, trait.detected.metadata) || null; + } catch (err) { + if (err instanceof FatalDiagnosticError) { + trait.toErrored([err.toDiagnostic()]); + return; + } else { + throw err; + } + } } if (preanalysis !== null) { preanalyzeQueue !.push(preanalysis.then(analyze)); diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 14c85acb07..1937c75f6d 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -3552,6 +3552,54 @@ runInEachFileSystem(os => { }); }); + // Run checks that are present in preanalysis phase in both sync and async mode, to make sure + // the error messages are consistently thrown from `analyzeSync` and `analyzeAsync` functions. + ['sync', 'async'].forEach(mode => { + describe(`preanalysis phase checks [${mode}]`, () => { + let driveDiagnostics: () => Promise>; + beforeEach(() => { + if (mode === 'async') { + env.enablePreloading(); + driveDiagnostics = () => env.driveDiagnosticsAsync(); + } else { + driveDiagnostics = () => Promise.resolve(env.driveDiagnostics()); + } + }); + + it('should throw if @Component is missing a template', async() => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'test', + }) + export class TestCmp {} + `); + + const diags = await driveDiagnostics(); + expect(diags[0].messageText).toBe('component is missing a template'); + expect(diags[0].file !.fileName).toBe(absoluteFrom('/test.ts')); + }); + + it('should throw if `styleUrls` is defined incorrectly in @Component', async() => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'test', + template: '...', + styleUrls: '...' + }) + export class TestCmp {} + `); + + const diags = await driveDiagnostics(); + expect(diags[0].messageText).toBe('styleUrls must be an array of strings'); + expect(diags[0].file !.fileName).toBe(absoluteFrom('/test.ts')); + }); + }); + }); + describe('flat module indices', () => { it('should generate a basic flat module index', () => { env.tsconfig({