fix(ivy): ngcc - prefer JavaScript source files when resolving module imports (#30017)

Packages that do not follow APF may have the declaration files in the same
directory as one source format, typically ES5. This is problematic for ngcc,
as it needs to create a TypeScript program with all JavaScript sources of
an entry-point, whereas TypeScript's module resolution mechanism would have
resolved an internal module import to the external facing .d.ts declaration
file, instead of the JavaScript source file. This behavior results in the
program to be analysed being incomplete.

This commit introduces a custom compiler host that recognizes the above
scenario and rewires the resolution of a .d.ts declaration file to its
JavaScript counterpart, if applicable.

Fixes #29939

PR Close #30017
This commit is contained in:
JoostK
2019-04-21 21:29:15 +02:00
committed by Kara Erickson
parent 7ec8806dc5
commit 638ba4a2cf
3 changed files with 165 additions and 5 deletions

View File

@ -0,0 +1,120 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
import {MockFileSystem} from '../helpers/mock_file_system';
function createMockFileSystem() {
return new MockFileSystem({
'/node_modules/test': {
'package.json':
'{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}',
'index.d.ts': 'export * from "./public_api";',
'index.js': 'export * from "./public_api";',
'index.metadata.json': '...',
'public_api.d.ts': `
export * from "test/secondary";
export * from "./nested";
export declare class TestClass {};
`,
'public_api.js': `
export * from "test/secondary";
export * from "./nested";
export const TestClass = function() {};
`,
'root.d.ts': `
import * from 'other';
export declare class RootClass {};
`,
'root.js': `
import * from 'other';
export const RootClass = function() {};
`,
'nested': {
'index.d.ts': 'export * from "../root";',
'index.js': 'export * from "../root";',
},
'es2015': {
'index.js': 'export * from "./public_api";',
'public_api.js': 'export class TestClass {};',
'root.js': `
import * from 'other';
export class RootClass {};
`,
'nested': {
'index.js': 'export * from "../root";',
},
},
'secondary': {
'package.json':
'{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}',
'index.d.ts': 'export * from "./public_api";',
'index.js': 'export * from "./public_api";',
'index.metadata.json': '...',
'public_api.d.ts': 'export declare class SecondaryClass {};',
'public_api.js': 'export class SecondaryClass {};',
'es2015': {
'index.js': 'export * from "./public_api";',
'public_api.js': 'export class SecondaryClass {};',
},
},
},
'/node_modules/other': {
'package.json':
'{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}',
'index.d.ts': 'export * from "./public_api";',
'index.js': 'export * from "./public_api";',
'index.metadata.json': '...',
'public_api.d.ts': 'export declare class OtherClass {};',
'public_api.js': 'export class OtherClass {};',
'es2015': {
'index.js': 'export * from "./public_api";',
'public_api.js': 'export class OtherClass {};',
},
},
});
}
describe('entry point bundle', () => {
// https://github.com/angular/angular/issues/29939
it('should resolve JavaScript sources instead of declaration files if they are adjacent', () => {
const fs = createMockFileSystem();
const esm5bundle = makeEntryPointBundle(
fs, '/node_modules/test', './index.js', './index.d.ts', false, 'esm5', 'esm5', true) !;
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toEqual(jasmine.arrayWithExactContents([
// Modules from the entry-point itself should be source files
'/node_modules/test/index.js',
'/node_modules/test/public_api.js',
'/node_modules/test/nested/index.js',
'/node_modules/test/root.js',
// Modules from a secondary entry-point should be declaration files
'/node_modules/test/secondary/public_api.d.ts',
'/node_modules/test/secondary/index.d.ts',
// Modules resolved from "other" should be declaration files
'/node_modules/other/public_api.d.ts',
'/node_modules/other/index.d.ts',
]));
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
.toEqual(jasmine.arrayWithExactContents([
// All modules in the dts program should be declaration files
'/node_modules/test/index.d.ts',
'/node_modules/test/public_api.d.ts',
'/node_modules/test/nested/index.d.ts',
'/node_modules/test/root.d.ts',
'/node_modules/test/secondary/public_api.d.ts',
'/node_modules/test/secondary/index.d.ts',
'/node_modules/other/public_api.d.ts',
'/node_modules/other/index.d.ts',
]));
});
});