@openrewrite/rewrite 8.69.0-20251207-160255 → 8.69.0-20251207-214914

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 (68) hide show
  1. package/dist/cli/cli-utils.d.ts.map +1 -1
  2. package/dist/cli/cli-utils.js +3 -2
  3. package/dist/cli/cli-utils.js.map +1 -1
  4. package/dist/cli/rewrite.js +2 -1
  5. package/dist/cli/rewrite.js.map +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/javascript/add-import.d.ts.map +1 -1
  10. package/dist/javascript/add-import.js +99 -56
  11. package/dist/javascript/add-import.js.map +1 -1
  12. package/dist/javascript/parser.d.ts.map +1 -1
  13. package/dist/javascript/parser.js +48 -8
  14. package/dist/javascript/parser.js.map +1 -1
  15. package/dist/javascript/recipes/async-callback-in-sync-array-method.d.ts +40 -0
  16. package/dist/javascript/recipes/async-callback-in-sync-array-method.d.ts.map +1 -0
  17. package/dist/javascript/recipes/async-callback-in-sync-array-method.js +232 -0
  18. package/dist/javascript/recipes/async-callback-in-sync-array-method.js.map +1 -0
  19. package/dist/javascript/recipes/change-import.d.ts +51 -0
  20. package/dist/javascript/recipes/change-import.d.ts.map +1 -0
  21. package/dist/javascript/recipes/change-import.js +658 -0
  22. package/dist/javascript/recipes/change-import.js.map +1 -0
  23. package/dist/javascript/recipes/index.d.ts +3 -0
  24. package/dist/javascript/recipes/index.d.ts.map +1 -1
  25. package/dist/javascript/recipes/index.js +3 -0
  26. package/dist/javascript/recipes/index.js.map +1 -1
  27. package/dist/javascript/recipes/order-imports.d.ts +10 -0
  28. package/dist/javascript/recipes/order-imports.d.ts.map +1 -0
  29. package/dist/javascript/recipes/order-imports.js +240 -0
  30. package/dist/javascript/recipes/order-imports.js.map +1 -0
  31. package/dist/javascript/templating/index.d.ts +1 -1
  32. package/dist/javascript/templating/index.d.ts.map +1 -1
  33. package/dist/javascript/templating/index.js +2 -1
  34. package/dist/javascript/templating/index.js.map +1 -1
  35. package/dist/javascript/templating/rewrite.d.ts +36 -2
  36. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  37. package/dist/javascript/templating/rewrite.js +76 -0
  38. package/dist/javascript/templating/rewrite.js.map +1 -1
  39. package/dist/json/parser.js +78 -30
  40. package/dist/json/parser.js.map +1 -1
  41. package/dist/run.d.ts.map +1 -1
  42. package/dist/run.js +7 -4
  43. package/dist/run.js.map +1 -1
  44. package/dist/version.txt +1 -1
  45. package/package.json +1 -1
  46. package/src/cli/cli-utils.ts +3 -2
  47. package/src/cli/rewrite.ts +2 -1
  48. package/src/index.ts +3 -2
  49. package/src/javascript/add-import.ts +125 -69
  50. package/src/javascript/parser.ts +49 -8
  51. package/src/javascript/recipes/async-callback-in-sync-array-method.ts +237 -0
  52. package/src/javascript/recipes/change-import.ts +700 -0
  53. package/src/javascript/recipes/index.ts +3 -0
  54. package/src/javascript/recipes/order-imports.ts +242 -0
  55. package/src/javascript/templating/index.ts +2 -1
  56. package/src/javascript/templating/rewrite.ts +89 -4
  57. package/src/json/parser.ts +69 -24
  58. package/src/run.ts +5 -4
  59. package/dist/recipe/index.d.ts +0 -2
  60. package/dist/recipe/index.d.ts.map +0 -1
  61. package/dist/recipe/index.js +0 -21
  62. package/dist/recipe/index.js.map +0 -1
  63. package/dist/recipe/order-imports.d.ts +0 -10
  64. package/dist/recipe/order-imports.d.ts.map +0 -1
  65. package/dist/recipe/order-imports.js +0 -213
  66. package/dist/recipe/order-imports.js.map +0 -1
  67. package/src/recipe/index.ts +0 -17
  68. package/src/recipe/order-imports.ts +0 -195
@@ -0,0 +1,237 @@
1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Moderne Source Available License (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://docs.moderne.io/licensing/moderne-source-available-license
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {Recipe} from "../../recipe";
18
+ import {ExecutionContext} from "../../execution";
19
+ import {TreeVisitor} from "../../visitor";
20
+ import {JavaScriptVisitor} from "../visitor";
21
+ import {J} from "../../java";
22
+ import {JS} from "../tree";
23
+ import {Type} from "../../java";
24
+ import {markupWarn} from "../../markers";
25
+
26
+ /**
27
+ * Array methods that don't await async callbacks.
28
+ * When an async function is passed as a callback to these methods,
29
+ * the returned Promise is not awaited, leading to bugs.
30
+ */
31
+ const SYNC_ARRAY_METHODS = new Set([
32
+ 'some', // Returns first truthy value, but Promise is always truthy
33
+ 'every', // Returns first falsy value, but Promise is always truthy
34
+ 'find', // Returns first truthy value, but Promise is always truthy
35
+ 'findIndex', // Returns first truthy value, but Promise is always truthy
36
+ 'filter', // Filters based on truthy values, but Promise is always truthy
37
+ 'forEach', // Ignores return values entirely, async callbacks won't be awaited
38
+ ]);
39
+
40
+ /**
41
+ * Check if a type is a Promise type.
42
+ * Looks for types like Promise<T>, PromiseLike<T>, or the Promise class itself.
43
+ */
44
+ function isPromiseType(type?: Type): boolean {
45
+ if (!type) return false;
46
+
47
+ // Check for Class type with Promise name
48
+ if (Type.isClass(type)) {
49
+ const fqn = type.fullyQualifiedName;
50
+ return fqn === 'Promise' ||
51
+ fqn === 'PromiseLike' ||
52
+ fqn.endsWith('.Promise') ||
53
+ fqn.endsWith('.PromiseLike');
54
+ }
55
+
56
+ // Check for Parameterized type (e.g., Promise<boolean>)
57
+ if (Type.isParameterized(type)) {
58
+ return isPromiseType(type.type);
59
+ }
60
+
61
+ // Check for Union type (e.g., Promise<T> | undefined)
62
+ if (Type.isUnion(type)) {
63
+ return type.bounds.some(b => isPromiseType(b));
64
+ }
65
+
66
+ return false;
67
+ }
68
+
69
+ /**
70
+ * Check if an arrow function has an async modifier.
71
+ * In JavaScript, async is represented as a LanguageExtension modifier with keyword="async"
72
+ */
73
+ function hasAsyncModifier(arrowFunc: JS.ArrowFunction): boolean {
74
+ return arrowFunc.modifiers.some(m =>
75
+ m.type === J.ModifierType.Async ||
76
+ (m.type === J.ModifierType.LanguageExtension && m.keyword === 'async')
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Check if a function type returns a Promise.
82
+ */
83
+ function functionTypeReturnsPromise(funcType: Type): boolean {
84
+ if (!Type.isFunctionType(funcType)) return false;
85
+
86
+ const clazz = funcType as Type.Class;
87
+ if (clazz.typeParameters && clazz.typeParameters.length > 0) {
88
+ // First type parameter is typically R (return type) in TypeScript function types
89
+ // It's a GenericTypeVariable with bounds containing the actual type
90
+ const returnTypeParam = clazz.typeParameters[0];
91
+
92
+ // Check if it's directly a Promise type
93
+ if (isPromiseType(returnTypeParam)) {
94
+ return true;
95
+ }
96
+
97
+ // Check if it's a GenericTypeVariable with bounds
98
+ if (Type.isGenericTypeVariable(returnTypeParam)) {
99
+ const bounds = returnTypeParam.bounds;
100
+ if (bounds && bounds.some(b => isPromiseType(b))) {
101
+ return true;
102
+ }
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+
108
+ /**
109
+ * Check if a callback argument returns a Promise.
110
+ * This checks:
111
+ * 1. Explicit async modifier on arrow functions
112
+ * 2. Type annotation indicating Promise return type
113
+ * 3. Function references whose type indicates Promise return
114
+ */
115
+ function callbackReturnsPromise(arg: any): boolean {
116
+ // Handle RightPadded wrapper
117
+ const element = arg?.element ?? arg;
118
+
119
+ if (!element) return false;
120
+
121
+ // Check if it's an arrow function
122
+ if (element.kind === JS.Kind.ArrowFunction) {
123
+ const arrowFunc = element as JS.ArrowFunction;
124
+
125
+ // Check for async modifier
126
+ if (hasAsyncModifier(arrowFunc)) {
127
+ return true;
128
+ }
129
+
130
+ // Check if return type expression indicates Promise
131
+ const returnType = arrowFunc.returnTypeExpression;
132
+ if (returnType) {
133
+ // Check if the return type expression is an identifier named Promise
134
+ if (returnType.kind === J.Kind.Identifier) {
135
+ const id = returnType as J.Identifier;
136
+ if (id.simpleName === 'Promise' || id.simpleName === 'PromiseLike') {
137
+ return true;
138
+ }
139
+ }
140
+ // Check if it's a parameterized type like Promise<boolean>
141
+ if (returnType.kind === J.Kind.ParameterizedType) {
142
+ const pt = returnType as J.ParameterizedType;
143
+ // ParameterizedType has 'clazz' property which is the base type
144
+ const baseType = (pt as any).clazz;
145
+ if (baseType?.kind === J.Kind.Identifier) {
146
+ const clazz = baseType as J.Identifier;
147
+ if (clazz.simpleName === 'Promise' || clazz.simpleName === 'PromiseLike') {
148
+ return true;
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Check type attribution if available
155
+ const funcType = (arrowFunc as any).type;
156
+ if (funcType && functionTypeReturnsPromise(funcType)) {
157
+ return true;
158
+ }
159
+ }
160
+
161
+ // Check if it's a function reference (Identifier) with type attribution
162
+ if (element.kind === J.Kind.Identifier) {
163
+ const identifier = element as J.Identifier;
164
+ const funcType = identifier.type;
165
+ if (funcType && functionTypeReturnsPromise(funcType)) {
166
+ return true;
167
+ }
168
+ }
169
+
170
+ return false;
171
+ }
172
+
173
+ /**
174
+ * Detects async callbacks passed to synchronous array methods.
175
+ *
176
+ * This is a common bug pattern in JavaScript/TypeScript where async functions
177
+ * are passed to array methods like `.some()`, `.every()`, `.find()`, etc.
178
+ * These methods don't await the promises returned by async callbacks, leading
179
+ * to bugs where Promise objects are treated as truthy values.
180
+ *
181
+ * Example of buggy code:
182
+ * ```typescript
183
+ * // BUG: .some() doesn't await, so any Promise is truthy
184
+ * const hasAdmin = users.some(async user => {
185
+ * return await checkPermission(user, 'admin');
186
+ * });
187
+ * // hasAdmin is ALWAYS true because Promise objects are truthy!
188
+ *
189
+ * // CORRECT: Use a for loop with await
190
+ * let hasAdmin = false;
191
+ * for (const user of users) {
192
+ * if (await checkPermission(user, 'admin')) {
193
+ * hasAdmin = true;
194
+ * break;
195
+ * }
196
+ * }
197
+ * ```
198
+ *
199
+ * This recipe reports occurrences but doesn't auto-fix because the correct
200
+ * fix depends on the context (could use for...of loop, Promise.all, etc.).
201
+ */
202
+ export class AsyncCallbackInSyncArrayMethod extends Recipe {
203
+ readonly name = "org.openrewrite.javascript.cleanup.async-callback-in-sync-array-method";
204
+ readonly displayName: string = "Detect async callbacks in synchronous array methods";
205
+ readonly description: string = "Detects async callbacks passed to array methods like .some(), .every(), .filter() which don't await promises. This is a common bug where Promise objects are always truthy.";
206
+ readonly tags = ["javascript", "typescript", "async", "bug", "cleanup"];
207
+
208
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
209
+ return new class extends JavaScriptVisitor<ExecutionContext> {
210
+ override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
211
+ let m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
212
+
213
+ const methodName = m.name?.simpleName;
214
+ if (!methodName || !SYNC_ARRAY_METHODS.has(methodName)) {
215
+ return m;
216
+ }
217
+
218
+ // Check the arguments for async callbacks
219
+ const args = m.arguments?.elements;
220
+ if (!args || args.length === 0) {
221
+ return m;
222
+ }
223
+
224
+ const firstArg = args[0];
225
+ if (callbackReturnsPromise(firstArg)) {
226
+ return markupWarn(
227
+ m,
228
+ `Async callback passed to .${methodName}()`,
229
+ `Array methods like .${methodName}() don't await async callbacks, so Promises are treated as truthy values. Consider using a for...of loop with await instead.`
230
+ );
231
+ }
232
+
233
+ return m;
234
+ }
235
+ }();
236
+ }
237
+ }