Skip to content

Commit

Permalink
fix(compiler-cli): support ModuleWithProviders literal detection wi…
Browse files Browse the repository at this point in the history
…th `typeof` (#54650)

As part of improving test safety of the compiler, I've noticed that
we have a special pass for detecting external `ModuleWithProviders`
where we detect the module type from an object literal.

This literal is structured like the following: `{ngModule: T}`. The
detection currently takes `T` directly, but in practice it should be
`typeof T` to satisfy the `ModuleWithProviders` type that is accepted
as part of `Component#imports`.

This commit adds support for this, so that we can fix the unit test
in preparation for using the real Angular core types in ngtsc tests.

PR Close #54650
  • Loading branch information
devversion authored and pkozlowski-opensource committed Mar 6, 2024
1 parent bd60fb1 commit 5afa4f0
Show file tree
Hide file tree
Showing 4 changed files with 14 additions and 5 deletions.
Expand Up @@ -11,7 +11,7 @@ import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {Reference} from '../../../imports';
import {ForeignFunctionResolver, SyntheticValue} from '../../../partial_evaluator';
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost, typeNodeToValueExpr} from '../../../reflection';
import {ClassDeclaration, entityNameToValue, isNamedClassDeclaration, ReflectionHost, typeNodeToValueExpr} from '../../../reflection';

/**
* Creates a foreign function resolver to detect a `ModuleWithProviders<T>` type in a return type
Expand Down Expand Up @@ -92,7 +92,16 @@ export function createModuleWithProvidersResolver(
const ngModuleType = ts.isPropertySignature(m) && ts.isIdentifier(m.name) &&
m.name.text === 'ngModule' && m.type ||
null;
const ngModuleExpression = ngModuleType && typeNodeToValueExpr(ngModuleType);

let ngModuleExpression: ts.Expression|null = null;

// Handle `: typeof X` or `: X` cases.
if (ngModuleType !== null && ts.isTypeQueryNode(ngModuleType)) {
ngModuleExpression = entityNameToValue(ngModuleType.exprName);
} else if (ngModuleType !== null) {
ngModuleExpression = typeNodeToValueExpr(ngModuleType);
}

if (ngModuleExpression) {
return ngModuleExpression;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/reflection/index.ts
Expand Up @@ -7,6 +7,6 @@
*/

export * from './src/host';
export {typeNodeToValueExpr} from './src/type_to_value';
export {typeNodeToValueExpr, entityNameToValue} from './src/type_to_value';
export {TypeScriptReflectionHost, filterToMembersWithDecorator, reflectIdentifierOfDeclaration, reflectNameOfDeclaration, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/typescript';
export {isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from './src/util';
Expand Up @@ -272,7 +272,7 @@ function resolveTypeSymbols(typeRef: ts.TypeReferenceNode, checker: ts.TypeCheck
return {local, decl, symbolNames};
}

function entityNameToValue(node: ts.EntityName): ts.Expression|null {
export function entityNameToValue(node: ts.EntityName): ts.Expression|null {
if (ts.isQualifiedName(node)) {
const left = entityNameToValue(node.left);
return left !== null ? ts.factory.createPropertyAccessExpression(left, node.right) : null;
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Expand Up @@ -3824,7 +3824,7 @@ function allTests(os: string) {
export interface MyType extends ModuleWithProviders {}
declare class RouterModule {
static forRoot(): (MyType)&{ngModule:RouterModule};
static forRoot(): (MyType)&{ngModule: typeof RouterModule};
static ɵmod: ɵɵNgModuleDeclaration<RouterModule, never, never, never>;
}
`);
Expand Down

0 comments on commit 5afa4f0

Please sign in to comment.