Skip to content

Commit

Permalink
refactor(compiler-cli): speed up compiler tests by caching Angular (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
devversion authored and pkozlowski-opensource committed Mar 6, 2024
1 parent 8a8b682 commit 2df8584
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import ts from 'typescript';

import {basename} from '../../file_system';

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

Expand Down
6 changes: 4 additions & 2 deletions packages/compiler-cli/src/ngtsc/testing/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {AbsoluteFsPath, dirname, getFileSystem, getSourceFileOrError, NgtscCompi
import {DeclarationNode} from '../../reflection';
import {getTokenAtPosition} from '../../util/src/typescript';

import {NgtscTestCompilerHost} from './compiler_host';

export function makeProgram(
files: {name: AbsoluteFsPath, contents: string, isRoot?: boolean}[],
options?: ts.CompilerOptions, host?: ts.CompilerHost, checkForErrors: boolean = true):
Expand All @@ -30,7 +32,7 @@ export function makeProgram(
moduleResolution: ts.ModuleResolutionKind.Node10,
...options
};
const compilerHost = new NgtscCompilerHost(fs, compilerOptions);
const compilerHost = new NgtscTestCompilerHost(fs, compilerOptions);
const rootNames = files.filter(file => file.isRoot !== false)
.map(file => compilerHost.getCanonicalFileName(file.name));
const program = ts.createProgram(rootNames, compilerOptions, compilerHost);
Expand Down Expand Up @@ -110,7 +112,7 @@ export function walkForDeclarations(name: string, rootNode: ts.Node): Declaratio
return chosenDecls;
}

export function isNamedDeclaration(node: ts.Node): node is ts.Declaration&{name: ts.Identifier} {
export function isNamedDeclaration(node: ts.Node): node is(ts.Declaration & {name: ts.Identifier}) {
const namedNode = node as {name?: ts.Identifier};
return namedNode.name !== undefined && ts.isIdentifier(namedNode.name);
}
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,13 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
const config = overrides.config ?? {};

const {program, host, options} = makeProgram(
files, {strictNullChecks: true, noImplicitAny: true, ...opts}, /* host */ undefined,
files, {
strictNullChecks: true,
skipLibCheck: true,
noImplicitAny: true,
...opts,
},
/* host */ undefined,
/* checkForErrors */ false);
const checker = program.getTypeChecker();
const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host);
Expand Down
1 change: 1 addition & 0 deletions packages/language-service/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jasmine_node_test(
"//packages/core:npm_package",
"@npm//rxjs",
],
shard_count = 4,
deps = [
":test_lib",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {getCachedSourceFile} from '@angular/compiler-cli/src/ngtsc/testing';
import ts from 'typescript';

interface TsProjectWithInternals {
// typescript/src/server/project.ts#ConfiguredProject
setCompilerHost?(host: ts.CompilerHost): void;
}

let patchedLanguageServiceProjectHost = false;

/**
* Updates `ts.server.Project` to use efficient test caching of source files
* that aren't expected to be changed. E.g. the default libs.
*/
export function patchLanguageServiceProjectsWithTestHost() {
if (patchedLanguageServiceProjectHost) {
return;
}
patchedLanguageServiceProjectHost = true;

(ts.server.Project.prototype as TsProjectWithInternals).setCompilerHost = (host) => {
const _originalHostGetSourceFile = host.getSourceFile;
const _originalHostGetSourceFileByPath = host.getSourceFileByPath;

host.getSourceFile =
(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
return getCachedSourceFile(fileName, () => host.readFile(fileName)) ??
_originalHostGetSourceFile.call(
host, fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
};

if (_originalHostGetSourceFileByPath !== undefined) {
host.getSourceFileByPath =
(fileName, path, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
return getCachedSourceFile(fileName, () => host.readFile(fileName)) ??
_originalHostGetSourceFileByPath.call(
host, fileName, path, languageVersionOrOptions, onError,
shouldCreateNewSourceFile);
};
}
};
}
6 changes: 5 additions & 1 deletion packages/language-service/testing/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ts from 'typescript/lib/tsserverlibrary';
import {LanguageService} from '../../src/language_service';

import {OpenBuffer} from './buffer';
import {patchLanguageServiceProjectsWithTestHost} from './language_service_test_cache';

export type ProjectFiles = {
[fileName: string]: string;
Expand Down Expand Up @@ -76,14 +77,17 @@ export class Project {

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

patchLanguageServiceProjectsWithTestHost();

// Ensure the project is live in the ProjectService.
// This creates the `ts.Program` by configuring the project and loading it!
projectService.openClientFile(entryFiles[0]);
projectService.closeClientFile(entryFiles[0]);

return new Project(projectName, projectService, tsConfigPath);
}

constructor(
private constructor(
readonly name: string, private projectService: ts.server.ProjectService,
private tsConfigPath: AbsoluteFsPath) {
// LS for project
Expand Down

0 comments on commit 2df8584

Please sign in to comment.