fix(compiler): do not throw when using abstract directive from other compilation unit (#33347)
Libraries can expose directive/component base classes that will be used by consumer applications. Using such a base class from another compilation unit works fine with "ngtsc", but when using "ngc", the compiler will thrown an error saying that the base class is not part of a NgModule. e.g. ``` Cannot determine the module for class X in Y! Add X to the NgModule to fix it. ``` This seems to be because the logic for distinguishing directives from abstract directives is scoped to the current compilation unit within ngc. This causes abstract directives from other compilation units to be considered as actual directives (causing the exception). PR Close #33347
This commit is contained in:
parent
1b607529a6
commit
355e54a410
|
@ -2308,5 +2308,32 @@ describe('ngc transformer command-line', () => {
|
|||
let exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
});
|
||||
|
||||
it('should be able to use abstract directive in other compilation units', () => {
|
||||
writeConfig();
|
||||
write('lib1/tsconfig.json', JSON.stringify({
|
||||
extends: '../tsconfig-base.json',
|
||||
compilerOptions: {rootDir: '.', outDir: '../node_modules/lib1_built'},
|
||||
}));
|
||||
write('lib1/index.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class BaseClass {}
|
||||
`);
|
||||
write('index.ts', `
|
||||
import {NgModule, Directive} from '@angular/core';
|
||||
import {BaseClass} from 'lib1_built';
|
||||
|
||||
@Directive({selector: 'my-dir'})
|
||||
export class MyDirective extends BaseClass {}
|
||||
|
||||
@NgModule({declarations: [MyDirective]})
|
||||
export class AppModule {}
|
||||
`);
|
||||
|
||||
expect(main(['-p', path.join(basePath, 'lib1/tsconfig.json')], errorSpy)).toBe(0);
|
||||
expect(main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1051,6 +1051,36 @@ runInEachFileSystem(os => {
|
|||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should be able to use abstract directive in other compilation units', () => {
|
||||
env.write('tsconfig.json', JSON.stringify({
|
||||
extends: './tsconfig-base.json',
|
||||
angularCompilerOptions: {enableIvy: true},
|
||||
compilerOptions: {rootDir: '.', outDir: '../node_modules/lib1_built'},
|
||||
}));
|
||||
env.write('index.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class BaseClass {}
|
||||
`);
|
||||
|
||||
expect(env.driveDiagnostics().length).toBe(0);
|
||||
|
||||
env.tsconfig();
|
||||
env.write('index.ts', `
|
||||
import {NgModule, Directive} from '@angular/core';
|
||||
import {BaseClass} from 'lib1_built';
|
||||
|
||||
@Directive({selector: 'my-dir'})
|
||||
export class MyDirective extends BaseClass {}
|
||||
|
||||
@NgModule({declarations: [MyDirective]})
|
||||
export class AppModule {}
|
||||
`);
|
||||
|
||||
expect(env.driveDiagnostics().length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not allow directives with no selector that are in NgModules', () => {
|
||||
env.write('main.ts', `
|
||||
import {Directive, NgModule} from '@angular/core';
|
||||
|
|
|
@ -881,22 +881,18 @@ export function analyzeFile(
|
|||
if (symbolMeta.__symbolic === 'class') {
|
||||
if (metadataResolver.isDirective(symbol)) {
|
||||
isNgSymbol = true;
|
||||
if (!isDeclarationFile) {
|
||||
// This directive either has a selector or doesn't. Selector-less directives get tracked
|
||||
// in abstractDirectives, not directives. The compiler doesn't deal with selector-less
|
||||
// directives at all, really, other than to persist their metadata. This is done so that
|
||||
// apps will have an easier time migrating to Ivy, which requires the selector-less
|
||||
// annotations to be applied.
|
||||
if (!metadataResolver.isAbstractDirective(symbol)) {
|
||||
// The directive is an ordinary directive.
|
||||
directives.push(symbol);
|
||||
} else {
|
||||
// The directive has no selector and is an "abstract" directive, so track it
|
||||
// accordingly.
|
||||
abstractDirectives.push(symbol);
|
||||
}
|
||||
} else {
|
||||
// This directive either has a selector or doesn't. Selector-less directives get tracked
|
||||
// in abstractDirectives, not directives. The compiler doesn't deal with selector-less
|
||||
// directives at all, really, other than to persist their metadata. This is done so that
|
||||
// apps will have an easier time migrating to Ivy, which requires the selector-less
|
||||
// annotations to be applied.
|
||||
if (!metadataResolver.isAbstractDirective(symbol)) {
|
||||
// The directive is an ordinary directive.
|
||||
directives.push(symbol);
|
||||
} else {
|
||||
// The directive has no selector and is an "abstract" directive, so track it
|
||||
// accordingly.
|
||||
abstractDirectives.push(symbol);
|
||||
}
|
||||
} else if (metadataResolver.isPipe(symbol)) {
|
||||
isNgSymbol = true;
|
||||
|
|
Loading…
Reference in New Issue