fix(ivy): generate type references to a default import (#29146)

This commit refactors and expands ngtsc's support for generating imports of
values from imports of types (this is used for example when importing a
class referenced in a type annotation in a constructor).

Previously, this logic handled "import {Foo} from" and "import * as foo
from" style imports, but failed on imports of default values ("import
Foo from"). This commit moves the type-to-value logic to a separate file and
expands it to cover the default import case. Doing this also required
augmenting the ImportManager to track default as well as non-default import
generation. The APIs were made a little cleaner at the same time.

PR Close #29146
This commit is contained in:
Alex Rickabaugh
2019-03-06 16:35:08 -08:00
committed by Kara Erickson
parent 37c5a26421
commit b6f6b1178f
14 changed files with 349 additions and 156 deletions

View File

@ -23,9 +23,17 @@ export class EsmRenderer extends Renderer {
/**
* Add the imports at the top of the file
*/
addImports(output: MagicString, imports: {name: string; as: string;}[]): void {
addImports(output: MagicString, imports: {
specifier: string; qualifier: string; isDefault: boolean
}[]): void {
// The imports get inserted at the very top of the file.
imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); });
imports.forEach(i => {
if (!i.isDefault) {
output.appendLeft(0, `import * as ${i.qualifier} from '${i.specifier}';\n`);
} else {
output.appendLeft(0, `import ${i.qualifier} from '${i.specifier}';\n`);
}
});
}
addExports(output: MagicString, entryPointBasePath: string, exports: {

View File

@ -245,7 +245,9 @@ export abstract class Renderer {
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
void;
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
protected abstract addImports(
output: MagicString,
imports: {specifier: string, qualifier: string, isDefault: boolean}[]): void;
protected abstract addExports(output: MagicString, entryPointBasePath: string, exports: {
identifier: string,
from: string

View File

@ -115,13 +115,24 @@ describe('Esm2015Renderer', () => {
it('should insert the given imports at the start of the source file', () => {
const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output, [{name: '@angular/core', as: 'i0'}, {name: '@angular/common', as: 'i1'}]);
renderer.addImports(output, [
{specifier: '@angular/core', qualifier: 'i0', isDefault: false},
{specifier: '@angular/common', qualifier: 'i1', isDefault: false}
]);
expect(output.toString()).toContain(`import * as i0 from '@angular/core';
import * as i1 from '@angular/common';
/* A copyright notice */`);
});
it('should insert a default import at the start of the source file', () => {
const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addImports(output, [
{specifier: 'test', qualifier: 'i0', isDefault: true},
]);
expect(output.toString()).toContain(`import i0 from 'test';`);
});
});
describe('addExports', () => {

View File

@ -152,13 +152,24 @@ describe('Esm5Renderer', () => {
it('should insert the given imports at the start of the source file', () => {
const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output, [{name: '@angular/core', as: 'i0'}, {name: '@angular/common', as: 'i1'}]);
renderer.addImports(output, [
{specifier: '@angular/core', qualifier: 'i0', isDefault: false},
{specifier: '@angular/common', qualifier: 'i1', isDefault: false}
]);
expect(output.toString()).toContain(`import * as i0 from '@angular/core';
import * as i1 from '@angular/common';
/* A copyright notice */`);
});
it('should insert a default import at the start of the source file', () => {
const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addImports(output, [
{specifier: 'test', qualifier: 'i0', isDefault: true},
]);
expect(output.toString()).toContain(`import i0 from 'test';`);
});
});
describe('addExports', () => {

View File

@ -23,7 +23,8 @@ class TestRenderer extends Renderer {
constructor(host: Esm2015ReflectionHost, isCore: boolean, bundle: EntryPointBundle) {
super(host, isCore, bundle, '/src', '/dist');
}
addImports(output: MagicString, imports: {name: string, as: string}[]) {
addImports(
output: MagicString, imports: {specifier: string, qualifier: string, isDefault: boolean}[]) {
output.prepend('\n// ADD IMPORTS\n');
}
addExports(output: MagicString, baseEntryPointPath: string, exports: {
@ -171,7 +172,7 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(addImportsSpy.calls.first().args[1]).toEqual([
{name: '@angular/core', as: 'ɵngcc0'}
{specifier: '@angular/core', qualifier: 'ɵngcc0', isDefault: false}
]);
});
@ -287,7 +288,9 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
expect(addDefinitionsSpy.calls.first().args[2])
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[1]).toEqual([{name: './r3_symbols', as: 'ɵngcc0'}]);
expect(addImportsSpy.calls.first().args[1]).toEqual([
{specifier: './r3_symbols', qualifier: 'ɵngcc0', isDefault: false}
]);
});
it('should render no imports in FESM bundles', () => {
@ -502,9 +505,9 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
expect(renderer.addImports).toHaveBeenCalledWith(jasmine.any(MagicString), [
{name: './module', as: 'ɵngcc0'},
{name: '@angular/core', as: 'ɵngcc1'},
{name: 'some-library', as: 'ɵngcc2'},
{specifier: './module', qualifier: 'ɵngcc0', isDefault: false},
{specifier: '@angular/core', qualifier: 'ɵngcc1', isDefault: false},
{specifier: 'some-library', qualifier: 'ɵngcc2', isDefault: false},
]);