@openrewrite/rewrite 8.69.0-20251209-122812 → 8.69.0-20251209-133839

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.
@@ -5,14 +5,16 @@ import { TabsAndIndentsStyle } from "./style";
5
5
  export declare class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
6
6
  private readonly tabsAndIndentsStyle;
7
7
  private stopAfter?;
8
- private readonly singleIndent;
8
+ private readonly indentSize;
9
+ private readonly useTabCharacter;
9
10
  constructor(tabsAndIndentsStyle: TabsAndIndentsStyle, stopAfter?: Tree | undefined);
11
+ private indentString;
10
12
  protected preVisit(tree: J, _p: P): Promise<J | undefined>;
11
13
  private setupCursorMessagesForTree;
12
14
  private getParentIndentContext;
13
15
  private computeMyIndent;
14
16
  private prefixContainsNewline;
15
- private isNestedJsxTag;
17
+ private isJsxChildElement;
16
18
  private computeIndentKind;
17
19
  postVisit(tree: J, _p: P): Promise<J | undefined>;
18
20
  private isInsideJsxTag;
@@ -27,7 +29,6 @@ export declare class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
27
29
  private postVisitLeftPadded;
28
30
  visitRightPadded<T extends J | boolean>(right: J.RightPadded<T>, p: P): Promise<J.RightPadded<T> | undefined>;
29
31
  private preVisitRightPadded;
30
- private elementPrefixContainsNewline;
31
32
  /**
32
33
  * Check if an element is a spread - either directly marked with Spread marker,
33
34
  * or a PropertyAssignment whose name has a Spread marker (for object spreads).
@@ -1 +1 @@
1
- {"version":3,"file":"tabs-and-indents-visitor.d.ts","sourceRoot":"","sources":["../../src/javascript/tabs-and-indents-visitor.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,iBAAiB,EAAC,MAAM,WAAW,CAAC;AAC5C,OAAO,EAGH,CAAC,EAOJ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAC,MAAM,EAAmB,IAAI,EAAC,MAAM,SAAS,CAAC;AAGtD,OAAO,EAAC,mBAAmB,EAAC,MAAM,SAAS,CAAC;AAI5C,qBAAa,qBAAqB,CAAC,CAAC,CAAE,SAAQ,iBAAiB,CAAC,CAAC,CAAC;IAGlD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAAuB,OAAO,CAAC,SAAS,CAAC;IAFzF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAET,mBAAmB,EAAE,mBAAmB,EAAU,SAAS,CAAC,EAAE,IAAI,YAAA;cAU/E,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKhE,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,sBAAsB;IA8B9B,OAAO,CAAC,eAAe;IAmCvB,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAaV,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA2ChE,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,uBAAuB;IAwD/B,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,iBAAiB;IAUZ,cAAc,CAAC,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAuBlG,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,kBAAkB;IAsCb,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC1E,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EACrB,CAAC,EAAE,CAAC,GACL,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IA2BvC,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,mBAAmB;IAmBd,gBAAgB,CAAC,CAAC,SAAS,CAAC,GAAG,OAAO,EAC/C,KAAK,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EACvB,CAAC,EAAE,CAAC,GACL,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAyBxC,OAAO,CAAC,mBAAmB;IA0D3B,OAAO,CAAC,4BAA4B;IAapC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgBjB,KAAK,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAanF,OAAO,CAAC,oBAAoB;IA8C5B,OAAO,CAAC,aAAa;CAMxB"}
1
+ {"version":3,"file":"tabs-and-indents-visitor.d.ts","sourceRoot":"","sources":["../../src/javascript/tabs-and-indents-visitor.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,iBAAiB,EAAC,MAAM,WAAW,CAAC;AAC5C,OAAO,EAGH,CAAC,EAOJ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAC,MAAM,EAAmB,IAAI,EAAC,MAAM,SAAS,CAAC;AAGtD,OAAO,EAAC,mBAAmB,EAAC,MAAM,SAAS,CAAC;AAK5C,qBAAa,qBAAqB,CAAC,CAAC,CAAE,SAAQ,iBAAiB,CAAC,CAAC,CAAC;IAIlD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAAuB,OAAO,CAAC,SAAS,CAAC;IAHzF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;gBAEb,mBAAmB,EAAE,mBAAmB,EAAU,SAAS,CAAC,EAAE,IAAI,YAAA;IAM/F,OAAO,CAAC,YAAY;cAOJ,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKhE,OAAO,CAAC,0BAA0B;IA8DlC,OAAO,CAAC,sBAAsB;IAmB9B,OAAO,CAAC,eAAe;IA0CvB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,iBAAiB;IAeV,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAkEhE,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,uBAAuB;IAyD/B,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,iBAAiB;IAUZ,cAAc,CAAC,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAuBlG,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,kBAAkB;IAmBb,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC1E,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EACrB,CAAC,EAAE,CAAC,GACL,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IA2BvC,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,mBAAmB;IAoBd,gBAAgB,CAAC,CAAC,SAAS,CAAC,GAAG,OAAO,EAC/C,KAAK,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EACvB,CAAC,EAAE,CAAC,GACL,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAyBxC,OAAO,CAAC,mBAAmB;IAyC3B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgBjB,KAAK,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAanF,OAAO,CAAC,oBAAoB;IA6C5B,OAAO,CAAC,aAAa;CAMxB"}
@@ -38,12 +38,14 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
38
38
  super();
39
39
  this.tabsAndIndentsStyle = tabsAndIndentsStyle;
40
40
  this.stopAfter = stopAfter;
41
- if (this.tabsAndIndentsStyle.useTabCharacter) {
42
- this.singleIndent = "\t";
43
- }
44
- else {
45
- this.singleIndent = " ".repeat(this.tabsAndIndentsStyle.indentSize);
41
+ this.indentSize = this.tabsAndIndentsStyle.indentSize;
42
+ this.useTabCharacter = this.tabsAndIndentsStyle.useTabCharacter;
43
+ }
44
+ indentString(indent) {
45
+ if (this.useTabCharacter) {
46
+ return "\t".repeat(Math.floor(indent / this.indentSize));
46
47
  }
48
+ return " ".repeat(indent);
47
49
  }
48
50
  preVisit(tree, _p) {
49
51
  return __awaiter(this, void 0, void 0, function* () {
@@ -52,52 +54,85 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
52
54
  });
53
55
  }
54
56
  setupCursorMessagesForTree(cursor, tree) {
55
- var _a, _b, _c, _d;
57
+ var _a, _b, _c;
58
+ // Check if this MethodInvocation starts a method chain (select.after has newline)
59
+ if (tree.kind === java_1.J.Kind.MethodInvocation) {
60
+ const mi = tree;
61
+ if (mi.select && mi.select.after.whitespace.includes("\n")) {
62
+ // This MethodInvocation has a chained method call after it
63
+ // Store the ORIGINAL parent indent context in "chainedIndentContext"
64
+ // This will be propagated down and used when we reach the chain's innermost element
65
+ const parentContext = this.getParentIndentContext(cursor);
66
+ cursor.messages.set("chainedIndentContext", parentContext);
67
+ // For children (arguments), use continuation indent
68
+ // But the prefix will be normalized in postVisit using chainedIndentContext
69
+ const [parentIndent, parentIndentKind] = parentContext;
70
+ cursor.messages.set("indentContext", [parentIndent + this.indentSize, parentIndentKind]);
71
+ return;
72
+ }
73
+ // Check if we're at the base of a chain (no select) and parent has chainedIndentContext
74
+ if (!mi.select) {
75
+ const chainedContext = (_a = cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("chainedIndentContext");
76
+ if (chainedContext !== undefined) {
77
+ // Consume the chainedIndentContext - this is the base of the chain
78
+ // The base element gets the original indent (no extra continuation)
79
+ // Propagate chainedIndentContext so the `name` child knows not to add indent
80
+ cursor.messages.set("indentContext", chainedContext);
81
+ cursor.messages.set("chainedIndentContext", chainedContext);
82
+ return;
83
+ }
84
+ }
85
+ }
86
+ // Check if we're the `name` of a MethodInvocation at the base of a chain
87
+ if (tree.kind === java_1.J.Kind.Identifier) {
88
+ const parentValue = (_b = cursor.parent) === null || _b === void 0 ? void 0 : _b.value;
89
+ if ((parentValue === null || parentValue === void 0 ? void 0 : parentValue.kind) === java_1.J.Kind.MethodInvocation) {
90
+ const parentMi = parentValue;
91
+ // Check if parent has chainedIndentContext (meaning it's at the base of a chain)
92
+ // and we are the `name` (not an argument or something else)
93
+ const parentChainedContext = (_c = cursor.parent) === null || _c === void 0 ? void 0 : _c.messages.get("chainedIndentContext");
94
+ if (parentChainedContext !== undefined && !parentMi.select) {
95
+ // We're the name of a chain-base MethodInvocation - use the chained indent directly
96
+ cursor.messages.set("indentContext", parentChainedContext);
97
+ return;
98
+ }
99
+ }
100
+ }
56
101
  const [parentMyIndent, parentIndentKind] = this.getParentIndentContext(cursor);
57
102
  const myIndent = this.computeMyIndent(tree, parentMyIndent, parentIndentKind);
58
- cursor.messages.set("myIndent", myIndent);
59
103
  // For Binary, behavior depends on whether it's already on a continuation line
104
+ let indentKind;
60
105
  if (tree.kind === java_1.J.Kind.Binary) {
61
- const hasNewline = ((_b = (_a = tree.prefix) === null || _a === void 0 ? void 0 : _a.whitespace) === null || _b === void 0 ? void 0 : _b.includes("\n")) ||
62
- ((_d = (_c = tree.prefix) === null || _c === void 0 ? void 0 : _c.comments) === null || _d === void 0 ? void 0 : _d.some(c => c.suffix.includes("\n")));
63
106
  // If Binary has newline OR parent is in align mode, children align
64
- const shouldAlign = hasNewline || parentIndentKind === 'align';
65
- cursor.messages.set("indentKind", shouldAlign ? 'align' : 'continuation');
107
+ const hasNewline = this.prefixContainsNewline(tree);
108
+ indentKind = (hasNewline || parentIndentKind === 'align') ? 'align' : 'continuation';
66
109
  }
67
110
  else {
68
- cursor.messages.set("indentKind", this.computeIndentKind(tree));
111
+ indentKind = this.computeIndentKind(tree);
69
112
  }
113
+ cursor.messages.set("indentContext", [myIndent, indentKind]);
70
114
  }
71
115
  getParentIndentContext(cursor) {
72
- // Find the nearest myIndent and the nearest indentKind separately
73
- // Container/RightPadded/LeftPadded inherit myIndent but don't have indentKind
74
- let parentIndent;
75
- let parentKind;
116
+ // Walk up the cursor chain to find the nearest indent context
117
+ // We need to walk because intermediate nodes like RightPadded may not have context set
76
118
  for (let c = cursor.parent; c != null; c = c.parent) {
77
- if (parentIndent === undefined) {
78
- const indent = c.messages.get("myIndent");
79
- if (indent !== undefined) {
80
- parentIndent = indent;
81
- }
82
- }
83
- if (parentKind === undefined) {
84
- const kind = c.messages.get("indentKind");
85
- if (kind !== undefined) {
86
- parentKind = kind;
87
- }
119
+ // chainedIndentContext stores the original context - prefer it
120
+ const chainedContext = c.messages.get("chainedIndentContext");
121
+ if (chainedContext !== undefined) {
122
+ return chainedContext;
88
123
  }
89
- // Found both, we can stop
90
- if (parentIndent !== undefined && parentKind !== undefined) {
91
- break;
124
+ const context = c.messages.get("indentContext");
125
+ if (context !== undefined) {
126
+ return context;
92
127
  }
93
128
  }
94
- return [parentIndent !== null && parentIndent !== void 0 ? parentIndent : "", parentKind !== null && parentKind !== void 0 ? parentKind : 'continuation'];
129
+ return [0, 'continuation'];
95
130
  }
96
131
  computeMyIndent(tree, parentMyIndent, parentIndentKind) {
97
132
  var _a, _b, _c;
98
- // CompilationUnit is the root - it always has myIndent="" regardless of prefix content
133
+ // CompilationUnit is the root - it always has myIndent=0 regardless of prefix content
99
134
  if (tree.kind === tree_1.JS.Kind.CompilationUnit) {
100
- return "";
135
+ return 0;
101
136
  }
102
137
  // TemplateExpressionSpan: reset indent context - template literal content determines its own indentation
103
138
  // The expression inside ${...} should be indented based on where it appears in the template, not outer code
@@ -107,44 +142,52 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
107
142
  const prefix = (_c = (_b = (_a = span.expression) === null || _a === void 0 ? void 0 : _a.prefix) === null || _b === void 0 ? void 0 : _b.whitespace) !== null && _c !== void 0 ? _c : "";
108
143
  const lastNewline = prefix.lastIndexOf("\n");
109
144
  if (lastNewline >= 0) {
110
- return prefix.slice(lastNewline + 1);
145
+ return prefix.length - lastNewline - 1;
111
146
  }
112
- return "";
147
+ return 0;
113
148
  }
114
149
  if (tree.kind === java_1.J.Kind.IfElse || parentIndentKind === 'align') {
115
150
  return parentMyIndent;
116
151
  }
152
+ // Certain structures don't add indent for themselves - they stay at parent level
153
+ // - TryCatch: catch clause is part of try statement
154
+ // - TypeLiteral: { members } in type definitions
155
+ // - EnumValueSet: enum members get same indent as the set itself
156
+ if (tree.kind === java_1.J.Kind.TryCatch || tree.kind === tree_1.JS.Kind.TypeLiteral || tree.kind === java_1.J.Kind.EnumValueSet) {
157
+ return parentMyIndent;
158
+ }
117
159
  // Only add indent if this element starts on a new line
118
160
  // Check both the element's prefix and any Spread marker's prefix
119
161
  const hasNewline = this.prefixContainsNewline(tree);
120
162
  if (!hasNewline) {
121
163
  // Special case for JSX: children of JsxTag don't have newlines in their prefix
122
- // (newlines are in text Literal nodes), but nested tags should still get block indent
123
- if (this.isNestedJsxTag(tree)) {
124
- return parentMyIndent + this.singleIndent;
164
+ // (newlines are in text Literal nodes), but JSX children should still get block indent
165
+ if (this.isJsxChildElement(tree)) {
166
+ return parentMyIndent + this.indentSize;
125
167
  }
126
168
  return parentMyIndent;
127
169
  }
128
170
  // Add indent for block children or continuation
129
- return parentMyIndent + this.singleIndent;
171
+ return parentMyIndent + this.indentSize;
130
172
  }
131
173
  prefixContainsNewline(tree) {
132
- var _a, _b, _c, _d, _e, _f;
133
- // Check the element's own prefix
134
- if (((_b = (_a = tree.prefix) === null || _a === void 0 ? void 0 : _a.whitespace) === null || _b === void 0 ? void 0 : _b.includes("\n")) ||
135
- ((_d = (_c = tree.prefix) === null || _c === void 0 ? void 0 : _c.comments) === null || _d === void 0 ? void 0 : _d.some(c => c.suffix.includes("\n")))) {
174
+ var _a, _b;
175
+ // Check if the element starts on a new line (only the last whitespace matters)
176
+ if (tree.prefix && (0, java_1.lastWhitespace)(tree.prefix).includes("\n")) {
136
177
  return true;
137
178
  }
138
179
  // For elements with Spread marker, check the Spread marker's prefix
139
- const spreadMarker = (_f = (_e = tree.markers) === null || _e === void 0 ? void 0 : _e.markers) === null || _f === void 0 ? void 0 : _f.find(m => m.kind === tree_1.JS.Markers.Spread);
180
+ const spreadMarker = (_b = (_a = tree.markers) === null || _a === void 0 ? void 0 : _a.markers) === null || _b === void 0 ? void 0 : _b.find(m => m.kind === tree_1.JS.Markers.Spread);
140
181
  if (spreadMarker && (0, java_1.spaceContainsNewline)(spreadMarker.prefix)) {
141
182
  return true;
142
183
  }
143
184
  return false;
144
185
  }
145
- isNestedJsxTag(tree) {
146
- // Check if this is a JsxTag whose parent is also a JsxTag
147
- if (tree.kind !== tree_1.JS.Kind.JsxTag) {
186
+ isJsxChildElement(tree) {
187
+ // Check if this is a JSX child element whose parent is a JsxTag
188
+ // JSX children (JsxTag, JsxEmbeddedExpression) don't have newlines in their own prefix
189
+ // (newlines are in text Literal nodes), but they should still get block indent
190
+ if (tree.kind !== tree_1.JS.Kind.JsxTag && tree.kind !== tree_1.JS.Kind.JsxEmbeddedExpression) {
148
191
  return false;
149
192
  }
150
193
  const parentTree = this.cursor.parentTree();
@@ -155,6 +198,8 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
155
198
  case java_1.J.Kind.Block:
156
199
  case java_1.J.Kind.Case:
157
200
  case tree_1.JS.Kind.JsxTag:
201
+ case tree_1.JS.Kind.TypeLiteral:
202
+ case java_1.J.Kind.EnumValueSet:
158
203
  return 'block';
159
204
  case tree_1.JS.Kind.CompilationUnit:
160
205
  return 'align';
@@ -164,19 +209,40 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
164
209
  }
165
210
  postVisit(tree, _p) {
166
211
  return __awaiter(this, void 0, void 0, function* () {
167
- var _a, _b, _c;
212
+ var _a, _b, _c, _d, _e;
168
213
  if (this.stopAfter != null && (0, tree_2.isScope)(this.stopAfter, tree)) {
169
214
  (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.root.messages.set("stop", true);
170
215
  }
171
- const myIndent = this.cursor.messages.get("myIndent");
172
- if (myIndent === undefined) {
216
+ // If parent has chainedIndentContext but no indentContext yet, we're exiting a chain element
217
+ // Set indentContext on parent = [chainedIndent + indentSize, chainedIndentKind]
218
+ // This only happens once at the innermost element; subsequent parents will already have indentContext
219
+ const parentChainedContext = (_b = this.cursor.parent) === null || _b === void 0 ? void 0 : _b.messages.get("chainedIndentContext");
220
+ const parentHasIndentContext = (_c = this.cursor.parent) === null || _c === void 0 ? void 0 : _c.messages.has("indentContext");
221
+ if (parentChainedContext !== undefined && !parentHasIndentContext) {
222
+ const [chainedIndent, chainedIndentKind] = parentChainedContext;
223
+ this.cursor.parent.messages.set("indentContext", [chainedIndent + this.indentSize, chainedIndentKind]);
224
+ }
225
+ const indentContext = this.cursor.messages.get("indentContext");
226
+ if (indentContext === undefined) {
173
227
  return tree;
174
228
  }
229
+ let [myIndent] = indentContext;
230
+ // For chain-start MethodInvocations, the prefix contains whitespace before the chain BASE
231
+ // Use chainedIndentContext (the original indent) for the prefix, not the continuation indent
232
+ const chainedContext = this.cursor.messages.get("chainedIndentContext");
233
+ if (chainedContext !== undefined && tree.kind === java_1.J.Kind.MethodInvocation) {
234
+ const mi = tree;
235
+ if (mi.select && mi.select.after.whitespace.includes("\n")) {
236
+ // This is a chain-start - use original indent for prefix normalization
237
+ myIndent = chainedContext[0];
238
+ }
239
+ }
175
240
  let result = tree;
241
+ const indentStr = this.indentString(myIndent);
176
242
  // Check if the element has a Spread marker - if so, normalize its prefix instead
177
- const spreadMarker = (_c = (_b = result.markers) === null || _b === void 0 ? void 0 : _b.markers) === null || _c === void 0 ? void 0 : _c.find(m => m.kind === tree_1.JS.Markers.Spread);
243
+ const spreadMarker = (_e = (_d = result.markers) === null || _d === void 0 ? void 0 : _d.markers) === null || _e === void 0 ? void 0 : _e.find(m => m.kind === tree_1.JS.Markers.Spread);
178
244
  if (spreadMarker && (0, java_1.spaceContainsNewline)(spreadMarker.prefix)) {
179
- const normalizedPrefix = (0, java_1.normalizeSpaceIndent)(spreadMarker.prefix, myIndent);
245
+ const normalizedPrefix = (0, java_1.normalizeSpaceIndent)(spreadMarker.prefix, indentStr);
180
246
  if (normalizedPrefix !== spreadMarker.prefix) {
181
247
  result = (0, immer_1.produce)(result, draft => {
182
248
  const spreadIdx = draft.markers.markers.findIndex(m => m.kind === tree_1.JS.Markers.Spread);
@@ -188,7 +254,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
188
254
  }
189
255
  else if (result.prefix && (0, java_1.spaceContainsNewline)(result.prefix)) {
190
256
  // Normalize the entire prefix space including comment suffixes
191
- const normalizedPrefix = (0, java_1.normalizeSpaceIndent)(result.prefix, myIndent);
257
+ const normalizedPrefix = (0, java_1.normalizeSpaceIndent)(result.prefix, indentStr);
192
258
  if (normalizedPrefix !== result.prefix) {
193
259
  result = (0, immer_1.produce)(result, draft => {
194
260
  draft.prefix = normalizedPrefix;
@@ -214,13 +280,14 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
214
280
  }
215
281
  // Check if this literal is the last child of a JsxTag - if so, its trailing whitespace
216
282
  // should use the parent tag's indent, not the content indent
217
- const parentIndent = this.cursor.parentTree().messages.get("myIndent");
283
+ const parentContext = this.cursor.parentTree().messages.get("indentContext");
284
+ const parentIndent = parentContext === null || parentContext === void 0 ? void 0 : parentContext[0];
218
285
  const isLastChild = parentIndent !== undefined && this.isLastChildOfJsxTag(literal);
219
286
  // For JSX text content, the newline is in the value, not the prefix.
220
287
  // Since the content IS effectively on a new line, it should get block child indent.
221
288
  // myIndent is the parent's indent (because Literal prefix has no newline),
222
- // so we need to add singleIndent for content lines.
223
- const contentIndent = myIndent + this.singleIndent;
289
+ // so we need to add indentSize for content lines.
290
+ const contentIndent = myIndent + this.indentSize;
224
291
  // Split by newlines and normalize each line's indentation
225
292
  const lines = literal.valueSource.split('\n');
226
293
  const result = [];
@@ -235,7 +302,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
235
302
  // Line has only whitespace (or is empty)
236
303
  if (isLastChild && i === lines.length - 1) {
237
304
  // Trailing whitespace of last child - use parent indent for closing tag alignment
238
- result.push(parentIndent);
305
+ result.push(this.indentString(parentIndent));
239
306
  }
240
307
  else if (i < lines.length - 1) {
241
308
  // Empty line in the middle (followed by more lines) - keep empty
@@ -243,12 +310,12 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
243
310
  }
244
311
  else {
245
312
  // Trailing whitespace of non-last-child - add content indent
246
- result.push(contentIndent);
313
+ result.push(this.indentString(contentIndent));
247
314
  }
248
315
  }
249
316
  else {
250
317
  // Line has content - add proper indent
251
- result.push(contentIndent + content);
318
+ result.push(this.indentString(contentIndent) + content);
252
319
  }
253
320
  }
254
321
  const normalizedValueSource = result.join('\n');
@@ -277,7 +344,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
277
344
  return block;
278
345
  }
279
346
  return (0, immer_1.produce)(block, draft => {
280
- draft.end = (0, java_1.replaceLastWhitespace)(draft.end, ws => (0, java_1.replaceIndentAfterLastNewline)(ws, myIndent));
347
+ draft.end = (0, java_1.replaceLastWhitespace)(draft.end, ws => (0, java_1.replaceIndentAfterLastNewline)(ws, this.indentString(myIndent)));
281
348
  });
282
349
  }
283
350
  visitContainer(container, p) {
@@ -285,7 +352,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
285
352
  // Create cursor for this container
286
353
  this.cursor = new tree_2.Cursor(container, this.cursor);
287
354
  // Pre-visit hook: set up cursor messages
288
- this.preVisitContainer(container);
355
+ this.preVisitContainer();
289
356
  // Visit children (similar to base visitor but without cursor management)
290
357
  let ret = (yield (0, visitor_2.produceAsync)(container, (draft) => __awaiter(this, void 0, void 0, function* () {
291
358
  draft.before = yield this.visitSpace(container.before, p);
@@ -299,56 +366,16 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
299
366
  return ret;
300
367
  });
301
368
  }
302
- preVisitContainer(container) {
303
- var _a, _b, _c, _d;
304
- let myIndent = (_b = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("myIndent")) !== null && _b !== void 0 ? _b : "";
305
- // Check if we're in a method chain - use chainedIndent if available
306
- // This ensures arguments inside method chains like `.select(arg)` inherit the chain's indent level
307
- // BUT stop at scope boundaries:
308
- // 1. Blocks - nested code inside callbacks should NOT use outer chainedIndent
309
- // 2. Other Containers - nested function call arguments should NOT use outer chainedIndent
310
- for (let c = this.cursor.parent; c; c = c.parent) {
311
- // Stop searching if we hit a Block (function body, arrow function body, etc.)
312
- // This prevents chainedIndent from leaking into nested scopes
313
- if (((_c = c.value) === null || _c === void 0 ? void 0 : _c.kind) === java_1.J.Kind.Block) {
314
- break;
315
- }
316
- // Stop searching if we hit another Container (arguments of another function call)
317
- // This prevents chainedIndent from leaking into nested function calls
318
- if (((_d = c.value) === null || _d === void 0 ? void 0 : _d.kind) === java_1.J.Kind.Container) {
319
- break;
320
- }
321
- const chainedIndent = c.messages.get("chainedIndent");
322
- if (chainedIndent !== undefined) {
323
- myIndent = chainedIndent;
324
- break;
325
- }
326
- }
327
- this.cursor.messages.set("myIndent", myIndent);
328
- this.cursor.messages.set("indentKind", 'continuation');
369
+ preVisitContainer() {
370
+ var _a;
371
+ const parentContext = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("indentContext");
372
+ const [myIndent] = parentContext !== null && parentContext !== void 0 ? parentContext : [0, 'continuation'];
373
+ this.cursor.messages.set("indentContext", [myIndent, 'continuation']);
329
374
  }
330
375
  postVisitContainer(container) {
331
- var _a, _b, _c, _d;
332
- let parentIndent = (_b = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("myIndent")) !== null && _b !== void 0 ? _b : "";
333
- // Check for chainedIndent for closing delimiter alignment in method chains
334
- // BUT stop at scope boundaries:
335
- // 1. Blocks - nested code inside callbacks should NOT use outer chainedIndent
336
- // 2. Other Containers - nested function call arguments should NOT use outer chainedIndent
337
- for (let c = this.cursor.parent; c; c = c.parent) {
338
- // Stop searching if we hit a Block (function body, arrow function body, etc.)
339
- if (((_c = c.value) === null || _c === void 0 ? void 0 : _c.kind) === java_1.J.Kind.Block) {
340
- break;
341
- }
342
- // Stop searching if we hit another Container (arguments of another function call)
343
- if (((_d = c.value) === null || _d === void 0 ? void 0 : _d.kind) === java_1.J.Kind.Container) {
344
- break;
345
- }
346
- const chainedIndent = c.messages.get("chainedIndent");
347
- if (chainedIndent !== undefined) {
348
- parentIndent = chainedIndent;
349
- break;
350
- }
351
- }
376
+ var _a;
377
+ const parentContext = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("indentContext");
378
+ const [parentIndent] = parentContext !== null && parentContext !== void 0 ? parentContext : [0, 'continuation'];
352
379
  // Normalize the last element's after whitespace (closing delimiter like `)`)
353
380
  // The closing delimiter should align with the parent's indent level
354
381
  if (container.elements.length > 0) {
@@ -356,7 +383,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
356
383
  if (effectiveLastWs.includes("\n")) {
357
384
  return (0, immer_1.produce)(container, draft => {
358
385
  const lastDraft = draft.elements[draft.elements.length - 1];
359
- lastDraft.after = (0, java_1.replaceLastWhitespace)(lastDraft.after, ws => (0, java_1.replaceIndentAfterLastNewline)(ws, parentIndent));
386
+ lastDraft.after = (0, java_1.replaceLastWhitespace)(lastDraft.after, ws => (0, java_1.replaceIndentAfterLastNewline)(ws, this.indentString(parentIndent)));
360
387
  });
361
388
  }
362
389
  }
@@ -387,26 +414,23 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
387
414
  });
388
415
  }
389
416
  preVisitLeftPadded(left) {
390
- var _a, _b, _c, _d;
391
- // Get parent indent from parent cursor
392
- const parentIndent = (_b = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("myIndent")) !== null && _b !== void 0 ? _b : "";
417
+ var _a, _b;
418
+ const parentContext = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("indentContext");
419
+ const [parentIndent, parentIndentKind] = parentContext !== null && parentContext !== void 0 ? parentContext : [0, 'continuation'];
393
420
  const hasNewline = left.before.whitespace.includes("\n");
394
421
  // Check if parent is a Binary in align mode - if so, don't add continuation indent
395
- // (Binary sets indentKind='align' when it's already on a continuation line)
396
- const parentValue = (_c = this.cursor.parent) === null || _c === void 0 ? void 0 : _c.value;
397
- const parentIndentKind = (_d = this.cursor.parent) === null || _d === void 0 ? void 0 : _d.messages.get("indentKind");
422
+ const parentValue = (_b = this.cursor.parent) === null || _b === void 0 ? void 0 : _b.value;
398
423
  const shouldAlign = (parentValue === null || parentValue === void 0 ? void 0 : parentValue.kind) === java_1.J.Kind.Binary && parentIndentKind === 'align';
399
424
  // Compute myIndent INCLUDING continuation if applicable
400
- // This ensures child elements see the correct parent indent
401
425
  let myIndent = parentIndent;
402
426
  if (hasNewline && !shouldAlign) {
403
- myIndent = parentIndent + this.singleIndent;
427
+ myIndent = parentIndent + this.indentSize;
404
428
  }
405
- this.cursor.messages.set("myIndent", myIndent);
429
+ this.cursor.messages.set("indentContext", [myIndent, 'continuation']);
406
430
  this.cursor.messages.set("hasNewline", hasNewline);
407
431
  }
408
432
  postVisitLeftPadded(left) {
409
- var _a, _b;
433
+ var _a;
410
434
  if (left === undefined) {
411
435
  return undefined;
412
436
  }
@@ -414,10 +438,11 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
414
438
  if (!hasNewline) {
415
439
  return left;
416
440
  }
417
- // Use the myIndent we computed in preVisitLeftPadded (which includes continuation if applicable)
418
- const targetIndent = (_b = this.cursor.messages.get("myIndent")) !== null && _b !== void 0 ? _b : "";
441
+ // Use the indent we computed in preVisitLeftPadded (which includes continuation if applicable)
442
+ const context = this.cursor.messages.get("indentContext");
443
+ const [targetIndent] = context !== null && context !== void 0 ? context : [0, 'continuation'];
419
444
  return (0, immer_1.produce)(left, draft => {
420
- draft.before.whitespace = (0, java_1.replaceIndentAfterLastNewline)(draft.before.whitespace, targetIndent);
445
+ draft.before.whitespace = (0, java_1.replaceIndentAfterLastNewline)(draft.before.whitespace, this.indentString(targetIndent));
421
446
  });
422
447
  }
423
448
  visitRightPadded(right, p) {
@@ -443,74 +468,42 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
443
468
  });
444
469
  }
445
470
  preVisitRightPadded(right) {
446
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
447
- // Get parent indent from parent cursor
448
- const parentIndent = (_b = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("myIndent")) !== null && _b !== void 0 ? _b : "";
449
- const parentIndentKind = (_d = (_c = this.cursor.parent) === null || _c === void 0 ? void 0 : _c.messages.get("indentKind")) !== null && _d !== void 0 ? _d : "";
450
- // Check if the `after` has a newline (e.g., in method chains like `db\n .from()`)
451
- const hasNewline = right.after.whitespace.includes("\n");
452
- // Only apply chainedIndent logic for method chains (when parent is a MethodInvocation)
453
- const parentKind = (_f = (_e = this.cursor.parent) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.kind;
454
- const isMethodChain = parentKind === java_1.J.Kind.MethodInvocation;
455
- let myIndent = parentIndent;
456
- if (hasNewline && isMethodChain) {
457
- // Search up the cursor hierarchy for an existing chainedIndent (to avoid stacking in method chains)
458
- let existingChainedIndent;
459
- for (let c = this.cursor.parent; c; c = c.parent) {
460
- existingChainedIndent = c.messages.get("chainedIndent");
461
- if (existingChainedIndent !== undefined) {
462
- break;
463
- }
464
- }
465
- if (existingChainedIndent === undefined) {
466
- myIndent = parentIndent + this.singleIndent;
467
- // Set chainedIndent on parent so further chain elements don't stack
468
- (_g = this.cursor.parent) === null || _g === void 0 ? void 0 : _g.messages.set("chainedIndent", myIndent);
469
- }
470
- else {
471
- myIndent = existingChainedIndent;
472
- }
471
+ var _a, _b, _c, _d, _e, _f, _g;
472
+ const parentContext = (_a = this.cursor.parent) === null || _a === void 0 ? void 0 : _a.messages.get("indentContext");
473
+ const [parentIndent, parentIndentKind] = parentContext !== null && parentContext !== void 0 ? parentContext : [0, 'continuation'];
474
+ // Check if parent has chainedIndentContext - if so, this is the select of a method chain
475
+ // Propagate chainedIndentContext but do NOT set indentContext
476
+ const parentChainedContext = (_b = this.cursor.parent) === null || _b === void 0 ? void 0 : _b.messages.get("chainedIndentContext");
477
+ if (parentChainedContext !== undefined) {
478
+ this.cursor.messages.set("chainedIndentContext", parentChainedContext);
479
+ // Do NOT set indentContext - child elements will use chainedIndentContext
480
+ return;
473
481
  }
474
- else if (parentIndentKind !== 'align' && (0, java_1.isJava)(right.element) && this.elementPrefixContainsNewline(right.element)) {
475
- myIndent = parentIndent + this.singleIndent;
482
+ // Check if Parentheses wraps a Binary expression - if so, let Binary handle its own indent
483
+ const rightPaddedParentKind = (_d = (_c = this.cursor.parent) === null || _c === void 0 ? void 0 : _c.value) === null || _d === void 0 ? void 0 : _d.kind;
484
+ const elementKind = (_e = right.element) === null || _e === void 0 ? void 0 : _e.kind;
485
+ const isParenthesesWrappingBinary = rightPaddedParentKind === java_1.J.Kind.Parentheses &&
486
+ elementKind === java_1.J.Kind.Binary;
487
+ let myIndent = parentIndent;
488
+ if (!isParenthesesWrappingBinary && parentIndentKind !== 'align' && (0, java_1.isJava)(right.element) && this.prefixContainsNewline(right.element)) {
489
+ myIndent = parentIndent + this.indentSize;
476
490
  // For spread elements with newlines, mark continuation as established
477
- // This allows subsequent elements on the SAME line to inherit the continuation level
478
491
  const element = right.element;
479
492
  if (this.isSpreadElement(element)) {
480
- (_h = this.cursor.parent) === null || _h === void 0 ? void 0 : _h.messages.set("continuationIndent", myIndent);
493
+ (_f = this.cursor.parent) === null || _f === void 0 ? void 0 : _f.messages.set("continuationIndent", myIndent);
481
494
  }
482
495
  }
483
- else if ((0, java_1.isJava)(right.element) && !this.elementPrefixContainsNewline(right.element)) {
496
+ else if ((0, java_1.isJava)(right.element) && !this.prefixContainsNewline(right.element)) {
484
497
  // Element has no newline - check if a previous sibling established continuation
485
- const continuationIndent = (_j = this.cursor.parent) === null || _j === void 0 ? void 0 : _j.messages.get("continuationIndent");
498
+ const continuationIndent = (_g = this.cursor.parent) === null || _g === void 0 ? void 0 : _g.messages.get("continuationIndent");
486
499
  if (continuationIndent !== undefined) {
487
500
  myIndent = continuationIndent;
488
501
  }
489
502
  }
490
- this.cursor.messages.set("myIndent", myIndent);
491
503
  // Set 'align' for most RightPadded elements to prevent double-continuation
492
- // EXCEPT when Parentheses wraps a Binary expression - in that case, the Binary's
493
- // children need continuation mode to get proper indentation for multi-line operands
494
- const rightPaddedParentKind = (_l = (_k = this.cursor.parent) === null || _k === void 0 ? void 0 : _k.value) === null || _l === void 0 ? void 0 : _l.kind;
495
- const elementKind = (_m = right.element) === null || _m === void 0 ? void 0 : _m.kind;
496
- const isParenthesesWrappingBinary = rightPaddedParentKind === java_1.J.Kind.Parentheses &&
497
- elementKind === java_1.J.Kind.Binary;
498
- if (!isParenthesesWrappingBinary) {
499
- this.cursor.messages.set("indentKind", "align");
500
- }
501
- }
502
- elementPrefixContainsNewline(element) {
503
- var _a, _b;
504
- // Check the element's own prefix
505
- if ((0, java_1.lastWhitespace)(element.prefix).includes("\n")) {
506
- return true;
507
- }
508
- // Also check for Spread marker's prefix
509
- const spreadMarker = (_b = (_a = element.markers) === null || _a === void 0 ? void 0 : _a.markers) === null || _b === void 0 ? void 0 : _b.find(m => m.kind === tree_1.JS.Markers.Spread);
510
- if (spreadMarker && (0, java_1.spaceContainsNewline)(spreadMarker.prefix)) {
511
- return true;
512
- }
513
- return false;
504
+ // EXCEPT when Parentheses wraps a Binary expression - use continuation so Binary children align
505
+ const indentKind = isParenthesesWrappingBinary ? 'continuation' : 'align';
506
+ this.cursor.messages.set("indentContext", [myIndent, indentKind]);
514
507
  }
515
508
  /**
516
509
  * Check if an element is a spread - either directly marked with Spread marker,
@@ -551,7 +544,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
551
544
  setupAncestorIndents() {
552
545
  const path = [];
553
546
  let anchorCursor;
554
- let anchorIndent = "";
547
+ let anchorIndent = 0;
555
548
  for (let c = this.cursor.parent; c; c = c.parent) {
556
549
  path.push(c);
557
550
  const v = c.value;
@@ -560,13 +553,13 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
560
553
  const idx = ws.lastIndexOf('\n');
561
554
  if (idx !== -1) {
562
555
  anchorCursor = c;
563
- anchorIndent = ws.substring(idx + 1);
556
+ anchorIndent = ws.length - idx - 1;
564
557
  }
565
558
  }
566
559
  if (v.kind === tree_1.JS.Kind.CompilationUnit) {
567
560
  if (!anchorCursor) {
568
561
  anchorCursor = c;
569
- anchorIndent = "";
562
+ anchorIndent = 0;
570
563
  }
571
564
  break;
572
565
  }
@@ -581,8 +574,7 @@ class TabsAndIndentsVisitor extends visitor_1.JavaScriptVisitor {
581
574
  const savedCursor = this.cursor;
582
575
  this.cursor = c;
583
576
  if (c === anchorCursor) {
584
- c.messages.set("myIndent", anchorIndent);
585
- c.messages.set("indentKind", this.computeIndentKind(v));
577
+ c.messages.set("indentContext", [anchorIndent, this.computeIndentKind(v)]);
586
578
  }
587
579
  else {
588
580
  this.setupCursorMessagesForTree(c, v);