Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 76d7188

Browse files
JoostKAndrewKushnir
authored andcommitted
fix(ngcc): consistently delegate to TypeScript host for typing files (#36089)
When ngcc is compiling an entry-point, it uses a `ReflectionHost` that is specific to its format, e.g. ES2015, ES5, UMD or CommonJS. During the compilation of that entry-point however, the reflector may be used to reflect into external libraries using their declaration files. Up until now this was achieved by letting all `ReflectionHost` classes consider their parent class for reflector queries, thereby ending up in the `TypeScriptReflectionHost` that is a common base class for all reflector hosts. This approach has proven to be prone to bugs, as failing to call into the base class would cause incompatibilities with reading from declaration files. The observation can be made that there's only two distinct kinds of reflection host queries: 1. the reflector query is about code that is part of the entry-point that is being compiled, or 2. the reflector query is for an external library that the entry-point depends on, in which case the information is reflected from the declaration files. The `ReflectionHost` that was chosen for the entry-point should serve only reflector queries for the first case, whereas a regular `TypeScriptReflectionHost` should be used for the second case. This avoids the problem where a format-specific `ReflectionHost` fails to handle the second case correctly, as it isn't even considered for such reflector queries. This commit introduces a `ReflectionHost` that delegates to the `TypeScriptReflectionHost` for AST nodes within declaration files, otherwise delegating to the format-specific `ReflectionHost`. Fixes #35078 Resolves FW-1859 PR Close #36089
1 parent daa2179 commit 76d7188

File tree

8 files changed

+229
-136
lines changed

8 files changed

+229
-136
lines changed

‎packages/compiler-cli/ngcc/src/host/commonjs_host.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
3434
}
3535

3636
getImportOfIdentifier(id: ts.Identifier): Import|null {
37-
const superImport = super.getImportOfIdentifier(id);
38-
if (superImport !== null) {
39-
return superImport;
40-
}
41-
4237
const requireCall = this.findCommonJsImport(id);
4338
if (requireCall === null) {
4439
return null;
@@ -47,8 +42,7 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
4742
}
4843

4944
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
50-
return (!id.getSourceFile().isDeclarationFile && this.getCommonJsImportedDeclaration(id)) ||
51-
super.getDeclarationOfIdentifier(id);
45+
return this.getCommonJsImportedDeclaration(id) || super.getDeclarationOfIdentifier(id);
5246
}
5347

5448
getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as ts from 'typescript';
10+
11+
import {ClassDeclaration, ClassMember, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost} from '../../../src/ngtsc/reflection';
12+
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
13+
14+
import {ModuleWithProvidersFunction, NgccClassSymbol, NgccReflectionHost, SwitchableVariableDeclaration} from './ngcc_host';
15+
16+
/**
17+
* A reflection host implementation that delegates reflector queries depending on whether they
18+
* reflect on declaration files (for dependent libraries) or source files within the entry-point
19+
* that is being compiled. The first type of queries are handled by the regular TypeScript
20+
* reflection host, whereas the other queries are handled by an `NgccReflectionHost` that is
21+
* specific to the entry-point's format.
22+
*/
23+
export class DelegatingReflectionHost implements NgccReflectionHost {
24+
constructor(private tsHost: ReflectionHost, private ngccHost: NgccReflectionHost) {}
25+
26+
getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null {
27+
if (isFromDtsFile(clazz)) {
28+
return this.tsHost.getConstructorParameters(clazz);
29+
}
30+
return this.ngccHost.getConstructorParameters(clazz);
31+
}
32+
33+
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
34+
if (isFromDtsFile(id)) {
35+
return this.tsHost.getDeclarationOfIdentifier(id);
36+
}
37+
return this.ngccHost.getDeclarationOfIdentifier(id);
38+
}
39+
40+
getDecoratorsOfDeclaration(declaration: ts.Declaration): Decorator[]|null {
41+
if (isFromDtsFile(declaration)) {
42+
return this.tsHost.getDecoratorsOfDeclaration(declaration);
43+
}
44+
return this.ngccHost.getDecoratorsOfDeclaration(declaration);
45+
}
46+
47+
getDefinitionOfFunction(fn: ts.Node): FunctionDefinition|null {
48+
if (isFromDtsFile(fn)) {
49+
return this.tsHost.getDefinitionOfFunction(fn);
50+
}
51+
return this.ngccHost.getDefinitionOfFunction(fn);
52+
}
53+
54+
getDtsDeclaration(declaration: ts.Declaration): ts.Declaration|null {
55+
if (isFromDtsFile(declaration)) {
56+
return this.tsHost.getDtsDeclaration(declaration);
57+
}
58+
return this.ngccHost.getDtsDeclaration(declaration);
59+
}
60+
61+
getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {
62+
if (isFromDtsFile(module)) {
63+
return this.tsHost.getExportsOfModule(module);
64+
}
65+
return this.ngccHost.getExportsOfModule(module);
66+
}
67+
68+
getGenericArityOfClass(clazz: ClassDeclaration): number|null {
69+
if (isFromDtsFile(clazz)) {
70+
return this.tsHost.getGenericArityOfClass(clazz);
71+
}
72+
return this.ngccHost.getGenericArityOfClass(clazz);
73+
}
74+
75+
getImportOfIdentifier(id: ts.Identifier): Import|null {
76+
if (isFromDtsFile(id)) {
77+
return this.tsHost.getImportOfIdentifier(id);
78+
}
79+
return this.ngccHost.getImportOfIdentifier(id);
80+
}
81+
82+
getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
83+
if (isFromDtsFile(clazz)) {
84+
return this.tsHost.getInternalNameOfClass(clazz);
85+
}
86+
return this.ngccHost.getInternalNameOfClass(clazz);
87+
}
88+
89+
getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier {
90+
if (isFromDtsFile(clazz)) {
91+
return this.tsHost.getAdjacentNameOfClass(clazz);
92+
}
93+
return this.ngccHost.getAdjacentNameOfClass(clazz);
94+
}
95+
96+
getMembersOfClass(clazz: ClassDeclaration): ClassMember[] {
97+
if (isFromDtsFile(clazz)) {
98+
return this.tsHost.getMembersOfClass(clazz);
99+
}
100+
return this.ngccHost.getMembersOfClass(clazz);
101+
}
102+
103+
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null {
104+
if (isFromDtsFile(declaration)) {
105+
return this.tsHost.getVariableValue(declaration);
106+
}
107+
return this.ngccHost.getVariableValue(declaration);
108+
}
109+
110+
hasBaseClass(clazz: ClassDeclaration): boolean {
111+
if (isFromDtsFile(clazz)) {
112+
return this.tsHost.hasBaseClass(clazz);
113+
}
114+
return this.ngccHost.hasBaseClass(clazz);
115+
}
116+
117+
getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
118+
if (isFromDtsFile(clazz)) {
119+
return this.tsHost.getBaseClassExpression(clazz);
120+
}
121+
return this.ngccHost.getBaseClassExpression(clazz);
122+
}
123+
124+
isClass(node: ts.Node): node is ClassDeclaration {
125+
if (isFromDtsFile(node)) {
126+
return this.tsHost.isClass(node);
127+
}
128+
return this.ngccHost.isClass(node);
129+
}
130+
131+
// Note: the methods below are specific to ngcc and the entry-point that is being compiled, so
132+
// they don't take declaration files into account.
133+
134+
findClassSymbols(sourceFile: ts.SourceFile): NgccClassSymbol[] {
135+
return this.ngccHost.findClassSymbols(sourceFile);
136+
}
137+
138+
getClassSymbol(node: ts.Node): NgccClassSymbol|undefined {
139+
return this.ngccHost.getClassSymbol(node);
140+
}
141+
142+
getDecoratorsOfSymbol(symbol: NgccClassSymbol): Decorator[]|null {
143+
return this.ngccHost.getDecoratorsOfSymbol(symbol);
144+
}
145+
146+
getModuleWithProvidersFunctions(sf: ts.SourceFile): ModuleWithProvidersFunction[] {
147+
return this.ngccHost.getModuleWithProvidersFunctions(sf);
148+
}
149+
150+
getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[] {
151+
return this.ngccHost.getSwitchableDeclarations(module);
152+
}
153+
154+
getEndOfClass(classSymbol: NgccClassSymbol): ts.Node {
155+
return this.ngccHost.getEndOfClass(classSymbol);
156+
}
157+
}

‎packages/compiler-cli/ngcc/src/host/esm5_host.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
4141
* @param clazz a `ClassDeclaration` representing the class over which to reflect.
4242
*/
4343
hasBaseClass(clazz: ClassDeclaration): boolean {
44-
if (super.hasBaseClass(clazz)) return true;
45-
4644
const classSymbol = this.getClassSymbol(clazz);
4745
if (classSymbol === undefined) {
4846
return false;
@@ -58,11 +56,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
5856
}
5957

6058
getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
61-
const superBaseClassIdentifier = super.getBaseClassExpression(clazz);
62-
if (superBaseClassIdentifier) {
63-
return superBaseClassIdentifier;
64-
}
65-
6659
const classSymbol = this.getClassSymbol(clazz);
6760
if (classSymbol === undefined) {
6861
return null;

‎packages/compiler-cli/ngcc/src/host/umd_host.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
3333
}
3434

3535
getImportOfIdentifier(id: ts.Identifier): Import|null {
36-
const superImport = super.getImportOfIdentifier(id);
37-
if (superImport !== null) {
38-
return superImport;
39-
}
40-
4136
// Is `id` a namespaced property access, e.g. `Directive` in `core.Directive`?
4237
// If so capture the symbol of the namespace, e.g. `core`.
4338
const nsIdentifier = findNamespaceOfIdentifier(id);
@@ -47,8 +42,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
4742
}
4843

4944
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
50-
return (!id.getSourceFile().isDeclarationFile && this.getUmdImportedDeclaration(id)) ||
51-
super.getDeclarationOfIdentifier(id);
45+
return this.getUmdImportedDeclaration(id) || super.getDeclarationOfIdentifier(id);
5246
}
5347

5448
getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {

‎packages/compiler-cli/ngcc/src/packages/transformer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
import * as ts from 'typescript';
99

1010
import {FileSystem} from '../../../src/ngtsc/file_system';
11+
import {TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
1112
import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
1213
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analysis/module_with_providers_analyzer';
1314
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
1415
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
1516
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
1617
import {CompiledFile} from '../analysis/types';
1718
import {CommonJsReflectionHost} from '../host/commonjs_host';
19+
import {DelegatingReflectionHost} from '../host/delegating_host';
1820
import {Esm2015ReflectionHost} from '../host/esm2015_host';
1921
import {Esm5ReflectionHost} from '../host/esm5_host';
2022
import {NgccReflectionHost} from '../host/ngcc_host';
@@ -69,7 +71,9 @@ export class Transformer {
6971
* @returns information about the files that were transformed.
7072
*/
7173
transform(bundle: EntryPointBundle): TransformResult {
72-
const reflectionHost = this.getHost(bundle);
74+
const ngccReflectionHost = this.getHost(bundle);
75+
const tsReflectionHost = new TypeScriptReflectionHost(bundle.src.program.getTypeChecker());
76+
const reflectionHost = new DelegatingReflectionHost(tsReflectionHost, ngccReflectionHost);
7377

7478
// Parse and analyze the files.
7579
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
@@ -81,7 +85,7 @@ export class Transformer {
8185
}
8286

8387
// Transform the source files and source maps.
84-
const srcFormatter = this.getRenderingFormatter(reflectionHost, bundle);
88+
const srcFormatter = this.getRenderingFormatter(ngccReflectionHost, bundle);
8589

8690
const renderer = new Renderer(reflectionHost, srcFormatter, this.fs, this.logger, bundle);
8791
let renderedFiles = renderer.renderProgram(

‎packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,30 +1606,6 @@ exports.ExternalModule = ExternalModule;
16061606
expect(importOfIdent).toEqual({name: 'a', from: './file_a'});
16071607
});
16081608

1609-
it('should find the import of an identifier in a declaration file', () => {
1610-
loadTestFiles([
1611-
{
1612-
name: _('/index.d.ts'),
1613-
contents: `
1614-
import {MyClass} from './myclass.d.ts';
1615-
export declare const a: MyClass;`
1616-
},
1617-
{
1618-
name: _('/myclass.d.ts'),
1619-
contents: `export declare class MyClass {}`,
1620-
}
1621-
]);
1622-
const bundle = makeTestBundleProgram(_('/index.d.ts'));
1623-
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
1624-
const variableNode =
1625-
getDeclaration(bundle.program, _('/index.d.ts'), 'a', isNamedVariableDeclaration);
1626-
const identifier =
1627-
((variableNode.type as ts.TypeReferenceNode).typeName as ts.Identifier);
1628-
1629-
const importOfIdent = host.getImportOfIdentifier(identifier !);
1630-
expect(importOfIdent).toEqual({name: 'MyClass', from: './myclass.d.ts'});
1631-
});
1632-
16331609
it('should return null if the identifier was not imported', () => {
16341610
loadTestFiles(IMPORTS_FILES);
16351611
const bundle = makeTestBundleProgram(_('/index.js'));
@@ -1813,39 +1789,6 @@ exports.ExternalModule = ExternalModule;
18131789
expect(importOfIdent.viaModule).toBe('lib');
18141790
});
18151791

1816-
it('should return the correct declaration of an identifier imported in a typings file',
1817-
() => {
1818-
const files = [
1819-
{
1820-
name: _('/node_modules/test-package/index.d.ts'),
1821-
contents: `
1822-
import {SubModule} from 'sub_module';
1823-
export const x = SubModule;
1824-
`,
1825-
},
1826-
{
1827-
name: _('/node_modules/sub_module/index.d.ts'),
1828-
contents: 'export class SubModule {}',
1829-
}
1830-
];
1831-
loadTestFiles(files);
1832-
const bundle = makeTestBundleProgram(files[0].name);
1833-
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
1834-
const expectedDeclaration = getDeclaration(
1835-
bundle.program, files[1].name, 'SubModule', isNamedClassDeclaration);
1836-
const x =
1837-
getDeclaration(bundle.program, files[0].name, 'x', isNamedVariableDeclaration);
1838-
if (x.initializer === undefined || !ts.isIdentifier(x.initializer)) {
1839-
return fail('Expected constant `x` to have an identifer as an initializer.');
1840-
}
1841-
const decl = host.getDeclarationOfIdentifier(x.initializer);
1842-
if (decl === null) {
1843-
return fail('Expected to find a declaration for ' + x.initializer.getText());
1844-
}
1845-
expect(decl.viaModule).toEqual('sub_module');
1846-
expect(decl.node).toBe(expectedDeclaration);
1847-
});
1848-
18491792
it('should recognize TypeScript helpers (as function declarations)', () => {
18501793
const file: TestFile = {
18511794
name: _('/test.js'),

‎packages/compiler-cli/ngcc/test/host/umd_host_spec.ts

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,29 +1775,6 @@ runInEachFileSystem(() => {
17751775
expect(importOfIdent).toEqual({name: 'a', from: './file_a'});
17761776
});
17771777

1778-
it('should find the import of an identifier in a declaration file', () => {
1779-
loadTestFiles([
1780-
{
1781-
name: _('/index.d.ts'),
1782-
contents: `
1783-
import {MyClass} from './myclass.d.ts';
1784-
export declare const a: MyClass;`
1785-
},
1786-
{
1787-
name: _('/myclass.d.ts'),
1788-
contents: `export declare class MyClass {}`,
1789-
}
1790-
]);
1791-
const bundle = makeTestBundleProgram(_('/index.d.ts'));
1792-
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
1793-
const variableNode =
1794-
getDeclaration(bundle.program, _('/index.d.ts'), 'a', isNamedVariableDeclaration);
1795-
const identifier = ((variableNode.type as ts.TypeReferenceNode).typeName as ts.Identifier);
1796-
1797-
const importOfIdent = host.getImportOfIdentifier(identifier !);
1798-
expect(importOfIdent).toEqual({name: 'MyClass', from: './myclass.d.ts'});
1799-
});
1800-
18011778
it('should return null if the identifier was not imported', () => {
18021779
loadTestFiles(IMPORTS_FILES);
18031780
const bundle = makeTestBundleProgram(_('/index.js'));
@@ -1940,39 +1917,6 @@ runInEachFileSystem(() => {
19401917
expect(actualDeclaration !.viaModule).toBe('@angular/core');
19411918
});
19421919

1943-
it('should return the correct declaration of an identifier imported in a typings file',
1944-
() => {
1945-
1946-
const FILES = [
1947-
{
1948-
name: _('/node_modules/test-package/index.d.ts'),
1949-
contents: `
1950-
import {SubModule} from 'sub_module';
1951-
export const x = SubModule;
1952-
`,
1953-
},
1954-
{
1955-
name: _('/node_modules/sub_module/index.d.ts'),
1956-
contents: `export class SubModule {}`,
1957-
}
1958-
];
1959-
loadTestFiles(FILES);
1960-
const bundle = makeTestBundleProgram(FILES[0].name);
1961-
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
1962-
const expectedDeclaration =
1963-
getDeclaration(bundle.program, FILES[1].name, 'SubModule', isNamedClassDeclaration);
1964-
const x = getDeclaration(bundle.program, FILES[0].name, 'x', isNamedVariableDeclaration);
1965-
if (x.initializer === undefined || !ts.isIdentifier(x.initializer)) {
1966-
return fail('Expected constant `x` to have an identifer as an initializer.');
1967-
}
1968-
const decl = host.getDeclarationOfIdentifier(x.initializer);
1969-
if (decl === null) {
1970-
return fail('Expected to find a declaration for ' + x.initializer.getText());
1971-
}
1972-
expect(decl.viaModule).toEqual('sub_module');
1973-
expect(decl.node).toBe(expectedDeclaration);
1974-
});
1975-
19761920
it('should recognize TypeScript helpers (as function declarations)', () => {
19771921
const file: TestFile = {
19781922
name: _('/test.js'),

0 commit comments

Comments
 (0)