diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts index 7cdd8d424d84e..be80b1248e946 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts @@ -704,6 +704,10 @@ export class StaticInterpreter { return this.visitTupleType(node, context); } else if (ts.isNamedTupleMember(node)) { return this.visitType(node.type, context); + } else if (ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.ReadonlyKeyword) { + return this.visitType(node.type, context); + } else if (ts.isTypeQueryNode(node)) { + return this.visitTypeQuery(node, context); } return DynamicValue.fromDynamicType(node); @@ -718,6 +722,20 @@ export class StaticInterpreter { return res; } + + private visitTypeQuery(node: ts.TypeQueryNode, context: Context): ResolvedValue { + if (!ts.isIdentifier(node.exprName)) { + return DynamicValue.fromUnknown(node); + } + + const decl = this.host.getDeclarationOfIdentifier(node.exprName); + if (decl === null) { + return DynamicValue.fromUnknownIdentifier(node.exprName); + } + + const declContext: Context = {...context, ...joinModuleContext(context, node, decl)}; + return this.visitAmbiguousDeclaration(decl, declContext); + } } function isFunctionOrMethodReference(ref: Reference): diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts index 93183fce49c99..2f1075f4ed057 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts @@ -371,6 +371,38 @@ runInEachFileSystem(() => { expect(evaluate(`declare const x: ['bar'];`, `[...x]`)).toEqual(['bar']); }); + // https://github.com/angular/angular/issues/48089 + it('supports declarations of readonly tuples with class references', () => { + const tuple = evaluate( + ` + import {External} from 'external'; + declare class Local {} + declare const x: readonly [typeof External, typeof Local];`, + `x`, [ + { + name: _('/node_modules/external/index.d.ts'), + contents: 'export declare class External {}' + }, + ]); + if (!Array.isArray(tuple)) { + return fail('Should have evaluated tuple as an array'); + } + const [external, local] = tuple; + if (!(external instanceof Reference)) { + return fail('Should have evaluated `typeof A` to a Reference'); + } + expect(ts.isClassDeclaration(external.node)).toBe(true); + expect(external.debugName).toBe('External'); + expect(external.ownedByModuleGuess).toBe('external'); + + if (!(local instanceof Reference)) { + return fail('Should have evaluated `typeof B` to a Reference'); + } + expect(ts.isClassDeclaration(local.node)).toBe(true); + expect(local.debugName).toBe('Local'); + expect(local.ownedByModuleGuess).toBeNull(); + }); + it('evaluates tuple elements it cannot understand to DynamicValue', () => { const value = evaluate(`declare const x: ['foo', string];`, `x`) as [string, DynamicValue]; diff --git a/packages/compiler-cli/test/ngtsc/standalone_spec.ts b/packages/compiler-cli/test/ngtsc/standalone_spec.ts index 32aacd8773212..fe5c6734b2cc4 100644 --- a/packages/compiler-cli/test/ngtsc/standalone_spec.ts +++ b/packages/compiler-cli/test/ngtsc/standalone_spec.ts @@ -870,6 +870,35 @@ runInEachFileSystem(() => { const jsCode = env.getContents('test.js'); expect(jsCode).toContain('dependencies: [StandalonePipe]'); }); + + it('should compile imports using a const tuple in external library', () => { + env.write('node_modules/external/index.d.ts', ` + import {ɵɵDirectiveDeclaration} from '@angular/core'; + + export declare class StandaloneDir { + static ɵdir: ɵɵDirectiveDeclaration; + } + + export declare const DECLARATIONS: readonly [typeof StandaloneDir]; + `); + env.write('test.ts', ` + import {Component, Directive} from '@angular/core'; + import {DECLARATIONS} from 'external'; + + @Component({ + standalone: true, + selector: 'test-cmp', + template: '
', + imports: [DECLARATIONS], + }) + export class TestCmp {} + `); + env.driveMain(); + + const jsCode = env.getContents('test.js'); + expect(jsCode).toContain('import * as i1 from "external";'); + expect(jsCode).toContain('dependencies: [i1.StandaloneDir]'); + }); }); describe('optimizations', () => {