@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,320 @@
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
+ export class MigrateInputToSignal extends Recipe {
15
+ readonly name = "org.openrewrite.angular.migration.migrate-input-to-signal";
16
+ readonly displayName: string = "Migrate `@Input()` to signal-based `input()`";
17
+ readonly description: string = "Converts `@Input()` decorated properties in Angular classes to signal-based " +
18
+ "`input()` declarations. For example, `@Input() name: string` becomes `name = input<string>()`, " +
19
+ "and `@Input({ required: true }) name!: string` becomes `name = input.required<string>()`.";
20
+
21
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
22
+ return new class extends JavaScriptVisitor<ExecutionContext> {
23
+ protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: ExecutionContext): Promise<J | undefined> {
24
+ let c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
25
+ if (!c) return c;
26
+
27
+ if (!c.leadingAnnotations.some(a => isIdentifier(a.annotationType)
28
+ && ANGULAR_DECORATORS.includes((a.annotationType as J.Identifier).simpleName))) {
29
+ return c;
30
+ }
31
+
32
+ let changed = false;
33
+ const newStatements = c.body.statements.map(stmt => {
34
+ const el = stmt.element;
35
+ if (el.kind !== J.Kind.VariableDeclarations) return stmt;
36
+
37
+ const varDecl = el as J.VariableDeclarations;
38
+ const inputIdx = varDecl.leadingAnnotations.findIndex(
39
+ a => isIdentifier(a.annotationType) && (a.annotationType as J.Identifier).simpleName === 'Input'
40
+ );
41
+ if (inputIdx === -1) return stmt;
42
+
43
+ const inputAnnotation = varDecl.leadingAnnotations[inputIdx];
44
+ const config = parseInputConfig(inputAnnotation);
45
+ const transformed = buildInputField(varDecl, inputIdx, config);
46
+ changed = true;
47
+ return {
48
+ ...stmt,
49
+ element: transformed,
50
+ markers: mkMarkers({kind: 'org.openrewrite.java.marker.Semicolon' as const, id: randomId()}),
51
+ };
52
+ });
53
+
54
+ if (!changed) return c;
55
+
56
+ maybeAddImport(this, {module: '@angular/core', member: 'input'});
57
+ maybeRemoveImport(this, '@angular/core', 'Input');
58
+
59
+ return create(c, (draft: any) => {
60
+ draft.body.statements = newStatements;
61
+ }) as J.ClassDeclaration;
62
+ }
63
+ };
64
+ }
65
+ }
66
+
67
+ interface InputConfig {
68
+ required: boolean;
69
+ alias?: string;
70
+ }
71
+
72
+ function parseInputConfig(annotation: J.Annotation): InputConfig {
73
+ const config: InputConfig = {required: false};
74
+ if (!annotation.arguments?.elements?.length) return config;
75
+
76
+ const firstArg = annotation.arguments.elements[0].element;
77
+
78
+ // @Input('alias') — string literal argument
79
+ if (isLiteral(firstArg) && typeof (firstArg as J.Literal).value === 'string') {
80
+ config.alias = (firstArg as J.Literal).value as string;
81
+ return config;
82
+ }
83
+
84
+ // @Input({ required: true, alias: 'foo' }) — object literal
85
+ if (firstArg.kind === J.Kind.NewClass) {
86
+ const obj = firstArg as J.NewClass;
87
+ if (obj.body) {
88
+ for (const stmt of obj.body.statements) {
89
+ const el = stmt.element;
90
+ if (el.kind === JS.Kind.PropertyAssignment) {
91
+ const prop = el as JS.PropertyAssignment;
92
+ const nameExpr = prop.name.element;
93
+ if (isIdentifier(nameExpr)) {
94
+ if (nameExpr.simpleName === 'required' && isLiteral(prop.initializer) && (prop.initializer as J.Literal).value === true) {
95
+ config.required = true;
96
+ } else if (nameExpr.simpleName === 'alias' && isLiteral(prop.initializer)) {
97
+ config.alias = (prop.initializer as J.Literal).value as string;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ return config;
106
+ }
107
+
108
+ function unwrapTypeIdentifier(typeExpr: any): any {
109
+ if (typeExpr?.typeIdentifier) return unwrapTypeIdentifier(typeExpr.typeIdentifier);
110
+ return typeExpr;
111
+ }
112
+
113
+ function buildInputCall(typeExpr: any, existingInitializer: any, config: InputConfig): any {
114
+ const hasType = !!typeExpr;
115
+ const unwrappedType = hasType ? unwrapTypeIdentifier(typeExpr) : null;
116
+
117
+ // Build type parameters: input<string>()
118
+ let typeParameters: any = undefined;
119
+ if (unwrappedType) {
120
+ typeParameters = {
121
+ kind: 'org.openrewrite.java.tree.JContainer',
122
+ before: emptySpace,
123
+ markers: emptyMarkers,
124
+ elements: [{
125
+ kind: J.Kind.RightPadded,
126
+ element: {...unwrappedType, id: randomId(), prefix: emptySpace},
127
+ after: emptySpace,
128
+ markers: emptyMarkers,
129
+ }],
130
+ };
131
+ }
132
+
133
+ // Build arguments list
134
+ const args: any[] = [];
135
+
136
+ // Default value argument
137
+ if (existingInitializer && !config.required) {
138
+ args.push({
139
+ kind: J.Kind.RightPadded,
140
+ element: {...existingInitializer.element, prefix: emptySpace},
141
+ after: emptySpace,
142
+ markers: emptyMarkers,
143
+ });
144
+ }
145
+
146
+ // Options argument: { alias: 'foo' }
147
+ if (config.alias) {
148
+ const aliasOption = buildOptionsObject([
149
+ buildPropertyAssignment('alias', config.alias),
150
+ ]);
151
+ if (args.length > 0) {
152
+ // Add space before options when there's a default value
153
+ args.push({
154
+ kind: J.Kind.RightPadded,
155
+ element: {...aliasOption, prefix: singleSpace},
156
+ after: emptySpace,
157
+ markers: emptyMarkers,
158
+ });
159
+ } else {
160
+ args.push({
161
+ kind: J.Kind.RightPadded,
162
+ element: {...aliasOption, prefix: emptySpace},
163
+ after: emptySpace,
164
+ markers: emptyMarkers,
165
+ });
166
+ }
167
+ }
168
+
169
+ // If no args, add empty
170
+ if (args.length === 0) {
171
+ args.push({
172
+ kind: J.Kind.RightPadded,
173
+ element: {kind: J.Kind.Empty, id: randomId(), prefix: emptySpace, markers: emptyMarkers},
174
+ after: emptySpace,
175
+ markers: emptyMarkers,
176
+ });
177
+ }
178
+
179
+ // For input.required<T>(), use select: input, name: required
180
+ const isRequired = config.required;
181
+
182
+ const call: any = {
183
+ kind: J.Kind.MethodInvocation,
184
+ id: randomId(),
185
+ prefix: singleSpace,
186
+ markers: emptyMarkers,
187
+ select: isRequired ? {
188
+ kind: J.Kind.RightPadded,
189
+ element: {
190
+ kind: J.Kind.Identifier,
191
+ id: randomId(),
192
+ prefix: emptySpace,
193
+ markers: emptyMarkers,
194
+ simpleName: 'input',
195
+ annotations: [],
196
+ type: undefined,
197
+ },
198
+ after: emptySpace,
199
+ markers: emptyMarkers,
200
+ } : undefined,
201
+ typeParameters,
202
+ name: {
203
+ kind: J.Kind.Identifier,
204
+ id: randomId(),
205
+ prefix: emptySpace,
206
+ markers: emptyMarkers,
207
+ simpleName: isRequired ? 'required' : 'input',
208
+ annotations: [],
209
+ type: undefined,
210
+ },
211
+ arguments: {
212
+ kind: 'org.openrewrite.java.tree.JContainer',
213
+ before: emptySpace,
214
+ markers: emptyMarkers,
215
+ elements: args,
216
+ },
217
+ methodType: undefined,
218
+ };
219
+
220
+ return call;
221
+ }
222
+
223
+ function buildPropertyAssignment(key: string, value: string): any {
224
+ return {
225
+ kind: JS.Kind.PropertyAssignment,
226
+ id: randomId(),
227
+ prefix: singleSpace,
228
+ markers: emptyMarkers,
229
+ name: {
230
+ kind: J.Kind.RightPadded,
231
+ element: {
232
+ kind: J.Kind.Identifier,
233
+ id: randomId(),
234
+ prefix: emptySpace,
235
+ markers: emptyMarkers,
236
+ simpleName: key,
237
+ annotations: [],
238
+ type: undefined,
239
+ },
240
+ after: emptySpace,
241
+ markers: emptyMarkers,
242
+ },
243
+ assigmentToken: 'Colon',
244
+ initializer: {
245
+ kind: J.Kind.Literal,
246
+ id: randomId(),
247
+ prefix: singleSpace,
248
+ markers: emptyMarkers,
249
+ value: value,
250
+ valueSource: `'${value}'`,
251
+ unicodeEscapes: undefined,
252
+ type: undefined,
253
+ },
254
+ };
255
+ }
256
+
257
+ function buildOptionsObject(properties: any[]): any {
258
+ return {
259
+ kind: J.Kind.NewClass,
260
+ id: randomId(),
261
+ prefix: emptySpace,
262
+ markers: emptyMarkers,
263
+ enclosing: undefined,
264
+ new: emptySpace,
265
+ clazz: undefined,
266
+ arguments: {
267
+ kind: 'org.openrewrite.java.tree.JContainer',
268
+ before: emptySpace,
269
+ markers: emptyMarkers,
270
+ elements: [],
271
+ },
272
+ body: {
273
+ kind: J.Kind.Block,
274
+ id: randomId(),
275
+ prefix: emptySpace,
276
+ markers: emptyMarkers,
277
+ static: {kind: J.Kind.RightPadded, element: false, after: emptySpace, markers: emptyMarkers},
278
+ statements: properties.map(p => ({
279
+ kind: J.Kind.RightPadded,
280
+ element: p,
281
+ after: emptySpace,
282
+ markers: emptyMarkers,
283
+ })),
284
+ end: singleSpace,
285
+ },
286
+ constructorType: undefined,
287
+ };
288
+ }
289
+
290
+ function buildInputField(varDecl: J.VariableDeclarations, inputAnnotationIdx: number, config: InputConfig): any {
291
+ const namedVar = varDecl.variables[0].element;
292
+ const inputCall = buildInputCall(
293
+ varDecl.typeExpression,
294
+ namedVar.initializer,
295
+ config,
296
+ );
297
+
298
+ const newAnnotations = [...varDecl.leadingAnnotations];
299
+ newAnnotations.splice(inputAnnotationIdx, 1);
300
+
301
+ return {
302
+ ...varDecl,
303
+ id: randomId(),
304
+ leadingAnnotations: newAnnotations,
305
+ typeExpression: undefined,
306
+ variables: [{
307
+ ...varDecl.variables[0],
308
+ element: {
309
+ ...namedVar,
310
+ prefix: emptySpace,
311
+ dimensionsAfterName: [],
312
+ initializer: {
313
+ before: singleSpace,
314
+ element: inputCall,
315
+ markers: emptyMarkers,
316
+ },
317
+ },
318
+ }],
319
+ };
320
+ }
@@ -0,0 +1,268 @@
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
+ export class MigrateOutputToSignal extends Recipe {
15
+ readonly name = "org.openrewrite.angular.migration.migrate-output-to-signal";
16
+ readonly displayName: string = "Migrate `@Output()` to signal-based `output()`";
17
+ readonly description: string = "Converts `@Output()` decorated properties using `EventEmitter` in Angular classes to " +
18
+ "signal-based `output()` declarations. For example, `@Output() clicked = new EventEmitter<void>()` becomes " +
19
+ "`clicked = output<void>()`.";
20
+
21
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
22
+ return new class extends JavaScriptVisitor<ExecutionContext> {
23
+ protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: ExecutionContext): Promise<J | undefined> {
24
+ let c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
25
+ if (!c) return c;
26
+
27
+ if (!c.leadingAnnotations.some(a => isIdentifier(a.annotationType)
28
+ && ANGULAR_DECORATORS.includes((a.annotationType as J.Identifier).simpleName))) {
29
+ return c;
30
+ }
31
+
32
+ let changed = false;
33
+ const newStatements = c.body.statements.map(stmt => {
34
+ const el = stmt.element;
35
+ if (el.kind !== J.Kind.VariableDeclarations) return stmt;
36
+
37
+ const varDecl = el as J.VariableDeclarations;
38
+ const outputIdx = varDecl.leadingAnnotations.findIndex(
39
+ a => isIdentifier(a.annotationType) && (a.annotationType as J.Identifier).simpleName === 'Output'
40
+ );
41
+ if (outputIdx === -1) return stmt;
42
+
43
+ const namedVar = varDecl.variables[0].element;
44
+ if (!namedVar.initializer) return stmt;
45
+
46
+ const initExpr = namedVar.initializer.element;
47
+ if (initExpr.kind !== J.Kind.NewClass) return stmt;
48
+
49
+ const newClass = initExpr as J.NewClass;
50
+ const emitterName = extractClassName(newClass);
51
+ if (emitterName !== 'EventEmitter') return stmt;
52
+
53
+ const outputAnnotation = varDecl.leadingAnnotations[outputIdx];
54
+ const alias = parseOutputAlias(outputAnnotation);
55
+ const typeParams = extractTypeParams(newClass);
56
+ const transformed = buildOutputField(varDecl, outputIdx, typeParams, alias);
57
+ changed = true;
58
+ return {
59
+ ...stmt,
60
+ element: transformed,
61
+ markers: mkMarkers({kind: 'org.openrewrite.java.marker.Semicolon' as const, id: randomId()}),
62
+ };
63
+ });
64
+
65
+ if (!changed) return c;
66
+
67
+ maybeAddImport(this, {module: '@angular/core', member: 'output'});
68
+ maybeRemoveImport(this, '@angular/core', 'Output');
69
+ maybeRemoveImport(this, '@angular/core', 'EventEmitter');
70
+
71
+ return create(c, (draft: any) => {
72
+ draft.body.statements = newStatements;
73
+ }) as J.ClassDeclaration;
74
+ }
75
+ };
76
+ }
77
+ }
78
+
79
+ function extractClassName(newClass: J.NewClass): string | undefined {
80
+ const clazz = newClass.class;
81
+ if (!clazz) return undefined;
82
+ if (isIdentifier(clazz)) return (clazz as J.Identifier).simpleName;
83
+ if (clazz.kind === J.Kind.ParameterizedType) {
84
+ const pt = clazz as J.ParameterizedType;
85
+ if (isIdentifier(pt.class)) return (pt.class as J.Identifier).simpleName;
86
+ }
87
+ return undefined;
88
+ }
89
+
90
+ function extractTypeParams(newClass: J.NewClass): any[] | undefined {
91
+ const clazz = newClass.class;
92
+ if (clazz?.kind === J.Kind.ParameterizedType) {
93
+ const pt = clazz as J.ParameterizedType;
94
+ if (pt.typeParameters?.elements?.length) {
95
+ return pt.typeParameters.elements.map(e => e.element);
96
+ }
97
+ }
98
+ return undefined;
99
+ }
100
+
101
+ function parseOutputAlias(annotation: J.Annotation): string | undefined {
102
+ if (!annotation.arguments?.elements?.length) return undefined;
103
+
104
+ const firstArg = annotation.arguments.elements[0].element;
105
+ if (isLiteral(firstArg) && typeof (firstArg as J.Literal).value === 'string') {
106
+ return (firstArg as J.Literal).value as string;
107
+ }
108
+ return undefined;
109
+ }
110
+
111
+ function unwrapTypeIdentifier(typeExpr: any): any {
112
+ if (typeExpr?.typeIdentifier) return unwrapTypeIdentifier(typeExpr.typeIdentifier);
113
+ return typeExpr;
114
+ }
115
+
116
+ function buildOutputField(varDecl: J.VariableDeclarations, outputAnnotationIdx: number, typeParams: any[] | undefined, alias: string | undefined): any {
117
+ const namedVar = varDecl.variables[0].element;
118
+
119
+ // Build type parameters for output<T>()
120
+ let typeParameters: any = undefined;
121
+ if (typeParams?.length) {
122
+ typeParameters = {
123
+ kind: 'org.openrewrite.java.tree.JContainer',
124
+ before: emptySpace,
125
+ markers: emptyMarkers,
126
+ elements: typeParams.map(tp => ({
127
+ kind: J.Kind.RightPadded,
128
+ element: {...unwrapTypeIdentifier(tp), id: randomId(), prefix: emptySpace},
129
+ after: emptySpace,
130
+ markers: emptyMarkers,
131
+ })),
132
+ };
133
+ }
134
+
135
+ // Build arguments
136
+ const args: any[] = [];
137
+ if (alias) {
138
+ args.push({
139
+ kind: J.Kind.RightPadded,
140
+ element: buildOptionsObject([buildPropertyAssignment('alias', alias)]),
141
+ after: emptySpace,
142
+ markers: emptyMarkers,
143
+ });
144
+ }
145
+ if (args.length === 0) {
146
+ args.push({
147
+ kind: J.Kind.RightPadded,
148
+ element: {kind: J.Kind.Empty, id: randomId(), prefix: emptySpace, markers: emptyMarkers},
149
+ after: emptySpace,
150
+ markers: emptyMarkers,
151
+ });
152
+ }
153
+
154
+ const outputCall: any = {
155
+ kind: J.Kind.MethodInvocation,
156
+ id: randomId(),
157
+ prefix: singleSpace,
158
+ markers: emptyMarkers,
159
+ select: undefined,
160
+ typeParameters,
161
+ name: {
162
+ kind: J.Kind.Identifier,
163
+ id: randomId(),
164
+ prefix: emptySpace,
165
+ markers: emptyMarkers,
166
+ simpleName: 'output',
167
+ annotations: [],
168
+ type: undefined,
169
+ },
170
+ arguments: {
171
+ kind: 'org.openrewrite.java.tree.JContainer',
172
+ before: emptySpace,
173
+ markers: emptyMarkers,
174
+ elements: args,
175
+ },
176
+ methodType: undefined,
177
+ };
178
+
179
+ const newAnnotations = [...varDecl.leadingAnnotations];
180
+ newAnnotations.splice(outputAnnotationIdx, 1);
181
+
182
+ return {
183
+ ...varDecl,
184
+ id: randomId(),
185
+ leadingAnnotations: newAnnotations,
186
+ typeExpression: undefined,
187
+ variables: [{
188
+ ...varDecl.variables[0],
189
+ element: {
190
+ ...namedVar,
191
+ prefix: emptySpace,
192
+ dimensionsAfterName: [],
193
+ initializer: {
194
+ before: singleSpace,
195
+ element: outputCall,
196
+ markers: emptyMarkers,
197
+ },
198
+ },
199
+ }],
200
+ };
201
+ }
202
+
203
+ function buildPropertyAssignment(key: string, value: string): any {
204
+ return {
205
+ kind: JS.Kind.PropertyAssignment,
206
+ id: randomId(),
207
+ prefix: singleSpace,
208
+ markers: emptyMarkers,
209
+ name: {
210
+ kind: J.Kind.RightPadded,
211
+ element: {
212
+ kind: J.Kind.Identifier,
213
+ id: randomId(),
214
+ prefix: emptySpace,
215
+ markers: emptyMarkers,
216
+ simpleName: key,
217
+ annotations: [],
218
+ type: undefined,
219
+ },
220
+ after: emptySpace,
221
+ markers: emptyMarkers,
222
+ },
223
+ assigmentToken: 'Colon',
224
+ initializer: {
225
+ kind: J.Kind.Literal,
226
+ id: randomId(),
227
+ prefix: singleSpace,
228
+ markers: emptyMarkers,
229
+ value: value,
230
+ valueSource: `'${value}'`,
231
+ unicodeEscapes: undefined,
232
+ type: undefined,
233
+ },
234
+ };
235
+ }
236
+
237
+ function buildOptionsObject(properties: any[]): any {
238
+ return {
239
+ kind: J.Kind.NewClass,
240
+ id: randomId(),
241
+ prefix: emptySpace,
242
+ markers: emptyMarkers,
243
+ enclosing: undefined,
244
+ new: emptySpace,
245
+ clazz: undefined,
246
+ arguments: {
247
+ kind: 'org.openrewrite.java.tree.JContainer',
248
+ before: emptySpace,
249
+ markers: emptyMarkers,
250
+ elements: [],
251
+ },
252
+ body: {
253
+ kind: J.Kind.Block,
254
+ id: randomId(),
255
+ prefix: emptySpace,
256
+ markers: emptyMarkers,
257
+ static: {kind: J.Kind.RightPadded, element: false, after: emptySpace, markers: emptyMarkers},
258
+ statements: properties.map(p => ({
259
+ kind: J.Kind.RightPadded,
260
+ element: p,
261
+ after: emptySpace,
262
+ markers: emptyMarkers,
263
+ })),
264
+ end: singleSpace,
265
+ },
266
+ constructorType: undefined,
267
+ };
268
+ }