@openrewrite/recipes-angular 0.0.0

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.
Files changed (106) hide show
  1. package/package.json +37 -0
  2. package/src/index.ts +321 -0
  3. package/src/migration/add-default-configuration.ts +121 -0
  4. package/src/migration/add-localize-polyfill.ts +51 -0
  5. package/src/migration/add-module-with-providers-generic.ts +102 -0
  6. package/src/migration/add-static-false-to-view-queries.ts +92 -0
  7. package/src/migration/add-testbed-teardown.ts +41 -0
  8. package/src/migration/enable-aot-build.ts +132 -0
  9. package/src/migration/explicit-standalone-flag.ts +82 -0
  10. package/src/migration/migrate-constructor-to-inject.ts +172 -0
  11. package/src/migration/migrate-input-to-signal.ts +320 -0
  12. package/src/migration/migrate-output-to-signal.ts +268 -0
  13. package/src/migration/migrate-query-to-signal.ts +276 -0
  14. package/src/migration/migrate-to-solution-style-tsconfig.ts +139 -0
  15. package/src/migration/move-document-to-core.ts +40 -0
  16. package/src/migration/remove-aot-summaries.ts +72 -0
  17. package/src/migration/remove-browser-module-with-server-transition.ts +185 -0
  18. package/src/migration/remove-component-factory-resolver.ts +48 -0
  19. package/src/migration/remove-default-project.ts +52 -0
  20. package/src/migration/remove-empty-ng-on-init.ts +80 -0
  21. package/src/migration/remove-enable-ivy.ts +63 -0
  22. package/src/migration/remove-entry-components.ts +75 -0
  23. package/src/migration/remove-es5-browser-support.ts +59 -0
  24. package/src/migration/remove-extract-css.ts +60 -0
  25. package/src/migration/remove-ie-polyfills.ts +118 -0
  26. package/src/migration/remove-module-id.ts +59 -0
  27. package/src/migration/remove-relative-link-resolution.ts +64 -0
  28. package/src/migration/remove-standalone-true.ts +50 -0
  29. package/src/migration/remove-static-false.ts +71 -0
  30. package/src/migration/remove-zone-js-polyfill.ts +55 -0
  31. package/src/migration/rename-after-render.ts +32 -0
  32. package/src/migration/rename-check-no-changes.ts +29 -0
  33. package/src/migration/rename-file.ts +72 -0
  34. package/src/migration/rename-pending-tasks.ts +30 -0
  35. package/src/migration/rename-zoneless-provider.ts +29 -0
  36. package/src/migration/replace-async-with-wait-for-async.ts +32 -0
  37. package/src/migration/replace-deep-zone-js-imports.ts +118 -0
  38. package/src/migration/replace-http-client-module.ts +276 -0
  39. package/src/migration/replace-initial-navigation.ts +73 -0
  40. package/src/migration/replace-inject-flags.ts +83 -0
  41. package/src/migration/replace-load-children-string.ts +48 -0
  42. package/src/migration/replace-node-sass-with-sass.ts +22 -0
  43. package/src/migration/replace-router-link-with-href.ts +37 -0
  44. package/src/migration/replace-testbed-get-with-inject.ts +33 -0
  45. package/src/migration/replace-untyped-forms.ts +59 -0
  46. package/src/migration/replace-validator-with-validators.ts +41 -0
  47. package/src/migration/replace-view-encapsulation-native.ts +51 -0
  48. package/src/migration/update-component-template-url.ts +186 -0
  49. package/src/migration/update-tsconfig-module.ts +75 -0
  50. package/src/migration/update-tsconfig-target.ts +61 -0
  51. package/src/migration/upgrade-to-angular-10.ts +52 -0
  52. package/src/migration/upgrade-to-angular-11.ts +52 -0
  53. package/src/migration/upgrade-to-angular-12.ts +43 -0
  54. package/src/migration/upgrade-to-angular-13.ts +45 -0
  55. package/src/migration/upgrade-to-angular-14.ts +44 -0
  56. package/src/migration/upgrade-to-angular-15.ts +43 -0
  57. package/src/migration/upgrade-to-angular-16.ts +57 -0
  58. package/src/migration/upgrade-to-angular-17.ts +43 -0
  59. package/src/migration/upgrade-to-angular-18.ts +69 -0
  60. package/src/migration/upgrade-to-angular-19.ts +52 -0
  61. package/src/migration/upgrade-to-angular-20.ts +47 -0
  62. package/src/migration/upgrade-to-angular-21.ts +53 -0
  63. package/src/migration/upgrade-to-angular-8.ts +54 -0
  64. package/src/migration/upgrade-to-angular-9.ts +69 -0
  65. package/src/search/find-analyze-for-entry-components-usage.ts +46 -0
  66. package/src/search/find-angular-decorator.ts +58 -0
  67. package/src/search/find-angular-http-usage.ts +35 -0
  68. package/src/search/find-animation-driver-matches-element.ts +38 -0
  69. package/src/search/find-async-test-helper-usage.ts +45 -0
  70. package/src/search/find-bare-module-with-providers.ts +47 -0
  71. package/src/search/find-browser-transfer-state-module-usage.ts +45 -0
  72. package/src/search/find-common-module-usage.ts +47 -0
  73. package/src/search/find-compiler-factory-usage.ts +51 -0
  74. package/src/search/find-date-pipe-default-timezone-usage.ts +46 -0
  75. package/src/search/find-effect-timing-usage.ts +28 -0
  76. package/src/search/find-empty-projectable-nodes.ts +68 -0
  77. package/src/search/find-fake-async-usage.ts +37 -0
  78. package/src/search/find-hammer-js-usage.ts +48 -0
  79. package/src/search/find-i18n-usage.ts +94 -0
  80. package/src/search/find-karma-usage.ts +47 -0
  81. package/src/search/find-load-children-string-usage.ts +43 -0
  82. package/src/search/find-missing-injectable.ts +75 -0
  83. package/src/search/find-ng-class-usage.ts +45 -0
  84. package/src/search/find-ng-style-usage.ts +45 -0
  85. package/src/search/find-path-match-type-usage.ts +44 -0
  86. package/src/search/find-platform-dynamic-server-usage.ts +38 -0
  87. package/src/search/find-platform-webworker-usage.ts +34 -0
  88. package/src/search/find-platform-worker-usage.ts +39 -0
  89. package/src/search/find-preserve-fragment-usage.ts +32 -0
  90. package/src/search/find-preserve-query-params-usage.ts +32 -0
  91. package/src/search/find-provided-in-deprecated-usage.ts +65 -0
  92. package/src/search/find-reflective-injector-usage.ts +45 -0
  93. package/src/search/find-render-application-usage.ts +47 -0
  94. package/src/search/find-render-component-type-usage.ts +46 -0
  95. package/src/search/find-render-module-factory-usage.ts +45 -0
  96. package/src/search/find-renderer-usage.ts +46 -0
  97. package/src/search/find-resource-cache-provider-usage.ts +38 -0
  98. package/src/search/find-root-renderer-usage.ts +47 -0
  99. package/src/search/find-rxjs-compat-usage.ts +40 -0
  100. package/src/search/find-server-transfer-state-module-usage.ts +38 -0
  101. package/src/search/find-setup-testing-router-usage.ts +45 -0
  102. package/src/search/find-testability-pending-request-usage.ts +38 -0
  103. package/src/search/find-undecorated-angular-class.ts +78 -0
  104. package/src/search/find-with-no-dom-reuse-usage.ts +46 -0
  105. package/src/search/find-wrapped-value-usage.ts +46 -0
  106. package/src/search/find-zone-js-usage.ts +43 -0
@@ -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, capture, pattern} from "@openrewrite/rewrite/javascript";
9
+ import {J} from "@openrewrite/rewrite/java";
10
+ import {create} from "mutative";
11
+
12
+ export class ReplaceTestBedGetWithInject extends Recipe {
13
+ readonly name = "org.openrewrite.angular.migration.replace-testbed-get-with-inject";
14
+ readonly displayName: string = "Replace `TestBed.get()` with `TestBed.inject()`";
15
+ readonly description: string = "Replaces deprecated `TestBed.get()` calls with `TestBed.inject()`. " +
16
+ "`TestBed.get()` was deprecated in Angular 9 and removed in Angular 13.";
17
+
18
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
19
+ return new class extends JavaScriptVisitor<ExecutionContext> {
20
+ override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
21
+ let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
22
+ if (!m) return m;
23
+
24
+ const args = capture<J>({variadic: true});
25
+ if (!await pattern`TestBed.get(${args})`.match(m, this.cursor)) return m;
26
+
27
+ return create(m, draft => {
28
+ (draft.name as any).simpleName = 'inject';
29
+ }) as J.MethodInvocation;
30
+ }
31
+ }();
32
+ }
33
+ }
@@ -0,0 +1,59 @@
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 {ChangeImport, JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
9
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+ import {create} from "mutative";
11
+
12
+ const MODULE = '@angular/forms';
13
+
14
+ const FORM_RENAMES: [string, string][] = [
15
+ ['FormControl', 'UntypedFormControl'],
16
+ ['FormGroup', 'UntypedFormGroup'],
17
+ ['FormArray', 'UntypedFormArray'],
18
+ ['FormBuilder', 'UntypedFormBuilder'],
19
+ ];
20
+
21
+ class RenameFormIdentifiers extends Recipe {
22
+ readonly name = "org.openrewrite.angular.migration.replace-untyped-forms.rename-identifiers";
23
+ readonly displayName: string = "Rename form identifiers to untyped variants";
24
+ readonly description: string = "Renames form class identifiers in code usages.";
25
+
26
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
27
+ const renameMap = new Map(FORM_RENAMES);
28
+ return new class extends JavaScriptVisitor<ExecutionContext> {
29
+ override async visitIdentifier(identifier: J.Identifier, p: ExecutionContext): Promise<J | undefined> {
30
+ let id = await super.visitIdentifier(identifier, p) as J.Identifier;
31
+ if (!id) return id;
32
+
33
+ const newName = renameMap.get(id.simpleName);
34
+ if (!newName) return id;
35
+
36
+ return create(id, draft => {
37
+ draft.simpleName = newName;
38
+ }) as J.Identifier;
39
+ }
40
+ }();
41
+ }
42
+ }
43
+
44
+ export class ReplaceUntypedForms extends Recipe {
45
+ readonly name = "org.openrewrite.angular.migration.replace-untyped-forms";
46
+ readonly displayName: string = "Replace form classes with untyped variants";
47
+ readonly description: string = "Renames `FormControl`, `FormGroup`, `FormArray`, and `FormBuilder` to their " +
48
+ "`Untyped*` equivalents in imports and usages. Angular 14 introduced strictly typed forms, " +
49
+ "requiring existing untyped usages to migrate to the `Untyped*` aliases.";
50
+
51
+ async recipeList(): Promise<Recipe[]> {
52
+ return [
53
+ ...FORM_RENAMES.map(([oldMember, newMember]) =>
54
+ new ChangeImport({oldModule: MODULE, oldMember, newModule: MODULE, newMember})
55
+ ),
56
+ new RenameFormIdentifiers(),
57
+ ];
58
+ }
59
+ }
@@ -0,0 +1,41 @@
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, JS} from "@openrewrite/rewrite/javascript";
9
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+ import {create} from "mutative";
11
+
12
+ export class ReplaceValidatorWithValidators extends Recipe {
13
+ readonly name = "org.openrewrite.angular.migration.replace-validator-with-validators";
14
+ readonly displayName: string = "Replace `validator`/`asyncValidator` with plural forms";
15
+ readonly description: string = "Renames the deprecated singular `validator` and `asyncValidator` " +
16
+ "property names to `validators` and `asyncValidators` (plural). " +
17
+ "Angular 10 deprecated the singular forms in favor of `AbstractControlOptions`.";
18
+
19
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
20
+ const replacements = new Map([
21
+ ['validator', 'validators'],
22
+ ['asyncValidator', 'asyncValidators'],
23
+ ]);
24
+ return new class extends JavaScriptVisitor<ExecutionContext> {
25
+ override async visitPropertyAssignment(prop: JS.PropertyAssignment, p: ExecutionContext): Promise<J | undefined> {
26
+ let pa = await super.visitPropertyAssignment(prop, p) as JS.PropertyAssignment;
27
+ if (!pa) return pa;
28
+
29
+ const nameExpr = pa.name.element;
30
+ if (!isIdentifier(nameExpr)) return pa;
31
+
32
+ const replacement = replacements.get(nameExpr.simpleName);
33
+ if (!replacement) return pa;
34
+
35
+ return create(pa, draft => {
36
+ (draft.name.element as any).simpleName = replacement;
37
+ }) as JS.PropertyAssignment;
38
+ }
39
+ }();
40
+ }
41
+ }
@@ -0,0 +1,51 @@
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, JS} from "@openrewrite/rewrite/javascript";
9
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+ import {create} from "mutative";
11
+
12
+ export class ReplaceViewEncapsulationNative extends Recipe {
13
+ readonly name = "org.openrewrite.angular.migration.replace-view-encapsulation-native";
14
+ readonly displayName: string = "Replace `ViewEncapsulation.Native` with `ViewEncapsulation.ShadowDom`";
15
+ readonly description: string = "Replaces `ViewEncapsulation.Native` with `ViewEncapsulation.ShadowDom`. " +
16
+ "`ViewEncapsulation.Native` was deprecated in Angular 6 and removed in Angular 11.";
17
+
18
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
19
+ return new class extends JavaScriptVisitor<ExecutionContext> {
20
+ override async visitPropertyAssignment(prop: JS.PropertyAssignment, p: ExecutionContext): Promise<J | undefined> {
21
+ let pa = await super.visitPropertyAssignment(prop, p) as JS.PropertyAssignment;
22
+ if (!pa) return pa;
23
+
24
+ const nameExpr = pa.name.element;
25
+ if (!isIdentifier(nameExpr) || nameExpr.simpleName !== 'encapsulation') return pa;
26
+
27
+ if (!pa.initializer) return pa;
28
+
29
+ if (pa.initializer.kind === J.Kind.FieldAccess) {
30
+ const fieldAccess = pa.initializer as J.FieldAccess;
31
+ if (fieldAccess.target.kind !== J.Kind.Identifier) return pa;
32
+ if ((fieldAccess.target as J.Identifier).simpleName !== 'ViewEncapsulation') return pa;
33
+ if (fieldAccess.name.element.simpleName !== 'Native') return pa;
34
+
35
+ return create(pa, draft => {
36
+ const fa = (draft as any).initializer;
37
+ fa.name = {
38
+ ...fieldAccess.name,
39
+ element: {
40
+ ...fieldAccess.name.element,
41
+ simpleName: 'ShadowDom'
42
+ }
43
+ };
44
+ }) as JS.PropertyAssignment;
45
+ }
46
+
47
+ return pa;
48
+ }
49
+ }();
50
+ }
51
+ }
@@ -0,0 +1,186 @@
1
+ import {ExecutionContext, Option, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {J, isIdentifier, isLiteral} from "@openrewrite/rewrite/java";
4
+ import {create} from "mutative";
5
+
6
+ /**
7
+ * Updates the templateUrl property in Angular @Component decorators.
8
+ *
9
+ * This recipe is useful for:
10
+ * - Refactoring template file paths after directory restructuring
11
+ * - Standardizing template path conventions
12
+ * - Mass updating template references across components
13
+ *
14
+ * Example transformations:
15
+ * - `templateUrl: './old-path.html'` → `templateUrl: './new-path.html'`
16
+ * - `templateUrl: './component.html'` → `templateUrl: './templates/component.html'`
17
+ */
18
+ export class UpdateComponentTemplateUrl extends Recipe {
19
+ readonly name = "org.openrewrite.angular.migration.update-component-template-url";
20
+ readonly displayName: string = "Update component `templateUrl`";
21
+ readonly description: string = "Updates the `templateUrl` property value in Angular `@Component` decorators. Useful for refactoring template file paths or standardizing path conventions.";
22
+
23
+ @Option({
24
+ displayName: "Old template URL",
25
+ description: "The template URL to replace. Can be an exact path (e.g., `./old.html`) or a pattern.",
26
+ example: "./old-template.html"
27
+ })
28
+ oldTemplateUrl!: string;
29
+
30
+ @Option({
31
+ displayName: "New template URL",
32
+ description: "The new template URL to use.",
33
+ example: "./new-template.html"
34
+ })
35
+ newTemplateUrl!: string;
36
+
37
+ @Option({
38
+ displayName: "Use regex pattern",
39
+ description: "If `true`, treats `oldTemplateUrl` as a regex pattern and supports capture groups in `newTemplateUrl`. Default: `false`",
40
+ example: "true",
41
+ required: false
42
+ })
43
+ useRegex?: boolean;
44
+
45
+ constructor(options?: { oldTemplateUrl?: string; newTemplateUrl?: string; useRegex?: boolean }) {
46
+ super(options);
47
+ this.oldTemplateUrl ??= '';
48
+ this.newTemplateUrl ??= '';
49
+ this.useRegex ??= false;
50
+ }
51
+
52
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
53
+ const oldTemplateUrl = this.oldTemplateUrl;
54
+ const newTemplateUrl = this.newTemplateUrl;
55
+ const useRegex = this.useRegex;
56
+
57
+ return new class extends JavaScriptVisitor<ExecutionContext> {
58
+ override async visitAnnotation(annotation: J.Annotation, p: ExecutionContext): Promise<J | undefined> {
59
+ let a = await super.visitAnnotation(annotation, p) as J.Annotation;
60
+
61
+ if (!a) {
62
+ return a;
63
+ }
64
+
65
+ // Check if this is a @Component decorator
66
+ const annotType = a.annotationType;
67
+ let isComponentDecorator = false;
68
+
69
+ if (isIdentifier(annotType)) {
70
+ isComponentDecorator = annotType.simpleName === 'Component';
71
+ }
72
+
73
+ if (!isComponentDecorator) {
74
+ return a;
75
+ }
76
+
77
+ // Find the templateUrl property in the decorator arguments
78
+ if (!a.arguments || !a.arguments.elements || a.arguments.elements.length === 0) {
79
+ return a;
80
+ }
81
+
82
+ const firstArg = a.arguments.elements[0].element;
83
+ if (firstArg.kind !== J.Kind.NewClass) {
84
+ return a;
85
+ }
86
+
87
+ const newClass = firstArg as J.NewClass;
88
+ if (!newClass.body) {
89
+ return a;
90
+ }
91
+
92
+ // Look for templateUrl property and check if it needs updating
93
+ let needsChange = false;
94
+ let updatedTemplateUrl: string | null = null;
95
+
96
+ for (const stmtPadded of newClass.body.statements) {
97
+ const stmt = stmtPadded.element;
98
+
99
+ if (stmt.kind === JS.Kind.PropertyAssignment) {
100
+ const propAssignment = stmt as JS.PropertyAssignment;
101
+ const nameExpr = propAssignment.name.element;
102
+
103
+ if (isIdentifier(nameExpr) && nameExpr.simpleName === 'templateUrl') {
104
+ const initializer = propAssignment.initializer;
105
+
106
+ if (initializer && isLiteral(initializer) && typeof initializer.value === 'string') {
107
+ const currentUrl = initializer.value as string;
108
+
109
+ if (useRegex) {
110
+ // Use regex pattern matching
111
+ const pattern = new RegExp(oldTemplateUrl);
112
+ if (pattern.test(currentUrl)) {
113
+ updatedTemplateUrl = currentUrl.replace(pattern, newTemplateUrl);
114
+ if (updatedTemplateUrl !== currentUrl) {
115
+ needsChange = true;
116
+ }
117
+ }
118
+ } else {
119
+ // Use exact string matching
120
+ if (currentUrl === oldTemplateUrl) {
121
+ updatedTemplateUrl = newTemplateUrl;
122
+ needsChange = true;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ if (!needsChange || !updatedTemplateUrl) {
131
+ return a;
132
+ }
133
+
134
+ // Apply the transformation
135
+ const finalUpdatedUrl = updatedTemplateUrl;
136
+ const modifiedAnnotation = create(a, draft => {
137
+ if (!draft.arguments || !draft.arguments.elements[0]) {
138
+ return;
139
+ }
140
+
141
+ const firstArgDraft = draft.arguments.elements[0].element as J.NewClass;
142
+ if (!firstArgDraft.body) {
143
+ return;
144
+ }
145
+
146
+ // Find and update the templateUrl property
147
+ for (let i = 0; i < firstArgDraft.body.statements.length; i++) {
148
+ const stmt = firstArgDraft.body.statements[i].element;
149
+
150
+ if (stmt.kind === JS.Kind.PropertyAssignment) {
151
+ const propAssignment = stmt as JS.PropertyAssignment;
152
+ const nameExpr = propAssignment.name.element;
153
+
154
+ if (isIdentifier(nameExpr) && nameExpr.simpleName === 'templateUrl') {
155
+ const initializer = propAssignment.initializer;
156
+
157
+ if (initializer && isLiteral(initializer)) {
158
+ // Create a new literal with the updated URL
159
+ const newLiteral: J.Literal = {
160
+ ...initializer,
161
+ value: finalUpdatedUrl,
162
+ valueSource: `'${finalUpdatedUrl.replace(/'/g, "\\'")}'`
163
+ };
164
+
165
+ // Create new property assignment with updated literal
166
+ const newPropAssignment: JS.PropertyAssignment = {
167
+ ...propAssignment,
168
+ initializer: newLiteral
169
+ };
170
+
171
+ // Update the statement
172
+ firstArgDraft.body.statements[i] = {
173
+ ...firstArgDraft.body.statements[i],
174
+ element: newPropAssignment
175
+ };
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }) as J.Annotation;
181
+
182
+ return modifiedAnnotation;
183
+ }
184
+ }();
185
+ }
186
+ }
@@ -0,0 +1,75 @@
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 {JsonVisitor, Json, getMemberKeyName, isObject, isLiteral} from "@openrewrite/rewrite/json";
9
+
10
+ const OUTDATED_MODULES = ['commonjs', 'amd', 'umd', 'system', 'es2015', 'es6', 'none'];
11
+ const OUTDATED_MODULE_RESOLUTIONS = ['classic'];
12
+
13
+ export class UpdateTsconfigModule extends Recipe {
14
+ readonly name = "org.openrewrite.angular.migration.update-tsconfig-module";
15
+ readonly displayName: string = "Update `tsconfig.json` module settings for Ivy";
16
+ readonly description: string = "Updates `compilerOptions.module` to `esnext` and `compilerOptions.moduleResolution` to `node` in `tsconfig.json`. " +
17
+ "Angular 9's Ivy compiler requires ES module format. Already-current values like `es2020`, `node16`, `nodenext`, or `bundler` are left unchanged.";
18
+
19
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
20
+ return new class extends JsonVisitor<ExecutionContext> {
21
+ protected async visitDocument(doc: Json.Document, p: ExecutionContext): Promise<Json | undefined> {
22
+ if (!doc.sourcePath.endsWith('tsconfig.json') &&
23
+ !doc.sourcePath.endsWith('tsconfig.app.json') &&
24
+ !doc.sourcePath.endsWith('tsconfig.lib.json')) {
25
+ return doc;
26
+ }
27
+ return super.visitDocument(doc, p);
28
+ }
29
+
30
+ private inCompilerOptions = false;
31
+
32
+ protected async visitMember(member: Json.Member, p: ExecutionContext): Promise<Json | undefined> {
33
+ const m = await super.visitMember(member, p) as Json.Member;
34
+ if (!m) return m;
35
+
36
+ if (getMemberKeyName(m) === 'compilerOptions' && isObject(m.value)) {
37
+ this.inCompilerOptions = true;
38
+ const result = await super.visitMember(m, p) as Json.Member;
39
+ this.inCompilerOptions = false;
40
+ return result;
41
+ }
42
+
43
+ if (!this.inCompilerOptions || !isLiteral(m.value)) return m;
44
+
45
+ const keyName = getMemberKeyName(m);
46
+ const literal = m.value as Json.Literal;
47
+ const value = (literal.value as string).toLowerCase();
48
+
49
+ if (keyName === 'module' && OUTDATED_MODULES.includes(value)) {
50
+ return {
51
+ ...m,
52
+ value: {
53
+ ...literal,
54
+ value: 'esnext',
55
+ source: literal.source.replace(literal.value as string, 'esnext')
56
+ } as Json.Literal
57
+ } as Json.Member;
58
+ }
59
+
60
+ if (keyName === 'moduleResolution' && OUTDATED_MODULE_RESOLUTIONS.includes(value)) {
61
+ return {
62
+ ...m,
63
+ value: {
64
+ ...literal,
65
+ value: 'node',
66
+ source: literal.source.replace(literal.value as string, 'node')
67
+ } as Json.Literal
68
+ } as Json.Member;
69
+ }
70
+
71
+ return m;
72
+ }
73
+ }();
74
+ }
75
+ }
@@ -0,0 +1,61 @@
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 {JsonVisitor, Json, getMemberKeyName, isObject, isLiteral} from "@openrewrite/rewrite/json";
9
+
10
+ const OUTDATED_TARGETS = ['es5', 'es2015', 'es2016', 'ES5', 'ES2015', 'ES2016'];
11
+
12
+ export class UpdateTsconfigTarget extends Recipe {
13
+ readonly name = "org.openrewrite.angular.migration.update-tsconfig-target";
14
+ readonly displayName: string = "Update `tsconfig.json` target to `es2017`";
15
+ readonly description: string = "Updates the `compilerOptions.target` in `tsconfig.json` from `es5`, `es2015`, or `es2016` to `es2017`. " +
16
+ "Angular 13 dropped IE11 support and requires at least ES2017.";
17
+
18
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
19
+ return new class extends JsonVisitor<ExecutionContext> {
20
+ protected async visitDocument(doc: Json.Document, p: ExecutionContext): Promise<Json | undefined> {
21
+ if (!doc.sourcePath.endsWith('tsconfig.json') &&
22
+ !doc.sourcePath.endsWith('tsconfig.app.json') &&
23
+ !doc.sourcePath.endsWith('tsconfig.lib.json')) {
24
+ return doc;
25
+ }
26
+ return super.visitDocument(doc, p);
27
+ }
28
+
29
+ private inCompilerOptions = false;
30
+
31
+ protected async visitMember(member: Json.Member, p: ExecutionContext): Promise<Json | undefined> {
32
+ const m = await super.visitMember(member, p) as Json.Member;
33
+ if (!m) return m;
34
+
35
+ if (getMemberKeyName(m) === 'compilerOptions' && isObject(m.value)) {
36
+ this.inCompilerOptions = true;
37
+ const result = await super.visitMember(m, p) as Json.Member;
38
+ this.inCompilerOptions = false;
39
+ return result;
40
+ }
41
+
42
+ if (this.inCompilerOptions && getMemberKeyName(m) === 'target' && isLiteral(m.value)) {
43
+ const literal = m.value as Json.Literal;
44
+ const value = literal.value as string;
45
+ if (OUTDATED_TARGETS.includes(value)) {
46
+ return {
47
+ ...m,
48
+ value: {
49
+ ...literal,
50
+ value: 'es2017',
51
+ source: literal.source.replace(value, 'es2017')
52
+ } as Json.Literal
53
+ } as Json.Member;
54
+ }
55
+ }
56
+
57
+ return m;
58
+ }
59
+ }();
60
+ }
61
+ }
@@ -0,0 +1,52 @@
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 {UpgradeToAngular9} from "./upgrade-to-angular-9";
10
+ import {RemoveEs5BrowserSupport} from "./remove-es5-browser-support";
11
+ import {FindBareModuleWithProviders} from "../search/find-bare-module-with-providers";
12
+ import {ReplaceValidatorWithValidators} from "./replace-validator-with-validators";
13
+ import {MigrateToSolutionStyleTsconfig} from "./migrate-to-solution-style-tsconfig";
14
+ import {RenameFile} from "./rename-file";
15
+
16
+ export class UpgradeToAngular10 extends Recipe {
17
+ readonly name = "org.openrewrite.angular.UpgradeToAngular10";
18
+ readonly displayName = "Upgrade to Angular 10";
19
+ readonly description = "Migrates Angular 9.x applications to Angular 10. " +
20
+ "This includes removing the deprecated `es5BrowserSupport` option from `angular.json`, " +
21
+ "renaming deprecated `validator`/`asyncValidator` to their plural forms, " +
22
+ "renaming `browserslist` to `.browserslistrc`, " +
23
+ "migrating to solution-style `tsconfig.json`, " +
24
+ "and upgrading Angular, TypeScript, and related dependency versions.";
25
+
26
+ async recipeList(): Promise<Recipe[]> {
27
+ return [
28
+ new UpgradeToAngular9(),
29
+ // Code transformations
30
+ new RemoveEs5BrowserSupport(),
31
+ new ReplaceValidatorWithValidators(),
32
+ new MigrateToSolutionStyleTsconfig(),
33
+ // Search: deprecated APIs
34
+ new FindBareModuleWithProviders(),
35
+ // Rename browserslist → .browserslistrc
36
+ new RenameFile({fileMatcher: "**/browserslist", fileName: ".browserslistrc"}),
37
+ // Angular core packages
38
+ new UpgradeDependencyVersion({packageName: "@angular/*", newVersion: "10.x"}),
39
+ // Angular CDK
40
+ new UpgradeDependencyVersion({packageName: "@angular/cdk", newVersion: "10.x"}),
41
+ // Angular DevKit
42
+ new UpgradeDependencyVersion({packageName: "@angular-devkit/*", newVersion: "10.x"}),
43
+ new UpgradeDependencyVersion({packageName: "@schematics/angular", newVersion: "10.x"}),
44
+ // zone.js
45
+ new UpgradeDependencyVersion({packageName: "zone.js", newVersion: "0.10.x"}),
46
+ // TypeScript
47
+ new UpgradeDependencyVersion({packageName: "typescript", newVersion: "3.9.x"}),
48
+ // tslib
49
+ new UpgradeDependencyVersion({packageName: "tslib", newVersion: "2.x"}),
50
+ ];
51
+ }
52
+ }
@@ -0,0 +1,52 @@
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 {UpgradeToAngular10} from "./upgrade-to-angular-10";
10
+ import {ReplaceViewEncapsulationNative} from "./replace-view-encapsulation-native";
11
+ import {RemoveExtractCss} from "./remove-extract-css";
12
+ import {FindPreserveQueryParamsUsage} from "../search/find-preserve-query-params-usage";
13
+ import {FindPreserveFragmentUsage} from "../search/find-preserve-fragment-usage";
14
+ import {FindWrappedValueUsage} from "../search/find-wrapped-value-usage";
15
+ import {ReplaceInitialNavigation} from "./replace-initial-navigation";
16
+
17
+ export class UpgradeToAngular11 extends Recipe {
18
+ readonly name = "org.openrewrite.angular.UpgradeToAngular11";
19
+ readonly displayName = "Upgrade to Angular 11";
20
+ readonly description = "Migrates Angular 10.x applications to Angular 11. " +
21
+ "This includes replacing `ViewEncapsulation.Native` with `ViewEncapsulation.ShadowDom`, " +
22
+ "removing the deprecated `extractCss` build option from `angular.json`, " +
23
+ "flagging deprecated string-based `loadChildren` and `preserveQueryParams` usage, " +
24
+ "and upgrading Angular, TypeScript, and related dependency versions.";
25
+
26
+ async recipeList(): Promise<Recipe[]> {
27
+ return [
28
+ new UpgradeToAngular10(),
29
+ // Code transformations
30
+ new ReplaceViewEncapsulationNative(),
31
+ new RemoveExtractCss(),
32
+ new ReplaceInitialNavigation(),
33
+ // Search: deprecated APIs
34
+ new FindPreserveQueryParamsUsage(),
35
+ new FindPreserveFragmentUsage(),
36
+ new FindWrappedValueUsage(),
37
+ // Angular core packages
38
+ new UpgradeDependencyVersion({packageName: "@angular/*", newVersion: "11.x"}),
39
+ // Angular CDK
40
+ new UpgradeDependencyVersion({packageName: "@angular/cdk", newVersion: "11.x"}),
41
+ // Angular DevKit
42
+ new UpgradeDependencyVersion({packageName: "@angular-devkit/*", newVersion: "11.x"}),
43
+ new UpgradeDependencyVersion({packageName: "@schematics/angular", newVersion: "11.x"}),
44
+ // zone.js
45
+ new UpgradeDependencyVersion({packageName: "zone.js", newVersion: "0.11.x"}),
46
+ // TypeScript
47
+ new UpgradeDependencyVersion({packageName: "typescript", newVersion: "4.0.x"}),
48
+ // tslib
49
+ new UpgradeDependencyVersion({packageName: "tslib", newVersion: "2.x"}),
50
+ ];
51
+ }
52
+ }
@@ -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 {Recipe} from "@openrewrite/rewrite";
8
+ import {UpgradeDependencyVersion} from "@openrewrite/rewrite/javascript";
9
+ import {UpgradeToAngular11} from "./upgrade-to-angular-11";
10
+ import {AddDefaultConfiguration} from "./add-default-configuration";
11
+ import {ReplaceNodeSassWithSass} from "./replace-node-sass-with-sass";
12
+ import {FindAsyncTestHelperUsage} from "../search/find-async-test-helper-usage";
13
+ import {FindCompilerFactoryUsage} from "../search/find-compiler-factory-usage";
14
+
15
+ export class UpgradeToAngular12 extends Recipe {
16
+ readonly name = "org.openrewrite.angular.UpgradeToAngular12";
17
+ readonly displayName = "Upgrade to Angular 12";
18
+ readonly description = "Migrates Angular 11.x applications to Angular 12. " +
19
+ "This includes adding `defaultConfiguration: \"production\"` to build targets in `angular.json`, " +
20
+ "replacing `node-sass` with `sass` (Dart Sass), flagging deprecated `async` test helper and View Engine APIs, " +
21
+ "and upgrading Angular, TypeScript, and related dependency versions.";
22
+
23
+ async recipeList(): Promise<Recipe[]> {
24
+ return [
25
+ new UpgradeToAngular11(),
26
+ // Code transformations
27
+ new AddDefaultConfiguration(),
28
+ new ReplaceNodeSassWithSass(),
29
+ // Search: deprecated APIs
30
+ new FindAsyncTestHelperUsage(),
31
+ new FindCompilerFactoryUsage(),
32
+ // Angular core packages
33
+ new UpgradeDependencyVersion({packageName: "@angular/*", newVersion: "12.x"}),
34
+ // Angular DevKit
35
+ new UpgradeDependencyVersion({packageName: "@angular-devkit/*", newVersion: "12.x"}),
36
+ new UpgradeDependencyVersion({packageName: "@schematics/angular", newVersion: "12.x"}),
37
+ // zone.js
38
+ new UpgradeDependencyVersion({packageName: "zone.js", newVersion: "0.11.x"}),
39
+ // TypeScript
40
+ new UpgradeDependencyVersion({packageName: "typescript", newVersion: "4.2.x"}),
41
+ ];
42
+ }
43
+ }