-
Notifications
You must be signed in to change notification settings - Fork 12k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
2624d89
commit 766d4a0
Showing
3 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
packages/schematics/angular/migrations/update-15/update-karma-main-file.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** | ||
* @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 { Rule, Tree } from '@angular-devkit/schematics'; | ||
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript'; | ||
import { readWorkspace } from '../../utility'; | ||
import { allTargetOptions } from '../../utility/workspace'; | ||
import { Builders } from '../../utility/workspace-models'; | ||
|
||
export default function (): Rule { | ||
return async (host) => { | ||
for (const file of await findTestMainFiles(host)) { | ||
updateTestFile(host, file); | ||
} | ||
}; | ||
} | ||
|
||
async function findTestMainFiles(host: Tree): Promise<Set<string>> { | ||
const testFiles = new Set<string>(); | ||
const workspace = await readWorkspace(host); | ||
|
||
// find all test.ts files. | ||
for (const project of workspace.projects.values()) { | ||
for (const target of project.targets.values()) { | ||
if (target.builder !== Builders.Karma) { | ||
continue; | ||
} | ||
|
||
for (const [, options] of allTargetOptions(target)) { | ||
if (typeof options.main === 'string') { | ||
testFiles.add(options.main); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return testFiles; | ||
} | ||
|
||
function updateTestFile(host: Tree, file: string): void { | ||
const content = host.readText(file); | ||
if (!content.includes('require.context')) { | ||
return; | ||
} | ||
|
||
const sourceFile = ts.createSourceFile( | ||
file, | ||
content.replace(/^\uFEFF/, ''), | ||
ts.ScriptTarget.Latest, | ||
true, | ||
); | ||
|
||
const usedVariableNames = new Set<string>(); | ||
const recorder = host.beginUpdate(sourceFile.fileName); | ||
|
||
ts.forEachChild(sourceFile, (node) => { | ||
if (ts.isVariableStatement(node)) { | ||
const variableDeclaration = node.declarationList.declarations[0]; | ||
|
||
if (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword)) { | ||
// `declare const require` | ||
if (variableDeclaration.name.getText() !== 'require') { | ||
return; | ||
} | ||
} else { | ||
// `const context = require.context('./', true, /\.spec\.ts$/);` | ||
if (!variableDeclaration.initializer?.getText().startsWith('require.context')) { | ||
return; | ||
} | ||
|
||
// add variable name as used. | ||
usedVariableNames.add(variableDeclaration.name.getText()); | ||
} | ||
|
||
// Delete node. | ||
recorder.remove(node.getFullStart(), node.getFullWidth()); | ||
} | ||
|
||
if ( | ||
usedVariableNames.size && | ||
ts.isExpressionStatement(node) && // context.keys().map(context); | ||
ts.isCallExpression(node.expression) && // context.keys().map(context); | ||
ts.isPropertyAccessExpression(node.expression.expression) && // context.keys().map | ||
ts.isCallExpression(node.expression.expression.expression) && // context.keys() | ||
ts.isPropertyAccessExpression(node.expression.expression.expression.expression) && // context.keys | ||
ts.isIdentifier(node.expression.expression.expression.expression.expression) && // context | ||
usedVariableNames.has(node.expression.expression.expression.expression.expression.getText()) | ||
) { | ||
// `context.keys().map(context);` | ||
// `context.keys().forEach(context);` | ||
recorder.remove(node.getFullStart(), node.getFullWidth()); | ||
} | ||
}); | ||
|
||
host.commitUpdate(recorder); | ||
} |
151 changes: 151 additions & 0 deletions
151
packages/schematics/angular/migrations/update-15/update-karma-main-file_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/** | ||
* @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 { tags } from '@angular-devkit/core'; | ||
import { EmptyTree } from '@angular-devkit/schematics'; | ||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; | ||
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; | ||
|
||
function createWorkspace(tree: UnitTestTree): void { | ||
const angularConfig: WorkspaceSchema = { | ||
version: 1, | ||
projects: { | ||
app: { | ||
root: '', | ||
sourceRoot: 'src', | ||
projectType: ProjectType.Application, | ||
prefix: 'app', | ||
architect: { | ||
test: { | ||
builder: Builders.Karma, | ||
options: { | ||
main: 'test.ts', | ||
karmaConfig: './karma.config.js', | ||
tsConfig: 'test-spec.json', | ||
}, | ||
configurations: { | ||
production: { | ||
main: 'test-multiple-context.ts', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); | ||
tree.create( | ||
'test.ts', | ||
tags.stripIndents` | ||
import { getTestBed } from '@angular/core/testing'; | ||
import { | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting | ||
} from '@angular/platform-browser-dynamic/testing'; | ||
declare const require: { | ||
context(path: string, deep?: boolean, filter?: RegExp): { | ||
<T>(id: string): T; | ||
keys(): string[]; | ||
}; | ||
}; | ||
// First, initialize the Angular testing environment. | ||
getTestBed().initTestEnvironment( | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting(), | ||
); | ||
// Then we find all the tests. | ||
const context = require.context('./', true, /\.spec\.ts$/); | ||
// And load the modules. | ||
context.keys().map(context); | ||
`, | ||
); | ||
|
||
tree.create( | ||
'test-multiple-context.ts', | ||
tags.stripIndents` | ||
import { getTestBed } from '@angular/core/testing'; | ||
import { | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting | ||
} from '@angular/platform-browser-dynamic/testing'; | ||
declare const require: { | ||
context(path: string, deep?: boolean, filter?: RegExp): { | ||
<T>(id: string): T; | ||
keys(): string[]; | ||
}; | ||
}; | ||
// First, initialize the Angular testing environment. | ||
getTestBed().initTestEnvironment( | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting(), | ||
); | ||
// Then we find all the tests. | ||
const context1 = require.context('./', true, /\.spec\.ts$/); | ||
const context2 = require.context('./', true, /\.spec\.ts$/); | ||
// And load the modules. | ||
context2.keys().forEach(context2); | ||
context1.keys().map(context1); | ||
`, | ||
); | ||
} | ||
|
||
describe(`Migration to karma builder main file (test.ts)`, () => { | ||
const schematicName = 'update-karma-main-file'; | ||
|
||
const schematicRunner = new SchematicTestRunner( | ||
'migrations', | ||
require.resolve('../migration-collection.json'), | ||
); | ||
|
||
let tree: UnitTestTree; | ||
beforeEach(() => { | ||
tree = new UnitTestTree(new EmptyTree()); | ||
createWorkspace(tree); | ||
}); | ||
|
||
it(`should remove 'declare const require' and 'require.context' usages`, async () => { | ||
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); | ||
expect(newTree.readText('test.ts')).toBe(tags.stripIndents` | ||
import { getTestBed } from '@angular/core/testing'; | ||
import { | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting | ||
} from '@angular/platform-browser-dynamic/testing'; | ||
// First, initialize the Angular testing environment. | ||
getTestBed().initTestEnvironment( | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting(), | ||
); | ||
`); | ||
}); | ||
|
||
it(`should remove multiple 'require.context' usages`, async () => { | ||
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); | ||
expect(newTree.readText('test-multiple-context.ts')).toBe(tags.stripIndents` | ||
import { getTestBed } from '@angular/core/testing'; | ||
import { | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting | ||
} from '@angular/platform-browser-dynamic/testing'; | ||
// First, initialize the Angular testing environment. | ||
getTestBed().initTestEnvironment( | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting(), | ||
); | ||
`); | ||
}); | ||
}); |