feat(core): add migration for `XhrFactory` import (#41313)
Automatically migrates `XhrFactory` from `@angular/common/http` to `@angular/common`. PR Close #41313
This commit is contained in:
parent
300d6d1e38
commit
95ff5ecb23
|
@ -28,5 +28,6 @@ pkg_npm(
|
|||
"//packages/core/schematics/migrations/undecorated-classes-with-decorated-fields",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-di",
|
||||
"//packages/core/schematics/migrations/wait-for-async",
|
||||
"//packages/core/schematics/migrations/xhr-factory",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -89,6 +89,11 @@
|
|||
"version": "12.0.0-beta",
|
||||
"description": "In Angular version 12, the type of ActivatedRouteSnapshot.fragment is nullable. This migration automatically adds non-null assertions to it.",
|
||||
"factory": "./migrations/activated-route-snapshot-fragment/index"
|
||||
},
|
||||
"migration-v12-xhr-factory": {
|
||||
"version": "12.0.0-next.6",
|
||||
"description": "`XhrFactory` has been moved from `@angular/common/http` to `@angular/common`.",
|
||||
"factory": "./migrations/xhr-factory/index"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "xhr-factory",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
visibility = [
|
||||
"//packages/core/schematics:__pkg__",
|
||||
"//packages/core/schematics/test:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//packages/core/schematics/utils",
|
||||
"@npm//@angular-devkit/schematics",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
## XhrFactory migration
|
||||
|
||||
Automatically migrates `XhrFactory` from `@angular/common/http` to `@angular/common`.
|
||||
|
||||
#### Before
|
||||
```ts
|
||||
import { XhrFactory } from '@angular/common/http';
|
||||
```
|
||||
|
||||
#### After
|
||||
```ts
|
||||
import { XhrFactory } from '@angular/common';
|
||||
```
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* @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 {DirEntry, Rule, UpdateRecorder} from '@angular-devkit/schematics';
|
||||
import * as ts from 'typescript';
|
||||
import {findImportSpecifier} from '../../utils/typescript/imports';
|
||||
|
||||
function* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {
|
||||
for (const path of directory.subfiles) {
|
||||
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
|
||||
const entry = directory.file(path);
|
||||
if (entry) {
|
||||
const content = entry.content;
|
||||
if (content.includes('XhrFactory')) {
|
||||
const source = ts.createSourceFile(
|
||||
entry.path,
|
||||
content.toString().replace(/^\uFEFF/, ''),
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
);
|
||||
|
||||
yield source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const path of directory.subdirs) {
|
||||
if (path === 'node_modules' || path.startsWith('.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield* visit(directory.dir(path));
|
||||
}
|
||||
}
|
||||
|
||||
export default function(): Rule {
|
||||
return tree => {
|
||||
const printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed});
|
||||
|
||||
for (const sourceFile of visit(tree.root)) {
|
||||
let recorder: UpdateRecorder|undefined;
|
||||
|
||||
const allImportDeclarations =
|
||||
sourceFile.statements.filter(n => ts.isImportDeclaration(n)) as ts.ImportDeclaration[];
|
||||
if (allImportDeclarations.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const httpCommonImport = findImportDeclaration('@angular/common/http', allImportDeclarations);
|
||||
if (!httpCommonImport) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const commonHttpNamedBinding = getNamedImports(httpCommonImport);
|
||||
if (commonHttpNamedBinding) {
|
||||
const commonHttpNamedImports = commonHttpNamedBinding.elements;
|
||||
const xhrFactorySpecifier = findImportSpecifier(commonHttpNamedImports, 'XhrFactory');
|
||||
|
||||
if (!xhrFactorySpecifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recorder = tree.beginUpdate(sourceFile.fileName);
|
||||
|
||||
// Remove 'XhrFactory' from '@angular/common/http'
|
||||
if (commonHttpNamedImports.length > 1) {
|
||||
// Remove 'XhrFactory' named import
|
||||
const index = commonHttpNamedBinding.getStart();
|
||||
const length = commonHttpNamedBinding.getWidth();
|
||||
|
||||
const newImports = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
ts.factory.updateNamedImports(
|
||||
commonHttpNamedBinding,
|
||||
commonHttpNamedBinding.elements.filter(e => e !== xhrFactorySpecifier)),
|
||||
sourceFile);
|
||||
recorder.remove(index, length).insertLeft(index, newImports);
|
||||
} else {
|
||||
// Remove '@angular/common/http' import
|
||||
const index = httpCommonImport.getFullStart();
|
||||
const length = httpCommonImport.getFullWidth();
|
||||
recorder.remove(index, length);
|
||||
}
|
||||
|
||||
// Import XhrFactory from @angular/common
|
||||
const commonImport = findImportDeclaration('@angular/common', allImportDeclarations);
|
||||
const commonNamedBinding = getNamedImports(commonImport);
|
||||
if (commonNamedBinding) {
|
||||
// Already has an import for '@angular/common', just add the named import.
|
||||
const index = commonNamedBinding.getStart();
|
||||
const length = commonNamedBinding.getWidth();
|
||||
const newImports = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
ts.factory.updateNamedImports(
|
||||
commonNamedBinding, [...commonNamedBinding.elements, xhrFactorySpecifier]),
|
||||
sourceFile);
|
||||
|
||||
recorder.remove(index, length).insertLeft(index, newImports);
|
||||
} else {
|
||||
// Add import to '@angular/common'
|
||||
const index = httpCommonImport.getFullStart();
|
||||
recorder.insertLeft(index, `\nimport { XhrFactory } from '@angular/common';`);
|
||||
}
|
||||
}
|
||||
|
||||
if (recorder) {
|
||||
tree.commitUpdate(recorder);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function findImportDeclaration(moduleSpecifier: string, importDeclarations: ts.ImportDeclaration[]):
|
||||
ts.ImportDeclaration|undefined {
|
||||
return importDeclarations.find(
|
||||
n => ts.isStringLiteral(n.moduleSpecifier) && n.moduleSpecifier.text === moduleSpecifier);
|
||||
}
|
||||
|
||||
function getNamedImports(importDeclaration: ts.ImportDeclaration|undefined): ts.NamedImports|
|
||||
undefined {
|
||||
const namedBindings = importDeclaration?.importClause?.namedBindings;
|
||||
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
||||
return namedBindings;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
|
@ -26,6 +26,7 @@ ts_library(
|
|||
"//packages/core/schematics/migrations/undecorated-classes-with-decorated-fields",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-di",
|
||||
"//packages/core/schematics/migrations/wait-for-async",
|
||||
"//packages/core/schematics/migrations/xhr-factory",
|
||||
"//packages/core/schematics/utils",
|
||||
"@npm//@angular-devkit/core",
|
||||
"@npm//@angular-devkit/schematics",
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @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';
|
||||
|
||||
describe('XhrFactory migration', () => {
|
||||
let tree: UnitTestTree;
|
||||
const runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
|
||||
|
||||
beforeEach(() => {
|
||||
tree = new UnitTestTree(new EmptyTree());
|
||||
});
|
||||
|
||||
it(`should replace 'XhrFactory' from '@angular/common/http' to '@angular/common'`, async () => {
|
||||
tree.create('/index.ts', tags.stripIndents`
|
||||
import { HttpClient } from '@angular/common';
|
||||
import { HttpErrorResponse, HttpResponse, XhrFactory } from '@angular/common/http';
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts')).toBe(tags.stripIndents`
|
||||
import { HttpClient, XhrFactory } from '@angular/common';
|
||||
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
|
||||
`);
|
||||
});
|
||||
|
||||
it(`should replace import for 'XhrFactory' to '@angular/common'`, async () => {
|
||||
tree.create('/index.ts', tags.stripIndents`
|
||||
import { Injecable } from '@angular/core';
|
||||
import { XhrFactory } from '@angular/common/http';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts')).toBe(tags.stripIndents`
|
||||
import { Injecable } from '@angular/core';
|
||||
import { XhrFactory } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
`);
|
||||
});
|
||||
|
||||
it(`should remove http import when 'XhrFactory' is the only imported symbol`, async () => {
|
||||
tree.create('/index.ts', tags.stripIndents`
|
||||
import { HttpClient } from '@angular/common';
|
||||
import { XhrFactory as XhrFactory2 } from '@angular/common/http';
|
||||
import { Injecable } from '@angular/core';
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts')).toBe(tags.stripIndents`
|
||||
import { HttpClient, XhrFactory as XhrFactory2 } from '@angular/common';
|
||||
import { Injecable } from '@angular/core';
|
||||
`);
|
||||
});
|
||||
|
||||
it(`should add named import when '@angular/common' is a namespace import`, async () => {
|
||||
tree.create('/index.ts', tags.stripIndents`
|
||||
import * as common from '@angular/common';
|
||||
import { XhrFactory } from '@angular/common/http';
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts')).toBe(tags.stripIndents`
|
||||
import * as common from '@angular/common';
|
||||
import { XhrFactory } from '@angular/common';
|
||||
`);
|
||||
});
|
||||
|
||||
async function runMigration(): Promise<void> {
|
||||
await runner.runSchematicAsync('migration-v12-xhr-factory', {}, tree).toPromise();
|
||||
}
|
||||
});
|
|
@ -3,7 +3,9 @@
|
|||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strict": true,
|
||||
"lib": ["es2015"],
|
||||
"moduleResolution": "node",
|
||||
"target": "es2019",
|
||||
"lib": ["es2019"],
|
||||
"types": ["jasmine"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
|
|
@ -110,7 +110,7 @@ export function replaceImport(
|
|||
|
||||
|
||||
/** Finds an import specifier with a particular name. */
|
||||
function findImportSpecifier(
|
||||
export function findImportSpecifier(
|
||||
nodes: ts.NodeArray<ts.ImportSpecifier>, specifierName: string): ts.ImportSpecifier|undefined {
|
||||
return nodes.find(element => {
|
||||
const {name, propertyName} = element;
|
||||
|
|
Loading…
Reference in New Issue