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

@ -895,7 +895,13 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
paramInfo[index] :
{decorators: null, typeExpression: null};
const nameNode = node.name;
return {name: getNameText(nameNode), nameNode, typeExpression, typeNode: null, decorators};
return {
name: getNameText(nameNode),
nameNode,
typeValueReference:
typeExpression !== null ? {local: true as true, expression: typeExpression} : null,
typeNode: null, decorators
};
});
}

View File

@ -12,6 +12,8 @@ import {ClassMemberKind, Import} from '../../../ngtsc/reflection';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {convertToDirectTsLibImport, getDeclaration, makeTestProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
const FILES = [
{
name: '/some_directive.js',
@ -262,8 +264,10 @@ describe('Fesm2015ReflectionHost [import helper style]', () => {
expect(parameters !.map(parameter => parameter.name)).toEqual([
'_viewContainer', '_template', 'injected'
]);
expect(parameters !.map(parameter => parameter.typeExpression !.getText())).toEqual([
'ViewContainerRef', 'TemplateRef', 'String'
expectTypeValueReferencesForParameters(parameters !, [
'ViewContainerRef',
'TemplateRef',
'String',
]);
});
@ -296,7 +300,10 @@ describe('Fesm2015ReflectionHost [import helper style]', () => {
const classNode = getDeclaration(
program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration);
const ctrDecorators = host.getConstructorParameters(classNode) !;
const identifierOfViewContainerRef = ctrDecorators[0].typeExpression !as ts.Identifier;
const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference !as{
local: true,
expression: ts.Identifier
}).expression;
const expectedDeclarationNode = getDeclaration(
program, '/some_directive.js', 'ViewContainerRef', ts.isClassDeclaration);

View File

@ -12,6 +12,8 @@ import {ClassMemberKind, Import} from '../../../ngtsc/reflection';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
const SOME_DIRECTIVE_FILE = {
name: '/some_directive.js',
contents: `
@ -930,9 +932,7 @@ describe('Fesm2015ReflectionHost', () => {
expect(parameters.map(parameter => parameter.name)).toEqual([
'_viewContainer', '_template', 'injected'
]);
expect(parameters.map(parameter => parameter.typeExpression !.getText())).toEqual([
'ViewContainerRef', 'TemplateRef', 'undefined'
]);
expectTypeValueReferencesForParameters(parameters, ['ViewContainerRef', 'TemplateRef', null]);
});
it('should throw if the symbol is not a class', () => {
@ -1293,7 +1293,10 @@ describe('Fesm2015ReflectionHost', () => {
const classNode =
getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration);
const ctrDecorators = host.getConstructorParameters(classNode) !;
const identifierOfViewContainerRef = ctrDecorators[0].typeExpression !as ts.Identifier;
const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference !as{
local: true,
expression: ts.Identifier
}).expression;
const expectedDeclarationNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', ts.isVariableDeclaration);

View File

@ -12,6 +12,8 @@ import {ClassMemberKind, Import} from '../../../ngtsc/reflection';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {convertToDirectTsLibImport, getDeclaration, makeTestProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
const FILES = [
{
name: '/some_directive.js',
@ -277,8 +279,10 @@ describe('Esm5ReflectionHost [import helper style]', () => {
expect(parameters !.map(parameter => parameter.name)).toEqual([
'_viewContainer', '_template', 'injected'
]);
expect(parameters !.map(parameter => parameter.typeExpression !.getText())).toEqual([
'ViewContainerRef', 'TemplateRef', 'String'
expectTypeValueReferencesForParameters(parameters !, [
'ViewContainerRef',
'TemplateRef',
'String',
]);
});
@ -332,7 +336,10 @@ describe('Esm5ReflectionHost [import helper style]', () => {
const classNode = getDeclaration(
program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration);
const ctrDecorators = host.getConstructorParameters(classNode) !;
const identifierOfViewContainerRef = ctrDecorators[0].typeExpression !as ts.Identifier;
const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference !as{
local: true,
expression: ts.Identifier
}).expression;
const expectedDeclarationNode = getDeclaration(
program, '/some_directive.js', 'ViewContainerRef', ts.isVariableDeclaration);

View File

@ -13,6 +13,8 @@ import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {getDeclaration, makeTestProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
const SOME_DIRECTIVE_FILE = {
name: '/some_directive.js',
contents: `
@ -918,8 +920,10 @@ describe('Esm5ReflectionHost', () => {
expect(parameters !.map(parameter => parameter.name)).toEqual([
'_viewContainer', '_template', 'injected'
]);
expect(parameters !.map(parameter => parameter.typeExpression !.getText())).toEqual([
'ViewContainerRef', 'TemplateRef', 'undefined'
expectTypeValueReferencesForParameters(parameters !, [
'ViewContainerRef',
'TemplateRef',
null,
]);
});
@ -1251,7 +1255,10 @@ describe('Esm5ReflectionHost', () => {
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration);
const ctrDecorators = host.getConstructorParameters(classNode) !;
const identifierOfViewContainerRef = ctrDecorators[0].typeExpression !as ts.Identifier;
const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference !as{
local: true,
expression: ts.Identifier
}).expression;
const expectedDeclarationNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', ts.isVariableDeclaration);

View File

@ -0,0 +1,31 @@
/**
* @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 * as ts from 'typescript';
import {CtorParameter} from '../../../ngtsc/reflection';
/**
* Check that a given list of `CtorParameter`s has `typeValueReference`s of specific `ts.Identifier`
* names.
*/
export function expectTypeValueReferencesForParameters(
parameters: CtorParameter[], expectedParams: (string | null)[]) {
parameters !.forEach((param, idx) => {
const expected = expectedParams[idx];
if (expected !== null) {
if (param.typeValueReference === null || !param.typeValueReference.local ||
!ts.isIdentifier(param.typeValueReference.expression)) {
fail(`Incorrect typeValueReference generated, expected ${expected}`);
} else {
expect(param.typeValueReference.expression.text).toEqual(expected);
}
}
});
}