diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_emitter.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_emitter.ts index 6bc966b411..4c7ed9151f 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_emitter.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_emitter.ts @@ -10,7 +10,8 @@ import {Reference} from '../../imports'; /** * A resolved type reference can either be a `Reference`, the original `ts.TypeReferenceNode` itself - * or null to indicate the no reference could be resolved. + * or null. A value of null indicates that no reference could be resolved or that the reference can + * not be emitted. */ export type ResolvedTypeReference = Reference|ts.TypeReferenceNode|null; @@ -69,10 +70,9 @@ export function canEmitType(type: ts.TypeNode, resolver: TypeReferenceResolver): return false; } - // If the type is a reference without a owning module, consider the type not to be eligible for - // emitting. - if (reference instanceof Reference && !reference.hasOwningModuleGuess) { - return false; + // If the type is a reference, consider the type to be eligible for emitting. + if (reference instanceof Reference) { + return true; } // The type can be emitted if either it does not have any type arguments, or all of them can be @@ -157,10 +157,6 @@ export class TypeEmitter { // Emit the type name. let typeName = type.typeName; if (reference instanceof Reference) { - if (!reference.hasOwningModuleGuess) { - throw new Error('A type reference to emit must be imported from an absolute module'); - } - const emittedType = this.emitReference(reference); if (!ts.isTypeReferenceNode(emittedType)) { throw new Error(`Expected TypeReferenceNode for emitted reference, got ${ diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_parameter_emitter.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_parameter_emitter.ts index 018b36a3f8..61a5f6d972 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_parameter_emitter.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_parameter_emitter.ts @@ -8,7 +8,7 @@ import * as ts from 'typescript'; import {OwningModule, Reference} from '../../imports'; -import {DeclarationNode, ReflectionHost} from '../../reflection'; +import {DeclarationNode, isNamedClassDeclaration, ReflectionHost} from '../../reflection'; import {canEmitType, ResolvedTypeReference, TypeEmitter} from './type_emitter'; @@ -92,9 +92,19 @@ export class TypeParameterEmitter { }; } + // If no owning module is known, the reference needs to be exported to be able to emit an import + // statement for it. If the declaration is not exported, null is returned to prevent emit. + if (owningModule === null && !this.isStaticallyExported(declaration.node)) { + return null; + } + return new Reference(declaration.node, owningModule); } + private isStaticallyExported(decl: DeclarationNode): boolean { + return isNamedClassDeclaration(decl) && this.reflector.isStaticallyExported(decl); + } + private isLocalTypeParameter(decl: DeclarationNode): boolean { // Checking for local type parameters only occurs during resolution of type parameters, so it is // guaranteed that type parameters are present. diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_parameter_emitter_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_parameter_emitter_spec.ts index 4857964057..ed56a33a50 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_parameter_emitter_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_parameter_emitter_spec.ts @@ -116,26 +116,34 @@ runInEachFileSystem(() => { expect(emit(emitter)).toEqual('>'); }); - it('cannot emit references to local declarations', () => { + it('can emit references to local, exported declarations', () => { const emitter = createEmitter(` - export class Local {}; + class Local {}; + export {Local}; + export class TestClass {}`); + + expect(emitter.canEmit()).toBe(true); + expect(emit(emitter)).toEqual(''); + }); + + it('cannot emit references to non-exported local declarations', () => { + const emitter = createEmitter(` + class Local {}; export class TestClass {}`); expect(emitter.canEmit()).toBe(false); - expect(() => emit(emitter)) - .toThrowError('A type reference to emit must be imported from an absolute module'); + expect(() => emit(emitter)).toThrowError('Unable to emit an unresolved reference'); }); it('cannot emit references to local declarations as nested type arguments', () => { const emitter = createEmitter(` import {NgIterable} from '@angular/core'; - export class Local {}; + class Local {}; export class TestClass> {}`); expect(emitter.canEmit()).toBe(false); - expect(() => emit(emitter)) - .toThrowError('A type reference to emit must be imported from an absolute module'); + expect(() => emit(emitter)).toThrowError('Unable to emit an unresolved reference'); }); it('can emit references into external modules within array types', () => { @@ -150,15 +158,14 @@ runInEachFileSystem(() => { it('cannot emit references to local declarations within array types', () => { const emitter = createEmitter(` - export class Local {}; + class Local {}; export class TestClass {}`); expect(emitter.canEmit()).toBe(false); - expect(() => emit(emitter)) - .toThrowError('A type reference to emit must be imported from an absolute module'); + expect(() => emit(emitter)).toThrowError('Unable to emit an unresolved reference'); }); - it('cannot emit references into relative files', () => { + it('can emit references into relative files', () => { const additionalFiles: TestFile[] = [{ name: absoluteFrom('/internal.ts'), contents: `export class Internal {}`, @@ -170,9 +177,8 @@ runInEachFileSystem(() => { export class TestClass {}`, additionalFiles); - expect(emitter.canEmit()).toBe(false); - expect(() => emit(emitter)) - .toThrowError('A type reference to emit must be imported from an absolute module'); + expect(emitter.canEmit()).toBe(true); + expect(emit(emitter)).toEqual(''); }); it('can emit references to interfaces', () => { @@ -246,8 +252,7 @@ runInEachFileSystem(() => { export class TestClass {}`); expect(emitter.canEmit()).toBe(false); - expect(() => emit(emitter)) - .toThrowError('A type reference to emit must be imported from an absolute module'); + expect(() => emit(emitter)).toThrowError('Unable to emit an unresolved reference'); }); }); });