fix(ivy): never use imported type references as values (#29111)

ngtsc occasionally converts a type reference (such as the type of a
parameter in a constructor) to a value reference (argument to a
directiveInject call). TypeScript has a bad habit of sometimes removing
the import statement associated with this type reference, because it's a
type only import when it initially looks at the file.

A solution to this is to always add an import to refer to a type position
value that's imported, and not rely on the existing import.

PR Close #29111
This commit is contained in:
Alex Rickabaugh
2019-03-04 11:43:55 -08:00
committed by Andrew Kushnir
parent 20a9dbef8e
commit 881807dc36
15 changed files with 308 additions and 87 deletions

View File

@ -116,12 +116,38 @@ describe('reflector', () => {
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(2);
expectParameter(args[0], 'bar', 'Bar');
expectParameter(args[1], 'otherBar', 'star.Bar');
expectParameter(args[0], 'bar', {moduleName: './bar', name: 'Bar'});
expectParameter(args[1], 'otherBar', {moduleName: './bar', name: 'Bar'});
});
it('should reflect an argument from an aliased import', () => {
const {program} = makeProgram([
{
name: 'bar.ts',
contents: `
export class Bar {}
`
},
{
name: 'entry.ts',
contents: `
import {Bar as LocalBar} from './bar';
it('should reflect an nullable argument', () => {
class Foo {
constructor(bar: LocalBar) {}
}
`
}
]);
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(1);
expectParameter(args[0], 'bar', {moduleName: './bar', name: 'Bar'});
});
it('should reflect a nullable argument', () => {
const {program} = makeProgram([
{
name: 'bar.ts',
@ -145,7 +171,7 @@ describe('reflector', () => {
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(1);
expectParameter(args[0], 'bar', 'Bar');
expectParameter(args[0], 'bar', {moduleName: './bar', name: 'Bar'});
});
});
@ -193,14 +219,24 @@ describe('reflector', () => {
});
function expectParameter(
param: CtorParameter, name: string, type?: string, decorator?: string,
decoratorFrom?: string): void {
param: CtorParameter, name: string, type?: string | {name: string, moduleName: string},
decorator?: string, decoratorFrom?: string): void {
expect(param.name !).toEqual(name);
if (type === undefined) {
expect(param.typeExpression).toBeNull();
expect(param.typeValueReference).toBeNull();
} else {
expect(param.typeExpression).not.toBeNull();
expect(argExpressionToString(param.typeExpression !)).toEqual(type);
if (param.typeValueReference === null) {
return fail(`Expected parameter ${name} to have a typeValueReference`);
}
if (param.typeValueReference.local && typeof type === 'string') {
expect(argExpressionToString(param.typeValueReference.expression)).toEqual(type);
} else if (!param.typeValueReference.local && typeof type !== 'string') {
expect(param.typeValueReference.moduleName).toEqual(type.moduleName);
expect(param.typeValueReference.name).toEqual(type.name);
} else {
return fail(
`Mismatch between typeValueReference and expected type: ${param.name} / ${param.typeValueReference.local}`);
}
}
if (decorator !== undefined) {
expect(param.decorators).not.toBeNull();