@openrewrite/rewrite 8.75.2 → 8.75.4
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.
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +97 -69
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.js +1 -3
- package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -1
- package/dist/javascript/format/whitespace-reconciler.d.ts +6 -0
- package/dist/javascript/format/whitespace-reconciler.d.ts.map +1 -1
- package/dist/javascript/format/whitespace-reconciler.js +31 -1
- package/dist/javascript/format/whitespace-reconciler.js.map +1 -1
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/recipes/order-imports.d.ts.map +1 -1
- package/dist/javascript/recipes/order-imports.js +5 -0
- package/dist/javascript/recipes/order-imports.js.map +1 -1
- package/dist/javascript/recipes/remove-dependency.d.ts +29 -0
- package/dist/javascript/recipes/remove-dependency.d.ts.map +1 -0
- package/dist/javascript/recipes/remove-dependency.js +261 -0
- package/dist/javascript/recipes/remove-dependency.js.map +1 -0
- package/dist/javascript/remove-import.d.ts.map +1 -1
- package/dist/javascript/remove-import.js +9 -8
- package/dist/javascript/remove-import.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +1 -0
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +18 -9
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +1 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +18 -7
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +7 -2
- package/dist/visitor.js.map +1 -1
- package/package.json +12 -11
- package/src/javascript/format/format.ts +99 -71
- package/src/javascript/format/minimum-viable-spacing-visitor.ts +1 -4
- package/src/javascript/format/whitespace-reconciler.ts +27 -2
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/recipes/order-imports.ts +6 -0
- package/src/javascript/recipes/remove-dependency.ts +345 -0
- package/src/javascript/remove-import.ts +10 -9
- package/src/rpc/request/prepare-recipe.ts +20 -9
- package/src/test/rewrite-test.ts +17 -3
- package/src/visitor.ts +7 -2
|
@@ -322,14 +322,14 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
322
322
|
|
|
323
323
|
protected async visitImportDeclaration(jsImport: JS.Import, p: P): Promise<J | undefined> {
|
|
324
324
|
const ret = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
325
|
+
// Mutative detects same-value assignments and returns the original reference
|
|
326
|
+
// when nothing actually changed, so no guard function is needed.
|
|
325
327
|
return produce(ret, draft => {
|
|
326
328
|
if (draft.importClause) {
|
|
327
|
-
// Space after 'import' keyword
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
const hasDefaultImport = !!draft.importClause.name;
|
|
332
|
-
draft.importClause.prefix.whitespace = (hasDefaultImport || draft.importClause.typeOnly) ? " " : "";
|
|
329
|
+
// Space after 'import' keyword always goes on importClause.prefix.
|
|
330
|
+
// The parser is consistent here: both ImportClause and NamedBindings share
|
|
331
|
+
// the same trivia span, but ImportClause consumes it first.
|
|
332
|
+
draft.importClause.prefix.whitespace = " ";
|
|
333
333
|
if (draft.importClause.name) {
|
|
334
334
|
// For import equals declarations (import X = Y), use assignment spacing
|
|
335
335
|
// For regular imports (import X from 'Y'), no space after name
|
|
@@ -338,22 +338,22 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
338
338
|
: "";
|
|
339
339
|
}
|
|
340
340
|
if (draft.importClause.namedBindings) {
|
|
341
|
-
|
|
342
|
-
draft.importClause.
|
|
341
|
+
const hasDefaultImport = !!draft.importClause.name;
|
|
342
|
+
if (hasDefaultImport || draft.importClause.typeOnly) {
|
|
343
|
+
draft.importClause.namedBindings.prefix.whitespace = " ";
|
|
344
|
+
}
|
|
343
345
|
if (draft.importClause.namedBindings.kind == JS.Kind.NamedImports) {
|
|
344
346
|
const ni = draft.importClause.namedBindings as Draft<JS.NamedImports>;
|
|
345
|
-
// Check if this is a multi-line import (any element's prefix has a newline)
|
|
346
347
|
const isMultiLine = ni.elements.elements.some(e => e.element.prefix.whitespace.includes("\n"));
|
|
347
348
|
if (!isMultiLine) {
|
|
348
|
-
|
|
349
|
-
ni.elements.elements[0].element.prefix.whitespace =
|
|
350
|
-
ni.elements.elements[ni.elements.elements.length - 1].after.whitespace =
|
|
349
|
+
const braceSpace = this.style.within.es6ImportExportBraces ? " " : "";
|
|
350
|
+
ni.elements.elements[0].element.prefix.whitespace = braceSpace;
|
|
351
|
+
ni.elements.elements[ni.elements.elements.length - 1].after.whitespace = braceSpace;
|
|
351
352
|
} else {
|
|
352
353
|
// Multi-line: apply beforeComma rule to last element's after (for trailing commas)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
ni.elements.elements[ni.elements.elements.length - 1].after.whitespace = this.style.other.beforeComma ? " " : "";
|
|
354
|
+
const lastAfter = ni.elements.elements[ni.elements.elements.length - 1].after;
|
|
355
|
+
if (!lastAfter.whitespace.includes("\n") && lastAfter.whitespace.trim() === "") {
|
|
356
|
+
lastAfter.whitespace = this.style.other.beforeComma ? " " : "";
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
}
|
|
@@ -751,6 +751,9 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
751
751
|
|
|
752
752
|
protected async visitVariableDeclarations(multiVariable: J.VariableDeclarations, p: P): Promise<J.VariableDeclarations> {
|
|
753
753
|
const v = await super.visitVariableDeclarations(multiVariable, p) as J.VariableDeclarations;
|
|
754
|
+
if (v.leadingAnnotations.length === 0) {
|
|
755
|
+
return v;
|
|
756
|
+
}
|
|
754
757
|
const parent = this.cursor.parentTree()?.value;
|
|
755
758
|
if (parent?.kind === J.Kind.Block) {
|
|
756
759
|
return produce(v, draft => {
|
|
@@ -769,6 +772,9 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
769
772
|
|
|
770
773
|
protected async visitMethodDeclaration(method: J.MethodDeclaration, p: P): Promise<J.MethodDeclaration> {
|
|
771
774
|
const m = await super.visitMethodDeclaration(method, p) as J.MethodDeclaration;
|
|
775
|
+
if (m.leadingAnnotations.length === 0) {
|
|
776
|
+
return m;
|
|
777
|
+
}
|
|
772
778
|
return produce(m, draft => {
|
|
773
779
|
draft.leadingAnnotations = this.withNewlines(draft.leadingAnnotations);
|
|
774
780
|
if (draft.leadingAnnotations.length > 0) {
|
|
@@ -789,21 +795,29 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
789
795
|
const e = await super.visitElse(elsePart, p) as J.If.Else;
|
|
790
796
|
const hasBody = e.body.element.kind === J.Kind.Block || e.body.element.kind === J.Kind.If;
|
|
791
797
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
798
|
+
if (!hasBody) {
|
|
799
|
+
return e;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const shouldHaveNewline = this.style.ifStatement.elseOnNewLine;
|
|
803
|
+
const hasNewline = e.prefix.whitespace.includes("\n");
|
|
804
|
+
if ((shouldHaveNewline && !hasNewline) || (!shouldHaveNewline && hasNewline)) {
|
|
805
|
+
return produce(e, draft => {
|
|
796
806
|
if (shouldHaveNewline && !hasNewline) {
|
|
797
807
|
draft.prefix.whitespace = "\n" + draft.prefix.whitespace;
|
|
798
|
-
} else
|
|
808
|
+
} else {
|
|
799
809
|
draft.prefix.whitespace = "";
|
|
800
810
|
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
return e;
|
|
803
814
|
}
|
|
804
815
|
|
|
805
816
|
protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: P): Promise<J.ClassDeclaration> {
|
|
806
817
|
const j = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
|
|
818
|
+
if (j.leadingAnnotations.length === 0) {
|
|
819
|
+
return j;
|
|
820
|
+
}
|
|
807
821
|
return produce(j, draft => {
|
|
808
822
|
draft.leadingAnnotations = this.withNewlines(draft.leadingAnnotations);
|
|
809
823
|
if (draft.leadingAnnotations.length > 0) {
|
|
@@ -821,61 +835,75 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
821
835
|
|
|
822
836
|
protected async visitBlock(block: J.Block, p: P): Promise<J.Block> {
|
|
823
837
|
const b = await super.visitBlock(block, p) as J.Block;
|
|
824
|
-
|
|
825
|
-
const parentKind = this.cursor.parent?.value.kind;
|
|
826
|
-
|
|
827
|
-
// Check if this is a "simple" block (empty or contains only a single J.Empty)
|
|
828
|
-
const isSimpleBlock = draft.statements.length === 0 ||
|
|
829
|
-
(draft.statements.length === 1 && draft.statements[0].element.kind === J.Kind.Empty);
|
|
838
|
+
const parentKind = this.cursor.parent?.value.kind;
|
|
830
839
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (draft.end.whitespace.includes("\n")) {
|
|
835
|
-
draft.end.whitespace = draft.end.whitespace.replace(/\n\s*/g, "");
|
|
836
|
-
}
|
|
837
|
-
// Also remove newlines from statement padding if there's a J.Empty
|
|
838
|
-
if (draft.statements.length === 1) {
|
|
839
|
-
if (draft.statements[0].element.prefix.whitespace.includes("\n")) {
|
|
840
|
-
draft.statements[0].element.prefix.whitespace = "";
|
|
841
|
-
}
|
|
842
|
-
if (draft.statements[0].after.whitespace.includes("\n")) {
|
|
843
|
-
draft.statements[0].after.whitespace = "";
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
};
|
|
840
|
+
// Check if this is a "simple" block (empty or contains only a single J.Empty)
|
|
841
|
+
const isSimpleBlock = b.statements.length === 0 ||
|
|
842
|
+
(b.statements.length === 1 && b.statements[0].element.kind === J.Kind.Empty);
|
|
847
843
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
844
|
+
// Object literals and type literals: always format empty ones as {} on single line
|
|
845
|
+
if (parentKind === J.Kind.NewClass || parentKind === JS.Kind.TypeLiteral) {
|
|
846
|
+
if (isSimpleBlock && this.blockIsMultiLine(b)) {
|
|
847
|
+
return produce(b, draft => {
|
|
848
|
+
this.formatBlockOnOneLine(draft);
|
|
849
|
+
});
|
|
854
850
|
}
|
|
851
|
+
return b;
|
|
852
|
+
}
|
|
855
853
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (!draft.end.whitespace.includes("\n")) {
|
|
869
|
-
draft.end = this.withNewlineSpace(draft.end);
|
|
870
|
-
}
|
|
854
|
+
if (isSimpleBlock) {
|
|
855
|
+
const isMethodOrFunctionBody = parentKind === J.Kind.Lambda ||
|
|
856
|
+
parentKind === J.Kind.MethodDeclaration;
|
|
857
|
+
const keepInOneLine = isMethodOrFunctionBody
|
|
858
|
+
? this.style.keepWhenReformatting.simpleMethodsInOneLine
|
|
859
|
+
: this.style.keepWhenReformatting.simpleBlocksInOneLine;
|
|
860
|
+
|
|
861
|
+
if (keepInOneLine) {
|
|
862
|
+
if (this.blockIsMultiLine(b)) {
|
|
863
|
+
return produce(b, draft => {
|
|
864
|
+
this.formatBlockOnOneLine(draft);
|
|
865
|
+
});
|
|
871
866
|
}
|
|
872
867
|
} else {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
868
|
+
if (!b.end.whitespace.includes("\n")) {
|
|
869
|
+
return produce(b, draft => {
|
|
870
|
+
draft.end = this.withNewlineSpace(draft.end);
|
|
871
|
+
});
|
|
876
872
|
}
|
|
877
873
|
}
|
|
878
|
-
}
|
|
874
|
+
} else {
|
|
875
|
+
// Non-simple blocks: ensure closing brace is on its own line
|
|
876
|
+
if (!b.end.whitespace.includes("\n") && !b.statements[b.statements.length - 1].after.whitespace.includes("\n")) {
|
|
877
|
+
return produce(b, draft => {
|
|
878
|
+
draft.end = this.withNewlineSpace(draft.end);
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return b;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
private blockIsMultiLine(b: J.Block): boolean {
|
|
887
|
+
if (b.end.whitespace.includes("\n")) return true;
|
|
888
|
+
if (b.statements.length === 1) {
|
|
889
|
+
if (b.statements[0].element.prefix.whitespace.includes("\n")) return true;
|
|
890
|
+
if (b.statements[0].after.whitespace.includes("\n")) return true;
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
private formatBlockOnOneLine(draft: Draft<J.Block>): void {
|
|
896
|
+
if (draft.end.whitespace.includes("\n")) {
|
|
897
|
+
draft.end.whitespace = draft.end.whitespace.replace(/\n\s*/g, "");
|
|
898
|
+
}
|
|
899
|
+
if (draft.statements.length === 1) {
|
|
900
|
+
if (draft.statements[0].element.prefix.whitespace.includes("\n")) {
|
|
901
|
+
draft.statements[0].element.prefix.whitespace = "";
|
|
902
|
+
}
|
|
903
|
+
if (draft.statements[0].after.whitespace.includes("\n")) {
|
|
904
|
+
draft.statements[0].after.whitespace = "";
|
|
905
|
+
}
|
|
906
|
+
}
|
|
879
907
|
}
|
|
880
908
|
|
|
881
909
|
protected async visitSwitch(aSwitch: J.Switch, p: P): Promise<J | undefined> {
|
|
@@ -1100,7 +1128,7 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
1100
1128
|
}
|
|
1101
1129
|
}
|
|
1102
1130
|
|
|
1103
|
-
private ensurePrefixHasNewLine
|
|
1131
|
+
private ensurePrefixHasNewLine(node: Draft<J>) {
|
|
1104
1132
|
if (!node.prefix) return;
|
|
1105
1133
|
|
|
1106
1134
|
// Check if newline already exists in the effective last whitespace
|
|
@@ -84,6 +84,7 @@ export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
84
84
|
|
|
85
85
|
// Note: typeParameters should NOT have space before them - they immediately follow the class name
|
|
86
86
|
// e.g., "class DataTable<Row>" not "class DataTable <Row>"
|
|
87
|
+
// Note: body.prefix spacing (space before '{') is handled by SpacesVisitor, not here.
|
|
87
88
|
|
|
88
89
|
if (c.extends && c.extends.before.whitespace === "") {
|
|
89
90
|
c = produce(c, draft => {
|
|
@@ -100,10 +101,6 @@ export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
c = produce(c, draft => {
|
|
104
|
-
draft.body.prefix.whitespace = "";
|
|
105
|
-
});
|
|
106
|
-
|
|
107
104
|
return c;
|
|
108
105
|
}
|
|
109
106
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {isIdentifier, isLiteral, isSpace, J, Type} from '../../java';
|
|
16
|
+
import {isIdentifier, isLiteral, isSpace, J, TextComment, Type} from '../../java';
|
|
17
17
|
import {JS} from "../tree";
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -163,7 +163,9 @@ export class WhitespaceReconciler {
|
|
|
163
163
|
|
|
164
164
|
// Space nodes - copy when reconciling, don't recurse
|
|
165
165
|
if (isSpace(original)) {
|
|
166
|
-
|
|
166
|
+
if (!this.shouldReconcile()) return original;
|
|
167
|
+
if (isSpace(formatted) && this.spacesEqual(original, formatted as J.Space)) return original;
|
|
168
|
+
return formatted;
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
// Track entering target subtree (using referential equality)
|
|
@@ -247,6 +249,10 @@ export class WhitespaceReconciler {
|
|
|
247
249
|
// Space values and markers: copy from formatted when reconciling
|
|
248
250
|
if ((isSpace(originalValue)) || key === 'markers') {
|
|
249
251
|
if (this.shouldReconcile() && formattedValue !== originalValue) {
|
|
252
|
+
// For spaces, check structural equality to avoid unnecessary new objects
|
|
253
|
+
if (isSpace(originalValue) && isSpace(formattedValue) && this.spacesEqual(originalValue, formattedValue as J.Space)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
250
256
|
result = { ...result, [key]: formattedValue } as VisitableNode;
|
|
251
257
|
}
|
|
252
258
|
continue;
|
|
@@ -297,6 +303,25 @@ export class WhitespaceReconciler {
|
|
|
297
303
|
return this.reconcileState === 'reconciling';
|
|
298
304
|
}
|
|
299
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Structurally compare two Space objects for equality.
|
|
308
|
+
* Currently only TextComment has additional properties (text, multiline);
|
|
309
|
+
* if new comment types are added, extend the comparison here.
|
|
310
|
+
*/
|
|
311
|
+
private spacesEqual(a: J.Space, b: J.Space): boolean {
|
|
312
|
+
if (a.whitespace !== b.whitespace) return false;
|
|
313
|
+
if (a.comments.length !== b.comments.length) return false;
|
|
314
|
+
for (let i = 0; i < a.comments.length; i++) {
|
|
315
|
+
const ca = a.comments[i], cb = b.comments[i];
|
|
316
|
+
if (ca.kind !== cb.kind || ca.suffix !== cb.suffix) return false;
|
|
317
|
+
if (ca.kind === J.Kind.TextComment) {
|
|
318
|
+
const ta = ca as TextComment, tb = cb as TextComment;
|
|
319
|
+
if (ta.text !== tb.text || ta.multiline !== tb.multiline) return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
300
325
|
/**
|
|
301
326
|
* Checks if two nodes with different kinds are semantically equivalent.
|
|
302
327
|
* This handles cases like Prettier's quoteProps option which can change
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
export * from "./add-dependency";
|
|
18
18
|
export * from "./async-callback-in-sync-array-method";
|
|
19
19
|
export * from "./auto-format";
|
|
20
|
+
export * from "./remove-dependency";
|
|
20
21
|
export * from "./upgrade-dependency-version";
|
|
21
22
|
export * from "./upgrade-transitive-dependency-version";
|
|
22
23
|
export * from "./order-imports";
|
|
@@ -88,6 +88,12 @@ export class OrderImports extends Recipe {
|
|
|
88
88
|
return originalImportPosition[aPadded.element.id] - originalImportPosition[bPadded.element.id];
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
// Check if anything actually changed
|
|
92
|
+
const alreadySorted = sortedSpecifiers.every((s, i) => s === imports[i]);
|
|
93
|
+
if (alreadySorted) {
|
|
94
|
+
return cu;
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
const cuWithImportsSorted = await produceAsync(cu, async draft => {
|
|
92
98
|
draft.statements = [...sortedSpecifiers, ...restStatements];
|
|
93
99
|
});
|