feat(ivy): reference external classes by their exported name (#27743)
Previously, ngtsc would assume that a given directive/pipe being imported from an external package was importable using the same name by which it was declared. This isn't always true; sometimes a package will export a directive under a different name. For example, Angular frequently prefixes directive names with the 'ɵ' character to indicate that they're part of the package's private API, and not for public consumption. This commit introduces the TsReferenceResolver class which, given a declaration to import and a module name to import it from, can determine the exported name of the declared class within the module. This allows ngtsc to pick the correct name by which to import the class instead of making assumptions about how it was exported. This resolver is used to select a correct symbol name when creating an AbsoluteReference. FW-517 #resolve FW-536 #resolve PR Close #27743
This commit is contained in:

committed by
Kara Erickson

parent
0b9094ec63
commit
1c39ad38d3
@ -420,10 +420,12 @@ describe('ngtsc behavioral tests', () => {
|
||||
env.write('node_modules/router/index.d.ts', `
|
||||
import {ModuleWithProviders} from '@angular/core';
|
||||
import * as internal from './internal';
|
||||
export {InternalRouterModule} from './internal';
|
||||
|
||||
declare class RouterModule {
|
||||
declare export class RouterModule {
|
||||
static forRoot(): ModuleWithProviders<internal.InternalRouterModule>;
|
||||
}
|
||||
|
||||
`);
|
||||
|
||||
env.write('node_modules/router/internal.d.ts', `
|
||||
@ -1195,40 +1197,81 @@ describe('ngtsc behavioral tests', () => {
|
||||
// Success is enough to indicate that this passes.
|
||||
});
|
||||
|
||||
it('should not emit multiple references to the same directive', () => {
|
||||
env.tsconfig();
|
||||
env.write('node_modules/external/index.d.ts', `
|
||||
import {ɵDirectiveDefWithMeta, ɵNgModuleDefWithMeta} from '@angular/core';
|
||||
describe('when processing external directives', () => {
|
||||
it('should not emit multiple references to the same directive', () => {
|
||||
env.tsconfig();
|
||||
env.write('node_modules/external/index.d.ts', `
|
||||
import {ɵDirectiveDefWithMeta, ɵNgModuleDefWithMeta} from '@angular/core';
|
||||
|
||||
export declare class ExternalDir {
|
||||
static ngDirectiveDef: ɵDirectiveDefWithMeta<ExternalDir, '[test]', never, never, never, never>;
|
||||
}
|
||||
|
||||
export declare class ExternalModule {
|
||||
static ngModuleDef: ɵNgModuleDefWithMeta<ExternalModule, [typeof ExternalDir], never, [typeof ExternalDir]>;
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
import {ExternalModule} from 'external';
|
||||
|
||||
@Component({
|
||||
template: '<div test></div>',
|
||||
})
|
||||
class Cmp {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Cmp],
|
||||
// Multiple imports of the same module used to result in duplicate directive references
|
||||
// in the output.
|
||||
imports: [ExternalModule, ExternalModule],
|
||||
})
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
export declare class ExternalDir {
|
||||
static ngDirectiveDef: ɵDirectiveDefWithMeta<ExternalDir, '[test]', never, never, never, never>;
|
||||
}
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toMatch(/directives: \[i1\.ExternalDir\]/);
|
||||
});
|
||||
|
||||
export declare class ExternalModule {
|
||||
static ngModuleDef: ɵNgModuleDefWithMeta<ExternalModule, [typeof ExternalDir], never, [typeof ExternalDir]>;
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
import {ExternalModule} from 'external';
|
||||
it('should import directives by their external name', () => {
|
||||
env.tsconfig();
|
||||
env.write('node_modules/external/index.d.ts', `
|
||||
import {ɵDirectiveDefWithMeta, ɵNgModuleDefWithMeta} from '@angular/core';
|
||||
import {InternalDir} from './internal';
|
||||
|
||||
@Component({
|
||||
template: '<div test></div>',
|
||||
})
|
||||
class Cmp {}
|
||||
export {InternalDir as ExternalDir} from './internal';
|
||||
|
||||
@NgModule({
|
||||
declarations: [Cmp],
|
||||
// Multiple imports of the same module used to result in duplicate directive references
|
||||
// in the output.
|
||||
imports: [ExternalModule, ExternalModule],
|
||||
})
|
||||
class Module {}
|
||||
`);
|
||||
export declare class ExternalModule {
|
||||
static ngModuleDef: ɵNgModuleDefWithMeta<ExternalModule, [typeof InternalDir], never, [typeof InternalDir]>;
|
||||
}
|
||||
`);
|
||||
env.write('node_modules/external/internal.d.ts', `
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toMatch(/directives: \[i1\.ExternalDir\]/);
|
||||
export declare class InternalDir {
|
||||
static ngDirectiveDef: ɵDirectiveDefWithMeta<InternalDir, '[test]', never, never, never, never>;
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
import {ExternalModule} from 'external';
|
||||
|
||||
@Component({
|
||||
template: '<div test></div>',
|
||||
})
|
||||
class Cmp {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Cmp],
|
||||
imports: [ExternalModule],
|
||||
})
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toMatch(/directives: \[i1\.ExternalDir\]/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flat module indices', () => {
|
||||
|
Reference in New Issue
Block a user