fix(compiler-cli): transform type references in generic type parameter default (#42492)
When a component/directive has a generic type parameter, the template type checker attempts to translate the type parameter such that the type parameters can be replicated in the type constructor that is emitted into the typecheck file. Type parameters with a default clause would incorrectly be emitted into the typecheck file using the original `ts.TypeNode` for the default clause, such that `ts.TypeReferenceNode`s within the default clause would likely be invalid (i.e. referencing a type for which no import is present in the typecheck file). This did not result in user-facing type-check errors as errors reported in type constructors are not translated into template positions Regardless, this commit ensures that `ts.TypeReferenceNode`s within defaults are properly translated into the typecheck file. PR Close #42492
This commit is contained in:
parent
16aaa23d4e
commit
729eea5716
|
@ -32,14 +32,18 @@ export class TypeParameterEmitter {
|
|||
}
|
||||
|
||||
return this.typeParameters.every(typeParam => {
|
||||
if (typeParam.constraint === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return canEmitType(typeParam.constraint, type => this.resolveTypeReference(type));
|
||||
return this.canEmitType(typeParam.constraint) && this.canEmitType(typeParam.default);
|
||||
});
|
||||
}
|
||||
|
||||
private canEmitType(type: ts.TypeNode|undefined): boolean {
|
||||
if (type === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return canEmitType(type, typeReference => this.resolveTypeReference(typeReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the type parameters using the provided emitter function for `Reference`s.
|
||||
*/
|
||||
|
@ -53,12 +57,14 @@ export class TypeParameterEmitter {
|
|||
return this.typeParameters.map(typeParam => {
|
||||
const constraint =
|
||||
typeParam.constraint !== undefined ? emitter.emitType(typeParam.constraint) : undefined;
|
||||
const defaultType =
|
||||
typeParam.default !== undefined ? emitter.emitType(typeParam.default) : undefined;
|
||||
|
||||
return ts.updateTypeParameterDeclaration(
|
||||
/* node */ typeParam,
|
||||
/* name */ typeParam.name,
|
||||
/* constraint */ constraint,
|
||||
/* defaultType */ typeParam.default);
|
||||
/* defaultType */ defaultType);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -222,5 +222,32 @@ runInEachFileSystem(() => {
|
|||
expect(emitter.canEmit()).toBe(true);
|
||||
expect(emit(emitter)).toEqual('<T extends test.MyType>');
|
||||
});
|
||||
|
||||
it('transforms generic type parameter defaults', () => {
|
||||
const additionalFiles: TestFile[] = [{
|
||||
name: absoluteFrom('/node_modules/types/index.d.ts'),
|
||||
contents: `export declare type MyType = string;`,
|
||||
}];
|
||||
const emitter = createEmitter(
|
||||
`
|
||||
import {MyType} from 'types';
|
||||
|
||||
export class TestClass<T extends MyType = MyType> {}`,
|
||||
additionalFiles);
|
||||
|
||||
expect(emitter.canEmit()).toBe(true);
|
||||
expect(emit(emitter)).toEqual('<T extends test.MyType = test.MyType>');
|
||||
});
|
||||
|
||||
it('cannot emit when a type parameter default cannot be emitted', () => {
|
||||
const emitter = createEmitter(`
|
||||
interface Local {}
|
||||
|
||||
export class TestClass<T extends object = Local> {}`);
|
||||
|
||||
expect(emitter.canEmit()).toBe(false);
|
||||
expect(() => emit(emitter))
|
||||
.toThrowError('A type reference to emit must be imported from an absolute module');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue