Skip to content

Commit

Permalink
fix(compiler-cli): consider the case of duplicate Angular decorators …
Browse files Browse the repository at this point in the history
…in local compilation diagnostics (#54139)

For cases like this:

```
@component({...})
@component({...})
export class SomeComp {
}
```

The `DecoratorHandler.detect` apparantly matches only one of the `@Component` decorator, leaving the other undetected which will be transformed by TS decorator helper and that breaks local compilation runtimes. But the error message only mentioned "custom" decorator, while in this case it is a "duplicate Angular" decorator. The respective error message is updated thus.

PR Close #54139
  • Loading branch information
pmvald authored and thePunderWoman committed Feb 5, 2024
1 parent 3b1926a commit 4b1d948
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 12 deletions.
19 changes: 10 additions & 9 deletions packages/compiler-cli/src/ngtsc/transform/src/compilation.ts
Expand Up @@ -259,9 +259,10 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
let record: ClassRecord|null = this.recordFor(clazz);
let foundTraits: PendingTrait<unknown, unknown, SemanticSymbol|null, unknown>[] = [];

// 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) {
Expand All @@ -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;
Expand Down Expand Up @@ -341,19 +342,19 @@ 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),
file: getSourceFile(clazz),
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 = [];
}
Expand Down
28 changes: 25 additions & 3 deletions packages/compiler-cli/test/ngtsc/local_compilation_spec.ts
Expand Up @@ -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';
Expand All @@ -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');
});
});
});
Expand Down

0 comments on commit 4b1d948

Please sign in to comment.