Skip to content

Commit 2df8584

Browse files
devversionpkozlowski-opensource
authored andcommitted
refactor(compiler-cli): speed up compiler tests by caching Angular (#54650)
Currently the `makeProgram` utility from `ngtsc/testing` does not use the test host by default- optimizing for source file caching. Additionally, the host can be updated to attempt caching of the `.d.ts` files from `@angular/core`— whether that's fake core, or the real core- is irrelevant. We are never caching if these changes between tests, so correctness is guaranteed. This commit reduces the type check test times form 80s to just 11 seconds, faster than what it was before with `fake_core`. The ngtsc tests also run significantly faster. From 40s to 30s PR Close #54650
1 parent 8a8b682 commit 2df8584

File tree

6 files changed

+70
-5
lines changed

6 files changed

+70
-5
lines changed

packages/compiler-cli/src/ngtsc/testing/src/cached_source_files.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import ts from 'typescript';
10+
1011
import {basename} from '../../file_system';
1112

1213
// A cache of source files that are typically used across tests and are expensive to parse.
@@ -28,7 +29,8 @@ let sourceFileCache = new Map<string, ts.SourceFile>();
2829
*/
2930
export function getCachedSourceFile(
3031
fileName: string, load: () => string | undefined): ts.SourceFile|null {
31-
if (!/^lib\..+\.d\.ts$/.test(basename(fileName))) {
32+
if (!/^lib\..+\.d\.ts$/.test(basename(fileName)) &&
33+
!/\/node_modules\/(@angular|rxjs)\//.test(fileName)) {
3234
return null;
3335
}
3436

packages/compiler-cli/src/ngtsc/testing/src/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {AbsoluteFsPath, dirname, getFileSystem, getSourceFileOrError, NgtscCompi
1414
import {DeclarationNode} from '../../reflection';
1515
import {getTokenAtPosition} from '../../util/src/typescript';
1616

17+
import {NgtscTestCompilerHost} from './compiler_host';
18+
1719
export function makeProgram(
1820
files: {name: AbsoluteFsPath, contents: string, isRoot?: boolean}[],
1921
options?: ts.CompilerOptions, host?: ts.CompilerHost, checkForErrors: boolean = true):
@@ -30,7 +32,7 @@ export function makeProgram(
3032
moduleResolution: ts.ModuleResolutionKind.Node10,
3133
...options
3234
};
33-
const compilerHost = new NgtscCompilerHost(fs, compilerOptions);
35+
const compilerHost = new NgtscTestCompilerHost(fs, compilerOptions);
3436
const rootNames = files.filter(file => file.isRoot !== false)
3537
.map(file => compilerHost.getCanonicalFileName(file.name));
3638
const program = ts.createProgram(rootNames, compilerOptions, compilerHost);
@@ -110,7 +112,7 @@ export function walkForDeclarations(name: string, rootNode: ts.Node): Declaratio
110112
return chosenDecls;
111113
}
112114

113-
export function isNamedDeclaration(node: ts.Node): node is ts.Declaration&{name: ts.Identifier} {
115+
export function isNamedDeclaration(node: ts.Node): node is(ts.Declaration & {name: ts.Identifier}) {
114116
const namedNode = node as {name?: ts.Identifier};
115117
return namedNode.name !== undefined && ts.isIdentifier(namedNode.name);
116118
}

packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,13 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
437437
const config = overrides.config ?? {};
438438

439439
const {program, host, options} = makeProgram(
440-
files, {strictNullChecks: true, noImplicitAny: true, ...opts}, /* host */ undefined,
440+
files, {
441+
strictNullChecks: true,
442+
skipLibCheck: true,
443+
noImplicitAny: true,
444+
...opts,
445+
},
446+
/* host */ undefined,
441447
/* checkForErrors */ false);
442448
const checker = program.getTypeChecker();
443449
const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host);

packages/language-service/test/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jasmine_node_test(
2525
"//packages/core:npm_package",
2626
"@npm//rxjs",
2727
],
28+
shard_count = 4,
2829
deps = [
2930
":test_lib",
3031
],
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC 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 {getCachedSourceFile} from '@angular/compiler-cli/src/ngtsc/testing';
10+
import ts from 'typescript';
11+
12+
interface TsProjectWithInternals {
13+
// typescript/src/server/project.ts#ConfiguredProject
14+
setCompilerHost?(host: ts.CompilerHost): void;
15+
}
16+
17+
let patchedLanguageServiceProjectHost = false;
18+
19+
/**
20+
* Updates `ts.server.Project` to use efficient test caching of source files
21+
* that aren't expected to be changed. E.g. the default libs.
22+
*/
23+
export function patchLanguageServiceProjectsWithTestHost() {
24+
if (patchedLanguageServiceProjectHost) {
25+
return;
26+
}
27+
patchedLanguageServiceProjectHost = true;
28+
29+
(ts.server.Project.prototype as TsProjectWithInternals).setCompilerHost = (host) => {
30+
const _originalHostGetSourceFile = host.getSourceFile;
31+
const _originalHostGetSourceFileByPath = host.getSourceFileByPath;
32+
33+
host.getSourceFile =
34+
(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
35+
return getCachedSourceFile(fileName, () => host.readFile(fileName)) ??
36+
_originalHostGetSourceFile.call(
37+
host, fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
38+
};
39+
40+
if (_originalHostGetSourceFileByPath !== undefined) {
41+
host.getSourceFileByPath =
42+
(fileName, path, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
43+
return getCachedSourceFile(fileName, () => host.readFile(fileName)) ??
44+
_originalHostGetSourceFileByPath.call(
45+
host, fileName, path, languageVersionOrOptions, onError,
46+
shouldCreateNewSourceFile);
47+
};
48+
}
49+
};
50+
}

packages/language-service/testing/src/project.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ts from 'typescript/lib/tsserverlibrary';
1414
import {LanguageService} from '../../src/language_service';
1515

1616
import {OpenBuffer} from './buffer';
17+
import {patchLanguageServiceProjectsWithTestHost} from './language_service_test_cache';
1718

1819
export type ProjectFiles = {
1920
[fileName: string]: string;
@@ -76,14 +77,17 @@ export class Project {
7677

7778
writeTsconfig(fs, tsConfigPath, entryFiles, angularCompilerOptions, tsCompilerOptions);
7879

80+
patchLanguageServiceProjectsWithTestHost();
81+
7982
// Ensure the project is live in the ProjectService.
83+
// This creates the `ts.Program` by configuring the project and loading it!
8084
projectService.openClientFile(entryFiles[0]);
8185
projectService.closeClientFile(entryFiles[0]);
8286

8387
return new Project(projectName, projectService, tsConfigPath);
8488
}
8589

86-
constructor(
90+
private constructor(
8791
readonly name: string, private projectService: ts.server.ProjectService,
8892
private tsConfigPath: AbsoluteFsPath) {
8993
// LS for project

0 commit comments

Comments
 (0)