Skip to content

Commit e0ff321

Browse files
devversionalxhub
authored andcommitted
build: prepare testing infrastructure for code splitting of core package (angular#60487)
When we switch to relative imports, shared `.d.ts` chunks can be generated. We need to also pull these into our mock virtual FS testing environments. Notably this does not cause a test slow-down because we are talking about very few extra `.d.ts` chunk files. In our experiments before, with no dts bundling, we saw test time increase from e.g. 20seconds to 100seconds. The 20s are still the same locally! In addition, since code for definitions can now reside in shared `.d.ts` chunks, the language service tests need to be adjusted in cases where they assert for code definition locations in `@angular/core`. A new helper prepares for more code to be moved into arbitrary `.d.ts` files; we should simply assert the definition comes out of `node_modules/@angular/core`. PR Close angular#60487
1 parent a7183d8 commit e0ff321

4 files changed

Lines changed: 58 additions & 63 deletions

File tree

packages/compiler-cli/src/ngtsc/typecheck/testing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ts_library(
2323
"//packages/compiler-cli/src/ngtsc/typecheck",
2424
"//packages/compiler-cli/src/ngtsc/typecheck/api",
2525
"//packages/compiler-cli/src/ngtsc/util",
26+
"@npm//tinyglobby",
2627
"@npm//typescript",
2728
],
2829
)

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

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import {readFileSync} from 'fs';
2323
import path from 'path';
2424
import ts from 'typescript';
25+
import {globSync} from 'tinyglobby';
2526

2627
import {
2728
absoluteFrom,
@@ -151,19 +152,19 @@ export function typescriptLibDts(): TestFile {
151152
};
152153
}
153154

155+
let _angularCoreDts: TestFile[] | null = null;
154156
export function angularCoreDtsFiles(): TestFile[] {
157+
if (_angularCoreDts !== null) {
158+
return _angularCoreDts;
159+
}
160+
155161
const directory = resolveFromRunfiles('angular/packages/core/npm_package');
162+
const dtsFiles = globSync('**/*.d.ts', {cwd: directory});
156163

157-
return [
158-
{
159-
name: absoluteFrom('/node_modules/@angular/core/index.d.ts'),
160-
contents: readFileSync(path.join(directory, 'index.d.ts'), 'utf8'),
161-
},
162-
{
163-
name: absoluteFrom('/node_modules/@angular/core/primitives/signals/index.d.ts'),
164-
contents: readFileSync(path.join(directory, 'primitives/signals/index.d.ts'), 'utf8'),
165-
},
166-
];
164+
return (_angularCoreDts = dtsFiles.map((fileName) => ({
165+
name: absoluteFrom(`/node_modules/@angular/core/${fileName}`),
166+
contents: readFileSync(path.join(directory, fileName), 'utf8'),
167+
})));
167168
}
168169

169170
export function angularAnimationsDts(): TestFile {
@@ -246,12 +247,7 @@ export function ngForDts(): TestFile {
246247

247248
export function ngForTypeCheckTarget(): TypeCheckingTarget {
248249
const dts = ngForDts();
249-
return {
250-
...dts,
251-
fileName: dts.name,
252-
source: dts.contents,
253-
templates: {},
254-
};
250+
return {...dts, fileName: dts.name, source: dts.contents, templates: {}};
255251
}
256252

257253
export const ALL_ENABLED_CONFIG: Readonly<TypeCheckingConfig> = {
@@ -425,9 +421,7 @@ export function tcb(
425421
checkTwoWayBoundEvents: true,
426422
...config,
427423
};
428-
options = options || {
429-
emitSpans: false,
430-
};
424+
options = options || {emitSpans: false};
431425

432426
const fileName = absoluteFrom('/type-check-file.ts');
433427

@@ -518,18 +512,12 @@ export function setup(
518512
}
519513
}
520514

521-
files.push({
522-
name: target.fileName,
523-
contents,
524-
});
515+
files.push({name: target.fileName, contents});
525516

526517
if (!target.fileName.endsWith('.d.ts')) {
527518
const shimName = TypeCheckShimGenerator.shimFor(target.fileName);
528519
shims.set(target.fileName, shimName);
529-
files.push({
530-
name: shimName,
531-
contents: 'export const MODULE = true;',
532-
});
520+
files.push({name: shimName, contents: 'export const MODULE = true;'});
533521
}
534522
}
535523

@@ -538,12 +526,7 @@ export function setup(
538526

539527
const {program, host, options} = makeProgram(
540528
files,
541-
{
542-
strictNullChecks: true,
543-
skipLibCheck: true,
544-
noImplicitAny: true,
545-
...opts,
546-
},
529+
{strictNullChecks: true, skipLibCheck: true, noImplicitAny: true, ...opts},
547530
/* host */ undefined,
548531
/* checkForErrors */ false,
549532
);
@@ -585,10 +568,7 @@ export function setup(
585568
if (shims.has(target.fileName)) {
586569
const shimFileName = shims.get(target.fileName)!;
587570
const shimSf = getSourceFileOrError(program, shimFileName);
588-
sfExtensionData(shimSf).fileShim = {
589-
extension: 'ngtypecheck',
590-
generatedFrom: target.fileName,
591-
};
571+
sfExtensionData(shimSf).fileShim = {extension: 'ngtypecheck', generatedFrom: target.fileName};
592572
}
593573

594574
for (const className of Object.keys(target.templates)) {
@@ -669,10 +649,7 @@ export function setup(
669649
if (!scopeMap.has(clazz)) {
670650
// This class wasn't part of the target set of components with templates, but is
671651
// probably a declaration used in one of them. Return an empty scope.
672-
const emptyScope: ScopeData = {
673-
dependencies: [],
674-
isPoisoned: false,
675-
};
652+
const emptyScope: ScopeData = {dependencies: [], isPoisoned: false};
676653
return {
677654
kind: ComponentScopeKind.NgModule,
678655
ngModule,
@@ -723,11 +700,7 @@ export function setup(
723700
typeCheckScopeRegistry,
724701
NOOP_PERF_RECORDER,
725702
);
726-
return {
727-
templateTypeChecker,
728-
program,
729-
programStrategy,
730-
};
703+
return {templateTypeChecker, program, programStrategy};
731704
}
732705

733706
/**
@@ -746,14 +719,7 @@ export function diagnose(
746719
const sfPath = absoluteFrom('/main.ts');
747720
const {program, templateTypeChecker} = setup(
748721
[
749-
{
750-
fileName: sfPath,
751-
templates: {
752-
'TestComponent': template,
753-
},
754-
source,
755-
declarations,
756-
},
722+
{fileName: sfPath, templates: {'TestComponent': template}, source, declarations},
757723
...additionalSources.map((testFile) => ({
758724
fileName: testFile.name,
759725
source: testFile.contents,
@@ -908,10 +874,7 @@ function getDirectiveMetaFromDeclaration(
908874
* Synthesize `ScopeData` metadata from an array of `TestDeclaration`s.
909875
*/
910876
function makeScope(program: ts.Program, sf: ts.SourceFile, decls: TestDeclaration[]): ScopeData {
911-
const scope: ScopeData = {
912-
dependencies: [],
913-
isPoisoned: false,
914-
};
877+
const scope: ScopeData = {dependencies: [], isPoisoned: false};
915878

916879
for (const decl of decls) {
917880
let declSf = sf;

packages/language-service/test/type_definitions_spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/te
1010

1111
import {
1212
assertFileNames,
13+
assertFilePaths,
1314
assertTextSpans,
1415
humanizeDocumentSpanLike,
1516
LanguageServiceTestEnv,
@@ -119,7 +120,7 @@ describe('type definitions', () => {
119120
expect(definitions!.length).toEqual(1);
120121

121122
assertTextSpans(definitions, ['OutputEmitterRef']);
122-
assertFileNames(definitions, ['index.d.ts']);
123+
assertFilePaths(definitions, [/node_modules\/@angular\/core\/.*\.d\.ts/]);
123124
});
124125
});
125126

@@ -158,7 +159,7 @@ describe('type definitions', () => {
158159
expect(definitions!.length).toEqual(1);
159160

160161
assertTextSpans(definitions, ['OutputRef']);
161-
assertFileNames(definitions, ['index.d.ts']);
162+
assertFilePaths(definitions, [/node_modules\/@angular\/core\/.*\.d\.ts/]);
162163
});
163164
});
164165

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,38 @@ export function assertFileNames(refs: Array<{fileName: string}>, expectedFileNam
2222
expect(new Set(actualFileNames)).toEqual(new Set(expectedFileNames));
2323
}
2424

25+
/**
26+
* Expect that a list of objects with a `fileName` property matches a set
27+
* of file paths.
28+
*
29+
* This assertion is independent of the order of either list.
30+
*/
31+
export function assertFilePaths(refs: Array<{fileName: string}>, expectedPaths: RegExp[]) {
32+
const actualPaths = Array.from(new Set(refs.map((r) => r.fileName)));
33+
34+
if (actualPaths.length !== expectedPaths.length) {
35+
expect(actualPaths.length)
36+
.withContext('Expected expected paths to be the same size.')
37+
.toBe(expectedPaths.length);
38+
return;
39+
}
40+
41+
for (const pattern of expectedPaths) {
42+
const matching = actualPaths.findIndex((p) => pattern.test(p));
43+
if (matching !== -1) {
44+
actualPaths.splice(matching, 1);
45+
} else {
46+
expect(true)
47+
.withContext(
48+
`Expected ${pattern} to match a file path. ` +
49+
`Remaining unmatched paths: ${actualPaths.join(', ')}`,
50+
)
51+
.toBe(false);
52+
return;
53+
}
54+
}
55+
}
56+
2557
export function assertTextSpans(items: Array<{textSpan: string}>, expectedTextSpans: string[]) {
2658
const actualSpans = items.map((item) => item.textSpan);
2759
expect(new Set(actualSpans)).toEqual(new Set(expectedTextSpans));
@@ -97,9 +129,7 @@ export function humanizeDocumentSpanLike<T extends ts.DocumentSpan>(
97129
: undefined,
98130
};
99131
}
100-
type Stringy<T> = {
101-
[P in keyof T]: string;
102-
};
132+
type Stringy<T> = {[P in keyof T]: string};
103133

104134
export function getText(contents: string, textSpan: ts.TextSpan) {
105135
return contents.slice(textSpan.start, textSpan.start + textSpan.length);

0 commit comments

Comments
 (0)