fix(ivy): avoid missing imports for types that can be represented as values (#28941)

Prior to this change, TypeScript stripped out some imports in case we reference a type that can be represented as a value (for ex. classes). This fix ensures that we use correct symbol identifier, which makes TypeScript retain the necessary import statements.

PR Close #28941
This commit is contained in:
Andrew Kushnir
2019-02-22 18:06:25 -08:00
parent 91a161aa13
commit 772b24ccc3
3 changed files with 116 additions and 7 deletions

View File

@ -56,6 +56,7 @@ export class NgtscTestEnvironment {
env.write('tsconfig-base.json', `{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"noImplicitAny": true,

View File

@ -26,6 +26,9 @@ const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string):
return new RegExp(`i0\\.ɵcontentQuery\\(dirIndex, ${predicate}, ${descend}, ${maybeRef}\\)`);
};
const setClassMetadataRegExp = (expectedType: string): RegExp =>
new RegExp(`setClassMetadata(.*?${expectedType}.*?)`);
describe('ngtsc behavioral tests', () => {
let env !: NgtscTestEnvironment;
@ -1843,6 +1846,97 @@ describe('ngtsc behavioral tests', () => {
expect(jsContents).toContain('ɵsetClassMetadata(TestPipe, ');
});
it('should use imported types in setClassMetadata if they can be represented as values', () => {
env.tsconfig({});
env.write(`types.ts`, `
export class MyTypeA {}
export class MyTypeB {}
`);
env.write(`test.ts`, `
import {Component, Inject, Injectable} from '@angular/core';
import {MyTypeA, MyTypeB} from './types';
@Injectable({providedIn: 'root'})
export class SomeService {
constructor(arg: MyTypeA) {}
}
@Component({
selector: 'some-comp',
template: '...',
})
export class SomeComp {
constructor(@Inject('arg-token') arg: MyTypeB) {}
}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain(`import { MyTypeA, MyTypeB } from './types';`);
expect(jsContents).toMatch(setClassMetadataRegExp('type: MyTypeA'));
expect(jsContents).toMatch(setClassMetadataRegExp('type: MyTypeB'));
});
it('should use imported types in setClassMetadata if they can be represented as values and imported as `* as foo`',
() => {
env.tsconfig({});
env.write(`types.ts`, `
export class MyTypeA {}
export class MyTypeB {}
`);
env.write(`test.ts`, `
import {Component, Inject, Injectable} from '@angular/core';
import * as types from './types';
@Injectable({providedIn: 'root'})
export class SomeService {
constructor(arg: types.MyTypeA) {}
}
@Component({
selector: 'some-comp',
template: '...',
})
export class SomeComp {
constructor(@Inject('arg-token') arg: types.MyTypeB) {}
}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain(`import * as types from './types';`);
expect(jsContents).toMatch(setClassMetadataRegExp('type: types.MyTypeA'));
expect(jsContents).toMatch(setClassMetadataRegExp('type: types.MyTypeB'));
});
it('should use `undefined` in setClassMetadata if types can\'t be represented as values', () => {
env.tsconfig({});
env.write(`types.ts`, `
export type MyType = Map<any, any>;
`);
env.write(`test.ts`, `
import {Component, Inject, Injectable} from '@angular/core';
import {MyType} from './types';
@Component({
selector: 'some-comp',
template: '...',
})
export class SomeComp {
constructor(@Inject('arg-token') arg: MyType) {}
}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).not.toContain(`import { MyType } from './types';`);
// Note: `type: undefined` below, since MyType can't be represented as a value
expect(jsContents).toMatch(setClassMetadataRegExp('type: undefined'));
});
it('should not throw in case whitespaces and HTML comments are present inside <ng-content>',
() => {
env.tsconfig();