@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,276 @@
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, randomId, emptyMarkers, markers as mkMarkers, Recipe, TreeVisitor} from "@openrewrite/rewrite";
8
+ import {JavaScriptVisitor, JS, maybeAddImport, maybeRemoveImport} from "@openrewrite/rewrite/javascript";
9
+ import {J, isIdentifier, isLiteral, emptySpace, singleSpace} from "@openrewrite/rewrite/java";
10
+ import {create} from "mutative";
11
+
12
+ const ANGULAR_DECORATORS = ['Component', 'Directive', 'Pipe', 'Injectable'];
13
+
14
+ const QUERY_DECORATOR_MAP: Record<string, string> = {
15
+ 'ViewChild': 'viewChild',
16
+ 'ViewChildren': 'viewChildren',
17
+ 'ContentChild': 'contentChild',
18
+ 'ContentChildren': 'contentChildren',
19
+ };
20
+
21
+ export class MigrateQueryToSignal extends Recipe {
22
+ readonly name = "org.openrewrite.angular.migration.migrate-query-to-signal";
23
+ readonly displayName: string = "Migrate query decorators to signal-based functions";
24
+ readonly description: string = "Converts `@ViewChild()`, `@ViewChildren()`, `@ContentChild()`, and `@ContentChildren()` " +
25
+ "decorated properties to signal-based query functions. For example, " +
26
+ "`@ViewChild('ref') el: ElementRef` becomes `el = viewChild<ElementRef>('ref')`.";
27
+
28
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
29
+ return new class extends JavaScriptVisitor<ExecutionContext> {
30
+ protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: ExecutionContext): Promise<J | undefined> {
31
+ let c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
32
+ if (!c) return c;
33
+
34
+ if (!c.leadingAnnotations.some(a => isIdentifier(a.annotationType)
35
+ && ANGULAR_DECORATORS.includes((a.annotationType as J.Identifier).simpleName))) {
36
+ return c;
37
+ }
38
+
39
+ let changed = false;
40
+ const usedFunctions = new Set<string>();
41
+ const removedDecorators = new Set<string>();
42
+
43
+ const newStatements = c.body.statements.map(stmt => {
44
+ const el = stmt.element;
45
+ if (el.kind !== J.Kind.VariableDeclarations) return stmt;
46
+
47
+ const varDecl = el as J.VariableDeclarations;
48
+ let queryIdx = -1;
49
+ let queryName = '';
50
+ for (let i = 0; i < varDecl.leadingAnnotations.length; i++) {
51
+ const a = varDecl.leadingAnnotations[i];
52
+ if (isIdentifier(a.annotationType)) {
53
+ const name = (a.annotationType as J.Identifier).simpleName;
54
+ if (QUERY_DECORATOR_MAP[name]) {
55
+ queryIdx = i;
56
+ queryName = name;
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ if (queryIdx === -1) return stmt;
62
+
63
+ const annotation = varDecl.leadingAnnotations[queryIdx];
64
+ if (!annotation.arguments?.elements?.length) return stmt;
65
+
66
+ const funcName = QUERY_DECORATOR_MAP[queryName];
67
+ const transformed = buildQueryField(varDecl, queryIdx, funcName, annotation);
68
+ changed = true;
69
+ usedFunctions.add(funcName);
70
+ removedDecorators.add(queryName);
71
+ return {
72
+ ...stmt,
73
+ element: transformed,
74
+ markers: mkMarkers({kind: 'org.openrewrite.java.marker.Semicolon' as const, id: randomId()}),
75
+ };
76
+ });
77
+
78
+ if (!changed) return c;
79
+
80
+ for (const func of usedFunctions) {
81
+ maybeAddImport(this, {module: '@angular/core', member: func});
82
+ }
83
+ for (const dec of removedDecorators) {
84
+ maybeRemoveImport(this, '@angular/core', dec);
85
+ }
86
+ maybeRemoveImport(this, '@angular/core', 'QueryList');
87
+
88
+ return create(c, (draft: any) => {
89
+ draft.body.statements = newStatements;
90
+ }) as J.ClassDeclaration;
91
+ }
92
+ };
93
+ }
94
+ }
95
+
96
+ function unwrapTypeIdentifier(typeExpr: any): any {
97
+ if (typeExpr?.typeIdentifier) return unwrapTypeIdentifier(typeExpr.typeIdentifier);
98
+ return typeExpr;
99
+ }
100
+
101
+ function unwrapQueryListType(typeExpr: any): any {
102
+ const unwrapped = unwrapTypeIdentifier(typeExpr);
103
+ if (unwrapped?.kind === J.Kind.ParameterizedType) {
104
+ const pt = unwrapped as J.ParameterizedType;
105
+ if (isIdentifier(pt.class) && (pt.class as J.Identifier).simpleName === 'QueryList') {
106
+ if (pt.typeParameters?.elements?.length) {
107
+ return pt.typeParameters.elements[0].element;
108
+ }
109
+ }
110
+ }
111
+ return unwrapped;
112
+ }
113
+
114
+ interface QueryConfig {
115
+ selector: any;
116
+ selectorIsString: boolean;
117
+ options: any[] | undefined;
118
+ }
119
+
120
+ function parseQueryAnnotation(annotation: J.Annotation): QueryConfig {
121
+ const args = annotation.arguments!.elements;
122
+ const selector = args[0].element;
123
+ const selectorIsString = isLiteral(selector) && typeof (selector as J.Literal).value === 'string';
124
+
125
+ let options: any[] | undefined;
126
+ if (args.length > 1) {
127
+ const secondArg = args[1].element;
128
+ if (secondArg.kind === J.Kind.NewClass) {
129
+ const obj = secondArg as J.NewClass;
130
+ if (obj.body) {
131
+ const filteredProps = obj.body.statements.filter((stmt: any) => {
132
+ const el = stmt.element;
133
+ if (el.kind === JS.Kind.PropertyAssignment) {
134
+ const prop = el as JS.PropertyAssignment;
135
+ const nameExpr = prop.name.element;
136
+ if (isIdentifier(nameExpr) && nameExpr.simpleName === 'static') {
137
+ return false;
138
+ }
139
+ }
140
+ return true;
141
+ });
142
+ if (filteredProps.length > 0) {
143
+ options = filteredProps;
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ return {selector, selectorIsString, options};
150
+ }
151
+
152
+ function buildQueryField(varDecl: J.VariableDeclarations, annotationIdx: number, funcName: string, annotation: J.Annotation): any {
153
+ const namedVar = varDecl.variables[0].element;
154
+ const config = parseQueryAnnotation(annotation);
155
+
156
+ // Determine type parameter
157
+ const propertyType = varDecl.typeExpression ? unwrapQueryListType(varDecl.typeExpression) : null;
158
+ const needsTypeParam = config.selectorIsString && propertyType && !config.options?.some((stmt: any) => {
159
+ const el = stmt.element;
160
+ return el.kind === JS.Kind.PropertyAssignment
161
+ && isIdentifier((el as JS.PropertyAssignment).name.element)
162
+ && ((el as JS.PropertyAssignment).name.element as J.Identifier).simpleName === 'read';
163
+ });
164
+
165
+ let typeParameters: any = undefined;
166
+ if (needsTypeParam && propertyType) {
167
+ typeParameters = {
168
+ kind: 'org.openrewrite.java.tree.JContainer',
169
+ before: emptySpace,
170
+ markers: emptyMarkers,
171
+ elements: [{
172
+ kind: J.Kind.RightPadded,
173
+ element: {...propertyType, id: randomId(), prefix: emptySpace},
174
+ after: emptySpace,
175
+ markers: emptyMarkers,
176
+ }],
177
+ };
178
+ }
179
+
180
+ // Build arguments
181
+ const args: any[] = [];
182
+
183
+ // First arg: selector
184
+ args.push({
185
+ kind: J.Kind.RightPadded,
186
+ element: {...config.selector, id: randomId(), prefix: emptySpace},
187
+ after: emptySpace,
188
+ markers: emptyMarkers,
189
+ });
190
+
191
+ // Second arg: options object (if any remain after filtering)
192
+ if (config.options) {
193
+ args.push({
194
+ kind: J.Kind.RightPadded,
195
+ element: buildOptionsObject(config.options),
196
+ after: emptySpace,
197
+ markers: emptyMarkers,
198
+ });
199
+ }
200
+
201
+ const queryCall: any = {
202
+ kind: J.Kind.MethodInvocation,
203
+ id: randomId(),
204
+ prefix: singleSpace,
205
+ markers: emptyMarkers,
206
+ select: undefined,
207
+ typeParameters,
208
+ name: {
209
+ kind: J.Kind.Identifier,
210
+ id: randomId(),
211
+ prefix: emptySpace,
212
+ markers: emptyMarkers,
213
+ simpleName: funcName,
214
+ annotations: [],
215
+ type: undefined,
216
+ },
217
+ arguments: {
218
+ kind: 'org.openrewrite.java.tree.JContainer',
219
+ before: emptySpace,
220
+ markers: emptyMarkers,
221
+ elements: args,
222
+ },
223
+ methodType: undefined,
224
+ };
225
+
226
+ const newAnnotations = [...varDecl.leadingAnnotations];
227
+ newAnnotations.splice(annotationIdx, 1);
228
+
229
+ return {
230
+ ...varDecl,
231
+ id: randomId(),
232
+ leadingAnnotations: newAnnotations,
233
+ typeExpression: undefined,
234
+ variables: [{
235
+ ...varDecl.variables[0],
236
+ element: {
237
+ ...namedVar,
238
+ prefix: emptySpace,
239
+ dimensionsAfterName: [],
240
+ initializer: {
241
+ before: singleSpace,
242
+ element: queryCall,
243
+ markers: emptyMarkers,
244
+ },
245
+ },
246
+ }],
247
+ };
248
+ }
249
+
250
+ function buildOptionsObject(statements: any[]): any {
251
+ return {
252
+ kind: J.Kind.NewClass,
253
+ id: randomId(),
254
+ prefix: singleSpace,
255
+ markers: emptyMarkers,
256
+ enclosing: undefined,
257
+ new: emptySpace,
258
+ clazz: undefined,
259
+ arguments: {
260
+ kind: 'org.openrewrite.java.tree.JContainer',
261
+ before: emptySpace,
262
+ markers: emptyMarkers,
263
+ elements: [],
264
+ },
265
+ body: {
266
+ kind: J.Kind.Block,
267
+ id: randomId(),
268
+ prefix: emptySpace,
269
+ markers: emptyMarkers,
270
+ static: {kind: J.Kind.RightPadded, element: false, after: emptySpace, markers: emptyMarkers},
271
+ statements,
272
+ end: singleSpace,
273
+ },
274
+ constructorType: undefined,
275
+ };
276
+ }
@@ -0,0 +1,139 @@
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, ScanningRecipe, SourceFile, Tree, TreePrinters, TreeVisitor} from "@openrewrite/rewrite";
8
+ import {isJson, Json, JsonParser, JsonVisitor, getMemberKeyName, isLiteral} from "@openrewrite/rewrite/json";
9
+
10
+ interface Accumulator {
11
+ originalTsconfigContent: string | null;
12
+ baseAlreadyExists: boolean;
13
+ extendingPaths: string[];
14
+ }
15
+
16
+ export class MigrateToSolutionStyleTsconfig extends ScanningRecipe<Accumulator> {
17
+ readonly name = "org.openrewrite.angular.migration.migrate-to-solution-style-tsconfig";
18
+ readonly displayName: string = "Migrate to solution-style tsconfig";
19
+ readonly description: string = "Migrates a project to use a solution-style `tsconfig.json`. " +
20
+ "The original `tsconfig.json` content is moved to `tsconfig.base.json` (with project-specific " +
21
+ "fields removed), and `tsconfig.json` is replaced with a solution-style config that references " +
22
+ "the project's TypeScript configurations. Other tsconfig files that extend `./tsconfig.json` " +
23
+ "are updated to extend `./tsconfig.base.json`.";
24
+
25
+ initialValue(_ctx: ExecutionContext): Accumulator {
26
+ return {
27
+ originalTsconfigContent: null,
28
+ baseAlreadyExists: false,
29
+ extendingPaths: [],
30
+ };
31
+ }
32
+
33
+ async scanner(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
34
+ return new class extends TreeVisitor<Tree, ExecutionContext> {
35
+ protected async accept(tree: Tree, _ctx: ExecutionContext): Promise<Tree | undefined> {
36
+ if (!isJson(tree)) return tree;
37
+ const doc = tree as Json.Document;
38
+
39
+ if (doc.sourcePath === 'tsconfig.base.json') {
40
+ acc.baseAlreadyExists = true;
41
+ return doc;
42
+ }
43
+
44
+ if (doc.sourcePath === 'tsconfig.json') {
45
+ const content = await TreePrinters.print(doc);
46
+ try {
47
+ const parsed = JSON.parse(content);
48
+ if (Array.isArray(parsed.files) && parsed.files.length === 0 && parsed.references) {
49
+ return doc;
50
+ }
51
+ acc.originalTsconfigContent = content;
52
+ } catch {}
53
+ return doc;
54
+ }
55
+
56
+ if (/^tsconfig\.\w+\.json$/.test(doc.sourcePath)) {
57
+ const content = await TreePrinters.print(doc);
58
+ try {
59
+ const parsed = JSON.parse(content);
60
+ if (parsed.extends === './tsconfig.json') {
61
+ acc.extendingPaths.push(doc.sourcePath);
62
+ }
63
+ } catch {}
64
+ }
65
+
66
+ return doc;
67
+ }
68
+ };
69
+ }
70
+
71
+ async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
72
+ if (!acc.originalTsconfigContent || acc.baseAlreadyExists) {
73
+ return super.editorWithData(acc);
74
+ }
75
+
76
+ return new class extends JsonVisitor<ExecutionContext> {
77
+ private isExtendingFile = false;
78
+
79
+ protected async visitDocument(doc: Json.Document, ctx: ExecutionContext): Promise<Json | undefined> {
80
+ if (doc.sourcePath === 'tsconfig.json') {
81
+ const solutionConfig: Record<string, any> = {files: []};
82
+ if (acc.extendingPaths.length > 0) {
83
+ solutionConfig.references = acc.extendingPaths.map(p => ({path: `./${p}`}));
84
+ }
85
+ const text = JSON.stringify(solutionConfig, null, 2) + '\n';
86
+ const parsed = await new JsonParser({}).parseOne({
87
+ text,
88
+ sourcePath: doc.sourcePath
89
+ }) as Json.Document;
90
+ return {
91
+ ...doc,
92
+ value: parsed.value,
93
+ eof: parsed.eof
94
+ } as Json.Document;
95
+ }
96
+
97
+ this.isExtendingFile = acc.extendingPaths.includes(doc.sourcePath);
98
+ return super.visitDocument(doc, ctx);
99
+ }
100
+
101
+ protected async visitMember(member: Json.Member, p: ExecutionContext): Promise<Json | undefined> {
102
+ const m = await super.visitMember(member, p) as Json.Member;
103
+ if (!this.isExtendingFile) return m;
104
+ if (getMemberKeyName(m) !== 'extends') return m;
105
+ if (!isLiteral(m.value)) return m;
106
+ const literal = m.value as Json.Literal;
107
+ if (literal.value !== './tsconfig.json') return m;
108
+
109
+ return {
110
+ ...m,
111
+ value: {
112
+ ...literal,
113
+ value: './tsconfig.base.json',
114
+ source: literal.source.replace('./tsconfig.json', './tsconfig.base.json'),
115
+ } as Json.Literal
116
+ } as Json.Member;
117
+ }
118
+ };
119
+ }
120
+
121
+ async generate(acc: Accumulator, _ctx: ExecutionContext): Promise<SourceFile[]> {
122
+ if (!acc.originalTsconfigContent || acc.baseAlreadyExists) {
123
+ return [];
124
+ }
125
+
126
+ const parsed = JSON.parse(acc.originalTsconfigContent);
127
+ delete parsed.files;
128
+ delete parsed.include;
129
+ delete parsed.exclude;
130
+ delete parsed.references;
131
+
132
+ const text = JSON.stringify(parsed, null, 2) + '\n';
133
+ const doc = await new JsonParser({}).parseOne({
134
+ text,
135
+ sourcePath: 'tsconfig.base.json'
136
+ });
137
+ return [doc];
138
+ }
139
+ }
@@ -0,0 +1,40 @@
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 {ChangeImport} from "@openrewrite/rewrite/javascript";
9
+
10
+ const MODULE_HIERARCHY = ['@angular/platform-browser', '@angular/common', '@angular/core'];
11
+
12
+ export class MoveDocumentImport extends Recipe {
13
+ readonly name = "org.openrewrite.angular.migration.move-document-import";
14
+ readonly displayName: string;
15
+ readonly description: string;
16
+ private readonly targetModule: string;
17
+
18
+ constructor(options?: { targetModule?: string }) {
19
+ super();
20
+ this.targetModule = options?.targetModule ?? '@angular/core';
21
+ this.displayName = `Move \`DOCUMENT\` import to \`${this.targetModule}\``;
22
+ this.description = `Moves the \`DOCUMENT\` import from older Angular modules to \`${this.targetModule}\`.`;
23
+ }
24
+
25
+ async recipeList(): Promise<Recipe[]> {
26
+ const targetIndex = MODULE_HIERARCHY.indexOf(this.targetModule);
27
+ return MODULE_HIERARCHY
28
+ .filter((_, i) => i < targetIndex)
29
+ .map(oldModule => new ChangeImport({
30
+ oldModule,
31
+ oldMember: 'DOCUMENT',
32
+ newModule: this.targetModule,
33
+ }));
34
+ }
35
+ }
36
+
37
+ /**
38
+ * @deprecated Use {@link MoveDocumentImport} instead.
39
+ */
40
+ export const MoveDocumentToCore = MoveDocumentImport;
@@ -0,0 +1,72 @@
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, capture, pattern} from "@openrewrite/rewrite/javascript";
9
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+ import {create} from "mutative";
11
+
12
+ const TESTBED_METHODS = ['configureTestingModule', 'initTestEnvironment'];
13
+
14
+ export class RemoveAotSummaries extends Recipe {
15
+ readonly name = "org.openrewrite.angular.migration.remove-aot-summaries";
16
+ readonly displayName: string = "Remove `aotSummaries` from TestBed";
17
+ readonly description: string = "Removes the `aotSummaries` property from `TestBed.configureTestingModule()` and " +
18
+ "`TestBed.initTestEnvironment()` calls. The `aotSummaries` parameter was removed in Angular 14 " +
19
+ "as it was only needed for the View Engine compiler.";
20
+
21
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
22
+ return new class extends JavaScriptVisitor<ExecutionContext> {
23
+ override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
24
+ let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
25
+ if (!m) return m;
26
+
27
+ const testBedArgs = capture({variadic: true});
28
+ if (!await pattern`TestBed.configureTestingModule(${testBedArgs})`.match(m, this.cursor)
29
+ && !await pattern`TestBed.initTestEnvironment(${testBedArgs})`.match(m, this.cursor)) return m;
30
+
31
+ const args = (m as any).arguments?.elements;
32
+ if (!args || args.length === 0) return m;
33
+
34
+ for (let argIdx = 0; argIdx < args.length; argIdx++) {
35
+ const arg = args[argIdx].element;
36
+ if (arg.kind !== J.Kind.NewClass) continue;
37
+
38
+ const obj = arg as J.NewClass;
39
+ if (!obj.body) continue;
40
+
41
+ let aotIndex = -1;
42
+ for (let i = 0; i < obj.body.statements.length; i++) {
43
+ const stmt = obj.body.statements[i].element;
44
+ if (stmt.kind === JS.Kind.PropertyAssignment) {
45
+ const prop = stmt as JS.PropertyAssignment;
46
+ const nameExpr = prop.name.element;
47
+ if (isIdentifier(nameExpr) && nameExpr.simpleName === 'aotSummaries') {
48
+ aotIndex = i;
49
+ break;
50
+ }
51
+ }
52
+ }
53
+
54
+ if (aotIndex === -1) continue;
55
+
56
+ if (obj.body.statements.length === 1) {
57
+ return create(m, draft => {
58
+ (draft as any).arguments.elements = (draft as any).arguments.elements.filter((_: any, i: number) => i !== argIdx);
59
+ }) as J.MethodInvocation;
60
+ }
61
+
62
+ return create(m, draft => {
63
+ const body = ((draft as any).arguments.elements[argIdx].element as any).body;
64
+ body.statements = body.statements.filter((_: any, i: number) => i !== aotIndex);
65
+ }) as J.MethodInvocation;
66
+ }
67
+
68
+ return m;
69
+ }
70
+ }();
71
+ }
72
+ }