@openrewrite/rewrite 8.70.0-20251218-163048 → 8.70.0-20251219-160440

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 (92) hide show
  1. package/dist/java/type.d.ts +5 -0
  2. package/dist/java/type.d.ts.map +1 -1
  3. package/dist/java/type.js +12 -0
  4. package/dist/java/type.js.map +1 -1
  5. package/dist/javascript/assertions.d.ts.map +1 -1
  6. package/dist/javascript/assertions.js +9 -0
  7. package/dist/javascript/assertions.js.map +1 -1
  8. package/dist/javascript/comparator.d.ts.map +1 -1
  9. package/dist/javascript/comparator.js +5 -9
  10. package/dist/javascript/comparator.js.map +1 -1
  11. package/dist/javascript/{format.d.ts → format/format.d.ts} +14 -33
  12. package/dist/javascript/format/format.d.ts.map +1 -0
  13. package/dist/javascript/{format.js → format/format.js} +28 -313
  14. package/dist/javascript/{format.js.map → format/format.js.map} +1 -1
  15. package/dist/javascript/format/index.d.ts +3 -0
  16. package/dist/javascript/format/index.d.ts.map +1 -0
  17. package/dist/javascript/format/index.js +38 -0
  18. package/dist/javascript/format/index.js.map +1 -0
  19. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +28 -0
  20. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -0
  21. package/dist/javascript/format/minimum-viable-spacing-visitor.js +308 -0
  22. package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -0
  23. package/dist/javascript/format/normalize-whitespace-visitor.d.ts +14 -0
  24. package/dist/javascript/format/normalize-whitespace-visitor.d.ts.map +1 -0
  25. package/dist/javascript/format/normalize-whitespace-visitor.js +65 -0
  26. package/dist/javascript/format/normalize-whitespace-visitor.js.map +1 -0
  27. package/dist/javascript/format/prettier-config-loader.d.ts +92 -0
  28. package/dist/javascript/format/prettier-config-loader.d.ts.map +1 -0
  29. package/dist/javascript/format/prettier-config-loader.js +419 -0
  30. package/dist/javascript/format/prettier-config-loader.js.map +1 -0
  31. package/dist/javascript/format/prettier-format.d.ts +111 -0
  32. package/dist/javascript/format/prettier-format.d.ts.map +1 -0
  33. package/dist/javascript/format/prettier-format.js +496 -0
  34. package/dist/javascript/format/prettier-format.js.map +1 -0
  35. package/dist/javascript/{tabs-and-indents-visitor.d.ts → format/tabs-and-indents-visitor.d.ts} +4 -4
  36. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -0
  37. package/dist/javascript/{tabs-and-indents-visitor.js → format/tabs-and-indents-visitor.js} +7 -7
  38. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -0
  39. package/dist/javascript/format/whitespace-reconciler.d.ts +106 -0
  40. package/dist/javascript/format/whitespace-reconciler.d.ts.map +1 -0
  41. package/dist/javascript/format/whitespace-reconciler.js +291 -0
  42. package/dist/javascript/format/whitespace-reconciler.js.map +1 -0
  43. package/dist/javascript/markers.d.ts.map +1 -1
  44. package/dist/javascript/markers.js +21 -0
  45. package/dist/javascript/markers.js.map +1 -1
  46. package/dist/javascript/parser.d.ts +15 -3
  47. package/dist/javascript/parser.d.ts.map +1 -1
  48. package/dist/javascript/parser.js +107 -24
  49. package/dist/javascript/parser.js.map +1 -1
  50. package/dist/javascript/recipes/auto-format.d.ts +3 -0
  51. package/dist/javascript/recipes/auto-format.d.ts.map +1 -1
  52. package/dist/javascript/recipes/auto-format.js +22 -1
  53. package/dist/javascript/recipes/auto-format.js.map +1 -1
  54. package/dist/javascript/style.d.ts +52 -1
  55. package/dist/javascript/style.d.ts.map +1 -1
  56. package/dist/javascript/style.js +43 -2
  57. package/dist/javascript/style.js.map +1 -1
  58. package/dist/test/rewrite-test.d.ts +3 -4
  59. package/dist/test/rewrite-test.d.ts.map +1 -1
  60. package/dist/test/rewrite-test.js +6 -18
  61. package/dist/test/rewrite-test.js.map +1 -1
  62. package/dist/version.txt +1 -1
  63. package/dist/yaml/assertions.d.ts +4 -0
  64. package/dist/yaml/assertions.d.ts.map +1 -0
  65. package/dist/yaml/assertions.js +31 -0
  66. package/dist/yaml/assertions.js.map +1 -0
  67. package/dist/yaml/index.d.ts +2 -1
  68. package/dist/yaml/index.d.ts.map +1 -1
  69. package/dist/yaml/index.js +2 -1
  70. package/dist/yaml/index.js.map +1 -1
  71. package/package.json +4 -3
  72. package/src/java/type.ts +12 -0
  73. package/src/javascript/assertions.ts +9 -0
  74. package/src/javascript/comparator.ts +6 -11
  75. package/src/javascript/{format.ts → format/format.ts} +35 -267
  76. package/src/javascript/format/index.ts +21 -0
  77. package/src/javascript/format/minimum-viable-spacing-visitor.ts +256 -0
  78. package/src/javascript/format/normalize-whitespace-visitor.ts +42 -0
  79. package/src/javascript/format/prettier-config-loader.ts +422 -0
  80. package/src/javascript/format/prettier-format.ts +622 -0
  81. package/src/javascript/{tabs-and-indents-visitor.ts → format/tabs-and-indents-visitor.ts} +8 -8
  82. package/src/javascript/format/whitespace-reconciler.ts +345 -0
  83. package/src/javascript/markers.ts +19 -0
  84. package/src/javascript/parser.ts +107 -20
  85. package/src/javascript/recipes/auto-format.ts +28 -1
  86. package/src/javascript/style.ts +41 -2
  87. package/src/test/rewrite-test.ts +6 -18
  88. package/src/yaml/assertions.ts +28 -0
  89. package/src/yaml/index.ts +2 -1
  90. package/dist/javascript/format.d.ts.map +0 -1
  91. package/dist/javascript/tabs-and-indents-visitor.d.ts.map +0 -1
  92. package/dist/javascript/tabs-and-indents-visitor.js.map +0 -1
@@ -13,19 +13,23 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import {JS} from "./tree";
17
- import {JavaScriptVisitor} from "./visitor";
18
- import {Comment, J, lastWhitespace, replaceLastWhitespace, Statement} from "../java";
16
+ import {JS} from "../tree";
17
+ import {JavaScriptVisitor} from "../visitor";
18
+ import {Comment, J, lastWhitespace, replaceLastWhitespace, Statement} from "../../java";
19
19
  import {Draft, produce} from "immer";
20
- import {Cursor, isScope, Tree} from "../tree";
21
- import {BlankLinesStyle, getStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "./style";
22
- import {NamedStyles} from "../style";
23
- import {produceAsync} from "../visitor";
24
- import {findMarker} from "../markers";
25
- import {Generator} from "./markers";
20
+ import {Cursor, isScope, Tree} from "../../tree";
21
+ import {BlankLinesStyle, getStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
22
+ import {NamedStyles} from "../../style";
23
+ import {produceAsync} from "../../visitor";
24
+ import {Generator} from "../markers";
26
25
  import {TabsAndIndentsVisitor} from "./tabs-and-indents-visitor";
26
+ import {NormalizeWhitespaceVisitor} from "./normalize-whitespace-visitor";
27
+ import {MinimumViableSpacingVisitor} from "./minimum-viable-spacing-visitor";
28
+ import {applyPrettierFormatting, getPrettierStyle} from "./prettier-format";
27
29
 
28
30
  export {TabsAndIndentsVisitor} from "./tabs-and-indents-visitor";
31
+ export {NormalizeWhitespaceVisitor} from "./normalize-whitespace-visitor";
32
+ export {MinimumViableSpacingVisitor} from "./minimum-viable-spacing-visitor";
29
33
 
30
34
  export const maybeAutoFormat = async <J2 extends J, P>(before: J2, after: J2, p: P, stopAfter?: J, parent?: Cursor): Promise<J2> => {
31
35
  if (before !== after) {
@@ -34,7 +38,13 @@ export const maybeAutoFormat = async <J2 extends J, P>(before: J2, after: J2, p:
34
38
  return after;
35
39
  }
36
40
 
37
- export const autoFormat = async <J2 extends J, P>(j: J2, p: P, stopAfter?: J, parent?: Cursor, styles?: NamedStyles[]): Promise<J2> =>
41
+ export const autoFormat = async <J2 extends J, P>(
42
+ j: J2,
43
+ p: P,
44
+ stopAfter?: J,
45
+ parent?: Cursor,
46
+ styles?: NamedStyles<string>[]
47
+ ): Promise<J2> =>
38
48
  (await new AutoformatVisitor(stopAfter, styles).visit(j, p, parent) as J2);
39
49
 
40
50
  /**
@@ -44,16 +54,26 @@ export const autoFormat = async <J2 extends J, P>(j: J2, p: P, stopAfter?: J, pa
44
54
  * 1. Styles passed to the constructor
45
55
  * 2. Styles from source file markers (NamedStyles)
46
56
  * 3. IntelliJ defaults
57
+ *
58
+ * When a PrettierStyle is present (either in the styles array or as a marker on the source file),
59
+ * Prettier is used for formatting. Otherwise, built-in formatting visitors are used.
47
60
  */
48
61
  export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
49
- private readonly styles?: NamedStyles[];
62
+ private readonly styles?: NamedStyles<string>[];
50
63
 
51
- constructor(private stopAfter?: Tree, styles?: NamedStyles[]) {
64
+ constructor(private stopAfter?: Tree, styles?: NamedStyles<string>[]) {
52
65
  super();
53
66
  this.styles = styles;
54
67
  }
55
68
 
56
69
  async visit<R extends J>(tree: Tree, p: P, cursor?: Cursor): Promise<R | undefined> {
70
+ // Check for PrettierStyle in styles array or as marker on source file
71
+ // If found, delegate entirely to Prettier (skip other formatting visitors)
72
+ const prettierStyle = getPrettierStyle(tree, cursor, this.styles);
73
+ if (prettierStyle) {
74
+ return applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
75
+ }
76
+
57
77
  const visitors = [
58
78
  new NormalizeWhitespaceVisitor(this.stopAfter),
59
79
  new MinimumViableSpacingVisitor(this.stopAfter),
@@ -75,29 +95,6 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
75
95
  }
76
96
  }
77
97
 
78
- export class NormalizeWhitespaceVisitor<P> extends JavaScriptVisitor<P> {
79
- // called NormalizeFormat in Java
80
- // Ensures that whitespace is on the outermost AST element possible
81
-
82
- constructor(private stopAfter?: Tree) {
83
- super();
84
- }
85
-
86
- override async visit<R extends J>(tree: Tree, p: P, parent?: Cursor): Promise<R | undefined> {
87
- if (this.cursor?.getNearestMessage("stop") != null) {
88
- return tree as R;
89
- }
90
- return super.visit(tree, p, parent);
91
- }
92
-
93
- override async postVisit(tree: J, p: P): Promise<J | undefined> {
94
- if (this.stopAfter != null && isScope(this.stopAfter, tree)) {
95
- this.cursor?.root.messages.set("stop", true);
96
- }
97
- return super.postVisit(tree, p);
98
- }
99
- }
100
-
101
98
  export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
102
99
  constructor(private style: SpacesStyle, private stopAfter?: Tree) {
103
100
  super();
@@ -929,238 +926,6 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
929
926
  }
930
927
  }
931
928
 
932
-
933
- export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
934
- constructor(private stopAfter?: Tree) {
935
- super();
936
- }
937
-
938
- override async visit<R extends J>(tree: Tree, p: P, parent?: Cursor): Promise<R | undefined> {
939
- if (this.cursor?.getNearestMessage("stop") != null) {
940
- return tree as R;
941
- }
942
- return super.visit(tree, p, parent);
943
- }
944
-
945
- override async postVisit(tree: J, p: P): Promise<J | undefined> {
946
- if (this.stopAfter != null && isScope(this.stopAfter, tree)) {
947
- this.cursor?.root.messages.set("stop", true);
948
- }
949
- return super.postVisit(tree, p);
950
- }
951
-
952
- protected async visitAwait(await_: JS.Await, p: P): Promise<J | undefined> {
953
- const ret = await super.visitAwait(await_, p) as JS.Await;
954
- return produce(ret, draft => {
955
- this.ensureSpace(draft.expression.prefix)
956
- });
957
- }
958
-
959
- protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: P): Promise<J | undefined> {
960
- let c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
961
- let first = c.leadingAnnotations.length === 0;
962
-
963
- if (c.modifiers.length > 0) {
964
- if (!first && c.modifiers[0].prefix.whitespace === "") {
965
- c = produce(c, draft => {
966
- this.ensureSpace(draft.modifiers[0].prefix);
967
- });
968
- }
969
- c = produce(c, draft => {
970
- for (let i = 1; i < draft.modifiers.length; i++) {
971
- this.ensureSpace(draft.modifiers[i].prefix);
972
- }
973
- });
974
- first = false;
975
- }
976
-
977
- if (c.classKind.prefix.whitespace === "" && !first) {
978
- c = produce(c, draft => {
979
- this.ensureSpace(draft.classKind.prefix);
980
- });
981
- first = false;
982
- }
983
-
984
- // anonymous classes have an empty name
985
- if (c.name.simpleName !== "") {
986
- c = produce(c, draft => {
987
- this.ensureSpace(draft.name.prefix);
988
- });
989
- }
990
-
991
- // Note: typeParameters should NOT have space before them - they immediately follow the class name
992
- // e.g., "class DataTable<Row>" not "class DataTable <Row>"
993
-
994
- if (c.extends && c.extends.before.whitespace === "") {
995
- c = produce(c, draft => {
996
- this.ensureSpace(draft.extends!.before);
997
- });
998
- }
999
-
1000
- if (c.implements && c.implements.before.whitespace === "") {
1001
- c = produce(c, draft => {
1002
- this.ensureSpace(draft.implements!.before);
1003
- if (draft.implements != undefined && draft.implements.elements.length > 0) {
1004
- this.ensureSpace(draft.implements.elements[0].element.prefix);
1005
- }
1006
- });
1007
- }
1008
-
1009
- c = produce(c, draft => {
1010
- draft.body.prefix.whitespace = "";
1011
- });
1012
-
1013
- return c;
1014
- }
1015
-
1016
- protected async visitMethodDeclaration(method: J.MethodDeclaration, p: P): Promise<J | undefined> {
1017
- let m = await super.visitMethodDeclaration(method, p) as J.MethodDeclaration;
1018
- let first = m.leadingAnnotations.length === 0;
1019
-
1020
- if (m.modifiers.length > 0) {
1021
- if (!first && m.modifiers[0].prefix.whitespace === "") {
1022
- m = produce(m, draft => {
1023
- this.ensureSpace(draft.modifiers[0].prefix);
1024
- });
1025
- }
1026
- m = produce(m, draft => {
1027
- for (let i = 1; i < draft.modifiers.length; i++) {
1028
- this.ensureSpace(draft.modifiers[i].prefix);
1029
- }
1030
- });
1031
- first = false;
1032
- }
1033
-
1034
- // FunctionDeclaration marker check must come AFTER modifiers processing
1035
- // to avoid adding unwanted space before the first modifier (e.g., 'async')
1036
- if (findMarker(method, JS.Markers.FunctionDeclaration)) {
1037
- first = false;
1038
- }
1039
-
1040
- if (!first && m.name.prefix.whitespace === "") {
1041
- m = produce(m, draft => {
1042
- this.ensureSpace(draft.name.prefix);
1043
- });
1044
- }
1045
-
1046
- if (m.throws && m.throws.before.whitespace === "") {
1047
- m = produce(m, draft => {
1048
- this.ensureSpace(draft.throws!.before);
1049
- });
1050
- }
1051
-
1052
- return m;
1053
- }
1054
-
1055
- protected async visitNamespaceDeclaration(namespaceDeclaration: JS.NamespaceDeclaration, p: P): Promise<J | undefined> {
1056
- const ret = await super.visitNamespaceDeclaration(namespaceDeclaration, p) as JS.NamespaceDeclaration;
1057
- return produce(ret, draft => {
1058
- if (draft.modifiers.length > 0) {
1059
- draft.keywordType.before.whitespace=" ";
1060
- }
1061
- this.ensureSpace(draft.name.element.prefix);
1062
- });
1063
- }
1064
-
1065
- protected async visitNewClass(newClass: J.NewClass, p: P): Promise<J | undefined> {
1066
- const ret = await super.visitNewClass(newClass, p) as J.NewClass;
1067
- return produce(ret, draft => {
1068
- if (draft.class) {
1069
- if (draft.class.kind == J.Kind.Identifier) {
1070
- this.ensureSpace((draft.class as Draft<J.Identifier>).prefix);
1071
- }
1072
- }
1073
- });
1074
- }
1075
-
1076
- protected async visitReturn(returnNode: J.Return, p: P): Promise<J | undefined> {
1077
- const r = await super.visitReturn(returnNode, p) as J.Return;
1078
- if (r.expression && r.expression.prefix.whitespace === "" &&
1079
- !r.markers.markers.find(m => m.id === "org.openrewrite.java.marker.ImplicitReturn")) {
1080
- return produce(r, draft => {
1081
- this.ensureSpace(draft.expression!.prefix);
1082
- });
1083
- }
1084
- return r;
1085
- }
1086
-
1087
- protected async visitThrow(thrown: J.Throw, p: P): Promise<J | undefined> {
1088
- const ret = await super.visitThrow(thrown, p) as J.Throw;
1089
- return ret && produce(ret, draft => {
1090
- this.ensureSpace(draft.exception.prefix);
1091
- });
1092
- }
1093
-
1094
- protected async visitTypeDeclaration(typeDeclaration: JS.TypeDeclaration, p: P): Promise<J | undefined> {
1095
- const ret = await super.visitTypeDeclaration(typeDeclaration, p) as JS.TypeDeclaration;
1096
- return produce(ret, draft => {
1097
- if (draft.modifiers.length > 0) {
1098
- this.ensureSpace(draft.name.before);
1099
- }
1100
- this.ensureSpace(draft.name.element.prefix);
1101
- });
1102
- }
1103
-
1104
- protected async visitTypeOf(typeOf: JS.TypeOf, p: P): Promise<J | undefined> {
1105
- const ret = await super.visitTypeOf(typeOf, p) as JS.TypeOf;
1106
- return produce(ret, draft => {
1107
- this.ensureSpace(draft.expression.prefix);
1108
- });
1109
- }
1110
-
1111
- protected async visitTypeParameter(typeParam: J.TypeParameter, p: P): Promise<J | undefined> {
1112
- const ret = await super.visitTypeParameter(typeParam, p) as J.TypeParameter;
1113
- return produce(ret, draft => {
1114
- if (draft.bounds && draft.bounds.elements.length > 0) {
1115
- this.ensureSpace(draft.bounds.before);
1116
- this.ensureSpace(draft.bounds.elements[0].element.prefix);
1117
- }
1118
- });
1119
- }
1120
-
1121
- protected async visitVariableDeclarations(v: J.VariableDeclarations, p: P): Promise<J | undefined> {
1122
- let ret = await super.visitVariableDeclarations(v, p) as J.VariableDeclarations;
1123
- let first = ret.leadingAnnotations.length === 0;
1124
-
1125
- if (first && ret.modifiers.length > 0) {
1126
- ret = produce(ret, draft => {
1127
- for (let i = 1; i < draft.modifiers.length; i++) {
1128
- this.ensureSpace(draft.modifiers[i].prefix);
1129
- }
1130
- });
1131
- first = false;
1132
- }
1133
-
1134
- if (!first) {
1135
- ret = produce(ret, draft => {
1136
- this.ensureSpace(draft.variables[0].element.prefix);
1137
- });
1138
- }
1139
-
1140
- return ret;
1141
- }
1142
-
1143
-
1144
- protected async visitCase(caseNode: J.Case, p: P): Promise<J | undefined> {
1145
- const c = await super.visitCase(caseNode, p) as J.Case;
1146
-
1147
- if (c.guard && c.caseLabels.elements.length > 0 && c.caseLabels.elements[c.caseLabels.elements.length - 1].after.whitespace === "") {
1148
- return produce(c, draft => {
1149
- const last = draft.caseLabels.elements.length - 1;
1150
- draft.caseLabels.elements[last].after.whitespace = " ";
1151
- });
1152
- }
1153
-
1154
- return c;
1155
- }
1156
-
1157
- private ensureSpace(spaceDraft: Draft<J.Space>) {
1158
- if (spaceDraft.whitespace.length === 0 && spaceDraft.comments.length === 0) {
1159
- spaceDraft.whitespace = " ";
1160
- }
1161
- }
1162
- }
1163
-
1164
929
  export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1165
930
  constructor(private readonly style: BlankLinesStyle, private stopAfter?: Tree) {
1166
931
  super();
@@ -1363,3 +1128,6 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1363
1128
  return super.postVisit(tree, p);
1364
1129
  }
1365
1130
  }
1131
+
1132
+ // Re-export prettier formatting utilities
1133
+ export {prettierFormat} from "./prettier-format";
@@ -0,0 +1,21 @@
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
+ // Main formatting entry point (includes AutoformatVisitor, autoFormat, maybeAutoFormat, and individual visitors)
18
+ export * from "./format";
19
+
20
+ // Prettier formatting (public API)
21
+ export {prettierFormat} from "./prettier-format";
@@ -0,0 +1,256 @@
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
+ import {JavaScriptVisitor} from "../visitor";
17
+ import {J} from "../../java";
18
+ import {Cursor, isScope, Tree} from "../../tree";
19
+ import {JS} from "../tree";
20
+ import {Draft, produce} from "immer";
21
+ import {findMarker} from "../../markers";
22
+
23
+ /**
24
+ * Ensures minimum viable spacing between AST elements.
25
+ * Adds required spaces where they are missing (e.g., after keywords).
26
+ */
27
+ export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
28
+ constructor(private stopAfter?: Tree) {
29
+ super();
30
+ }
31
+
32
+ override async visit<R extends J>(tree: Tree, p: P, parent?: Cursor): Promise<R | undefined> {
33
+ if (this.cursor?.getNearestMessage("stop") != null) {
34
+ return tree as R;
35
+ }
36
+ return super.visit(tree, p, parent);
37
+ }
38
+
39
+ override async postVisit(tree: J, p: P): Promise<J | undefined> {
40
+ if (this.stopAfter != null && isScope(this.stopAfter, tree)) {
41
+ this.cursor?.root.messages.set("stop", true);
42
+ }
43
+ return super.postVisit(tree, p);
44
+ }
45
+
46
+ protected async visitAwait(await_: JS.Await, p: P): Promise<J | undefined> {
47
+ const ret = await super.visitAwait(await_, p) as JS.Await;
48
+ return produce(ret, draft => {
49
+ this.ensureSpace(draft.expression.prefix)
50
+ });
51
+ }
52
+
53
+ protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: P): Promise<J | undefined> {
54
+ let c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
55
+ let first = c.leadingAnnotations.length === 0;
56
+
57
+ if (c.modifiers.length > 0) {
58
+ if (!first && c.modifiers[0].prefix.whitespace === "") {
59
+ c = produce(c, draft => {
60
+ this.ensureSpace(draft.modifiers[0].prefix);
61
+ });
62
+ }
63
+ c = produce(c, draft => {
64
+ for (let i = 1; i < draft.modifiers.length; i++) {
65
+ this.ensureSpace(draft.modifiers[i].prefix);
66
+ }
67
+ });
68
+ first = false;
69
+ }
70
+
71
+ if (c.classKind.prefix.whitespace === "" && !first) {
72
+ c = produce(c, draft => {
73
+ this.ensureSpace(draft.classKind.prefix);
74
+ });
75
+ first = false;
76
+ }
77
+
78
+ // anonymous classes have an empty name
79
+ if (c.name.simpleName !== "") {
80
+ c = produce(c, draft => {
81
+ this.ensureSpace(draft.name.prefix);
82
+ });
83
+ }
84
+
85
+ // Note: typeParameters should NOT have space before them - they immediately follow the class name
86
+ // e.g., "class DataTable<Row>" not "class DataTable <Row>"
87
+
88
+ if (c.extends && c.extends.before.whitespace === "") {
89
+ c = produce(c, draft => {
90
+ this.ensureSpace(draft.extends!.before);
91
+ });
92
+ }
93
+
94
+ if (c.implements && c.implements.before.whitespace === "") {
95
+ c = produce(c, draft => {
96
+ this.ensureSpace(draft.implements!.before);
97
+ if (draft.implements != undefined && draft.implements.elements.length > 0) {
98
+ this.ensureSpace(draft.implements.elements[0].element.prefix);
99
+ }
100
+ });
101
+ }
102
+
103
+ c = produce(c, draft => {
104
+ draft.body.prefix.whitespace = "";
105
+ });
106
+
107
+ return c;
108
+ }
109
+
110
+ protected async visitMethodDeclaration(method: J.MethodDeclaration, p: P): Promise<J | undefined> {
111
+ let m = await super.visitMethodDeclaration(method, p) as J.MethodDeclaration;
112
+ let first = m.leadingAnnotations.length === 0;
113
+
114
+ if (m.modifiers.length > 0) {
115
+ if (!first && m.modifiers[0].prefix.whitespace === "") {
116
+ m = produce(m, draft => {
117
+ this.ensureSpace(draft.modifiers[0].prefix);
118
+ });
119
+ }
120
+ m = produce(m, draft => {
121
+ for (let i = 1; i < draft.modifiers.length; i++) {
122
+ this.ensureSpace(draft.modifiers[i].prefix);
123
+ }
124
+ });
125
+ first = false;
126
+ }
127
+
128
+ // FunctionDeclaration marker check must come AFTER modifiers processing
129
+ // to avoid adding unwanted space before the first modifier (e.g., 'async')
130
+ if (findMarker(method, JS.Markers.FunctionDeclaration)) {
131
+ first = false;
132
+ }
133
+
134
+ if (!first && m.name.prefix.whitespace === "") {
135
+ m = produce(m, draft => {
136
+ this.ensureSpace(draft.name.prefix);
137
+ });
138
+ }
139
+
140
+ if (m.throws && m.throws.before.whitespace === "") {
141
+ m = produce(m, draft => {
142
+ this.ensureSpace(draft.throws!.before);
143
+ });
144
+ }
145
+
146
+ return m;
147
+ }
148
+
149
+ protected async visitNamespaceDeclaration(namespaceDeclaration: JS.NamespaceDeclaration, p: P): Promise<J | undefined> {
150
+ const ret = await super.visitNamespaceDeclaration(namespaceDeclaration, p) as JS.NamespaceDeclaration;
151
+ return produce(ret, draft => {
152
+ if (draft.modifiers.length > 0) {
153
+ draft.keywordType.before.whitespace=" ";
154
+ }
155
+ this.ensureSpace(draft.name.element.prefix);
156
+ });
157
+ }
158
+
159
+ protected async visitNewClass(newClass: J.NewClass, p: P): Promise<J | undefined> {
160
+ const ret = await super.visitNewClass(newClass, p) as J.NewClass;
161
+ return produce(ret, draft => {
162
+ if (draft.class) {
163
+ if (draft.class.kind == J.Kind.Identifier) {
164
+ this.ensureSpace((draft.class as Draft<J.Identifier>).prefix);
165
+ }
166
+ }
167
+ });
168
+ }
169
+
170
+ protected async visitReturn(returnNode: J.Return, p: P): Promise<J | undefined> {
171
+ const r = await super.visitReturn(returnNode, p) as J.Return;
172
+ if (r.expression && r.expression.prefix.whitespace === "" &&
173
+ !r.markers.markers.find(m => m.id === "org.openrewrite.java.marker.ImplicitReturn")) {
174
+ return produce(r, draft => {
175
+ this.ensureSpace(draft.expression!.prefix);
176
+ });
177
+ }
178
+ return r;
179
+ }
180
+
181
+ protected async visitThrow(thrown: J.Throw, p: P): Promise<J | undefined> {
182
+ const ret = await super.visitThrow(thrown, p) as J.Throw;
183
+ return ret && produce(ret, draft => {
184
+ this.ensureSpace(draft.exception.prefix);
185
+ });
186
+ }
187
+
188
+ protected async visitTypeDeclaration(typeDeclaration: JS.TypeDeclaration, p: P): Promise<J | undefined> {
189
+ const ret = await super.visitTypeDeclaration(typeDeclaration, p) as JS.TypeDeclaration;
190
+ return produce(ret, draft => {
191
+ if (draft.modifiers.length > 0) {
192
+ this.ensureSpace(draft.name.before);
193
+ }
194
+ this.ensureSpace(draft.name.element.prefix);
195
+ });
196
+ }
197
+
198
+ protected async visitTypeOf(typeOf: JS.TypeOf, p: P): Promise<J | undefined> {
199
+ const ret = await super.visitTypeOf(typeOf, p) as JS.TypeOf;
200
+ return produce(ret, draft => {
201
+ this.ensureSpace(draft.expression.prefix);
202
+ });
203
+ }
204
+
205
+ protected async visitTypeParameter(typeParam: J.TypeParameter, p: P): Promise<J | undefined> {
206
+ const ret = await super.visitTypeParameter(typeParam, p) as J.TypeParameter;
207
+ return produce(ret, draft => {
208
+ if (draft.bounds && draft.bounds.elements.length > 0) {
209
+ this.ensureSpace(draft.bounds.before);
210
+ this.ensureSpace(draft.bounds.elements[0].element.prefix);
211
+ }
212
+ });
213
+ }
214
+
215
+ protected async visitVariableDeclarations(v: J.VariableDeclarations, p: P): Promise<J | undefined> {
216
+ let ret = await super.visitVariableDeclarations(v, p) as J.VariableDeclarations;
217
+ let first = ret.leadingAnnotations.length === 0;
218
+
219
+ if (first && ret.modifiers.length > 0) {
220
+ ret = produce(ret, draft => {
221
+ for (let i = 1; i < draft.modifiers.length; i++) {
222
+ this.ensureSpace(draft.modifiers[i].prefix);
223
+ }
224
+ });
225
+ first = false;
226
+ }
227
+
228
+ if (!first) {
229
+ ret = produce(ret, draft => {
230
+ this.ensureSpace(draft.variables[0].element.prefix);
231
+ });
232
+ }
233
+
234
+ return ret;
235
+ }
236
+
237
+
238
+ protected async visitCase(caseNode: J.Case, p: P): Promise<J | undefined> {
239
+ const c = await super.visitCase(caseNode, p) as J.Case;
240
+
241
+ if (c.guard && c.caseLabels.elements.length > 0 && c.caseLabels.elements[c.caseLabels.elements.length - 1].after.whitespace === "") {
242
+ return produce(c, draft => {
243
+ const last = draft.caseLabels.elements.length - 1;
244
+ draft.caseLabels.elements[last].after.whitespace = " ";
245
+ });
246
+ }
247
+
248
+ return c;
249
+ }
250
+
251
+ private ensureSpace(spaceDraft: Draft<J.Space>) {
252
+ if (spaceDraft.whitespace.length === 0 && spaceDraft.comments.length === 0) {
253
+ spaceDraft.whitespace = " ";
254
+ }
255
+ }
256
+ }
@@ -0,0 +1,42 @@
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
+ import {JavaScriptVisitor} from "../visitor";
17
+ import {J} from "../../java";
18
+ import {Cursor, isScope, Tree} from "../../tree";
19
+
20
+ /**
21
+ * Ensures that whitespace is on the outermost AST element possible.
22
+ * Called NormalizeFormat in Java.
23
+ */
24
+ export class NormalizeWhitespaceVisitor<P> extends JavaScriptVisitor<P> {
25
+ constructor(private stopAfter?: Tree) {
26
+ super();
27
+ }
28
+
29
+ override async visit<R extends J>(tree: Tree, p: P, parent?: Cursor): Promise<R | undefined> {
30
+ if (this.cursor?.getNearestMessage("stop") != null) {
31
+ return tree as R;
32
+ }
33
+ return super.visit(tree, p, parent);
34
+ }
35
+
36
+ override async postVisit(tree: J, p: P): Promise<J | undefined> {
37
+ if (this.stopAfter != null && isScope(this.stopAfter, tree)) {
38
+ this.cursor?.root.messages.set("stop", true);
39
+ }
40
+ return super.postVisit(tree, p);
41
+ }
42
+ }