@openrewrite/rewrite 8.69.0 → 8.69.1

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 (93) 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} +15 -33
  12. package/dist/javascript/format/format.d.ts.map +1 -0
  13. package/dist/javascript/{format.js → format/format.js} +56 -313
  14. package/dist/javascript/format/format.js.map +1 -0
  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 +5 -4
  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} +59 -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/format.js.map +0 -1
  92. package/dist/javascript/tabs-and-indents-visitor.d.ts.map +0 -1
  93. 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();
@@ -580,6 +577,30 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
580
577
  });
581
578
  }
582
579
 
580
+ protected async visitTypeParameter(typeParam: J.TypeParameter, p: P): Promise<J | undefined> {
581
+ const ret = await super.visitTypeParameter(typeParam, p) as J.TypeParameter;
582
+ return produce(ret, draft => {
583
+ if (draft.bounds && draft.bounds.elements.length >= 2) {
584
+ const constraintType = draft.bounds.elements[0];
585
+ const defaultType = draft.bounds.elements[1];
586
+ const hasConstraint = constraintType.element.kind !== J.Kind.Empty;
587
+ const hasDefault = defaultType.element.kind !== J.Kind.Empty;
588
+
589
+ if (hasConstraint) {
590
+ // Space before '=' for default type (in the `after` of constraint type)
591
+ if (hasDefault && !constraintType.after.whitespace.includes("\n")) {
592
+ constraintType.after.whitespace = this.style.aroundOperators.assignment ? " " : "";
593
+ }
594
+ } else if (hasDefault) {
595
+ // No constraint, just default: space before '=' is in bounds.before
596
+ if (!draft.bounds.before.whitespace.includes("\n")) {
597
+ draft.bounds.before.whitespace = this.style.aroundOperators.assignment ? " " : "";
598
+ }
599
+ }
600
+ }
601
+ });
602
+ }
603
+
583
604
  private async spaceAfterRightPadded<T extends J>(right: J.RightPadded<T>, spaceAfter: boolean): Promise<J.RightPadded<T>> {
584
605
  if (right.after.comments.length > 0) {
585
606
  // Perform the space rule for the suffix of the last comment only. Same as IntelliJ.
@@ -905,238 +926,6 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
905
926
  }
906
927
  }
907
928
 
908
-
909
- export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
910
- constructor(private stopAfter?: Tree) {
911
- super();
912
- }
913
-
914
- override async visit<R extends J>(tree: Tree, p: P, parent?: Cursor): Promise<R | undefined> {
915
- if (this.cursor?.getNearestMessage("stop") != null) {
916
- return tree as R;
917
- }
918
- return super.visit(tree, p, parent);
919
- }
920
-
921
- override async postVisit(tree: J, p: P): Promise<J | undefined> {
922
- if (this.stopAfter != null && isScope(this.stopAfter, tree)) {
923
- this.cursor?.root.messages.set("stop", true);
924
- }
925
- return super.postVisit(tree, p);
926
- }
927
-
928
- protected async visitAwait(await_: JS.Await, p: P): Promise<J | undefined> {
929
- const ret = await super.visitAwait(await_, p) as JS.Await;
930
- return produce(ret, draft => {
931
- this.ensureSpace(draft.expression.prefix)
932
- });
933
- }
934
-
935
- protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: P): Promise<J | undefined> {
936
- let c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
937
- let first = c.leadingAnnotations.length === 0;
938
-
939
- if (c.modifiers.length > 0) {
940
- if (!first && c.modifiers[0].prefix.whitespace === "") {
941
- c = produce(c, draft => {
942
- this.ensureSpace(draft.modifiers[0].prefix);
943
- });
944
- }
945
- c = produce(c, draft => {
946
- for (let i = 1; i < draft.modifiers.length; i++) {
947
- this.ensureSpace(draft.modifiers[i].prefix);
948
- }
949
- });
950
- first = false;
951
- }
952
-
953
- if (c.classKind.prefix.whitespace === "" && !first) {
954
- c = produce(c, draft => {
955
- this.ensureSpace(draft.classKind.prefix);
956
- });
957
- first = false;
958
- }
959
-
960
- // anonymous classes have an empty name
961
- if (c.name.simpleName !== "") {
962
- c = produce(c, draft => {
963
- this.ensureSpace(draft.name.prefix);
964
- });
965
- }
966
-
967
- // Note: typeParameters should NOT have space before them - they immediately follow the class name
968
- // e.g., "class DataTable<Row>" not "class DataTable <Row>"
969
-
970
- if (c.extends && c.extends.before.whitespace === "") {
971
- c = produce(c, draft => {
972
- this.ensureSpace(draft.extends!.before);
973
- });
974
- }
975
-
976
- if (c.implements && c.implements.before.whitespace === "") {
977
- c = produce(c, draft => {
978
- this.ensureSpace(draft.implements!.before);
979
- if (draft.implements != undefined && draft.implements.elements.length > 0) {
980
- this.ensureSpace(draft.implements.elements[0].element.prefix);
981
- }
982
- });
983
- }
984
-
985
- c = produce(c, draft => {
986
- draft.body.prefix.whitespace = "";
987
- });
988
-
989
- return c;
990
- }
991
-
992
- protected async visitMethodDeclaration(method: J.MethodDeclaration, p: P): Promise<J | undefined> {
993
- let m = await super.visitMethodDeclaration(method, p) as J.MethodDeclaration;
994
- let first = m.leadingAnnotations.length === 0;
995
-
996
- if (m.modifiers.length > 0) {
997
- if (!first && m.modifiers[0].prefix.whitespace === "") {
998
- m = produce(m, draft => {
999
- this.ensureSpace(draft.modifiers[0].prefix);
1000
- });
1001
- }
1002
- m = produce(m, draft => {
1003
- for (let i = 1; i < draft.modifiers.length; i++) {
1004
- this.ensureSpace(draft.modifiers[i].prefix);
1005
- }
1006
- });
1007
- first = false;
1008
- }
1009
-
1010
- // FunctionDeclaration marker check must come AFTER modifiers processing
1011
- // to avoid adding unwanted space before the first modifier (e.g., 'async')
1012
- if (findMarker(method, JS.Markers.FunctionDeclaration)) {
1013
- first = false;
1014
- }
1015
-
1016
- if (!first && m.name.prefix.whitespace === "") {
1017
- m = produce(m, draft => {
1018
- this.ensureSpace(draft.name.prefix);
1019
- });
1020
- }
1021
-
1022
- if (m.throws && m.throws.before.whitespace === "") {
1023
- m = produce(m, draft => {
1024
- this.ensureSpace(draft.throws!.before);
1025
- });
1026
- }
1027
-
1028
- return m;
1029
- }
1030
-
1031
- protected async visitNamespaceDeclaration(namespaceDeclaration: JS.NamespaceDeclaration, p: P): Promise<J | undefined> {
1032
- const ret = await super.visitNamespaceDeclaration(namespaceDeclaration, p) as JS.NamespaceDeclaration;
1033
- return produce(ret, draft => {
1034
- if (draft.modifiers.length > 0) {
1035
- draft.keywordType.before.whitespace=" ";
1036
- }
1037
- this.ensureSpace(draft.name.element.prefix);
1038
- });
1039
- }
1040
-
1041
- protected async visitNewClass(newClass: J.NewClass, p: P): Promise<J | undefined> {
1042
- const ret = await super.visitNewClass(newClass, p) as J.NewClass;
1043
- return produce(ret, draft => {
1044
- if (draft.class) {
1045
- if (draft.class.kind == J.Kind.Identifier) {
1046
- this.ensureSpace((draft.class as Draft<J.Identifier>).prefix);
1047
- }
1048
- }
1049
- });
1050
- }
1051
-
1052
- protected async visitReturn(returnNode: J.Return, p: P): Promise<J | undefined> {
1053
- const r = await super.visitReturn(returnNode, p) as J.Return;
1054
- if (r.expression && r.expression.prefix.whitespace === "" &&
1055
- !r.markers.markers.find(m => m.id === "org.openrewrite.java.marker.ImplicitReturn")) {
1056
- return produce(r, draft => {
1057
- this.ensureSpace(draft.expression!.prefix);
1058
- });
1059
- }
1060
- return r;
1061
- }
1062
-
1063
- protected async visitThrow(thrown: J.Throw, p: P): Promise<J | undefined> {
1064
- const ret = await super.visitThrow(thrown, p) as J.Throw;
1065
- return ret && produce(ret, draft => {
1066
- this.ensureSpace(draft.exception.prefix);
1067
- });
1068
- }
1069
-
1070
- protected async visitTypeDeclaration(typeDeclaration: JS.TypeDeclaration, p: P): Promise<J | undefined> {
1071
- const ret = await super.visitTypeDeclaration(typeDeclaration, p) as JS.TypeDeclaration;
1072
- return produce(ret, draft => {
1073
- if (draft.modifiers.length > 0) {
1074
- this.ensureSpace(draft.name.before);
1075
- }
1076
- this.ensureSpace(draft.name.element.prefix);
1077
- });
1078
- }
1079
-
1080
- protected async visitTypeOf(typeOf: JS.TypeOf, p: P): Promise<J | undefined> {
1081
- const ret = await super.visitTypeOf(typeOf, p) as JS.TypeOf;
1082
- return produce(ret, draft => {
1083
- this.ensureSpace(draft.expression.prefix);
1084
- });
1085
- }
1086
-
1087
- protected async visitTypeParameter(typeParam: J.TypeParameter, p: P): Promise<J | undefined> {
1088
- const ret = await super.visitTypeParameter(typeParam, p) as J.TypeParameter;
1089
- return produce(ret, draft => {
1090
- if (draft.bounds && draft.bounds.elements.length > 0) {
1091
- this.ensureSpace(draft.bounds.before);
1092
- this.ensureSpace(draft.bounds.elements[0].element.prefix);
1093
- }
1094
- });
1095
- }
1096
-
1097
- protected async visitVariableDeclarations(v: J.VariableDeclarations, p: P): Promise<J | undefined> {
1098
- let ret = await super.visitVariableDeclarations(v, p) as J.VariableDeclarations;
1099
- let first = ret.leadingAnnotations.length === 0;
1100
-
1101
- if (first && ret.modifiers.length > 0) {
1102
- ret = produce(ret, draft => {
1103
- for (let i = 1; i < draft.modifiers.length; i++) {
1104
- this.ensureSpace(draft.modifiers[i].prefix);
1105
- }
1106
- });
1107
- first = false;
1108
- }
1109
-
1110
- if (!first) {
1111
- ret = produce(ret, draft => {
1112
- this.ensureSpace(draft.variables[0].element.prefix);
1113
- });
1114
- }
1115
-
1116
- return ret;
1117
- }
1118
-
1119
-
1120
- protected async visitCase(caseNode: J.Case, p: P): Promise<J | undefined> {
1121
- const c = await super.visitCase(caseNode, p) as J.Case;
1122
-
1123
- if (c.guard && c.caseLabels.elements.length > 0 && c.caseLabels.elements[c.caseLabels.elements.length - 1].after.whitespace === "") {
1124
- return produce(c, draft => {
1125
- const last = draft.caseLabels.elements.length - 1;
1126
- draft.caseLabels.elements[last].after.whitespace = " ";
1127
- });
1128
- }
1129
-
1130
- return c;
1131
- }
1132
-
1133
- private ensureSpace(spaceDraft: Draft<J.Space>) {
1134
- if (spaceDraft.whitespace.length === 0 && spaceDraft.comments.length === 0) {
1135
- spaceDraft.whitespace = " ";
1136
- }
1137
- }
1138
- }
1139
-
1140
929
  export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1141
930
  constructor(private readonly style: BlankLinesStyle, private stopAfter?: Tree) {
1142
931
  super();
@@ -1339,3 +1128,6 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1339
1128
  return super.postVisit(tree, p);
1340
1129
  }
1341
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
+ }