@jacobknightley/fabric-format 0.0.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 (50) hide show
  1. package/README.md +196 -0
  2. package/dist/cell-formatter.d.ts +75 -0
  3. package/dist/cell-formatter.js +144 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +435 -0
  6. package/dist/formatters/index.d.ts +19 -0
  7. package/dist/formatters/index.js +76 -0
  8. package/dist/formatters/python/config.d.ts +33 -0
  9. package/dist/formatters/python/config.js +29 -0
  10. package/dist/formatters/python/index.d.ts +7 -0
  11. package/dist/formatters/python/index.js +13 -0
  12. package/dist/formatters/python/python-formatter.d.ts +51 -0
  13. package/dist/formatters/python/python-formatter.js +180 -0
  14. package/dist/formatters/sparksql/constants.d.ts +16 -0
  15. package/dist/formatters/sparksql/constants.js +16 -0
  16. package/dist/formatters/sparksql/fmt-detector.d.ts +65 -0
  17. package/dist/formatters/sparksql/fmt-detector.js +84 -0
  18. package/dist/formatters/sparksql/formatter.d.ts +24 -0
  19. package/dist/formatters/sparksql/formatter.js +1276 -0
  20. package/dist/formatters/sparksql/formatting-context.d.ts +154 -0
  21. package/dist/formatters/sparksql/formatting-context.js +363 -0
  22. package/dist/formatters/sparksql/generated/SqlBaseLexer.d.ts +529 -0
  23. package/dist/formatters/sparksql/generated/SqlBaseLexer.js +2609 -0
  24. package/dist/formatters/sparksql/generated/SqlBaseParser.d.ts +8195 -0
  25. package/dist/formatters/sparksql/generated/SqlBaseParser.js +48793 -0
  26. package/dist/formatters/sparksql/generated/SqlBaseParserListener.d.ts +910 -0
  27. package/dist/formatters/sparksql/generated/SqlBaseParserListener.js +2730 -0
  28. package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.d.ts +456 -0
  29. package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.js +1822 -0
  30. package/dist/formatters/sparksql/generated/builtinFunctions.d.ts +8 -0
  31. package/dist/formatters/sparksql/generated/builtinFunctions.js +510 -0
  32. package/dist/formatters/sparksql/index.d.ts +11 -0
  33. package/dist/formatters/sparksql/index.js +22 -0
  34. package/dist/formatters/sparksql/output-builder.d.ts +89 -0
  35. package/dist/formatters/sparksql/output-builder.js +191 -0
  36. package/dist/formatters/sparksql/parse-tree-analyzer.d.ts +264 -0
  37. package/dist/formatters/sparksql/parse-tree-analyzer.js +1956 -0
  38. package/dist/formatters/sparksql/sql-formatter.d.ts +25 -0
  39. package/dist/formatters/sparksql/sql-formatter.js +56 -0
  40. package/dist/formatters/sparksql/token-utils.d.ts +68 -0
  41. package/dist/formatters/sparksql/token-utils.js +155 -0
  42. package/dist/formatters/sparksql/types.d.ts +264 -0
  43. package/dist/formatters/sparksql/types.js +7 -0
  44. package/dist/formatters/types.d.ts +57 -0
  45. package/dist/formatters/types.js +7 -0
  46. package/dist/index.d.ts +18 -0
  47. package/dist/index.js +41 -0
  48. package/dist/notebook-formatter.d.ts +107 -0
  49. package/dist/notebook-formatter.js +424 -0
  50. package/package.json +63 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Formatting Context - State Management During Token Processing
3
+ *
4
+ * This module manages the mutable state that tracks where we are
5
+ * in the formatting process. It provides a clean interface for:
6
+ * - Depth tracking (subquery, DDL, CASE, parentheses)
7
+ * - Clause state (after SELECT, WHERE, etc.)
8
+ * - Previous token tracking (for spacing decisions)
9
+ * - Multi-arg function and window expansion state
10
+ */
11
+ import type { FormattingState, ExpandedFunction, PendingComment, MultiArgFunctionInfo, WindowDefInfo, PivotInfo } from './types.js';
12
+ /**
13
+ * Creates a fresh formatting state with default values.
14
+ */
15
+ export declare function createInitialState(): FormattingState;
16
+ /**
17
+ * Manages expanded multi-arg functions during formatting.
18
+ */
19
+ export declare class ExpandedFunctionStack {
20
+ private stack;
21
+ push(func: ExpandedFunction): void;
22
+ pop(): ExpandedFunction | undefined;
23
+ current(): ExpandedFunction | null;
24
+ get depth(): number;
25
+ isEmpty(): boolean;
26
+ isCloseParen(tokenIndex: number): boolean;
27
+ /**
28
+ * Check if token is a comma that should trigger a newline.
29
+ * For STACK, some commas are skipped (pair grouping).
30
+ */
31
+ isComma(tokenIndex: number): boolean;
32
+ }
33
+ /**
34
+ * Manages comment collection and output during formatting.
35
+ */
36
+ export declare class CommentManager {
37
+ private pending;
38
+ add(comment: PendingComment): void;
39
+ hasPending(): boolean;
40
+ getPending(): PendingComment[];
41
+ getInlineComments(): PendingComment[];
42
+ getOwnLineComments(): PendingComment[];
43
+ clear(): void;
44
+ /**
45
+ * Check if a comment was on its own line in the original input.
46
+ */
47
+ static checkWasOnOwnLine(commentTokenIndex: number, commentToken: any, allTokens: any[]): boolean;
48
+ /**
49
+ * Check if there was a blank line before this comment.
50
+ * A blank line means there was whitespace with at least one newline IMMEDIATELY before
51
+ * this comment AND the token before that WS was also a comment (since SIMPLE_COMMENT
52
+ * includes its trailing newline, any additional newline in WS indicates a blank line).
53
+ */
54
+ static checkHadBlankLineBefore(commentTokenIndex: number, allTokens: any[]): boolean;
55
+ }
56
+ /**
57
+ * Indentation calculator - centralizes all indent logic.
58
+ */
59
+ export declare class IndentCalculator {
60
+ private readonly indentUnit;
61
+ /**
62
+ * Get base indent for current depth (subquery + DDL depth).
63
+ */
64
+ getBaseIndent(subqueryDepth: number, ddlDepth?: number): string;
65
+ /**
66
+ * Get indent for SELECT/GROUP BY/ORDER BY first item (5 spaces from base).
67
+ */
68
+ getFirstItemIndent(subqueryDepth: number, ddlDepth?: number): string;
69
+ /**
70
+ * Get indent for list comma (4 spaces from base).
71
+ */
72
+ getCommaIndent(subqueryDepth: number, ddlDepth?: number): string;
73
+ /**
74
+ * Get indent for ON clause in JOIN (4 spaces from base).
75
+ */
76
+ getOnClauseIndent(subqueryDepth: number, ddlDepth?: number): string;
77
+ /**
78
+ * Get indent for WHEN/ELSE in multi-WHEN CASE (8 spaces from base).
79
+ */
80
+ getCaseWhenIndent(subqueryDepth: number, ddlDepth?: number): string;
81
+ /**
82
+ * Get indent for END in multi-WHEN CASE (5 spaces from base, matches CASE).
83
+ */
84
+ getCaseEndIndent(subqueryDepth: number, ddlDepth?: number): string;
85
+ /**
86
+ * Get indent for nested CASE content (after THEN CASE).
87
+ * Uses caseDepth to add extra indent for each nesting level.
88
+ */
89
+ getCaseContentIndent(subqueryDepth: number, ddlDepth?: number, caseDepth?: number): string;
90
+ /**
91
+ * Get content indent for expanded multi-arg function's FIRST argument.
92
+ * First arg indent = 8 + (depth * 4) + 1 (for comma-first alignment)
93
+ * The +1 ensures first arg aligns with subsequent ",arg" items
94
+ */
95
+ getExpandedFunctionContentIndent(depth: number): number;
96
+ /**
97
+ * Get comma indent for expanded multi-arg function (subsequent args).
98
+ * Comma indent = 8 + (depth * 4) (no +1, comma takes that position)
99
+ */
100
+ getExpandedFunctionCommaIndent(depth: number): number;
101
+ /**
102
+ * Get close paren indent for expanded multi-arg function.
103
+ * Close paren = 4 + (depth * 4)
104
+ */
105
+ getExpandedFunctionCloseIndent(depth: number): number;
106
+ /**
107
+ * Get content indent for expanded window definition.
108
+ * Window content = (baseDepth * 4) + 8
109
+ */
110
+ getWindowContentIndent(baseDepth: number): number;
111
+ /**
112
+ * Get close paren indent for expanded window definition.
113
+ * Window close = (baseDepth * 4) + 4
114
+ */
115
+ getWindowCloseIndent(baseDepth: number): number;
116
+ /**
117
+ * Get content indent for expanded PIVOT/UNPIVOT clause.
118
+ * PIVOT content = (baseDepth * 4) + 4
119
+ */
120
+ getPivotContentIndent(baseDepth: number): number;
121
+ /**
122
+ * Get comma indent for expanded PIVOT/UNPIVOT clause.
123
+ * PIVOT comma = (baseDepth * 4) + 4 (same as content, comma-first style)
124
+ */
125
+ getPivotCommaIndent(baseDepth: number): number;
126
+ /**
127
+ * Get close paren indent for expanded PIVOT/UNPIVOT clause.
128
+ */
129
+ getPivotCloseIndent(baseDepth: number): number;
130
+ }
131
+ /**
132
+ * Determines if a +/-/~ operator is unary based on previous token.
133
+ * Tilde (~) is always unary (bitwise NOT), while +/- depend on context.
134
+ */
135
+ export declare function isUnaryOperator(text: string, prevTokenText: string, prevTokenType: number): boolean;
136
+ /**
137
+ * Decides if a multi-arg function should be expanded based on line width.
138
+ */
139
+ export declare function shouldExpandFunction(currentColumn: number, funcInfo: MultiArgFunctionInfo): boolean;
140
+ /**
141
+ * Decides if a window definition should be expanded based on line width.
142
+ * Also expands if any nested function within would expand.
143
+ *
144
+ * Uses grammar-derived relative offsets to calculate the actual column position
145
+ * of each nested function, ensuring accurate expansion decisions.
146
+ */
147
+ export declare function shouldExpandWindow(currentColumn: number, windowInfo: WindowDefInfo, multiArgFunctionInfo?: Map<number, {
148
+ spanLength: number;
149
+ }>): boolean;
150
+ /**
151
+ * Decides if a PIVOT/UNPIVOT clause should be expanded based on line width.
152
+ */
153
+ export declare function shouldExpandPivot(currentColumn: number, pivotInfo: PivotInfo): boolean;
154
+ export declare const indentCalc: IndentCalculator;
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Formatting Context - State Management During Token Processing
3
+ *
4
+ * This module manages the mutable state that tracks where we are
5
+ * in the formatting process. It provides a clean interface for:
6
+ * - Depth tracking (subquery, DDL, CASE, parentheses)
7
+ * - Clause state (after SELECT, WHERE, etc.)
8
+ * - Previous token tracking (for spacing decisions)
9
+ * - Multi-arg function and window expansion state
10
+ */
11
+ import { SqlBaseLexer, getSymbolicName } from './token-utils.js';
12
+ import { MAX_LINE_WIDTH } from './constants.js';
13
+ /**
14
+ * Creates a fresh formatting state with default values.
15
+ */
16
+ export function createInitialState() {
17
+ return {
18
+ // Depth tracking
19
+ subqueryDepth: 0,
20
+ ddlDepth: 0,
21
+ caseDepth: 0,
22
+ insideParens: 0,
23
+ insideFunctionArgs: 0,
24
+ complexTypeDepth: 0, // Tracks nesting in ARRAY<>, MAP<>, STRUCT<>
25
+ // Position tracking
26
+ currentColumn: 0,
27
+ isFirstNonWsToken: true,
28
+ // Clause state
29
+ afterSelectKeyword: false,
30
+ afterGroupByKeyword: false,
31
+ afterOrderByKeyword: false,
32
+ afterWhereKeyword: false,
33
+ afterHavingKeyword: false,
34
+ afterSetKeyword: false,
35
+ afterValuesKeyword: false,
36
+ // List state
37
+ currentClauseIsMultiItem: false,
38
+ isFirstListItem: true,
39
+ justOutputCommaFirstStyle: false,
40
+ // Previous token tracking
41
+ prevWasFunctionName: false,
42
+ prevWasBuiltInFunctionKeyword: false,
43
+ prevTokenText: '',
44
+ prevTokenType: -1,
45
+ prevTokenWasUnaryOperator: false,
46
+ // Hint handling
47
+ insideHint: false,
48
+ hintContent: [],
49
+ // Multi-arg function expansion
50
+ justOutputMultiArgFunctionNewline: false,
51
+ // Window expansion
52
+ justOutputWindowNewline: false,
53
+ // PIVOT/UNPIVOT expansion
54
+ justOutputPivotNewline: false,
55
+ // IN list wrapping
56
+ inListContentStartColumn: null,
57
+ insideInList: false,
58
+ justOutputInListWrapNewline: false,
59
+ // Simple query compaction - stack-based for nested queries
60
+ compactQueryStack: []
61
+ };
62
+ }
63
+ /**
64
+ * Manages expanded multi-arg functions during formatting.
65
+ */
66
+ export class ExpandedFunctionStack {
67
+ stack = [];
68
+ push(func) {
69
+ this.stack.push(func);
70
+ }
71
+ pop() {
72
+ return this.stack.pop();
73
+ }
74
+ current() {
75
+ return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
76
+ }
77
+ get depth() {
78
+ return this.stack.length;
79
+ }
80
+ isEmpty() {
81
+ return this.stack.length === 0;
82
+ }
83
+ isCloseParen(tokenIndex) {
84
+ const current = this.current();
85
+ return current !== null && current.closeParenIndex === tokenIndex;
86
+ }
87
+ /**
88
+ * Check if token is a comma that should trigger a newline.
89
+ * For STACK, some commas are skipped (pair grouping).
90
+ */
91
+ isComma(tokenIndex) {
92
+ const current = this.current();
93
+ if (current === null || !current.commaIndices.has(tokenIndex)) {
94
+ return false;
95
+ }
96
+ // If this comma is in skipNewlineCommas, don't treat it as expanded comma
97
+ if (current.skipNewlineCommas?.has(tokenIndex)) {
98
+ return false;
99
+ }
100
+ return true;
101
+ }
102
+ }
103
+ /**
104
+ * Manages comment collection and output during formatting.
105
+ */
106
+ export class CommentManager {
107
+ pending = [];
108
+ add(comment) {
109
+ this.pending.push(comment);
110
+ }
111
+ hasPending() {
112
+ return this.pending.length > 0;
113
+ }
114
+ getPending() {
115
+ return this.pending;
116
+ }
117
+ getInlineComments() {
118
+ return this.pending.filter(c => !c.wasOnOwnLine);
119
+ }
120
+ getOwnLineComments() {
121
+ return this.pending.filter(c => c.wasOnOwnLine);
122
+ }
123
+ clear() {
124
+ this.pending = [];
125
+ }
126
+ /**
127
+ * Check if a comment was on its own line in the original input.
128
+ */
129
+ static checkWasOnOwnLine(commentTokenIndex, commentToken, allTokens) {
130
+ if (commentToken.column === 0) {
131
+ return true;
132
+ }
133
+ for (let k = commentTokenIndex - 1; k >= 0; k--) {
134
+ const prevToken = allTokens[k];
135
+ if (!prevToken)
136
+ continue;
137
+ if (prevToken.channel !== 1)
138
+ break;
139
+ if (prevToken.type === SqlBaseLexer.WS &&
140
+ prevToken.text &&
141
+ prevToken.text.includes('\n')) {
142
+ return true;
143
+ }
144
+ }
145
+ return false;
146
+ }
147
+ /**
148
+ * Check if there was a blank line before this comment.
149
+ * A blank line means there was whitespace with at least one newline IMMEDIATELY before
150
+ * this comment AND the token before that WS was also a comment (since SIMPLE_COMMENT
151
+ * includes its trailing newline, any additional newline in WS indicates a blank line).
152
+ */
153
+ static checkHadBlankLineBefore(commentTokenIndex, allTokens) {
154
+ // Only check the immediately preceding token
155
+ if (commentTokenIndex < 1)
156
+ return false;
157
+ const prevToken = allTokens[commentTokenIndex - 1];
158
+ if (!prevToken)
159
+ return false;
160
+ // If immediate predecessor is not WS, there's no blank line
161
+ if (prevToken.type !== SqlBaseLexer.WS)
162
+ return false;
163
+ // Check if the WS has newlines
164
+ if (!prevToken.text || !prevToken.text.includes('\n'))
165
+ return false;
166
+ // Now check what's before the WS
167
+ if (commentTokenIndex < 2) {
168
+ // WS at start of file with newlines = blank line at top
169
+ return true;
170
+ }
171
+ const tokenBeforeWs = allTokens[commentTokenIndex - 2];
172
+ if (!tokenBeforeWs)
173
+ return false;
174
+ // If token before WS is a comment, this WS newline represents a blank line
175
+ // (because comments include their trailing newline)
176
+ if (tokenBeforeWs.type === SqlBaseLexer.SIMPLE_COMMENT ||
177
+ tokenBeforeWs.type === SqlBaseLexer.BRACKETED_COMMENT) {
178
+ return true;
179
+ }
180
+ // If token before WS is something else (like code), check for 2+ newlines
181
+ // (one newline ends the code line, a second indicates blank line)
182
+ const newlineCount = (prevToken.text.match(/\n/g) || []).length;
183
+ return newlineCount >= 2;
184
+ }
185
+ }
186
+ /**
187
+ * Indentation calculator - centralizes all indent logic.
188
+ */
189
+ export class IndentCalculator {
190
+ indentUnit = ' '; // 4 spaces
191
+ /**
192
+ * Get base indent for current depth (subquery + DDL depth).
193
+ */
194
+ getBaseIndent(subqueryDepth, ddlDepth = 0) {
195
+ return this.indentUnit.repeat(subqueryDepth + ddlDepth);
196
+ }
197
+ /**
198
+ * Get indent for SELECT/GROUP BY/ORDER BY first item (5 spaces from base).
199
+ */
200
+ getFirstItemIndent(subqueryDepth, ddlDepth = 0) {
201
+ return this.getBaseIndent(subqueryDepth, ddlDepth) + ' ';
202
+ }
203
+ /**
204
+ * Get indent for list comma (4 spaces from base).
205
+ */
206
+ getCommaIndent(subqueryDepth, ddlDepth = 0) {
207
+ return this.getBaseIndent(subqueryDepth, ddlDepth) + this.indentUnit;
208
+ }
209
+ /**
210
+ * Get indent for ON clause in JOIN (4 spaces from base).
211
+ */
212
+ getOnClauseIndent(subqueryDepth, ddlDepth = 0) {
213
+ return this.getBaseIndent(subqueryDepth, ddlDepth) + this.indentUnit;
214
+ }
215
+ /**
216
+ * Get indent for WHEN/ELSE in multi-WHEN CASE (8 spaces from base).
217
+ */
218
+ getCaseWhenIndent(subqueryDepth, ddlDepth = 0) {
219
+ return this.getBaseIndent(subqueryDepth, ddlDepth) + ' ';
220
+ }
221
+ /**
222
+ * Get indent for END in multi-WHEN CASE (5 spaces from base, matches CASE).
223
+ */
224
+ getCaseEndIndent(subqueryDepth, ddlDepth = 0) {
225
+ return this.getBaseIndent(subqueryDepth, ddlDepth) + ' ';
226
+ }
227
+ /**
228
+ * Get indent for nested CASE content (after THEN CASE).
229
+ * Uses caseDepth to add extra indent for each nesting level.
230
+ */
231
+ getCaseContentIndent(subqueryDepth, ddlDepth = 0, caseDepth = 0) {
232
+ const baseIndent = this.getBaseIndent(subqueryDepth, ddlDepth);
233
+ // Each case level adds 8 + 4 spaces (WHEN indent + one level)
234
+ const caseIndent = ' '.repeat(caseDepth); // 12 spaces per case level
235
+ return baseIndent + caseIndent;
236
+ }
237
+ /**
238
+ * Get content indent for expanded multi-arg function's FIRST argument.
239
+ * First arg indent = 8 + (depth * 4) + 1 (for comma-first alignment)
240
+ * The +1 ensures first arg aligns with subsequent ",arg" items
241
+ */
242
+ getExpandedFunctionContentIndent(depth) {
243
+ return 8 + (depth * 4) + 1;
244
+ }
245
+ /**
246
+ * Get comma indent for expanded multi-arg function (subsequent args).
247
+ * Comma indent = 8 + (depth * 4) (no +1, comma takes that position)
248
+ */
249
+ getExpandedFunctionCommaIndent(depth) {
250
+ return 8 + (depth * 4);
251
+ }
252
+ /**
253
+ * Get close paren indent for expanded multi-arg function.
254
+ * Close paren = 4 + (depth * 4)
255
+ */
256
+ getExpandedFunctionCloseIndent(depth) {
257
+ return 4 + (depth * 4);
258
+ }
259
+ /**
260
+ * Get content indent for expanded window definition.
261
+ * Window content = (baseDepth * 4) + 8
262
+ */
263
+ getWindowContentIndent(baseDepth) {
264
+ return (baseDepth * 4) + 8;
265
+ }
266
+ /**
267
+ * Get close paren indent for expanded window definition.
268
+ * Window close = (baseDepth * 4) + 4
269
+ */
270
+ getWindowCloseIndent(baseDepth) {
271
+ return (baseDepth * 4) + 4;
272
+ }
273
+ /**
274
+ * Get content indent for expanded PIVOT/UNPIVOT clause.
275
+ * PIVOT content = (baseDepth * 4) + 4
276
+ */
277
+ getPivotContentIndent(baseDepth) {
278
+ return (baseDepth * 4) + 4;
279
+ }
280
+ /**
281
+ * Get comma indent for expanded PIVOT/UNPIVOT clause.
282
+ * PIVOT comma = (baseDepth * 4) + 4 (same as content, comma-first style)
283
+ */
284
+ getPivotCommaIndent(baseDepth) {
285
+ return (baseDepth * 4) + 4;
286
+ }
287
+ /**
288
+ * Get close paren indent for expanded PIVOT/UNPIVOT clause.
289
+ */
290
+ getPivotCloseIndent(baseDepth) {
291
+ return baseDepth * 4;
292
+ }
293
+ }
294
+ /**
295
+ * Determines if a +/-/~ operator is unary based on previous token.
296
+ * Tilde (~) is always unary (bitwise NOT), while +/- depend on context.
297
+ */
298
+ export function isUnaryOperator(text, prevTokenText, prevTokenType) {
299
+ // Tilde is always unary (bitwise NOT)
300
+ if (text === '~')
301
+ return true;
302
+ if (text !== '+' && text !== '-')
303
+ return false;
304
+ // Check if previous token indicates unary context
305
+ if (prevTokenText === '' || prevTokenText === '(' ||
306
+ prevTokenText === '[' || prevTokenText === ',') {
307
+ return true;
308
+ }
309
+ // Check if previous token was a keyword/operator that expects an expression
310
+ const prevSymbolic = prevTokenType >= 0 ? getSymbolicName(prevTokenType) : null;
311
+ if (!prevSymbolic)
312
+ return false;
313
+ const expectsExpression = new Set([
314
+ 'SELECT', 'WHERE', 'HAVING', 'ON', 'AND', 'OR',
315
+ 'WHEN', 'THEN', 'ELSE', 'RETURN', 'CASE',
316
+ 'EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'NSEQ',
317
+ 'PLUS', 'MINUS', 'ASTERISK', 'SLASH', 'PERCENT', 'DIV',
318
+ 'AS', 'SET'
319
+ ]);
320
+ return expectsExpression.has(prevSymbolic);
321
+ }
322
+ /**
323
+ * Decides if a multi-arg function should be expanded based on line width.
324
+ */
325
+ export function shouldExpandFunction(currentColumn, funcInfo) {
326
+ return currentColumn + funcInfo.spanLength > MAX_LINE_WIDTH;
327
+ }
328
+ /**
329
+ * Decides if a window definition should be expanded based on line width.
330
+ * Also expands if any nested function within would expand.
331
+ *
332
+ * Uses grammar-derived relative offsets to calculate the actual column position
333
+ * of each nested function, ensuring accurate expansion decisions.
334
+ */
335
+ export function shouldExpandWindow(currentColumn, windowInfo, multiArgFunctionInfo) {
336
+ // Direct span check for window itself
337
+ if (currentColumn + windowInfo.spanLength > MAX_LINE_WIDTH) {
338
+ return true;
339
+ }
340
+ // Check if any nested function would expand at its actual position
341
+ // Use the relative offset to calculate where the nested function actually sits
342
+ if (multiArgFunctionInfo && windowInfo.nestedFunctions.length > 0) {
343
+ for (const { funcIdx, relativeOffset } of windowInfo.nestedFunctions) {
344
+ const funcInfo = multiArgFunctionInfo.get(funcIdx);
345
+ if (funcInfo) {
346
+ // Calculate the actual column where this nested function's content starts
347
+ const nestedFuncColumn = currentColumn + relativeOffset;
348
+ if (nestedFuncColumn + funcInfo.spanLength > MAX_LINE_WIDTH) {
349
+ return true; // Nested function would expand at its position, so expand OVER too
350
+ }
351
+ }
352
+ }
353
+ }
354
+ return false;
355
+ }
356
+ /**
357
+ * Decides if a PIVOT/UNPIVOT clause should be expanded based on line width.
358
+ */
359
+ export function shouldExpandPivot(currentColumn, pivotInfo) {
360
+ return currentColumn + pivotInfo.spanLength > MAX_LINE_WIDTH;
361
+ }
362
+ // Export singleton indent calculator
363
+ export const indentCalc = new IndentCalculator();