Skip to content

Commit 4d69da5

Browse files
petebacondarwinatscott
authored andcommitted
refactor(ngcc): move shared code into DependencyHostBase (angular#37075)
The various dependency hosts had a lot of duplicated code. This commit refactors them to move this into the base class. PR Close angular#37075
1 parent 7f98b87 commit 4d69da5

File tree

5 files changed

+95
-183
lines changed

5 files changed

+95
-183
lines changed

packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts

Lines changed: 20 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,22 @@ import * as ts from 'typescript';
99

1010
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
1111
import {isRequireCall, isWildcardReexportStatement, RequireCall} from '../host/commonjs_umd_utils';
12+
import {isAssignment, isAssignmentStatement} from '../host/esm2015_host';
1213

1314
import {DependencyHostBase} from './dependency_host';
14-
import {ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
1515

1616
/**
1717
* Helper functions for computing dependencies.
1818
*/
1919
export class CommonJsDependencyHost extends DependencyHostBase {
20-
/**
21-
* Compute the dependencies of the given file.
22-
*
23-
* @param file An absolute path to the file whose dependencies we want to get.
24-
* @param dependencies A set that will have the absolute paths of resolved entry points added to
25-
* it.
26-
* @param missing A set that will have the dependencies that could not be found added to it.
27-
* @param deepImports A set that will have the import paths that exist but cannot be mapped to
28-
* entry-points, i.e. deep-imports.
29-
* @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck
30-
* in a circular dependency loop.
31-
*/
32-
protected recursivelyCollectDependencies(
33-
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
34-
deepImports: Set<AbsoluteFsPath>, alreadySeen: Set<AbsoluteFsPath>): void {
35-
const fromContents = this.fs.readFile(file);
36-
37-
if (!this.hasRequireCalls(fromContents)) {
38-
// Avoid parsing the source file as there are no imports.
39-
return;
40-
}
20+
protected canSkipFile(fileContents: string): boolean {
21+
return !hasRequireCalls(fileContents);
22+
}
4123

24+
protected extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
4225
// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
4326
const sf =
44-
ts.createSourceFile(file, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
27+
ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
4528
const requireCalls: RequireCall[] = [];
4629

4730
for (const stmt of sf.statements) {
@@ -92,37 +75,20 @@ export class CommonJsDependencyHost extends DependencyHostBase {
9275
}
9376
}
9477

95-
const importPaths = new Set(requireCalls.map(call => call.arguments[0].text));
96-
for (const importPath of importPaths) {
97-
const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file);
98-
if (resolvedModule === null) {
99-
missing.add(importPath);
100-
} else if (resolvedModule instanceof ResolvedRelativeModule) {
101-
const internalDependency = resolvedModule.modulePath;
102-
if (!alreadySeen.has(internalDependency)) {
103-
alreadySeen.add(internalDependency);
104-
this.recursivelyCollectDependencies(
105-
internalDependency, dependencies, missing, deepImports, alreadySeen);
106-
}
107-
} else if (resolvedModule instanceof ResolvedDeepImport) {
108-
deepImports.add(resolvedModule.importPath);
109-
} else {
110-
dependencies.add(resolvedModule.entryPointPath);
111-
}
112-
}
78+
return new Set(requireCalls.map(call => call.arguments[0].text));
11379
}
80+
}
11481

115-
/**
116-
* Check whether a source file needs to be parsed for imports.
117-
* This is a performance short-circuit, which saves us from creating
118-
* a TypeScript AST unnecessarily.
119-
*
120-
* @param source The content of the source file to check.
121-
*
122-
* @returns false if there are definitely no require calls
123-
* in this file, true otherwise.
124-
*/
125-
private hasRequireCalls(source: string): boolean {
126-
return /require\(['"]/.test(source);
127-
}
82+
/**
83+
* Check whether a source file needs to be parsed for imports.
84+
* This is a performance short-circuit, which saves us from creating
85+
* a TypeScript AST unnecessarily.
86+
*
87+
* @param source The content of the source file to check.
88+
*
89+
* @returns false if there are definitely no require calls
90+
* in this file, true otherwise.
91+
*/
92+
export function hasRequireCalls(source: string): boolean {
93+
return /require\(['"]/.test(source);
12894
}

packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_s
99
import {EntryPoint} from '../packages/entry_point';
1010
import {resolveFileWithPostfixes} from '../utils';
1111

12-
import {ModuleResolver} from './module_resolver';
12+
import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
1313

1414
export interface DependencyHost {
1515
collectDependencies(
@@ -65,7 +65,54 @@ export abstract class DependencyHostBase implements DependencyHost {
6565
* @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck
6666
* in a circular dependency loop.
6767
*/
68-
protected abstract recursivelyCollectDependencies(
68+
protected recursivelyCollectDependencies(
6969
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
70-
deepImports: Set<AbsoluteFsPath>, alreadySeen: Set<AbsoluteFsPath>): void;
70+
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
71+
const fromContents = this.fs.readFile(file);
72+
if (this.canSkipFile(fromContents)) {
73+
return;
74+
}
75+
const imports = this.extractImports(file, fromContents);
76+
for (const importPath of imports) {
77+
const resolved =
78+
this.processImport(importPath, file, dependencies, missing, deepImports, alreadySeen);
79+
if (!resolved) {
80+
missing.add(importPath);
81+
}
82+
}
83+
}
84+
85+
protected abstract canSkipFile(fileContents: string): boolean;
86+
protected abstract extractImports(file: AbsoluteFsPath, fileContents: string): Set<string>;
87+
88+
/**
89+
* Resolve the given `importPath` from `file` and add it to the appropriate set.
90+
*
91+
* If the import is local to this package then follow it by calling
92+
* `recursivelyCollectDependencies()`.
93+
*
94+
* @returns `true` if the import was resolved (to an entry-point, a local import, or a
95+
* deep-import), `false` otherwise.
96+
*/
97+
protected processImport(
98+
importPath: string, file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>,
99+
missing: Set<string>, deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): boolean {
100+
const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file);
101+
if (resolvedModule === null) {
102+
return false;
103+
}
104+
if (resolvedModule instanceof ResolvedRelativeModule) {
105+
const internalDependency = resolvedModule.modulePath;
106+
if (!alreadySeen.has(internalDependency)) {
107+
alreadySeen.add(internalDependency);
108+
this.recursivelyCollectDependencies(
109+
internalDependency, dependencies, missing, deepImports, alreadySeen);
110+
}
111+
} else if (resolvedModule instanceof ResolvedDeepImport) {
112+
deepImports.add(resolvedModule.importPath);
113+
} else {
114+
dependencies.add(resolvedModule.entryPointPath);
115+
}
116+
return true;
117+
}
71118
}

packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,77 +8,25 @@
88
import * as ts from 'typescript';
99
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
1010
import {DependencyHostBase} from './dependency_host';
11-
import {ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
1211

1312
/**
1413
* Helper functions for computing dependencies.
1514
*/
1615
export class EsmDependencyHost extends DependencyHostBase {
17-
/**
18-
* Compute the dependencies of the given file.
19-
*
20-
* @param file An absolute path to the file whose dependencies we want to get.
21-
* @param dependencies A set that will have the absolute paths of resolved entry points added to
22-
* it.
23-
* @param missing A set that will have the dependencies that could not be found added to it.
24-
* @param deepImports A set that will have the import paths that exist but cannot be mapped to
25-
* entry-points, i.e. deep-imports.
26-
* @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck
27-
* in a circular dependency loop.
28-
*/
29-
protected recursivelyCollectDependencies(
30-
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
31-
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
32-
const fromContents = this.fs.readFile(file);
33-
34-
if (!hasImportOrReexportStatements(fromContents)) {
35-
// Avoid parsing the source file as there are no imports.
36-
return;
37-
}
16+
protected canSkipFile(fileContents: string): boolean {
17+
return !hasImportOrReexportStatements(fileContents);
18+
}
3819

20+
protected extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
21+
const imports: string[] = [];
3922
// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
4023
const sf =
41-
ts.createSourceFile(file, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
42-
sf.statements
43-
// filter out statements that are not imports or reexports
44-
.filter(isStringImportOrReexport)
45-
// Grab the id of the module that is being imported
46-
.map(stmt => stmt.moduleSpecifier.text)
47-
.forEach(importPath => {
48-
const resolved =
49-
this.processImport(importPath, file, dependencies, missing, deepImports, alreadySeen);
50-
if (!resolved) {
51-
missing.add(importPath);
52-
}
53-
});
54-
}
55-
56-
/**
57-
* Resolve the given `importPath` from `file` and add it to the appropriate set.
58-
*
59-
* @returns `true` if the import was resolved (to an entry-point, a local import, or a
60-
* deep-import).
61-
*/
62-
protected processImport(
63-
importPath: string, file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>,
64-
missing: Set<string>, deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): boolean {
65-
const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file);
66-
if (resolvedModule === null) {
67-
return false;
68-
}
69-
if (resolvedModule instanceof ResolvedRelativeModule) {
70-
const internalDependency = resolvedModule.modulePath;
71-
if (!alreadySeen.has(internalDependency)) {
72-
alreadySeen.add(internalDependency);
73-
this.recursivelyCollectDependencies(
74-
internalDependency, dependencies, missing, deepImports, alreadySeen);
75-
}
76-
} else if (resolvedModule instanceof ResolvedDeepImport) {
77-
deepImports.add(resolvedModule.importPath);
78-
} else {
79-
dependencies.add(resolvedModule.entryPointPath);
80-
}
81-
return true;
24+
ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
25+
return new Set(sf.statements
26+
// filter out statements that are not imports or reexports
27+
.filter(isStringImportOrReexport)
28+
// Grab the id of the module that is being imported
29+
.map(stmt => stmt.moduleSpecifier.text));
8230
}
8331
}
8432

packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,85 +6,36 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import * as ts from 'typescript';
9+
910
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
1011
import {getImportsOfUmdModule, parseStatementForUmdModule} from '../host/umd_host';
12+
13+
import {hasRequireCalls} from './commonjs_dependency_host';
1114
import {DependencyHostBase} from './dependency_host';
12-
import {ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
1315

1416
/**
1517
* Helper functions for computing dependencies.
1618
*/
1719
export class UmdDependencyHost extends DependencyHostBase {
18-
/**
19-
* Compute the dependencies of the given file.
20-
*
21-
* @param file An absolute path to the file whose dependencies we want to get.
22-
* @param dependencies A set that will have the absolute paths of resolved entry points added to
23-
* it.
24-
* @param missing A set that will have the dependencies that could not be found added to it.
25-
* @param deepImports A set that will have the import paths that exist but cannot be mapped to
26-
* entry-points, i.e. deep-imports.
27-
* @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck
28-
* in a circular dependency loop.
29-
*/
30-
protected recursivelyCollectDependencies(
31-
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
32-
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
33-
const fromContents = this.fs.readFile(file);
34-
35-
if (!this.hasRequireCalls(fromContents)) {
36-
// Avoid parsing the source file as there are no imports.
37-
return;
38-
}
20+
protected canSkipFile(fileContents: string): boolean {
21+
return !hasRequireCalls(fileContents);
22+
}
3923

24+
protected extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
4025
// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
4126
const sf =
42-
ts.createSourceFile(file, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
27+
ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
4328

4429
if (sf.statements.length !== 1) {
45-
return;
30+
return new Set();
4631
}
4732

4833
const umdModule = parseStatementForUmdModule(sf.statements[0]);
4934
const umdImports = umdModule && getImportsOfUmdModule(umdModule);
5035
if (umdImports === null) {
51-
return;
36+
return new Set();
5237
}
5338

54-
umdImports.forEach(umdImport => {
55-
const resolvedModule = this.moduleResolver.resolveModuleImport(umdImport.path, file);
56-
if (resolvedModule) {
57-
if (resolvedModule instanceof ResolvedRelativeModule) {
58-
const internalDependency = resolvedModule.modulePath;
59-
if (!alreadySeen.has(internalDependency)) {
60-
alreadySeen.add(internalDependency);
61-
this.recursivelyCollectDependencies(
62-
internalDependency, dependencies, missing, deepImports, alreadySeen);
63-
}
64-
} else {
65-
if (resolvedModule instanceof ResolvedDeepImport) {
66-
deepImports.add(resolvedModule.importPath);
67-
} else {
68-
dependencies.add(resolvedModule.entryPointPath);
69-
}
70-
}
71-
} else {
72-
missing.add(umdImport.path);
73-
}
74-
});
75-
}
76-
77-
/**
78-
* Check whether a source file needs to be parsed for imports.
79-
* This is a performance short-circuit, which saves us from creating
80-
* a TypeScript AST unnecessarily.
81-
*
82-
* @param source The content of the source file to check.
83-
*
84-
* @returns false if there are definitely no require calls
85-
* in this file, true otherwise.
86-
*/
87-
private hasRequireCalls(source: string): boolean {
88-
return /require\(['"]/.test(source);
39+
return new Set(umdImports.map(i => i.path));
8940
}
9041
}

packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ runInEachFileSystem(() => {
2727
});
2828

2929
describe('collectDependencies()', () => {
30-
it('should not generate a TS AST if the source does not contain any imports or re-exports',
30+
it('should not try to extract import paths if the source does not contain any imports or re-exports',
3131
() => {
32-
spyOn(ts, 'createSourceFile');
32+
const extractImportsSpy = spyOn(host as any, 'extractImports');
3333
host.collectDependencies(
3434
_('/no/imports/or/re-exports/index.js'), createDependencyInfo());
35-
expect(ts.createSourceFile).not.toHaveBeenCalled();
35+
expect(extractImportsSpy).not.toHaveBeenCalled();
3636
});
3737

3838
it('should resolve all the external imports of the source file', () => {

0 commit comments

Comments
 (0)