@openrewrite/recipes-angular 1.8.0 → 1.9.0-20260804-162757
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -2
- package/dist/index.js.map +1 -1
- package/dist/migration/add-eager-change-detection.d.ts +8 -0
- package/dist/migration/add-eager-change-detection.d.ts.map +1 -0
- package/dist/migration/add-eager-change-detection.js +90 -0
- package/dist/migration/add-eager-change-detection.js.map +1 -0
- package/dist/migration/add-no-incremental-hydration.d.ts +8 -0
- package/dist/migration/add-no-incremental-hydration.d.ts.map +1 -0
- package/dist/migration/add-no-incremental-hydration.js +69 -0
- package/dist/migration/add-no-incremental-hydration.js.map +1 -0
- package/dist/migration/add-strict-templates-false.d.ts +8 -0
- package/dist/migration/add-strict-templates-false.d.ts.map +1 -0
- package/dist/migration/add-strict-templates-false.js +129 -0
- package/dist/migration/add-strict-templates-false.js.map +1 -0
- package/dist/migration/remove-with-fetch.d.ts +8 -0
- package/dist/migration/remove-with-fetch.d.ts.map +1 -0
- package/dist/migration/remove-with-fetch.js +73 -0
- package/dist/migration/remove-with-fetch.js.map +1 -0
- package/dist/migration/rename-angular-js-globals.d.ts +8 -0
- package/dist/migration/rename-angular-js-globals.d.ts.map +1 -0
- package/dist/migration/rename-angular-js-globals.js +56 -0
- package/dist/migration/rename-angular-js-globals.js.map +1 -0
- package/dist/migration/rename-default-change-detection.d.ts +8 -0
- package/dist/migration/rename-default-change-detection.d.ts.map +1 -0
- package/dist/migration/rename-default-change-detection.js +52 -0
- package/dist/migration/rename-default-change-detection.js.map +1 -0
- package/dist/migration/replace-create-ng-module-ref.d.ts +8 -0
- package/dist/migration/replace-create-ng-module-ref.d.ts.map +1 -0
- package/dist/migration/replace-create-ng-module-ref.js +51 -0
- package/dist/migration/replace-create-ng-module-ref.js.map +1 -0
- package/dist/migration/upgrade-to-angular-22.d.ts +14 -0
- package/dist/migration/upgrade-to-angular-22.d.ts.map +1 -0
- package/dist/migration/upgrade-to-angular-22.js +96 -0
- package/dist/migration/upgrade-to-angular-22.js.map +1 -0
- package/dist/search/find-can-match-usage.d.ts +8 -0
- package/dist/search/find-can-match-usage.d.ts.map +1 -0
- package/dist/search/find-can-match-usage.js +65 -0
- package/dist/search/find-can-match-usage.js.map +1 -0
- package/dist/search/find-check-no-changes-usage.d.ts +8 -0
- package/dist/search/find-check-no-changes-usage.d.ts.map +1 -0
- package/dist/search/find-check-no-changes-usage.js +50 -0
- package/dist/search/find-check-no-changes-usage.js.map +1 -0
- package/dist/search/find-http-xhr-backend-usage.d.ts +8 -0
- package/dist/search/find-http-xhr-backend-usage.d.ts.map +1 -0
- package/dist/search/find-http-xhr-backend-usage.js +65 -0
- package/dist/search/find-http-xhr-backend-usage.js.map +1 -0
- package/dist/search/find-implicit-params-inheritance.d.ts +8 -0
- package/dist/search/find-implicit-params-inheritance.d.ts.map +1 -0
- package/dist/search/find-implicit-params-inheritance.js +74 -0
- package/dist/search/find-implicit-params-inheritance.js.map +1 -0
- package/dist/search/find-provide-routes-usage.d.ts +8 -0
- package/dist/search/find-provide-routes-usage.d.ts.map +1 -0
- package/dist/search/find-provide-routes-usage.js +64 -0
- package/dist/search/find-provide-routes-usage.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +39 -0
- package/src/migration/add-eager-change-detection.ts +72 -0
- package/src/migration/add-no-incremental-hydration.ts +49 -0
- package/src/migration/add-strict-templates-false.ts +113 -0
- package/src/migration/remove-with-fetch.ts +53 -0
- package/src/migration/rename-angular-js-globals.ts +37 -0
- package/src/migration/rename-default-change-detection.ts +33 -0
- package/src/migration/replace-create-ng-module-ref.ts +32 -0
- package/src/migration/upgrade-to-angular-22.ts +76 -0
- package/src/search/find-can-match-usage.ts +45 -0
- package/src/search/find-check-no-changes-usage.ts +29 -0
- package/src/search/find-http-xhr-backend-usage.ts +44 -0
- package/src/search/find-implicit-params-inheritance.ts +49 -0
- package/src/search/find-provide-routes-usage.ts +43 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, produceAsync, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
const RENAMES: Record<string, string> = {
|
|
12
|
+
getAngularLib: 'getAngularJSGlobal',
|
|
13
|
+
setAngularLib: 'setAngularJSGlobal',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class RenameAngularJsGlobals extends Recipe {
|
|
17
|
+
readonly name = "org.openrewrite.angular.migration.rename-angular-js-globals";
|
|
18
|
+
readonly displayName: string = "Rename `getAngularLib`/`setAngularLib` to `getAngularJSGlobal`/`setAngularJSGlobal`";
|
|
19
|
+
readonly description: string = "Renames the `getAngularLib` and `setAngularLib` functions from `@angular/upgrade` to " +
|
|
20
|
+
"`getAngularJSGlobal` and `setAngularJSGlobal`. The deprecated aliases were removed in Angular 22.";
|
|
21
|
+
|
|
22
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
23
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
24
|
+
override async visitIdentifier(identifier: J.Identifier, p: ExecutionContext): Promise<J | undefined> {
|
|
25
|
+
const id = await super.visitIdentifier(identifier, p) as J.Identifier;
|
|
26
|
+
if (!id) return id;
|
|
27
|
+
|
|
28
|
+
const replacement = RENAMES[id.simpleName];
|
|
29
|
+
if (!replacement) return id;
|
|
30
|
+
|
|
31
|
+
return produceAsync(id, async draft => {
|
|
32
|
+
draft.simpleName = replacement;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
export class RenameDefaultChangeDetection extends Recipe {
|
|
12
|
+
readonly name = "org.openrewrite.angular.migration.rename-default-change-detection";
|
|
13
|
+
readonly displayName: string = "Rename `ChangeDetectionStrategy.Default` to `ChangeDetectionStrategy.Eager`";
|
|
14
|
+
readonly description: string = "Renames the deprecated `ChangeDetectionStrategy.Default` enum member to its Angular 22 " +
|
|
15
|
+
"replacement `ChangeDetectionStrategy.Eager`. Both members have the same value, so the change is behavior preserving.";
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
19
|
+
protected async visitFieldAccess(fieldAccess: J.FieldAccess, p: ExecutionContext): Promise<J | undefined> {
|
|
20
|
+
const f = await super.visitFieldAccess(fieldAccess, p) as J.FieldAccess;
|
|
21
|
+
if (!f) return f;
|
|
22
|
+
|
|
23
|
+
if (!isIdentifier(f.target) || f.target.simpleName !== 'ChangeDetectionStrategy') return f;
|
|
24
|
+
if (f.name.element.simpleName !== 'Default') return f;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...f,
|
|
28
|
+
name: {...f.name, element: {...f.name.element, simpleName: 'Eager'}}
|
|
29
|
+
} as J.FieldAccess;
|
|
30
|
+
}
|
|
31
|
+
}();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, produceAsync, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, maybeAddImport, maybeRemoveImport} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
export class ReplaceCreateNgModuleRef extends Recipe {
|
|
12
|
+
readonly name = "org.openrewrite.angular.migration.replace-create-ng-module-ref";
|
|
13
|
+
readonly displayName: string = "Replace `createNgModuleRef` with `createNgModule`";
|
|
14
|
+
readonly description: string = "Renames `createNgModuleRef` to `createNgModule` in imports and usages. " +
|
|
15
|
+
"The deprecated `createNgModuleRef` alias was removed in Angular 22.";
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
19
|
+
override async visitIdentifier(identifier: J.Identifier, p: ExecutionContext): Promise<J | undefined> {
|
|
20
|
+
const id = await super.visitIdentifier(identifier, p) as J.Identifier;
|
|
21
|
+
if (!id || id.simpleName !== 'createNgModuleRef') return id;
|
|
22
|
+
|
|
23
|
+
maybeAddImport(this, {module: '@angular/core', member: 'createNgModule'});
|
|
24
|
+
maybeRemoveImport(this, '@angular/core', 'createNgModuleRef');
|
|
25
|
+
|
|
26
|
+
return produceAsync(id, async draft => {
|
|
27
|
+
draft.simpleName = 'createNgModule';
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Recipe} from "@openrewrite/rewrite";
|
|
8
|
+
import {upgradeDependencyVersion} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {UpgradeToAngular21CodeChanges} from "./upgrade-to-angular-21";
|
|
10
|
+
import {FindProvideRoutesUsage} from "../search/find-provide-routes-usage";
|
|
11
|
+
import {FindCheckNoChangesUsage} from "../search/find-check-no-changes-usage";
|
|
12
|
+
import {FindCanMatchUsage} from "../search/find-can-match-usage";
|
|
13
|
+
import {FindHttpXhrBackendUsage} from "../search/find-http-xhr-backend-usage";
|
|
14
|
+
import {FindImplicitParamsInheritance} from "../search/find-implicit-params-inheritance";
|
|
15
|
+
import {AddEagerChangeDetection} from "./add-eager-change-detection";
|
|
16
|
+
import {RenameDefaultChangeDetection} from "./rename-default-change-detection";
|
|
17
|
+
import {AddNoIncrementalHydration} from "./add-no-incremental-hydration";
|
|
18
|
+
import {AddStrictTemplatesFalse} from "./add-strict-templates-false";
|
|
19
|
+
import {RemoveWithFetch} from "./remove-with-fetch";
|
|
20
|
+
import {ReplaceCreateNgModuleRef} from "./replace-create-ng-module-ref";
|
|
21
|
+
import {RenameAngularJsGlobals} from "./rename-angular-js-globals";
|
|
22
|
+
|
|
23
|
+
export class UpgradeToAngular22CodeChanges extends Recipe {
|
|
24
|
+
readonly name = "org.openrewrite.angular.UpgradeToAngular22CodeChanges";
|
|
25
|
+
readonly displayName = "Upgrade to Angular 22 (code changes)";
|
|
26
|
+
readonly description = "The source changes of the Angular 22 migration, without the dependency version bumps. " +
|
|
27
|
+
"Use `UpgradeToAngular22` unless you are composing a migration to a later major.";
|
|
28
|
+
|
|
29
|
+
async recipeList(): Promise<Recipe[]> {
|
|
30
|
+
return [
|
|
31
|
+
new UpgradeToAngular21CodeChanges(),
|
|
32
|
+
// Removed and behaviorally changed APIs that need human review
|
|
33
|
+
new FindProvideRoutesUsage(),
|
|
34
|
+
new FindCheckNoChangesUsage(),
|
|
35
|
+
new FindCanMatchUsage(),
|
|
36
|
+
new FindHttpXhrBackendUsage(),
|
|
37
|
+
new FindImplicitParamsInheritance(),
|
|
38
|
+
// Change detection defaults
|
|
39
|
+
new AddEagerChangeDetection(),
|
|
40
|
+
new RenameDefaultChangeDetection(),
|
|
41
|
+
// Hydration and template type checking defaults
|
|
42
|
+
new AddNoIncrementalHydration(),
|
|
43
|
+
new AddStrictTemplatesFalse(),
|
|
44
|
+
// Removed and deprecated APIs
|
|
45
|
+
new RemoveWithFetch(),
|
|
46
|
+
new ReplaceCreateNgModuleRef(),
|
|
47
|
+
new RenameAngularJsGlobals(),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class UpgradeToAngular22 extends Recipe {
|
|
53
|
+
readonly name = "org.openrewrite.angular.UpgradeToAngular22";
|
|
54
|
+
readonly displayName = "Upgrade to Angular 22";
|
|
55
|
+
readonly description = "Migrates Angular 21.x applications to Angular 22. " +
|
|
56
|
+
"This includes running the Angular 21 migration first, making the pre-existing change detection, " +
|
|
57
|
+
"incremental hydration, and strict template defaults explicit, replacing APIs removed in Angular 22, " +
|
|
58
|
+
"flagging the router and `HttpClient` behavior changes for review, and upgrading TypeScript to 6.0.x.";
|
|
59
|
+
|
|
60
|
+
async recipeList(): Promise<Recipe[]> {
|
|
61
|
+
return [
|
|
62
|
+
new UpgradeToAngular22CodeChanges(),
|
|
63
|
+
// Angular core packages
|
|
64
|
+
await upgradeDependencyVersion({packagePattern: "@angular/*", newVersion: "22.x"}),
|
|
65
|
+
// Angular DevKit; `architect` and `build-webpack` kept the 0.<major><minor>.x CLI scheme
|
|
66
|
+
await upgradeDependencyVersion({packageName: "@angular-devkit/core", newVersion: "22.x"}),
|
|
67
|
+
await upgradeDependencyVersion({packagePattern: "@angular-devkit/s*", newVersion: "22.x"}),
|
|
68
|
+
await upgradeDependencyVersion({packageName: "@angular-devkit/build-angular", newVersion: "22.x"}),
|
|
69
|
+
await upgradeDependencyVersion({packagePattern: "@angular-devkit/architect*", newVersion: "0.2201.x"}),
|
|
70
|
+
await upgradeDependencyVersion({packageName: "@angular-devkit/build-webpack", newVersion: "0.2201.x"}),
|
|
71
|
+
await upgradeDependencyVersion({packageName: "@schematics/angular", newVersion: "22.x"}),
|
|
72
|
+
// TypeScript
|
|
73
|
+
await upgradeDependencyVersion({packageName: "typescript", newVersion: "6.0.x"}),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
const CAN_MATCH_TYPES = ['CanMatch', 'CanMatchFn'];
|
|
12
|
+
|
|
13
|
+
export class FindCanMatchUsage extends Recipe {
|
|
14
|
+
readonly name = "org.openrewrite.angular.search.find-can-match-usage";
|
|
15
|
+
readonly displayName: string = "Find `CanMatch` guard usage";
|
|
16
|
+
readonly description: string = "Finds imports of `CanMatch` and `CanMatchFn` from `@angular/router`. " +
|
|
17
|
+
"In Angular 22 the `currentSnapshot` parameter became required, so class based guards must declare a third argument.";
|
|
18
|
+
|
|
19
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
20
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
21
|
+
protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
|
|
22
|
+
const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
23
|
+
if (!imp.moduleSpecifier) return imp;
|
|
24
|
+
|
|
25
|
+
const moduleSpec = imp.moduleSpecifier.element;
|
|
26
|
+
if (moduleSpec.kind !== J.Kind.Literal) return imp;
|
|
27
|
+
if ((moduleSpec as J.Literal).value !== '@angular/router') return imp;
|
|
28
|
+
|
|
29
|
+
const namedBindings = imp.importClause?.namedBindings;
|
|
30
|
+
if (namedBindings?.kind === JS.Kind.NamedImports) {
|
|
31
|
+
const named = namedBindings as JS.NamedImports;
|
|
32
|
+
for (const specifier of named.elements.elements) {
|
|
33
|
+
const spec = specifier.element as JS.ImportSpecifier;
|
|
34
|
+
if (isIdentifier(spec.specifier) && CAN_MATCH_TYPES.includes(spec.specifier.simpleName)) {
|
|
35
|
+
return foundSearchResult(imp,
|
|
36
|
+
"The currentSnapshot parameter of CanMatchFn is required as of Angular 22; class implementations of CanMatch must declare the third argument.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return imp;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
export class FindCheckNoChangesUsage extends Recipe {
|
|
12
|
+
readonly name = "org.openrewrite.angular.search.find-check-no-changes-usage";
|
|
13
|
+
readonly displayName: string = "Find `ChangeDetectorRef.checkNoChanges` usage";
|
|
14
|
+
readonly description: string = "Finds calls to `checkNoChanges()`, which was removed from `ChangeDetectorRef` in Angular 22. " +
|
|
15
|
+
"In tests, use `fixture.detectChanges()` instead.";
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
19
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
20
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
21
|
+
if (!m || !m.select) return m;
|
|
22
|
+
if (!isIdentifier(m.name) || m.name.simpleName !== 'checkNoChanges') return m;
|
|
23
|
+
|
|
24
|
+
return foundSearchResult(m,
|
|
25
|
+
"ChangeDetectorRef.checkNoChanges() was removed in Angular 22. Use fixture.detectChanges() in tests instead.");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
export class FindHttpXhrBackendUsage extends Recipe {
|
|
12
|
+
readonly name = "org.openrewrite.angular.search.find-http-xhr-backend-usage";
|
|
13
|
+
readonly displayName: string = "Find `HttpXhrBackend` usage";
|
|
14
|
+
readonly description: string = "Finds imports of `HttpXhrBackend` from `@angular/common/http`. " +
|
|
15
|
+
"Angular 22 uses the Fetch API by default; add `withXhr()` to `provideHttpClient()` to keep using `XMLHttpRequest`, " +
|
|
16
|
+
"which is also required for upload progress reports.";
|
|
17
|
+
|
|
18
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
19
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
20
|
+
protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
|
|
21
|
+
const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
22
|
+
if (!imp.moduleSpecifier) return imp;
|
|
23
|
+
|
|
24
|
+
const moduleSpec = imp.moduleSpecifier.element;
|
|
25
|
+
if (moduleSpec.kind !== J.Kind.Literal) return imp;
|
|
26
|
+
if ((moduleSpec as J.Literal).value !== '@angular/common/http') return imp;
|
|
27
|
+
|
|
28
|
+
const namedBindings = imp.importClause?.namedBindings;
|
|
29
|
+
if (namedBindings?.kind === JS.Kind.NamedImports) {
|
|
30
|
+
const named = namedBindings as JS.NamedImports;
|
|
31
|
+
for (const specifier of named.elements.elements) {
|
|
32
|
+
const spec = specifier.element as JS.ImportSpecifier;
|
|
33
|
+
if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'HttpXhrBackend') {
|
|
34
|
+
return foundSearchResult(imp,
|
|
35
|
+
"HttpXhrBackend is no longer the default in Angular 22. Use provideHttpClient(withXhr()) to keep XMLHttpRequest based requests. See https://angular.dev/api/common/http/withXhr");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return imp;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
export class FindImplicitParamsInheritance extends Recipe {
|
|
12
|
+
readonly name = "org.openrewrite.angular.search.find-implicit-params-inheritance";
|
|
13
|
+
readonly displayName: string = "Find router configuration relying on the default `paramsInheritanceStrategy`";
|
|
14
|
+
readonly description: string = "Finds `provideRouter()` and `RouterModule.forRoot()` calls that do not set `paramsInheritanceStrategy`. " +
|
|
15
|
+
"Angular 22 changed the default from `emptyOnly` to `always`, so route parameters and data are now inherited from all parent routes.";
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
19
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
20
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
21
|
+
if (!m || !isIdentifier(m.name)) return m;
|
|
22
|
+
|
|
23
|
+
const isProvideRouter = !m.select && m.name.simpleName === 'provideRouter';
|
|
24
|
+
const isForRoot = m.name.simpleName === 'forRoot' &&
|
|
25
|
+
!!m.select && isIdentifier(m.select.element) && m.select.element.simpleName === 'RouterModule';
|
|
26
|
+
if (!isProvideRouter && !isForRoot) return m;
|
|
27
|
+
|
|
28
|
+
if (await declaresParamsInheritanceStrategy(m, p)) return m;
|
|
29
|
+
|
|
30
|
+
return foundSearchResult(m,
|
|
31
|
+
"paramsInheritanceStrategy defaults to 'always' as of Angular 22. Set it to 'emptyOnly' to keep the previous behavior.");
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function declaresParamsInheritanceStrategy(method: J.MethodInvocation, p: ExecutionContext): Promise<boolean> {
|
|
38
|
+
let found = false;
|
|
39
|
+
const detector = new class extends JavaScriptVisitor<ExecutionContext> {
|
|
40
|
+
override async visitIdentifier(identifier: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
|
|
41
|
+
if (identifier.simpleName === 'paramsInheritanceStrategy') found = true;
|
|
42
|
+
return identifier;
|
|
43
|
+
}
|
|
44
|
+
}();
|
|
45
|
+
for (const element of method.arguments?.elements ?? []) {
|
|
46
|
+
await detector.visit(element.element, p);
|
|
47
|
+
}
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
|
|
11
|
+
export class FindProvideRoutesUsage extends Recipe {
|
|
12
|
+
readonly name = "org.openrewrite.angular.search.find-provide-routes-usage";
|
|
13
|
+
readonly displayName: string = "Find `provideRoutes` usage";
|
|
14
|
+
readonly description: string = "Finds imports of `provideRoutes` from `@angular/router`, which was removed in Angular 22. " +
|
|
15
|
+
"Use `provideRouter()`, or provide the `ROUTES` multi token directly.";
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
19
|
+
protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
|
|
20
|
+
const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
21
|
+
if (!imp.moduleSpecifier) return imp;
|
|
22
|
+
|
|
23
|
+
const moduleSpec = imp.moduleSpecifier.element;
|
|
24
|
+
if (moduleSpec.kind !== J.Kind.Literal) return imp;
|
|
25
|
+
if ((moduleSpec as J.Literal).value !== '@angular/router') return imp;
|
|
26
|
+
|
|
27
|
+
const namedBindings = imp.importClause?.namedBindings;
|
|
28
|
+
if (namedBindings?.kind === JS.Kind.NamedImports) {
|
|
29
|
+
const named = namedBindings as JS.NamedImports;
|
|
30
|
+
for (const specifier of named.elements.elements) {
|
|
31
|
+
const spec = specifier.element as JS.ImportSpecifier;
|
|
32
|
+
if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'provideRoutes') {
|
|
33
|
+
return foundSearchResult(imp,
|
|
34
|
+
"provideRoutes() was removed in Angular 22. Use provideRouter(), or the ROUTES multi token if necessary.");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return imp;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|