fix(ngcc): correctly match aliased classes between src and dts files (#34254)
The naïve matching algorithm we previously used to match declarations in source files to declarations in typings files was based only on the name of the thing being declared. This did not handle cases where the declared item had been exported via an alias - a common scenario when one of the two file sets (source or typings) has been flattened, while the other has not. The new algorithm tries to overcome this by creating two maps of export name to declaration (i.e. `Map<string, ts.Declaration>`). One for the source files and one for the typings files. It then joins these two together by matching export names, resulting in a new map that maps source declarations to typings declarations directly (i.e. `Map<ts.Declaration, ts.Declaration>`). This new map can handle the declaration names being different between the source and typings as long as they are ultimately both exported with the same alias name. Further more, there is one map for "public exports", i.e. exported via the root of the source tree (the entry-point), and another map for "private exports", which are exported from individual files in the source tree but not necessarily from the root. This second map can be used to "guess" the mapping between exports in a deep (non-flat) file tree, which can be used by ngcc to add required private exports to the entry-point. Fixes #33593 PR Close #34254
This commit is contained in:

committed by
Kara Erickson

parent
e9fb5fdb89
commit
f22a6eb00e
@ -10,12 +10,12 @@ import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
|
||||
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {ClassMemberKind, CtorParameter, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {ClassMemberKind, CtorParameter, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {getRootFiles, makeTestBundleProgram} from '../helpers/utils';
|
||||
import {getRootFiles, makeTestBundleProgram, makeTestDtsBundleProgram} from '../helpers/utils';
|
||||
|
||||
import {expectTypeValueReferencesForParameters} from './util';
|
||||
|
||||
@ -568,8 +568,12 @@ runInEachFileSystem(() => {
|
||||
{name: _('/ep/src/missing-class.js'), contents: 'export class MissingClass2 {}'},
|
||||
{
|
||||
name: _('/ep/src/flat-file.js'),
|
||||
contents:
|
||||
'export class Class1 {}\nexport class MissingClass1 {}\nexport class MissingClass2 {}\class Class3 {}\nexport {Class3 as xClass3};',
|
||||
contents: `
|
||||
export class Class1 {}
|
||||
export class MissingClass1 {}
|
||||
export class MissingClass2 {}
|
||||
class SourceClass {}
|
||||
export {SourceClass as AliasedClass};`,
|
||||
},
|
||||
{
|
||||
name: _('/ep/src/shadow-class.js'),
|
||||
@ -594,20 +598,25 @@ runInEachFileSystem(() => {
|
||||
},
|
||||
{
|
||||
name: _('/ep/typings/class2.d.ts'),
|
||||
contents:
|
||||
`export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';`
|
||||
contents: `
|
||||
export declare class Class2 {}
|
||||
export declare interface SomeInterface {}
|
||||
export {TypingsClass as AliasedClass} from './typings-class';
|
||||
`
|
||||
},
|
||||
{name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'},
|
||||
{
|
||||
name: _('/ep/typings/internal.d.ts'),
|
||||
contents: `export declare class InternalClass {}\nexport declare class Class2 {}`
|
||||
},
|
||||
{name: _('/ep/typings/class3.d.ts'), contents: `export declare class Class3 {}`},
|
||||
{
|
||||
name: _('/ep/typings/typings-class.d.ts'),
|
||||
contents: `export declare class TypingsClass {}`
|
||||
},
|
||||
{name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`},
|
||||
{name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'},
|
||||
];
|
||||
|
||||
|
||||
MODULE_WITH_PROVIDERS_PROGRAM = [
|
||||
{
|
||||
name: _('/src/index.js'),
|
||||
@ -1967,8 +1976,8 @@ runInEachFileSystem(() => {
|
||||
it('should find the dts declaration for exported functions', () => {
|
||||
loadTestFiles(TYPINGS_SRC_FILES);
|
||||
loadTestFiles(TYPINGS_DTS_FILES);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
||||
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
||||
const bundle = makeTestBundleProgram(_('/ep/src/func1.js'));
|
||||
const dts = makeTestDtsBundleProgram(_('/ep/typings/func1.d.ts'), _('/'));
|
||||
const mooFn = getDeclaration(
|
||||
bundle.program, _('/ep/src/func1.js'), 'mooFn', isNamedFunctionDeclaration);
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts);
|
||||
@ -2023,7 +2032,7 @@ runInEachFileSystem(() => {
|
||||
() => {
|
||||
loadTestFiles(TYPINGS_SRC_FILES);
|
||||
loadTestFiles(TYPINGS_DTS_FILES);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
||||
const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js'));
|
||||
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
||||
const class1 = getDeclaration(
|
||||
bundle.program, _('/ep/src/flat-file.js'), 'Class1', isNamedClassDeclaration);
|
||||
@ -2036,14 +2045,22 @@ runInEachFileSystem(() => {
|
||||
it('should find aliased exports', () => {
|
||||
loadTestFiles(TYPINGS_SRC_FILES);
|
||||
loadTestFiles(TYPINGS_DTS_FILES);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
||||
const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js'));
|
||||
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
||||
const class3 = getDeclaration(
|
||||
bundle.program, _('/ep/src/flat-file.js'), 'Class3', isNamedClassDeclaration);
|
||||
const sourceClass = getDeclaration(
|
||||
bundle.program, _('/ep/src/flat-file.js'), 'SourceClass', isNamedClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclaration(class3);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual(_('/ep/typings/class3.d.ts'));
|
||||
const dtsDeclaration = host.getDtsDeclaration(sourceClass);
|
||||
if (dtsDeclaration === null) {
|
||||
return fail('Expected dts class to be found');
|
||||
}
|
||||
if (!isNamedClassDeclaration(dtsDeclaration)) {
|
||||
return fail('Expected a named class to be found.');
|
||||
}
|
||||
expect(dtsDeclaration.name.text).toEqual('TypingsClass');
|
||||
expect(_(dtsDeclaration.getSourceFile().fileName))
|
||||
.toEqual(_('/ep/typings/typings-class.d.ts'));
|
||||
});
|
||||
|
||||
it('should find the dts file that contains a matching class declaration, even if the class is not publicly exported',
|
||||
@ -2061,25 +2078,25 @@ runInEachFileSystem(() => {
|
||||
.toEqual(_('/ep/typings/internal.d.ts'));
|
||||
});
|
||||
|
||||
it('should prefer the publicly exported class if there are multiple classes with the same name',
|
||||
it('should match publicly and internal exported classes correctly, even if they have the same name',
|
||||
() => {
|
||||
loadTestFiles(TYPINGS_SRC_FILES);
|
||||
loadTestFiles(TYPINGS_DTS_FILES);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
||||
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
||||
const class2 = getDeclaration(
|
||||
bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedClassDeclaration);
|
||||
const internalClass2 = getDeclaration(
|
||||
bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts);
|
||||
|
||||
const class2 = getDeclaration(
|
||||
bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedClassDeclaration);
|
||||
const class2DtsDeclaration = host.getDtsDeclaration(class2);
|
||||
expect(class2DtsDeclaration !.getSourceFile().fileName)
|
||||
.toEqual(_('/ep/typings/class2.d.ts'));
|
||||
|
||||
const internalClass2 = getDeclaration(
|
||||
bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedClassDeclaration);
|
||||
const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2);
|
||||
expect(internalClass2DtsDeclaration !.getSourceFile().fileName)
|
||||
.toEqual(_('/ep/typings/class2.d.ts'));
|
||||
.toEqual(_('/ep/typings/internal.d.ts'));
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user