Skip to content

Commit 766d4a0

Browse files
committed
feat(@schematics/angular): add migration to remove require calls from karma builder main file
With the recent changes in build-angular the `require.context` calls have become unneeded.
1 parent 2624d89 commit 766d4a0

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

packages/schematics/angular/migrations/migration-collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"version": "15.0.0",
2020
"factory": "./update-15/update-workspace-config",
2121
"description": "Remove options from 'angular.json' that are no longer supported by the official builders."
22+
},
23+
"update-karma-main-file": {
24+
"version": "15.0.0",
25+
"factory": "./update-15/update-karma-main-file",
26+
"description": "Remove no longer needed require calls in Karma builder main file."
2227
}
2328
}
2429
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 { Rule, Tree } from '@angular-devkit/schematics';
10+
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
11+
import { readWorkspace } from '../../utility';
12+
import { allTargetOptions } from '../../utility/workspace';
13+
import { Builders } from '../../utility/workspace-models';
14+
15+
export default function (): Rule {
16+
return async (host) => {
17+
for (const file of await findTestMainFiles(host)) {
18+
updateTestFile(host, file);
19+
}
20+
};
21+
}
22+
23+
async function findTestMainFiles(host: Tree): Promise<Set<string>> {
24+
const testFiles = new Set<string>();
25+
const workspace = await readWorkspace(host);
26+
27+
// find all test.ts files.
28+
for (const project of workspace.projects.values()) {
29+
for (const target of project.targets.values()) {
30+
if (target.builder !== Builders.Karma) {
31+
continue;
32+
}
33+
34+
for (const [, options] of allTargetOptions(target)) {
35+
if (typeof options.main === 'string') {
36+
testFiles.add(options.main);
37+
}
38+
}
39+
}
40+
}
41+
42+
return testFiles;
43+
}
44+
45+
function updateTestFile(host: Tree, file: string): void {
46+
const content = host.readText(file);
47+
if (!content.includes('require.context')) {
48+
return;
49+
}
50+
51+
const sourceFile = ts.createSourceFile(
52+
file,
53+
content.replace(/^\uFEFF/, ''),
54+
ts.ScriptTarget.Latest,
55+
true,
56+
);
57+
58+
const usedVariableNames = new Set<string>();
59+
const recorder = host.beginUpdate(sourceFile.fileName);
60+
61+
ts.forEachChild(sourceFile, (node) => {
62+
if (ts.isVariableStatement(node)) {
63+
const variableDeclaration = node.declarationList.declarations[0];
64+
65+
if (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword)) {
66+
// `declare const require`
67+
if (variableDeclaration.name.getText() !== 'require') {
68+
return;
69+
}
70+
} else {
71+
// `const context = require.context('./', true, /\.spec\.ts$/);`
72+
if (!variableDeclaration.initializer?.getText().startsWith('require.context')) {
73+
return;
74+
}
75+
76+
// add variable name as used.
77+
usedVariableNames.add(variableDeclaration.name.getText());
78+
}
79+
80+
// Delete node.
81+
recorder.remove(node.getFullStart(), node.getFullWidth());
82+
}
83+
84+
if (
85+
usedVariableNames.size &&
86+
ts.isExpressionStatement(node) && // context.keys().map(context);
87+
ts.isCallExpression(node.expression) && // context.keys().map(context);
88+
ts.isPropertyAccessExpression(node.expression.expression) && // context.keys().map
89+
ts.isCallExpression(node.expression.expression.expression) && // context.keys()
90+
ts.isPropertyAccessExpression(node.expression.expression.expression.expression) && // context.keys
91+
ts.isIdentifier(node.expression.expression.expression.expression.expression) && // context
92+
usedVariableNames.has(node.expression.expression.expression.expression.expression.getText())
93+
) {
94+
// `context.keys().map(context);`
95+
// `context.keys().forEach(context);`
96+
recorder.remove(node.getFullStart(), node.getFullWidth());
97+
}
98+
});
99+
100+
host.commitUpdate(recorder);
101+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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 { tags } from '@angular-devkit/core';
10+
import { EmptyTree } from '@angular-devkit/schematics';
11+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
12+
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
13+
14+
function createWorkspace(tree: UnitTestTree): void {
15+
const angularConfig: WorkspaceSchema = {
16+
version: 1,
17+
projects: {
18+
app: {
19+
root: '',
20+
sourceRoot: 'src',
21+
projectType: ProjectType.Application,
22+
prefix: 'app',
23+
architect: {
24+
test: {
25+
builder: Builders.Karma,
26+
options: {
27+
main: 'test.ts',
28+
karmaConfig: './karma.config.js',
29+
tsConfig: 'test-spec.json',
30+
},
31+
configurations: {
32+
production: {
33+
main: 'test-multiple-context.ts',
34+
},
35+
},
36+
},
37+
},
38+
},
39+
},
40+
};
41+
42+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
43+
tree.create(
44+
'test.ts',
45+
tags.stripIndents`
46+
import { getTestBed } from '@angular/core/testing';
47+
import {
48+
BrowserDynamicTestingModule,
49+
platformBrowserDynamicTesting
50+
} from '@angular/platform-browser-dynamic/testing';
51+
52+
declare const require: {
53+
context(path: string, deep?: boolean, filter?: RegExp): {
54+
<T>(id: string): T;
55+
keys(): string[];
56+
};
57+
};
58+
59+
// First, initialize the Angular testing environment.
60+
getTestBed().initTestEnvironment(
61+
BrowserDynamicTestingModule,
62+
platformBrowserDynamicTesting(),
63+
);
64+
65+
// Then we find all the tests.
66+
const context = require.context('./', true, /\.spec\.ts$/);
67+
// And load the modules.
68+
context.keys().map(context);
69+
`,
70+
);
71+
72+
tree.create(
73+
'test-multiple-context.ts',
74+
tags.stripIndents`
75+
import { getTestBed } from '@angular/core/testing';
76+
import {
77+
BrowserDynamicTestingModule,
78+
platformBrowserDynamicTesting
79+
} from '@angular/platform-browser-dynamic/testing';
80+
81+
declare const require: {
82+
context(path: string, deep?: boolean, filter?: RegExp): {
83+
<T>(id: string): T;
84+
keys(): string[];
85+
};
86+
};
87+
88+
// First, initialize the Angular testing environment.
89+
getTestBed().initTestEnvironment(
90+
BrowserDynamicTestingModule,
91+
platformBrowserDynamicTesting(),
92+
);
93+
94+
// Then we find all the tests.
95+
const context1 = require.context('./', true, /\.spec\.ts$/);
96+
const context2 = require.context('./', true, /\.spec\.ts$/);
97+
// And load the modules.
98+
context2.keys().forEach(context2);
99+
context1.keys().map(context1);
100+
`,
101+
);
102+
}
103+
104+
describe(`Migration to karma builder main file (test.ts)`, () => {
105+
const schematicName = 'update-karma-main-file';
106+
107+
const schematicRunner = new SchematicTestRunner(
108+
'migrations',
109+
require.resolve('../migration-collection.json'),
110+
);
111+
112+
let tree: UnitTestTree;
113+
beforeEach(() => {
114+
tree = new UnitTestTree(new EmptyTree());
115+
createWorkspace(tree);
116+
});
117+
118+
it(`should remove 'declare const require' and 'require.context' usages`, async () => {
119+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
120+
expect(newTree.readText('test.ts')).toBe(tags.stripIndents`
121+
import { getTestBed } from '@angular/core/testing';
122+
import {
123+
BrowserDynamicTestingModule,
124+
platformBrowserDynamicTesting
125+
} from '@angular/platform-browser-dynamic/testing';
126+
127+
// First, initialize the Angular testing environment.
128+
getTestBed().initTestEnvironment(
129+
BrowserDynamicTestingModule,
130+
platformBrowserDynamicTesting(),
131+
);
132+
`);
133+
});
134+
135+
it(`should remove multiple 'require.context' usages`, async () => {
136+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
137+
expect(newTree.readText('test-multiple-context.ts')).toBe(tags.stripIndents`
138+
import { getTestBed } from '@angular/core/testing';
139+
import {
140+
BrowserDynamicTestingModule,
141+
platformBrowserDynamicTesting
142+
} from '@angular/platform-browser-dynamic/testing';
143+
144+
// First, initialize the Angular testing environment.
145+
getTestBed().initTestEnvironment(
146+
BrowserDynamicTestingModule,
147+
platformBrowserDynamicTesting(),
148+
);
149+
`);
150+
});
151+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy