@openrewrite/rewrite 8.69.0-20251207-184829 → 8.69.0-20251207-220615

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 (53) 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/parser.d.ts.map +1 -1
  10. package/dist/javascript/parser.js +48 -8
  11. package/dist/javascript/parser.js.map +1 -1
  12. package/dist/javascript/recipes/auto-format.d.ts +24 -0
  13. package/dist/javascript/recipes/auto-format.d.ts.map +1 -0
  14. package/dist/javascript/recipes/auto-format.js +58 -0
  15. package/dist/javascript/recipes/auto-format.js.map +1 -0
  16. package/dist/javascript/recipes/change-import.d.ts +51 -0
  17. package/dist/javascript/recipes/change-import.d.ts.map +1 -0
  18. package/dist/javascript/recipes/change-import.js +658 -0
  19. package/dist/javascript/recipes/change-import.js.map +1 -0
  20. package/dist/javascript/recipes/index.d.ts +3 -0
  21. package/dist/javascript/recipes/index.d.ts.map +1 -1
  22. package/dist/javascript/recipes/index.js +3 -0
  23. package/dist/javascript/recipes/index.js.map +1 -1
  24. package/dist/javascript/recipes/order-imports.d.ts +10 -0
  25. package/dist/javascript/recipes/order-imports.d.ts.map +1 -0
  26. package/dist/javascript/recipes/order-imports.js +240 -0
  27. package/dist/javascript/recipes/order-imports.js.map +1 -0
  28. package/dist/javascript/style.js +2 -2
  29. package/dist/javascript/style.js.map +1 -1
  30. package/dist/json/parser.js +78 -30
  31. package/dist/json/parser.js.map +1 -1
  32. package/dist/version.txt +1 -1
  33. package/package.json +1 -1
  34. package/src/cli/cli-utils.ts +3 -2
  35. package/src/cli/rewrite.ts +2 -1
  36. package/src/index.ts +3 -2
  37. package/src/javascript/parser.ts +49 -8
  38. package/src/javascript/recipes/auto-format.ts +44 -0
  39. package/src/javascript/recipes/change-import.ts +700 -0
  40. package/src/javascript/recipes/index.ts +3 -0
  41. package/src/javascript/recipes/order-imports.ts +242 -0
  42. package/src/javascript/style.ts +2 -2
  43. package/src/json/parser.ts +69 -24
  44. package/dist/recipe/index.d.ts +0 -2
  45. package/dist/recipe/index.d.ts.map +0 -1
  46. package/dist/recipe/index.js +0 -21
  47. package/dist/recipe/index.js.map +0 -1
  48. package/dist/recipe/order-imports.d.ts +0 -10
  49. package/dist/recipe/order-imports.d.ts.map +0 -1
  50. package/dist/recipe/order-imports.js +0 -213
  51. package/dist/recipe/order-imports.js.map +0 -1
  52. package/src/recipe/index.ts +0 -17
  53. package/src/recipe/order-imports.ts +0 -195
@@ -15,4 +15,7 @@
15
15
  */
16
16
 
17
17
  export * from "./async-callback-in-sync-array-method";
18
+ export * from "./auto-format";
18
19
  export * from "./upgrade-dependency-version";
20
+ export * from "./order-imports";
21
+ export * from "./change-import";
@@ -0,0 +1,242 @@
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 {produceAsync, TreeVisitor} from "../../visitor";
19
+ import {ExecutionContext} from "../../execution";
20
+ import {JavaScriptVisitor, JS} from "../index";
21
+ import {J} from "../../java";
22
+ import {Draft, produce} from "immer";
23
+ import {SpacesStyle, styleFromSourceFile, StyleKind} from "../style";
24
+
25
+ /**
26
+ * Import type categories for sorting order:
27
+ * 1. Side-effect imports (no specifier): import 'module';
28
+ * 2. Namespace imports: import * as foo from 'module';
29
+ * 3. Default imports: import foo from 'module';
30
+ * 4. Named imports: import { foo } from 'module';
31
+ * 5. Type imports: import type { Foo } from 'module';
32
+ */
33
+ enum ImportCategory {
34
+ SideEffect = 0,
35
+ Namespace = 1,
36
+ Default = 2,
37
+ Named = 3,
38
+ Type = 4
39
+ }
40
+
41
+ export class OrderImports extends Recipe {
42
+ readonly name = "org.openrewrite.javascript.cleanup.order-imports";
43
+ readonly displayName = "Order imports";
44
+ readonly description = "Sort imports by category and module path. Categories: side-effect, namespace, default, named, type. Within each category, imports are sorted alphabetically by module path. Named specifiers within each import are also sorted alphabetically.";
45
+
46
+
47
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
48
+ return new class extends JavaScriptVisitor<ExecutionContext> {
49
+
50
+ protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
51
+ const importCount = this.countImports(cu);
52
+ if (importCount === 0) {
53
+ return cu;
54
+ }
55
+
56
+ const imports = cu.statements.slice(0, importCount) as J.RightPadded<JS.Import>[];
57
+ const originalImportPosition = Object.fromEntries(imports.map((item, i) => [item.element.id, i]));
58
+ const restStatements = cu.statements.slice(importCount);
59
+
60
+ // Get style for consistent brace spacing
61
+ const spacesStyle = styleFromSourceFile(StyleKind.SpacesStyle, cu) as SpacesStyle | undefined;
62
+ const useBraceSpaces = spacesStyle?.within.es6ImportExportBraces ?? false;
63
+
64
+ // Sort named specifiers within each import
65
+ const sortedSpecifiers = this.sortNamedSpecifiersWithinImports(imports, useBraceSpaces);
66
+
67
+ // Sort imports by category and module path
68
+ sortedSpecifiers.sort((aPadded, bPadded) => {
69
+ const a = aPadded.element;
70
+ const b = bPadded.element;
71
+
72
+ // First, compare by category
73
+ const categoryA = this.getImportCategory(a);
74
+ const categoryB = this.getImportCategory(b);
75
+ if (categoryA !== categoryB) {
76
+ return categoryA - categoryB;
77
+ }
78
+
79
+ // Within same category, sort by module path (case-insensitive)
80
+ const modulePathA = this.getModulePath(a).toLowerCase();
81
+ const modulePathB = this.getModulePath(b).toLowerCase();
82
+ const pathComparison = modulePathA.localeCompare(modulePathB);
83
+ if (pathComparison !== 0) {
84
+ return pathComparison;
85
+ }
86
+
87
+ // Tiebreaker: keep original order for stability
88
+ return originalImportPosition[aPadded.element.id] - originalImportPosition[bPadded.element.id];
89
+ });
90
+
91
+ const cuWithImportsSorted = await produceAsync(cu, async draft => {
92
+ draft.statements = [...sortedSpecifiers, ...restStatements];
93
+ });
94
+
95
+ return produce(cuWithImportsSorted!, draft => {
96
+ for (let i = 0; i < importCount; i++) {
97
+ draft.statements[i].element.prefix.whitespace = i > 0 ? "\n" : "";
98
+ }
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Determine the category of an import for sorting purposes.
104
+ */
105
+ private getImportCategory(import_: JS.Import): ImportCategory {
106
+ // Type imports: import type { Foo } from 'module'
107
+ if (import_.importClause?.typeOnly) {
108
+ return ImportCategory.Type;
109
+ }
110
+
111
+ // Side-effect imports: import 'module'
112
+ if (import_.importClause === undefined) {
113
+ return ImportCategory.SideEffect;
114
+ }
115
+
116
+ // Namespace imports: import * as foo from 'module'
117
+ if (import_.importClause.namedBindings?.kind === JS.Kind.Alias) {
118
+ const alias = import_.importClause.namedBindings as JS.Alias;
119
+ if (alias.propertyName.element.simpleName === "*") {
120
+ return ImportCategory.Namespace;
121
+ }
122
+ }
123
+
124
+ // Default imports (without named imports): import foo from 'module'
125
+ if (import_.importClause.name && !import_.importClause.namedBindings) {
126
+ return ImportCategory.Default;
127
+ }
128
+
129
+ // Default with named imports or just named imports: import foo, { bar } from 'module' or import { foo } from 'module'
130
+ return ImportCategory.Named;
131
+ }
132
+
133
+ /**
134
+ * Extract the module path from an import statement.
135
+ */
136
+ private getModulePath(import_: JS.Import): string {
137
+ if (import_.moduleSpecifier?.element.kind === J.Kind.Literal) {
138
+ const literal = import_.moduleSpecifier.element as J.Literal;
139
+ // Remove quotes from the value
140
+ return String(literal.value ?? '');
141
+ }
142
+ return '';
143
+ }
144
+
145
+ private countImports(cu: JS.CompilationUnit): number {
146
+ let i = 0;
147
+ while ((i < cu.statements.length) && (cu.statements[i].element.kind === JS.Kind.Import)) {
148
+ i++;
149
+ }
150
+ return i;
151
+ }
152
+
153
+ /**
154
+ * Sort named specifiers within each import statement alphabetically.
155
+ */
156
+ private sortNamedSpecifiersWithinImports(imports: J.RightPadded<JS.Import>[], useBraceSpaces: boolean): J.RightPadded<JS.Import>[] {
157
+ const ret = [];
158
+ for (const importPadded of imports) {
159
+ const import_ = importPadded.element;
160
+ if (this.hasNamedImports(import_)) {
161
+ const importSorted = produce(import_, draft => {
162
+ const namedBindings = draft.importClause!.namedBindings as Draft<JS.NamedImports>;
163
+ let elements = namedBindings.elements.elements;
164
+
165
+ if (elements.length <= 1) {
166
+ return; // Nothing to sort
167
+ }
168
+
169
+ // Handle trailing comma
170
+ const trailingComma = elements.length > 0 &&
171
+ elements[elements.length - 1].markers?.markers.find(m => m.kind === J.Markers.TrailingComma);
172
+ if (trailingComma) {
173
+ elements[elements.length - 1].markers.markers =
174
+ elements[elements.length - 1].markers.markers.filter(m => m.kind !== J.Markers.TrailingComma);
175
+ }
176
+
177
+ // Sort by the imported name (not alias)
178
+ elements.sort((a, b) => {
179
+ const nameA = this.getSpecifierSortKey(a.element as JS.ImportSpecifier);
180
+ const nameB = this.getSpecifierSortKey(b.element as JS.ImportSpecifier);
181
+ return nameA.localeCompare(nameB);
182
+ });
183
+
184
+ // Normalize spacing based on es6ImportExportBraces style
185
+ const braceSpace = useBraceSpaces ? " " : "";
186
+ for (let i = 0; i < elements.length; i++) {
187
+ if (i === 0) {
188
+ // First element: space after opening brace based on style
189
+ elements[i].element.prefix = {kind: J.Kind.Space, whitespace: braceSpace, comments: []};
190
+ } else {
191
+ // Other elements: space after comma
192
+ elements[i].element.prefix = {kind: J.Kind.Space, whitespace: ' ', comments: []};
193
+ }
194
+ }
195
+ // Last element: space before closing brace based on style
196
+ elements[elements.length - 1].after = {kind: J.Kind.Space, whitespace: braceSpace, comments: []};
197
+
198
+ // Restore trailing comma to last element
199
+ if (trailingComma && elements.length > 0 &&
200
+ !elements[elements.length - 1].markers.markers.find(m => m.kind === J.Markers.TrailingComma)) {
201
+ elements[elements.length - 1].markers.markers.push(trailingComma);
202
+ }
203
+ });
204
+
205
+ ret.push(produce(importPadded, draft => {
206
+ draft.element = importSorted;
207
+ }));
208
+ } else {
209
+ ret.push(importPadded);
210
+ }
211
+ }
212
+ return ret;
213
+ }
214
+
215
+ /**
216
+ * Check if an import has named imports that can be sorted.
217
+ */
218
+ private hasNamedImports(import_: JS.Import): boolean {
219
+ if (import_.importClause?.namedBindings?.kind === JS.Kind.NamedImports) {
220
+ const namedImports = import_.importClause.namedBindings as JS.NamedImports;
221
+ return namedImports.elements.kind === J.Kind.Container &&
222
+ namedImports.elements.elements.length > 1;
223
+ }
224
+ return false;
225
+ }
226
+
227
+ /**
228
+ * Get the sort key for an import specifier (the original name, not alias).
229
+ */
230
+ private getSpecifierSortKey(specifier: JS.ImportSpecifier): string {
231
+ if (specifier.specifier.kind === JS.Kind.Alias) {
232
+ // import { foo as bar } - sort by 'foo'
233
+ return (specifier.specifier as JS.Alias).propertyName.element.simpleName;
234
+ } else if (specifier.specifier.kind === J.Kind.Identifier) {
235
+ // import { foo } - sort by 'foo'
236
+ return (specifier.specifier as J.Identifier).simpleName;
237
+ }
238
+ return '';
239
+ }
240
+ }
241
+ }
242
+ }
@@ -234,7 +234,7 @@ export namespace IntelliJ {
234
234
  additive: true,
235
235
  multiplicative: true,
236
236
  shift: true,
237
- unary: true,
237
+ unary: false,
238
238
  arrowFunction: true,
239
239
  beforeUnaryNotAndNotNull: false,
240
240
  afterUnaryNotAndNotNull: false
@@ -378,7 +378,7 @@ export namespace IntelliJ {
378
378
  additive: true,
379
379
  multiplicative: true,
380
380
  shift: true,
381
- unary: true,
381
+ unary: false,
382
382
  arrowFunction: true,
383
383
  beforeUnaryNotAndNotNull: false,
384
384
  afterUnaryNotAndNotNull: false
@@ -79,21 +79,38 @@ class ParseJsonReader extends ParserSourceReader {
79
79
  }
80
80
  if (Array.isArray(parsed)) {
81
81
  this.cursor++; // skip '['
82
- const values = parsed.map((p, i) => {
83
- const element = this.json(p) as Json.Value;
82
+ let values: Json.RightPadded<Json.Value>[];
83
+ if (parsed.length === 0) {
84
+ // Empty array - capture whitespace in an Empty element
84
85
  const afterWhitespace = this.whitespace();
85
- // Check if there's a comma after this element
86
- const hasComma = this.source[this.cursor] === ',';
87
- if (hasComma) {
88
- this.cursor++; // skip ','
89
- }
90
- return {
86
+ values = [{
91
87
  kind: Json.Kind.RightPadded,
92
- element,
88
+ element: {
89
+ kind: Json.Kind.Empty,
90
+ id: randomId(),
91
+ prefix: emptySpace,
92
+ markers: emptyMarkers
93
+ } satisfies Json.Empty as Json.Empty,
93
94
  after: space(afterWhitespace),
94
95
  markers: emptyMarkers
95
- } satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>;
96
- });
96
+ } satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>];
97
+ } else {
98
+ values = parsed.map((p, i) => {
99
+ const element = this.json(p) as Json.Value;
100
+ const afterWhitespace = this.whitespace();
101
+ // Check if there's a comma after this element
102
+ const hasComma = this.source[this.cursor] === ',';
103
+ if (hasComma) {
104
+ this.cursor++; // skip ','
105
+ }
106
+ return {
107
+ kind: Json.Kind.RightPadded,
108
+ element,
109
+ after: space(afterWhitespace),
110
+ markers: emptyMarkers
111
+ } satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>;
112
+ });
113
+ }
97
114
  this.cursor++; // skip ']'
98
115
  return {
99
116
  kind: Json.Kind.Array,
@@ -103,21 +120,38 @@ class ParseJsonReader extends ParserSourceReader {
103
120
  } else if (parsed !== null && typeof parsed === "object") {
104
121
  this.cursor++; // skip '{'
105
122
  const keys = Object.keys(parsed);
106
- const members = keys.map((key, i) => {
107
- const element = this.member(parsed, key);
123
+ let members: Json.RightPadded<Json.Member>[];
124
+ if (keys.length === 0) {
125
+ // Empty object - capture whitespace in an Empty element
108
126
  const afterWhitespace = this.whitespace();
109
- // Check if there's a comma after this element
110
- const hasComma = this.source[this.cursor] === ',';
111
- if (hasComma) {
112
- this.cursor++; // skip ','
113
- }
114
- return {
127
+ members = [{
115
128
  kind: Json.Kind.RightPadded,
116
- element,
129
+ element: {
130
+ kind: Json.Kind.Empty,
131
+ id: randomId(),
132
+ prefix: emptySpace,
133
+ markers: emptyMarkers
134
+ } satisfies Json.Empty as Json.Empty as unknown as Json.Member,
117
135
  after: space(afterWhitespace),
118
136
  markers: emptyMarkers
119
- } satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>;
120
- });
137
+ } satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>];
138
+ } else {
139
+ members = keys.map((key, i) => {
140
+ const element = this.member(parsed, key);
141
+ const afterWhitespace = this.whitespace();
142
+ // Check if there's a comma after this element
143
+ const hasComma = this.source[this.cursor] === ',';
144
+ if (hasComma) {
145
+ this.cursor++; // skip ','
146
+ }
147
+ return {
148
+ kind: Json.Kind.RightPadded,
149
+ element,
150
+ after: space(afterWhitespace),
151
+ markers: emptyMarkers
152
+ } satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>;
153
+ });
154
+ }
121
155
  this.cursor++; // skip '}'
122
156
  return {
123
157
  kind: Json.Kind.Object,
@@ -147,11 +181,22 @@ class ParseJsonReader extends ParserSourceReader {
147
181
  value: parsed
148
182
  } satisfies Json.Literal as Json.Literal;
149
183
  } else if (typeof parsed === "number") {
150
- this.cursor += parsed.toString().length;
184
+ // Extract original source to preserve precision for large numbers
185
+ const sourceStart = this.cursor;
186
+ // Numbers can have optional sign, digits, decimal point, and exponent
187
+ while (this.cursor < this.source.length) {
188
+ const char = this.source[this.cursor];
189
+ if (/[\d.eE+\-]/.test(char)) {
190
+ this.cursor++;
191
+ } else {
192
+ break;
193
+ }
194
+ }
195
+ const source = this.source.slice(sourceStart, this.cursor);
151
196
  return {
152
197
  kind: Json.Kind.Literal,
153
198
  ...base,
154
- source: parsed.toString(),
199
+ source,
155
200
  value: parsed,
156
201
  } satisfies Json.Literal as Json.Literal;
157
202
  } else if (typeof parsed === "boolean") {
@@ -1,2 +0,0 @@
1
- export { OrderImports } from "./order-imports";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAC"}
@@ -1,21 +0,0 @@
1
- "use strict";
2
- /*
3
- * Copyright 2025 the original author or authors.
4
- * <p>
5
- * Licensed under the Moderne Source Available License (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- * <p>
9
- * https://docs.moderne.io/licensing/moderne-source-available-license
10
- * <p>
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.OrderImports = void 0;
19
- var order_imports_1 = require("./order-imports");
20
- Object.defineProperty(exports, "OrderImports", { enumerable: true, get: function () { return order_imports_1.OrderImports; } });
21
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAEH,iDAA6C;AAArC,6GAAA,YAAY,OAAA"}
@@ -1,10 +0,0 @@
1
- import { Recipe } from "../recipe";
2
- import { TreeVisitor } from "../visitor";
3
- import { ExecutionContext } from "../execution";
4
- export declare class OrderImports extends Recipe {
5
- name: string;
6
- displayName: string;
7
- description: string;
8
- editor(): Promise<TreeVisitor<any, ExecutionContext>>;
9
- }
10
- //# sourceMappingURL=order-imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"order-imports.d.ts","sourceRoot":"","sources":["../../src/recipe/order-imports.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AACjC,OAAO,EAAe,WAAW,EAAC,MAAM,YAAY,CAAC;AACrD,OAAO,EAAC,gBAAgB,EAAC,MAAM,cAAc,CAAC;AAM9C,qBAAa,YAAa,SAAQ,MAAM;IACpC,IAAI,SAAkC;IACtC,WAAW,SAAmB;IAC9B,WAAW,SAAoG;IAGzG,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;CAoK9D"}
@@ -1,213 +0,0 @@
1
- "use strict";
2
- /*
3
- * Copyright 2025 the original author or authors.
4
- * <p>
5
- * Licensed under the Moderne Source Available License (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- * <p>
9
- * https://docs.moderne.io/licensing/moderne-source-available-license
10
- * <p>
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
- return new (P || (P = Promise))(function (resolve, reject) {
20
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
- step((generator = generator.apply(thisArg, _arguments || [])).next());
24
- });
25
- };
26
- Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.OrderImports = void 0;
28
- const recipe_1 = require("../recipe");
29
- const visitor_1 = require("../visitor");
30
- const javascript_1 = require("../javascript");
31
- const java_1 = require("../java");
32
- const immer_1 = require("immer");
33
- const format_1 = require("../javascript/format");
34
- class OrderImports extends recipe_1.Recipe {
35
- constructor() {
36
- super(...arguments);
37
- this.name = "org.openrewrite.OrderImports";
38
- this.displayName = "Order imports";
39
- this.description = "Sort top-level imports alphabetically within groups: no qualifier, asterisk, multiple, single.";
40
- }
41
- editor() {
42
- return __awaiter(this, void 0, void 0, function* () {
43
- return new class extends javascript_1.JavaScriptVisitor {
44
- visitJsCompilationUnit(cu, p) {
45
- return __awaiter(this, void 0, void 0, function* () {
46
- const importCount = this.countImports(cu);
47
- const imports = cu.statements.slice(0, importCount);
48
- const originalImportPosition = Object.fromEntries(imports.map((item, i) => [item.element.id, i]));
49
- const restStatements = cu.statements.slice(importCount);
50
- const sortedImports = yield this.sortNamesWithinEachLine(imports);
51
- sortedImports.sort((aPadded, bPadded) => {
52
- const a = aPadded.element;
53
- const b = bPadded.element;
54
- const noSpecifier = (a.importClause == undefined ? 1 : 0) - (b.importClause == undefined ? 1 : 0);
55
- if (noSpecifier != 0) {
56
- return -noSpecifier;
57
- }
58
- const asterisk = this.isAsteriskImport(a) - this.isAsteriskImport(b);
59
- if (asterisk != 0) {
60
- return -asterisk;
61
- }
62
- const multipleImport = this.isMultipleImport(a) - this.isMultipleImport(b);
63
- if (multipleImport != 0) {
64
- return -multipleImport;
65
- }
66
- const comparedSpecifiers = this.compareStringArrays(this.extractImportSpecifierNames(a), this.extractImportSpecifierNames(b));
67
- if (comparedSpecifiers != 0) {
68
- return comparedSpecifiers;
69
- }
70
- // Tiebreaker, keep the sort stable
71
- return originalImportPosition[aPadded.element.id] - originalImportPosition[bPadded.element.id];
72
- });
73
- const cuWithImportsSorted = yield (0, visitor_1.produceAsync)(cu, (draft) => __awaiter(this, void 0, void 0, function* () {
74
- draft.statements = [...sortedImports, ...restStatements];
75
- }));
76
- return (0, immer_1.produce)(cuWithImportsSorted, draft => {
77
- for (let i = 0; i < importCount; i++) {
78
- draft.statements[i].element.prefix.whitespace = i > 0 ? "\n" : "";
79
- }
80
- // TODO deal with comments in the whitespace around imports
81
- });
82
- });
83
- }
84
- isAsteriskImport(import_) {
85
- if (import_.importClause != undefined) {
86
- if (import_.importClause.namedBindings != undefined) {
87
- if (import_.importClause.namedBindings.kind == javascript_1.JS.Kind.Alias) {
88
- return import_.importClause.namedBindings.propertyName.element.simpleName == "*" ? 1 : 0;
89
- }
90
- }
91
- }
92
- return 0;
93
- }
94
- isMultipleImport(import_) {
95
- if (import_.importClause != undefined) {
96
- if (import_.importClause.namedBindings != undefined) {
97
- if (import_.importClause.namedBindings.kind == javascript_1.JS.Kind.NamedImports) {
98
- const namedImports = import_.importClause.namedBindings;
99
- if (namedImports.elements.kind == java_1.J.Kind.Container) {
100
- return namedImports.elements.elements.length > 1 ? 1 : 0;
101
- }
102
- }
103
- }
104
- }
105
- return 0;
106
- }
107
- extractImportSpecifierNames(import_) {
108
- const names = [];
109
- if (import_.importClause != undefined) {
110
- if (import_.importClause.namedBindings != undefined) {
111
- if (import_.importClause.namedBindings.kind == javascript_1.JS.Kind.NamedImports) {
112
- const namedImports = import_.importClause.namedBindings;
113
- if (namedImports.elements.kind == java_1.J.Kind.Container) {
114
- const elements = namedImports.elements.elements;
115
- for (let i = 0; i < elements.length; i++) {
116
- const importSpecifier = elements[i].element;
117
- if (importSpecifier.specifier.kind == java_1.J.Kind.Identifier) {
118
- names.push(importSpecifier.specifier.simpleName);
119
- }
120
- else if (importSpecifier.specifier.kind == javascript_1.JS.Kind.Alias) {
121
- names.push(importSpecifier.specifier.propertyName.element.simpleName);
122
- }
123
- else {
124
- throw new Error("Unknown kind " + elements[i].kind);
125
- }
126
- }
127
- }
128
- }
129
- else if (import_.importClause.namedBindings.kind == javascript_1.JS.Kind.Alias) {
130
- const alias = import_.importClause.namedBindings;
131
- names.push(alias.propertyName.element.simpleName);
132
- if (alias.alias.kind == java_1.J.Kind.Identifier) {
133
- names.push(alias.alias.simpleName);
134
- }
135
- }
136
- }
137
- }
138
- return names;
139
- }
140
- compareStringArrays(a, b) {
141
- let i = 0;
142
- while (i < a.length && i < b.length) {
143
- const comparison = a[i].localeCompare(b[i]);
144
- if (comparison !== 0) {
145
- return comparison;
146
- }
147
- i++;
148
- }
149
- if (i < a.length) {
150
- return 1;
151
- }
152
- else if (i < b.length) {
153
- return -1;
154
- }
155
- return 0;
156
- }
157
- countImports(cu) {
158
- let i = 0;
159
- while ((i < cu.statements.length) && (cu.statements[i].element.kind === javascript_1.JS.Kind.Import)) {
160
- i++;
161
- }
162
- return i;
163
- }
164
- sortNamesWithinEachLine(imports) {
165
- return __awaiter(this, void 0, void 0, function* () {
166
- const ret = [];
167
- for (const importPadded of imports) {
168
- const import_ = importPadded.element;
169
- if (this.isMultipleImport(import_) == 1) {
170
- const importSorted = (0, immer_1.produce)(import_, draft => {
171
- var _a;
172
- let elements = draft.importClause.namedBindings.elements.elements;
173
- const trailingComma = elements.length > 0 && ((_a = elements[elements.length - 1].markers) === null || _a === void 0 ? void 0 : _a.markers.find(m => m.kind === java_1.J.Markers.TrailingComma));
174
- if (trailingComma) {
175
- elements[elements.length - 1].markers.markers = elements[elements.length - 1].markers.markers.filter(m => m.kind !== java_1.J.Markers.TrailingComma);
176
- }
177
- elements.sort((a, b) => {
178
- const namesExtracted = [a.element, b.element].map(expr => {
179
- const is = expr;
180
- if (is.specifier.kind == javascript_1.JS.Kind.Alias) {
181
- return is.specifier.propertyName.element.simpleName;
182
- }
183
- else if (is.specifier.kind == java_1.J.Kind.Identifier) {
184
- return is.specifier.simpleName;
185
- }
186
- else {
187
- throw new Error("Unsupported kind " + expr.kind);
188
- }
189
- });
190
- return namesExtracted[0].localeCompare(namesExtracted[1]);
191
- });
192
- if (trailingComma && elements.length > 0 && !elements[elements.length - 1].markers.markers.find(m => m.kind === java_1.J.Markers.TrailingComma)) {
193
- elements[elements.length - 1].markers.markers.push(trailingComma);
194
- }
195
- });
196
- const formatted = yield new format_1.AutoformatVisitor().visit(importSorted, {});
197
- ret.push((0, immer_1.produce)(importPadded, draft => {
198
- draft.element = formatted;
199
- }));
200
- }
201
- else {
202
- ret.push(importPadded);
203
- }
204
- }
205
- return ret;
206
- });
207
- }
208
- };
209
- });
210
- }
211
- }
212
- exports.OrderImports = OrderImports;
213
- //# sourceMappingURL=order-imports.js.map