Skip to content

Commit 89a5647

Browse files
committed
refactor(compiler-cli): resolve types when reading .d.ts metadata
1 parent c762ff3 commit 89a5647

5 files changed

Lines changed: 20 additions & 235 deletions

File tree

packages/compiler-cli/src/ngtsc/metadata/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ ts_project(
1212
"//packages/compiler",
1313
"//packages/compiler-cli/src/ngtsc/file_system",
1414
"//packages/compiler-cli/src/ngtsc/imports",
15-
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
1615
"//packages/compiler-cli/src/ngtsc/reflection",
1716
"//packages/compiler-cli/src/ngtsc/util",
1817
],

packages/compiler-cli/src/ngtsc/metadata/src/dts.ts

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
ReflectionHost,
1717
TypeValueReferenceKind,
1818
} from '../../reflection';
19-
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator/index';
2019
import {nodeDebugInfo} from '../../util/src/typescript';
2120

2221
import {
@@ -43,14 +42,10 @@ import {
4342
* from an upstream compilation already.
4443
*/
4544
export class DtsMetadataReader implements MetadataReader {
46-
private evaluator: PartialEvaluator;
47-
4845
constructor(
4946
private checker: ts.TypeChecker,
5047
private reflector: ReflectionHost,
51-
) {
52-
this.evaluator = new PartialEvaluator(this.reflector, this.checker, null);
53-
}
48+
) {}
5449

5550
/**
5651
* Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
@@ -81,16 +76,19 @@ export class DtsMetadataReader implements MetadataReader {
8176
// Read the ModuleData out of the type arguments.
8277
const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
8378

84-
const declarations = this.extractReferencesFromResolvedValue(
85-
this.evaluator.evaluateType(declarationMetadata, ref.bestGuessOwningModule),
79+
const declarations = extractReferencesFromType(
80+
this.checker,
81+
declarationMetadata,
8682
ref.bestGuessOwningModule,
8783
);
88-
const exports = this.extractReferencesFromResolvedValue(
89-
this.evaluator.evaluateType(exportMetadata, ref.bestGuessOwningModule),
84+
const exports = extractReferencesFromType(
85+
this.checker,
86+
exportMetadata,
9087
ref.bestGuessOwningModule,
9188
);
92-
const imports = this.extractReferencesFromResolvedValue(
93-
this.evaluator.evaluateType(importMetadata, ref.bestGuessOwningModule),
89+
const imports = extractReferencesFromType(
90+
this.checker,
91+
importMetadata,
9492
ref.bestGuessOwningModule,
9593
);
9694

@@ -118,30 +116,6 @@ export class DtsMetadataReader implements MetadataReader {
118116
};
119117
}
120118

121-
private extractReferencesFromResolvedValue(
122-
value: ResolvedValue,
123-
bestGuessOwningModule: OwningModule | null,
124-
): {result: Reference<ClassDeclaration>[]; isIncomplete: boolean} {
125-
const result: Reference<ClassDeclaration>[] = [];
126-
let isIncomplete = false;
127-
128-
if (Array.isArray(value)) {
129-
for (const element of value) {
130-
if (element instanceof Reference && this.reflector.isClass(element.node)) {
131-
result.push(element as Reference<ClassDeclaration>);
132-
} else {
133-
isIncomplete = true;
134-
}
135-
}
136-
} else if (value instanceof Reference && this.reflector.isClass(value.node)) {
137-
result.push(value as Reference<ClassDeclaration>);
138-
} else {
139-
isIncomplete = true;
140-
}
141-
142-
return {result, isIncomplete};
143-
}
144-
145119
/**
146120
* Read directive (or component) metadata from a referenced class in a .d.ts file.
147121
*/

packages/compiler-cli/src/ngtsc/partial_evaluator/src/interface.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import ts from 'typescript';
1010

11-
import {OwningModule, Reference} from '../../imports';
11+
import {Reference} from '../../imports';
1212
import {DependencyTracker} from '../../incremental/api';
1313
import {ReflectionHost} from '../../reflection';
1414

@@ -41,20 +41,4 @@ export class PartialEvaluator {
4141
foreignFunctionResolver,
4242
});
4343
}
44-
45-
evaluateType(
46-
typeNode: ts.TypeNode,
47-
owningModule: OwningModule | null = null,
48-
foreignFunctionResolver?: ForeignFunctionResolver,
49-
): ResolvedValue {
50-
const interpreter = new StaticInterpreter(this.host, this.checker, this.dependencyTracker);
51-
const sourceFile = typeNode.getSourceFile();
52-
return interpreter.visitType(typeNode, {
53-
originatingFile: sourceFile,
54-
absoluteModuleName: owningModule ? owningModule.specifier : null,
55-
resolutionContext: owningModule ? owningModule.resolutionContext : sourceFile.fileName,
56-
scope: new Map<ts.ParameterDeclaration, ResolvedValue>(),
57-
foreignFunctionResolver,
58-
});
59-
}
6044
}

packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts

Lines changed: 9 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,7 @@ import ts from 'typescript';
1111
import {Reference} from '../../imports';
1212
import {OwningModule} from '../../imports/src/references';
1313
import {DependencyTracker} from '../../incremental/api';
14-
import {
15-
Declaration,
16-
DeclarationNode,
17-
FunctionDefinition,
18-
ReflectionHost,
19-
reflectTypeEntityToDeclaration,
20-
} from '../../reflection';
21-
import {TypeEntityToDeclarationError} from '../../reflection/src/typescript';
14+
import {Declaration, DeclarationNode, FunctionDefinition, ReflectionHost} from '../../reflection';
2215
import {isDeclaration} from '../../util/src/typescript';
2316

2417
import {ArrayConcatBuiltinFn, ArraySliceBuiltinFn, StringConcatBuiltinFn} from './builtin';
@@ -714,7 +707,7 @@ export class StaticInterpreter {
714707
return new Reference(node, owningModule(context));
715708
}
716709

717-
public visitType(node: ts.TypeNode, context: Context): ResolvedValue {
710+
private visitType(node: ts.TypeNode, context: Context): ResolvedValue {
718711
if (ts.isLiteralTypeNode(node)) {
719712
return this.visitExpression(node.literal, context);
720713
} else if (ts.isTupleTypeNode(node)) {
@@ -725,8 +718,6 @@ export class StaticInterpreter {
725718
return this.visitType(node.type, context);
726719
} else if (ts.isTypeQueryNode(node)) {
727720
return this.visitTypeQuery(node, context);
728-
} else if (ts.isTypeReferenceNode(node)) {
729-
return this.visitTypeReference(node, context);
730721
}
731722

732723
return DynamicValue.fromDynamicType(node);
@@ -743,74 +734,18 @@ export class StaticInterpreter {
743734
}
744735

745736
private visitTypeQuery(node: ts.TypeQueryNode, context: Context): ResolvedValue {
746-
try {
747-
const result = reflectTypeEntityToDeclaration(node.exprName, this.checker);
748-
const declNode = result.node;
749-
const from = result.from;
750-
751-
let declContext = context;
752-
if (from !== null && !from.startsWith('.')) {
753-
declContext = {
754-
...context,
755-
absoluteModuleName: from,
756-
resolutionContext: node.getSourceFile().fileName,
757-
};
758-
}
759-
760-
return this.visitDeclaration(declNode, declContext);
761-
} catch (e) {
762-
if (e instanceof TypeEntityToDeclarationError) {
763-
return DynamicValue.fromUnknown(node);
764-
}
765-
throw e;
766-
}
767-
}
768-
769-
private visitTypeReference(node: ts.TypeReferenceNode, context: Context): ResolvedValue {
770-
const typeName = ts.isQualifiedName(node.typeName) ? node.typeName.right : node.typeName;
771-
if (!ts.isIdentifier(typeName)) {
737+
const exprName = ts.isQualifiedName(node.exprName) ? node.exprName.right : node.exprName;
738+
if (!ts.isIdentifier(exprName)) {
772739
return DynamicValue.fromUnknown(node);
773740
}
774741

775-
if (
776-
typeName.text === 'ReturnType' &&
777-
node.typeArguments !== undefined &&
778-
node.typeArguments.length === 1
779-
) {
780-
const typeArg = node.typeArguments[0];
781-
const evaluatedTypeArg = this.visitType(typeArg, context);
782-
783-
if (evaluatedTypeArg instanceof Reference) {
784-
const decl = evaluatedTypeArg.node;
785-
if (ts.isFunctionDeclaration(decl) || ts.isMethodDeclaration(decl)) {
786-
const rawType = decl.type;
787-
if (rawType !== undefined) {
788-
if (ts.isTypeReferenceNode(rawType)) {
789-
const mwpTypeName = ts.isQualifiedName(rawType.typeName)
790-
? rawType.typeName.right
791-
: rawType.typeName;
792-
if (ts.isIdentifier(mwpTypeName) && mwpTypeName.text === 'ModuleWithProviders') {
793-
if (rawType.typeArguments !== undefined && rawType.typeArguments.length === 1) {
794-
const arg = rawType.typeArguments[0];
795-
return this.visitType(arg, context);
796-
}
797-
}
798-
}
799-
}
800-
}
801-
}
802-
}
803-
804-
// Handle general type reference to a class!
805-
const decl = this.host.getDeclarationOfIdentifier(typeName);
806-
if (decl !== null && this.host.isClass(decl.node)) {
807-
return this.getReference(decl.node, {
808-
...context,
809-
...joinModuleContext(context, typeName, decl),
810-
});
742+
const decl = this.host.getDeclarationOfIdentifier(exprName);
743+
if (decl === null) {
744+
return DynamicValue.fromUnknownIdentifier(exprName);
811745
}
812746

813-
return DynamicValue.fromDynamicType(node);
747+
const declContext: Context = {...context, ...joinModuleContext(context, node, decl)};
748+
return this.visitDeclaration(decl.node, declContext);
814749
}
815750
}
816751

packages/compiler-cli/test/ngtsc/scope_spec.ts

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -253,113 +253,6 @@ runInEachFileSystem(() => {
253253
expect(error.code).toEqual(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
254254
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAClass');
255255
});
256-
257-
it('should support importing a variable that resolves to an array of modules from a .d.ts dependency', () => {
258-
env.write(
259-
'dep.d.ts',
260-
`
261-
import {ɵɵNgModuleDeclaration, ɵɵComponentDeclaration} from '@angular/core';
262-
export declare class CompA {
263-
static ɵcmp: ɵɵComponentDeclaration<CompA, 'comp-a', never, {}, {}, never, never, true, never>;
264-
}
265-
export declare class ModuleA {
266-
static ɵmod: ɵɵNgModuleDeclaration<ModuleA, [typeof CompA], never, [typeof CompA]>;
267-
}
268-
export declare const FOO: readonly [typeof ModuleA];
269-
`,
270-
);
271-
env.write(
272-
'lib.d.ts',
273-
`
274-
import {ɵɵNgModuleDeclaration} from '@angular/core';
275-
import {FOO} from './dep';
276-
export declare class LibModule {
277-
static ɵmod: ɵɵNgModuleDeclaration<LibModule, never, never, typeof FOO>;
278-
}
279-
`,
280-
);
281-
env.write(
282-
'test.ts',
283-
`
284-
import {Component, NgModule} from '@angular/core';
285-
import {LibModule} from './lib';
286-
287-
@Component({
288-
selector: 'test-cmp',
289-
template: '<comp-a></comp-a>',
290-
standalone: false,
291-
})
292-
export class TestCmp {}
293-
294-
@NgModule({
295-
declarations: [TestCmp],
296-
imports: [LibModule],
297-
})
298-
export class AppModule {}
299-
`,
300-
);
301-
302-
const diags = env.driveDiagnostics();
303-
expect(diags.length).toBe(0);
304-
});
305-
306-
it('should support ReturnType for forRoot calls in .d.ts files', () => {
307-
env.write(
308-
'dep.d.ts',
309-
`
310-
import {ɵɵNgModuleDeclaration, ɵɵComponentDeclaration} from '@angular/core';
311-
import {ModuleWithProviders} from '@angular/core';
312-
export declare class CompA {
313-
static ɵcmp: ɵɵComponentDeclaration<CompA, 'comp-a', never, {}, {}, never, never, true, never>;
314-
}
315-
export declare class ModuleA {
316-
static ɵmod: ɵɵNgModuleDeclaration<ModuleA, [typeof CompA], never, [typeof CompA]>;
317-
}
318-
export declare class FooModule {
319-
static forRoot(): ModuleWithProviders<ModuleA>;
320-
}
321-
`,
322-
);
323-
env.write(
324-
'lib.d.ts',
325-
`
326-
import {ɵɵNgModuleDeclaration} from '@angular/core';
327-
import {FooModule} from './dep';
328-
export declare class LibModule {
329-
static ɵmod: ɵɵNgModuleDeclaration<LibModule, never, never, [ReturnType<typeof FooModule.forRoot>]>;
330-
}
331-
`,
332-
);
333-
env.write(
334-
'test.ts',
335-
`
336-
import {Component, NgModule} from '@angular/core';
337-
import {LibModule} from './lib';
338-
339-
@Component({
340-
selector: 'test-cmp',
341-
template: '<comp-a></comp-a>',
342-
standalone: false,
343-
})
344-
export class TestCmp {}
345-
346-
@NgModule({
347-
declarations: [TestCmp],
348-
imports: [LibModule],
349-
})
350-
export class AppModule {}
351-
`,
352-
);
353-
354-
const diags = env.driveDiagnostics();
355-
if (diags.length > 0) {
356-
console.error(
357-
'DIAGNOSTIC:',
358-
ts.flattenDiagnosticMessageText(diags[0].messageText, '\n'),
359-
);
360-
}
361-
expect(diags.length).toBe(0);
362-
});
363256
});
364257

365258
describe('exports', () => {

0 commit comments

Comments
 (0)