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:

committed by
Andrew Kushnir

parent
20a9dbef8e
commit
881807dc36
@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
31
packages/compiler-cli/src/ngcc/test/host/util.ts
Normal file
31
packages/compiler-cli/src/ngcc/test/host/util.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user