diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index 156613cd51db5..531d1efa3df5d 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -259,9 +259,10 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { let record: ClassRecord|null = this.recordFor(clazz); let foundTraits: PendingTrait[] = []; - // A set to track the non-Angular decorators in local compilation mode. An error will be issued - // if non-Angular decorators is found in local compilation mode. - const nonNgDecoratorsInLocalMode = + // A set to track the undetected decorators (= either non-Angular decorators or Angular + // duplicate decorators) in local compilation mode. An error will be issued if such decorators + // are found. + const undetectedDecorators = this.compilationMode === CompilationMode.LOCAL ? new Set(decorators) : null; for (const handler of this.handlers) { @@ -270,8 +271,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { continue; } - if (nonNgDecoratorsInLocalMode !== null && result.decorator !== null) { - nonNgDecoratorsInLocalMode.delete(result.decorator); + if (undetectedDecorators !== null && result.decorator !== null) { + undetectedDecorators.delete(result.decorator); } const isPrimaryHandler = handler.precedence === HandlerPrecedence.PRIMARY; @@ -341,11 +342,11 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { } } - if (nonNgDecoratorsInLocalMode !== null && nonNgDecoratorsInLocalMode.size > 0 && - record !== null && record.metaDiagnostics === null) { + if (undetectedDecorators !== null && undetectedDecorators.size > 0 && record !== null && + record.metaDiagnostics === null) { // Custom decorators found in local compilation mode! In this mode we don't support custom // decorators yet. But will eventually do (b/320536434). For now a temporary error is thrown. - record.metaDiagnostics = [...nonNgDecoratorsInLocalMode].map( + record.metaDiagnostics = [...undetectedDecorators].map( decorator => ({ category: ts.DiagnosticCategory.Error, code: Number('-99' + ErrorCode.DECORATOR_UNEXPECTED), @@ -353,7 +354,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { start: decorator.node.getStart(), length: decorator.node.getWidth(), messageText: - 'In local compilation mode, Angular does not support custom decorators. Ensure all class decorators are from Angular.', + 'In local compilation mode, Angular does not support custom decorators or duplicate Angular decorators. Ensure all class decorators are from Angular and each decorator is used at most once for each class.', })); record.traits = foundTraits = []; } diff --git a/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts b/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts index 2a5f965a0ee4f..f7a588cc9a9a5 100644 --- a/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts +++ b/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts @@ -1989,7 +1989,7 @@ runInEachFileSystem( }); }); - describe('custom decorator', () => { + describe('custom/duplicate decorator', () => { it('should produce diagnostic for each custom decorator', () => { env.write('test.ts', ` import {Component} from '@angular/core'; @@ -2014,9 +2014,31 @@ runInEachFileSystem( expect(errors[0].code).toBe(ngErrorCode(ErrorCode.DECORATOR_UNEXPECTED)); expect(errors[1].code).toBe(ngErrorCode(ErrorCode.DECORATOR_UNEXPECTED)); expect(text1).toContain( - 'In local compilation mode, Angular does not support custom decorators'); + 'In local compilation mode, Angular does not support custom decorators or duplicate Angular decorators'); expect(text2).toContain( - 'In local compilation mode, Angular does not support custom decorators'); + 'In local compilation mode, Angular does not support custom decorators or duplicate Angular decorators'); + }); + + it('should produce diagnostic for duplicate Component decorator', () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + import {SomeServiceImpl} from './some-where'; + + @Component({template: 'Hello!'}) + @Component({template: 'Bye!'}) + export class SomeComp { + } + `); + + const errors = env.driveDiagnostics(); + + expect(errors.length).toBe(1); + + const text1 = ts.flattenDiagnosticMessageText(errors[0].messageText, '\n'); + + expect(errors[0].code).toBe(ngErrorCode(ErrorCode.DECORATOR_UNEXPECTED)); + expect(text1).toContain( + 'In local compilation mode, Angular does not support custom decorators or duplicate Angular decorators'); }); }); });