rawsql-ts 0.11.37-beta → 0.11.38-beta

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 (129) hide show
  1. package/dist/esm/index.min.js +54 -51
  2. package/dist/esm/index.min.js.map +3 -3
  3. package/dist/esm/src/models/Lexeme.d.ts +8 -0
  4. package/dist/esm/src/models/SqlComponent.d.ts +20 -0
  5. package/dist/esm/src/models/SqlComponent.js +46 -0
  6. package/dist/esm/src/models/SqlComponent.js.map +1 -1
  7. package/dist/esm/src/parsers/CommandExpressionParser.d.ts +9 -0
  8. package/dist/esm/src/parsers/CommandExpressionParser.js +118 -14
  9. package/dist/esm/src/parsers/CommandExpressionParser.js.map +1 -1
  10. package/dist/esm/src/parsers/CommonTableParser.d.ts +8 -0
  11. package/dist/esm/src/parsers/CommonTableParser.js +127 -35
  12. package/dist/esm/src/parsers/CommonTableParser.js.map +1 -1
  13. package/dist/esm/src/parsers/FromClauseParser.js +2 -2
  14. package/dist/esm/src/parsers/FromClauseParser.js.map +1 -1
  15. package/dist/esm/src/parsers/FullNameParser.js +13 -1
  16. package/dist/esm/src/parsers/FullNameParser.js.map +1 -1
  17. package/dist/esm/src/parsers/FunctionExpressionParser.js +37 -3
  18. package/dist/esm/src/parsers/FunctionExpressionParser.js.map +1 -1
  19. package/dist/esm/src/parsers/JoinClauseParser.d.ts +4 -0
  20. package/dist/esm/src/parsers/JoinClauseParser.js +54 -20
  21. package/dist/esm/src/parsers/JoinClauseParser.js.map +1 -1
  22. package/dist/esm/src/parsers/OrderByClauseParser.js +39 -8
  23. package/dist/esm/src/parsers/OrderByClauseParser.js.map +1 -1
  24. package/dist/esm/src/parsers/ParenExpressionParser.js +1 -1
  25. package/dist/esm/src/parsers/ParenExpressionParser.js.map +1 -1
  26. package/dist/esm/src/parsers/SelectClauseParser.d.ts +10 -0
  27. package/dist/esm/src/parsers/SelectClauseParser.js +124 -12
  28. package/dist/esm/src/parsers/SelectClauseParser.js.map +1 -1
  29. package/dist/esm/src/parsers/SelectQueryParser.d.ts +12 -0
  30. package/dist/esm/src/parsers/SelectQueryParser.js +277 -115
  31. package/dist/esm/src/parsers/SelectQueryParser.js.map +1 -1
  32. package/dist/esm/src/parsers/SourceAliasExpressionParser.js +15 -4
  33. package/dist/esm/src/parsers/SourceAliasExpressionParser.js.map +1 -1
  34. package/dist/esm/src/parsers/SourceParser.js +33 -2
  35. package/dist/esm/src/parsers/SourceParser.js.map +1 -1
  36. package/dist/esm/src/parsers/SqlPrintTokenParser.d.ts +62 -0
  37. package/dist/esm/src/parsers/SqlPrintTokenParser.js +700 -32
  38. package/dist/esm/src/parsers/SqlPrintTokenParser.js.map +1 -1
  39. package/dist/esm/src/parsers/SqlTokenizer.d.ts +1 -4
  40. package/dist/esm/src/parsers/SqlTokenizer.js +108 -52
  41. package/dist/esm/src/parsers/SqlTokenizer.js.map +1 -1
  42. package/dist/esm/src/parsers/ValueParser.d.ts +4 -0
  43. package/dist/esm/src/parsers/ValueParser.js +119 -20
  44. package/dist/esm/src/parsers/ValueParser.js.map +1 -1
  45. package/dist/esm/src/parsers/WithClauseParser.d.ts +4 -0
  46. package/dist/esm/src/parsers/WithClauseParser.js +64 -35
  47. package/dist/esm/src/parsers/WithClauseParser.js.map +1 -1
  48. package/dist/esm/src/tokenReaders/CommandTokenReader.js +9 -1
  49. package/dist/esm/src/tokenReaders/CommandTokenReader.js.map +1 -1
  50. package/dist/esm/src/transformers/LinePrinter.d.ts +17 -2
  51. package/dist/esm/src/transformers/LinePrinter.js +39 -10
  52. package/dist/esm/src/transformers/LinePrinter.js.map +1 -1
  53. package/dist/esm/src/transformers/SqlFormatter.d.ts +10 -0
  54. package/dist/esm/src/transformers/SqlFormatter.js +1 -1
  55. package/dist/esm/src/transformers/SqlFormatter.js.map +1 -1
  56. package/dist/esm/src/transformers/SqlPrinter.d.ts +6 -1
  57. package/dist/esm/src/transformers/SqlPrinter.js +76 -31
  58. package/dist/esm/src/transformers/SqlPrinter.js.map +1 -1
  59. package/dist/esm/src/utils/CommentEditor.d.ts +12 -5
  60. package/dist/esm/src/utils/CommentEditor.js +67 -28
  61. package/dist/esm/src/utils/CommentEditor.js.map +1 -1
  62. package/dist/esm/src/utils/stringUtils.js +21 -9
  63. package/dist/esm/src/utils/stringUtils.js.map +1 -1
  64. package/dist/esm/tsconfig.browser.tsbuildinfo +1 -1
  65. package/dist/index.min.js +54 -51
  66. package/dist/index.min.js.map +3 -3
  67. package/dist/src/models/Lexeme.d.ts +8 -0
  68. package/dist/src/models/SqlComponent.d.ts +20 -0
  69. package/dist/src/models/SqlComponent.js +46 -0
  70. package/dist/src/models/SqlComponent.js.map +1 -1
  71. package/dist/src/parsers/CommandExpressionParser.d.ts +9 -0
  72. package/dist/src/parsers/CommandExpressionParser.js +118 -14
  73. package/dist/src/parsers/CommandExpressionParser.js.map +1 -1
  74. package/dist/src/parsers/CommonTableParser.d.ts +8 -0
  75. package/dist/src/parsers/CommonTableParser.js +127 -35
  76. package/dist/src/parsers/CommonTableParser.js.map +1 -1
  77. package/dist/src/parsers/FromClauseParser.js +2 -2
  78. package/dist/src/parsers/FromClauseParser.js.map +1 -1
  79. package/dist/src/parsers/FullNameParser.js +13 -1
  80. package/dist/src/parsers/FullNameParser.js.map +1 -1
  81. package/dist/src/parsers/FunctionExpressionParser.js +37 -3
  82. package/dist/src/parsers/FunctionExpressionParser.js.map +1 -1
  83. package/dist/src/parsers/JoinClauseParser.d.ts +4 -0
  84. package/dist/src/parsers/JoinClauseParser.js +54 -20
  85. package/dist/src/parsers/JoinClauseParser.js.map +1 -1
  86. package/dist/src/parsers/OrderByClauseParser.js +39 -8
  87. package/dist/src/parsers/OrderByClauseParser.js.map +1 -1
  88. package/dist/src/parsers/ParenExpressionParser.js +1 -1
  89. package/dist/src/parsers/ParenExpressionParser.js.map +1 -1
  90. package/dist/src/parsers/SelectClauseParser.d.ts +10 -0
  91. package/dist/src/parsers/SelectClauseParser.js +124 -12
  92. package/dist/src/parsers/SelectClauseParser.js.map +1 -1
  93. package/dist/src/parsers/SelectQueryParser.d.ts +12 -0
  94. package/dist/src/parsers/SelectQueryParser.js +280 -115
  95. package/dist/src/parsers/SelectQueryParser.js.map +1 -1
  96. package/dist/src/parsers/SourceAliasExpressionParser.js +15 -4
  97. package/dist/src/parsers/SourceAliasExpressionParser.js.map +1 -1
  98. package/dist/src/parsers/SourceParser.js +33 -2
  99. package/dist/src/parsers/SourceParser.js.map +1 -1
  100. package/dist/src/parsers/SqlPrintTokenParser.d.ts +62 -0
  101. package/dist/src/parsers/SqlPrintTokenParser.js +700 -32
  102. package/dist/src/parsers/SqlPrintTokenParser.js.map +1 -1
  103. package/dist/src/parsers/SqlTokenizer.d.ts +1 -4
  104. package/dist/src/parsers/SqlTokenizer.js +108 -52
  105. package/dist/src/parsers/SqlTokenizer.js.map +1 -1
  106. package/dist/src/parsers/ValueParser.d.ts +4 -0
  107. package/dist/src/parsers/ValueParser.js +119 -20
  108. package/dist/src/parsers/ValueParser.js.map +1 -1
  109. package/dist/src/parsers/WithClauseParser.d.ts +4 -0
  110. package/dist/src/parsers/WithClauseParser.js +64 -35
  111. package/dist/src/parsers/WithClauseParser.js.map +1 -1
  112. package/dist/src/tokenReaders/CommandTokenReader.js +9 -1
  113. package/dist/src/tokenReaders/CommandTokenReader.js.map +1 -1
  114. package/dist/src/transformers/LinePrinter.d.ts +17 -2
  115. package/dist/src/transformers/LinePrinter.js +39 -10
  116. package/dist/src/transformers/LinePrinter.js.map +1 -1
  117. package/dist/src/transformers/SqlFormatter.d.ts +10 -0
  118. package/dist/src/transformers/SqlFormatter.js +4 -1
  119. package/dist/src/transformers/SqlFormatter.js.map +1 -1
  120. package/dist/src/transformers/SqlPrinter.d.ts +6 -1
  121. package/dist/src/transformers/SqlPrinter.js +76 -31
  122. package/dist/src/transformers/SqlPrinter.js.map +1 -1
  123. package/dist/src/utils/CommentEditor.d.ts +12 -5
  124. package/dist/src/utils/CommentEditor.js +67 -28
  125. package/dist/src/utils/CommentEditor.js.map +1 -1
  126. package/dist/src/utils/stringUtils.js +21 -9
  127. package/dist/src/utils/stringUtils.js.map +1 -1
  128. package/dist/tsconfig.tsbuildinfo +1 -1
  129. package/package.json +3 -3
@@ -118,10 +118,31 @@ export const PRESETS = {
118
118
  },
119
119
  };
120
120
  export class SqlPrintTokenParser {
121
+ static getSelfHandlingComponentTypes() {
122
+ if (!this._selfHandlingComponentTypes) {
123
+ this._selfHandlingComponentTypes = new Set([
124
+ SimpleSelectQuery.kind,
125
+ SelectItem.kind,
126
+ CaseKeyValuePair.kind,
127
+ SwitchCaseArgument.kind,
128
+ ColumnReference.kind,
129
+ LiteralValue.kind,
130
+ ParameterExpression.kind,
131
+ TableSource.kind,
132
+ SourceAliasExpression.kind,
133
+ TypeValue.kind,
134
+ FunctionCall.kind,
135
+ IdentifierString.kind,
136
+ QualifiedName.kind
137
+ ]);
138
+ }
139
+ return this._selfHandlingComponentTypes;
140
+ }
121
141
  constructor(options) {
122
- var _a, _b, _c, _d, _e, _f, _g;
142
+ var _a, _b, _c, _d, _e, _f, _g, _h;
123
143
  this.handlers = new Map();
124
144
  this.index = 1;
145
+ this.commentStyle = 'block';
125
146
  if (options === null || options === void 0 ? void 0 : options.preset) {
126
147
  const preset = options.preset;
127
148
  options = Object.assign(Object.assign({}, preset), options);
@@ -135,6 +156,7 @@ export class SqlPrintTokenParser {
135
156
  start: (_e = (_d = options === null || options === void 0 ? void 0 : options.identifierEscape) === null || _d === void 0 ? void 0 : _d.start) !== null && _e !== void 0 ? _e : '"',
136
157
  end: (_g = (_f = options === null || options === void 0 ? void 0 : options.identifierEscape) === null || _f === void 0 ? void 0 : _f.end) !== null && _g !== void 0 ? _g : '"'
137
158
  });
159
+ this.commentStyle = (_h = options === null || options === void 0 ? void 0 : options.commentStyle) !== null && _h !== void 0 ? _h : 'block';
138
160
  this.handlers.set(ValueList.kind, (expr) => this.visitValueList(expr));
139
161
  this.handlers.set(ColumnReference.kind, (expr) => this.visitColumnReference(expr));
140
162
  this.handlers.set(QualifiedName.kind, (expr) => this.visitQualifiedName(expr));
@@ -218,10 +240,23 @@ export class SqlPrintTokenParser {
218
240
  */
219
241
  visitBinarySelectQuery(arg) {
220
242
  const token = new SqlPrintToken(SqlPrintTokenType.container, '');
221
- // Add headerComments at the very beginning (before the first query)
222
- if (arg.headerComments && arg.headerComments.length > 0) {
223
- const headerCommentBlocks = this.createCommentBlocks(arg.headerComments);
224
- token.innerTokens.push(...headerCommentBlocks);
243
+ // Handle positioned comments for BinarySelectQuery (unified spec)
244
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
245
+ this.addPositionedCommentsToToken(token, arg);
246
+ // Clear positioned comments to prevent duplicate processing
247
+ arg.positionedComments = null;
248
+ }
249
+ else if (arg.headerComments && arg.headerComments.length > 0) {
250
+ // Fallback to legacy headerComments if no positioned comments
251
+ // For smart comment style, treat headerComments as a single multi-line block
252
+ if (this.commentStyle === 'smart' && arg.headerComments.length > 1) {
253
+ const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
254
+ token.innerTokens.push(mergedHeaderComment);
255
+ }
256
+ else {
257
+ const headerCommentBlocks = this.createCommentBlocks(arg.headerComments);
258
+ token.innerTokens.push(...headerCommentBlocks);
259
+ }
225
260
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
226
261
  }
227
262
  token.innerTokens.push(this.visit(arg.left));
@@ -249,7 +284,25 @@ export class SqlPrintTokenParser {
249
284
  token.innerTokens.push(SqlPrintTokenParser.DOT_TOKEN);
250
285
  }
251
286
  }
252
- token.innerTokens.push(arg.name.accept(this));
287
+ // Handle name and its comments carefully
288
+ // We need to prevent double processing by temporarily clearing the name's comments,
289
+ // then process them at the QualifiedName level
290
+ const originalNameComments = arg.name.positionedComments;
291
+ const originalNameLegacyComments = arg.name.comments;
292
+ // Temporarily clear name's comments to prevent double processing
293
+ arg.name.positionedComments = null;
294
+ arg.name.comments = null;
295
+ const nameToken = arg.name.accept(this);
296
+ token.innerTokens.push(nameToken);
297
+ // Restore original comments
298
+ arg.name.positionedComments = originalNameComments;
299
+ arg.name.comments = originalNameLegacyComments;
300
+ // Apply the name's comments to the qualified name token
301
+ if (this.hasPositionedComments(arg.name) || this.hasLegacyComments(arg.name)) {
302
+ this.addComponentComments(token, arg.name);
303
+ }
304
+ // Also handle any comments directly on the QualifiedName itself
305
+ this.addComponentComments(token, arg);
253
306
  return token;
254
307
  }
255
308
  visitPartitionByClause(arg) {
@@ -328,49 +381,312 @@ export class SqlPrintTokenParser {
328
381
  // Fallback (just in case)
329
382
  return { token, params: [] };
330
383
  }
384
+ /**
385
+ * Check if a component handles its own comments
386
+ */
387
+ componentHandlesOwnComments(component) {
388
+ // First check if component has a handlesOwnComments method
389
+ if ('handlesOwnComments' in component && typeof component.handlesOwnComments === 'function') {
390
+ return component.handlesOwnComments();
391
+ }
392
+ return SqlPrintTokenParser.getSelfHandlingComponentTypes().has(component.getKind());
393
+ }
331
394
  visit(arg) {
332
395
  const handler = this.handlers.get(arg.getKind());
333
396
  if (handler) {
334
397
  const token = handler(arg);
335
- // Check if component handles its own comments to avoid double processing
336
- const handlesOwnComments = 'handlesOwnComments' in arg && typeof arg.handlesOwnComments === 'function'
337
- ? arg.handlesOwnComments()
338
- : arg.getKind() === SimpleSelectQuery.kind; // Fallback for existing SimpleSelectQuery
339
- if (!handlesOwnComments) {
340
- this.addCommentsToToken(token, arg.comments);
398
+ if (!this.componentHandlesOwnComments(arg)) {
399
+ this.addComponentComments(token, arg);
341
400
  }
342
401
  return token;
343
402
  }
344
403
  throw new Error(`[SqlPrintTokenParser] No handler for kind: ${arg.getKind().toString()}`);
345
404
  }
405
+ /**
406
+ * Check if a component has positioned comments
407
+ */
408
+ hasPositionedComments(component) {
409
+ var _a, _b;
410
+ return ((_b = (_a = component.positionedComments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
411
+ }
412
+ /**
413
+ * Check if a component has legacy comments
414
+ */
415
+ hasLegacyComments(component) {
416
+ var _a, _b;
417
+ return ((_b = (_a = component.comments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
418
+ }
419
+ /**
420
+ * Centralized comment handling - checks positioned comments first, falls back to legacy
421
+ */
422
+ addComponentComments(token, component) {
423
+ if (this.hasPositionedComments(component)) {
424
+ this.addPositionedCommentsToToken(token, component);
425
+ }
426
+ else if (this.hasLegacyComments(component)) {
427
+ this.addCommentsToToken(token, component.comments);
428
+ }
429
+ }
430
+ /**
431
+ * Adds positioned comment tokens to a SqlPrintToken for inline formatting
432
+ */
433
+ addPositionedCommentsToToken(token, component) {
434
+ if (!this.hasPositionedComments(component)) {
435
+ return;
436
+ }
437
+ // Handle 'before' comments - add inline at the beginning with spaces
438
+ const beforeComments = component.getPositionedComments('before');
439
+ if (beforeComments.length > 0) {
440
+ const commentBlocks = this.createCommentBlocks(beforeComments);
441
+ // Create a single inline sequence: /* comment */ content
442
+ const beforeTokens = [];
443
+ for (const commentBlock of commentBlocks) {
444
+ beforeTokens.push(commentBlock);
445
+ beforeTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
446
+ }
447
+ // Insert before the existing content
448
+ token.innerTokens.unshift(...beforeTokens);
449
+ }
450
+ // Handle 'after' comments - add inline after the main content
451
+ const afterComments = component.getPositionedComments('after');
452
+ if (afterComments.length > 0) {
453
+ const commentBlocks = this.createCommentBlocks(afterComments);
454
+ // Append after comments with spaces for inline formatting
455
+ for (const commentBlock of commentBlocks) {
456
+ token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
457
+ token.innerTokens.push(commentBlock);
458
+ }
459
+ }
460
+ // Clear positioned comments to prevent duplicate processing (unified spec)
461
+ // Only clear for specific component types that are known to have duplication issues
462
+ const componentsWithDuplicationIssues = [
463
+ SqlPrintTokenContainerType.CaseExpression,
464
+ SqlPrintTokenContainerType.SwitchCaseArgument,
465
+ SqlPrintTokenContainerType.CaseKeyValuePair,
466
+ SqlPrintTokenContainerType.SelectClause, // SELECT clauses have manual + automatic processing
467
+ SqlPrintTokenContainerType.LiteralValue,
468
+ SqlPrintTokenContainerType.IdentifierString,
469
+ SqlPrintTokenContainerType.DistinctOn,
470
+ SqlPrintTokenContainerType.SourceAliasExpression,
471
+ SqlPrintTokenContainerType.SimpleSelectQuery,
472
+ SqlPrintTokenContainerType.WhereClause // WHERE clauses also have duplication issues
473
+ ];
474
+ if (token.containerType && componentsWithDuplicationIssues.includes(token.containerType)) {
475
+ component.positionedComments = null;
476
+ }
477
+ }
346
478
  /**
347
479
  * Adds comment tokens to a SqlPrintToken based on the comments array
348
480
  */
349
481
  addCommentsToToken(token, comments) {
350
- if (!comments || comments.length === 0) {
482
+ if (!(comments === null || comments === void 0 ? void 0 : comments.length)) {
351
483
  return;
352
484
  }
353
- // Create CommentBlock containers for each comment
354
- const commentBlocks = this.createCommentBlocks(comments);
355
- // Insert comment blocks and handle spacing
356
- if (commentBlocks.length > 0) {
485
+ // For multiple comments, create inline comment sequence instead of separate blocks
486
+ if (comments.length > 1) {
487
+ const inlineComments = this.createInlineCommentSequence(comments);
488
+ this.insertCommentBlocksWithSpacing(token, inlineComments);
489
+ }
490
+ else {
491
+ // Create CommentBlock containers for single comment
492
+ const commentBlocks = this.createCommentBlocks(comments);
357
493
  this.insertCommentBlocksWithSpacing(token, commentBlocks);
358
494
  }
359
495
  }
496
+ /**
497
+ * Creates inline comment sequence for multiple comments without newlines
498
+ */
499
+ createInlineCommentSequence(comments) {
500
+ const commentTokens = [];
501
+ for (let i = 0; i < comments.length; i++) {
502
+ const comment = comments[i];
503
+ if (comment.trim()) {
504
+ // Add comment token directly
505
+ const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatBlockComment(comment));
506
+ commentTokens.push(commentToken);
507
+ // Add space between comments (except after last comment)
508
+ if (i < comments.length - 1) {
509
+ const spaceToken = new SqlPrintToken(SqlPrintTokenType.space, ' ');
510
+ commentTokens.push(spaceToken);
511
+ }
512
+ }
513
+ }
514
+ return commentTokens;
515
+ }
360
516
  /**
361
517
  * Creates CommentBlock containers for the given comments.
362
518
  * Each CommentBlock contains: Comment -> CommentNewline -> Space
363
519
  * This structure supports both oneliner and multiline formatting modes.
364
520
  */
365
521
  createCommentBlocks(comments) {
522
+ if (this.commentStyle === 'smart') {
523
+ return this.createSmartCommentBlocks(comments);
524
+ }
525
+ // Block style (default) - each comment gets its own block
366
526
  const commentBlocks = [];
367
527
  for (const comment of comments) {
368
- if (comment.trim()) {
528
+ // Accept comments that have content after trim OR are separator lines OR are empty (for structure preservation)
529
+ const trimmed = comment.trim();
530
+ const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
531
+ if (trimmed || isSeparatorLine || comment === '') {
369
532
  commentBlocks.push(this.createSingleCommentBlock(comment));
370
533
  }
371
534
  }
372
535
  return commentBlocks;
373
536
  }
537
+ /**
538
+ * Creates smart comment blocks by merging consecutive block comments into multi-line format
539
+ */
540
+ createSmartCommentBlocks(comments) {
541
+ const commentBlocks = [];
542
+ const blockComments = [];
543
+ const flushBlockComments = () => {
544
+ if (blockComments.length > 0) {
545
+ if (blockComments.length === 1) {
546
+ // Single comment - keep as-is
547
+ commentBlocks.push(this.createSingleCommentBlock(blockComments[0]));
548
+ }
549
+ else {
550
+ // Multiple consecutive comments - create multi-line block
551
+ commentBlocks.push(this.createMultiLineCommentBlock(blockComments));
552
+ }
553
+ blockComments.length = 0;
554
+ }
555
+ };
556
+ for (const comment of comments) {
557
+ const trimmed = comment.trim();
558
+ const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
559
+ if (!trimmed && !isSeparatorLine && comment !== '') {
560
+ continue;
561
+ }
562
+ // Check if this is a block comment that should be merged
563
+ if (this.shouldMergeComment(trimmed)) {
564
+ blockComments.push(comment);
565
+ }
566
+ else {
567
+ // Flush any accumulated block comments first
568
+ flushBlockComments();
569
+ // Add this comment as-is
570
+ commentBlocks.push(this.createSingleCommentBlock(comment));
571
+ }
572
+ }
573
+ // Flush any remaining block comments
574
+ flushBlockComments();
575
+ return commentBlocks;
576
+ }
577
+ /**
578
+ * Determines if a comment should be merged with consecutive comments
579
+ */
580
+ shouldMergeComment(trimmed) {
581
+ const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
582
+ // Don't merge line comments unless they are separator-only lines
583
+ if (!isSeparatorLine && trimmed.startsWith('--')) {
584
+ return false;
585
+ }
586
+ // Don't merge if it's already a proper multi-line block comment
587
+ if (trimmed.startsWith('/*') && trimmed.endsWith('*/') && trimmed.includes('\n')) {
588
+ return false;
589
+ }
590
+ // Merge all other content including separator lines, plain text, and single-line block comments
591
+ // Separator lines within comment blocks should be merged together
592
+ return true;
593
+ }
594
+ /**
595
+ * Creates a multi-line block comment structure from consecutive comments
596
+ * Returns a CommentBlock containing multiple comment lines for proper LinePrinter integration
597
+ */
598
+ createMultiLineCommentBlock(comments) {
599
+ const lines = [];
600
+ for (const comment of comments) {
601
+ const trimmed = comment.trim();
602
+ // Remove existing /* */ markers if present and extract content
603
+ if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
604
+ const content = trimmed.slice(2, -2);
605
+ if (content.trim()) {
606
+ // Sanitize the content (only remove /* and */)
607
+ const sanitized = content
608
+ .replace(/\*\//g, '*') // Remove */ sequences
609
+ .replace(/\/\*/g, '*'); // Remove /* sequences
610
+ // Split multi-line content and add each line
611
+ const contentLines = sanitized.split('\n').map(line => line.trim()).filter(line => line);
612
+ lines.push(...contentLines);
613
+ }
614
+ }
615
+ else if (trimmed) {
616
+ // Sanitize plain text content
617
+ const sanitized = trimmed
618
+ .replace(/\*\//g, '*') // Remove */ sequences
619
+ .replace(/\/\*/g, '*'); // Remove /* sequences
620
+ // Split plain text content by lines
621
+ const contentLines = sanitized.split('\n').map(line => line.trim()).filter(line => line);
622
+ lines.push(...contentLines);
623
+ }
624
+ }
625
+ // Create multi-line comment block structure
626
+ const commentBlock = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommentBlock);
627
+ if (lines.length === 0) {
628
+ // Empty comment
629
+ const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, '/* */');
630
+ commentBlock.innerTokens.push(commentToken);
631
+ }
632
+ else {
633
+ // Opening /*
634
+ const openToken = new SqlPrintToken(SqlPrintTokenType.comment, '/*');
635
+ commentBlock.innerTokens.push(openToken);
636
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
637
+ // Content lines (each as separate comment token)
638
+ for (const line of lines) {
639
+ const lineToken = new SqlPrintToken(SqlPrintTokenType.comment, ` ${line}`);
640
+ commentBlock.innerTokens.push(lineToken);
641
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
642
+ }
643
+ // Closing */
644
+ const closeToken = new SqlPrintToken(SqlPrintTokenType.comment, '*/');
645
+ commentBlock.innerTokens.push(closeToken);
646
+ }
647
+ // Add final newline and space for standard structure
648
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
649
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
650
+ return commentBlock;
651
+ }
652
+ /**
653
+ * Creates a multi-line comment block specifically for headerComments
654
+ * headerComments come as pre-split lines, so we handle them differently
655
+ */
656
+ createHeaderMultiLineCommentBlock(headerComments) {
657
+ // Keep all lines including empty ones to preserve structure
658
+ const lines = headerComments;
659
+ // Create multi-line comment block structure
660
+ const commentBlock = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommentBlock);
661
+ if (lines.length === 0) {
662
+ // Empty comment
663
+ const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, '/* */');
664
+ commentBlock.innerTokens.push(commentToken);
665
+ }
666
+ else {
667
+ // Opening /*
668
+ const openToken = new SqlPrintToken(SqlPrintTokenType.comment, '/*');
669
+ commentBlock.innerTokens.push(openToken);
670
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
671
+ // Content lines (each as separate comment token)
672
+ for (const line of lines) {
673
+ // Sanitize the line content
674
+ const sanitized = line
675
+ .replace(/\*\//g, '*') // Remove */ sequences
676
+ .replace(/\/\*/g, '*'); // Remove /* sequences
677
+ const lineToken = new SqlPrintToken(SqlPrintTokenType.comment, ` ${sanitized}`);
678
+ commentBlock.innerTokens.push(lineToken);
679
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
680
+ }
681
+ // Closing */
682
+ const closeToken = new SqlPrintToken(SqlPrintTokenType.comment, '*/');
683
+ commentBlock.innerTokens.push(closeToken);
684
+ }
685
+ // Add final newline and space for standard structure
686
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
687
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
688
+ return commentBlock;
689
+ }
374
690
  /**
375
691
  * Creates a single CommentBlock with the standard structure:
376
692
  * Comment -> CommentNewline -> Space
@@ -381,8 +697,8 @@ export class SqlPrintTokenParser {
381
697
  */
382
698
  createSingleCommentBlock(comment) {
383
699
  const commentBlock = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommentBlock);
384
- // Add comment token
385
- const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatBlockComment(comment));
700
+ // Add comment token - preserve original format for line comments
701
+ const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatComment(comment));
386
702
  commentBlock.innerTokens.push(commentToken);
387
703
  // Add conditional newline token for multiline mode
388
704
  const commentNewlineToken = new SqlPrintToken(SqlPrintTokenType.commentNewline, '');
@@ -392,6 +708,40 @@ export class SqlPrintTokenParser {
392
708
  commentBlock.innerTokens.push(spaceToken);
393
709
  return commentBlock;
394
710
  }
711
+ /**
712
+ * Formats a comment, preserving line comment format for -- comments
713
+ * and converting others to block format for safety
714
+ */
715
+ formatComment(comment) {
716
+ const trimmed = comment.trim();
717
+ // Smart style processing
718
+ if (this.commentStyle === 'smart') {
719
+ return this.formatCommentSmart(trimmed);
720
+ }
721
+ // Default block style processing
722
+ // If it's already a line comment, preserve it
723
+ // But exclude separator lines (lines with only dashes, equals, etc.)
724
+ if (trimmed.startsWith('--') && !/^--[-=_+*#]*$/.test(trimmed)) {
725
+ return trimmed;
726
+ }
727
+ // If it's already a block comment, preserve it (but sanitize)
728
+ if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
729
+ // Pass the entire comment including /* and */ for proper sanitization
730
+ return this.formatBlockComment(trimmed);
731
+ }
732
+ // For plain text comments, convert to block format
733
+ return this.formatBlockComment(trimmed);
734
+ }
735
+ /**
736
+ * Formats comments using smart style rules:
737
+ * - Only multi-line block comment merging is supported
738
+ * - Single-line comments remain as block comments (no dash conversion)
739
+ */
740
+ formatCommentSmart(comment) {
741
+ // Smart style only affects multi-line comment merging at createSmartCommentBlocks level
742
+ // Individual comment formatting remains the same as block style
743
+ return this.formatBlockComment(comment);
744
+ }
395
745
  /**
396
746
  * Inserts comment blocks into a token and handles spacing logic.
397
747
  * Adds separator spaces for clause-level containers and manages duplicate space removal.
@@ -417,6 +767,18 @@ export class SqlPrintTokenParser {
417
767
  token.innerTokens.unshift(SqlPrintTokenParser.SPACE_TOKEN, ...commentBlocks);
418
768
  return;
419
769
  }
770
+ // Special handling for IdentifierString to add space before comment
771
+ if (token.containerType === SqlPrintTokenContainerType.IdentifierString) {
772
+ // Add space before comment if not already present
773
+ if (token.innerTokens.length > 0) {
774
+ const lastToken = token.innerTokens[token.innerTokens.length - 1];
775
+ if (lastToken.type !== SqlPrintTokenType.space) {
776
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
777
+ }
778
+ }
779
+ token.innerTokens.push(...commentBlocks);
780
+ return;
781
+ }
420
782
  token.innerTokens.unshift(...commentBlocks);
421
783
  // Add a separator space after comments only for certain container types
422
784
  // where comments need to be separated from main content
@@ -439,6 +801,38 @@ export class SqlPrintTokenParser {
439
801
  }
440
802
  }
441
803
  }
804
+ /**
805
+ * Handles positioned comments for ParenExpression with special spacing rules.
806
+ * ParenExpression comments should be adjacent to parentheses without separator spaces.
807
+ */
808
+ addPositionedCommentsToParenExpression(token, component) {
809
+ if (!component.positionedComments) {
810
+ return;
811
+ }
812
+ // For ParenExpression: (/* comment */ content /* comment */)
813
+ // Comments should be placed immediately after opening paren and before closing paren
814
+ // Handle 'before' comments - place after opening parenthesis without space
815
+ const beforeComments = component.getPositionedComments('before');
816
+ if (beforeComments.length > 0) {
817
+ const commentBlocks = this.createCommentBlocks(beforeComments);
818
+ // Insert after opening paren (index 1) without separator space
819
+ let insertIndex = 1;
820
+ for (const commentBlock of commentBlocks) {
821
+ token.innerTokens.splice(insertIndex, 0, commentBlock);
822
+ insertIndex++;
823
+ }
824
+ }
825
+ // Handle 'after' comments - place before closing parenthesis without space
826
+ const afterComments = component.getPositionedComments('after');
827
+ if (afterComments.length > 0) {
828
+ const commentBlocks = this.createCommentBlocks(afterComments);
829
+ // Insert before closing paren (last position) without separator space
830
+ const insertIndex = token.innerTokens.length;
831
+ for (const commentBlock of commentBlocks) {
832
+ token.innerTokens.splice(insertIndex - 1, 0, commentBlock);
833
+ }
834
+ }
835
+ }
442
836
  /**
443
837
  * Determines whether a separator space should be added after comments for the given container type.
444
838
  *
@@ -478,11 +872,21 @@ export class SqlPrintTokenParser {
478
872
  * Prevents SQL injection by removing dangerous comment sequences.
479
873
  */
480
874
  formatBlockComment(comment) {
481
- // Remove dangerous comment termination sequences to prevent SQL injection
482
- const sanitizedComment = comment
875
+ // Sanitize dangerous comment sequences to prevent SQL injection
876
+ let sanitizedComment = comment
483
877
  .replace(/\*\//g, '*') // Remove comment close sequences
484
- .replace(/\/\*/g, '*') // Remove comment open sequences
485
- .replace(/\r?\n/g, ' ') // Replace newlines with spaces for single-line comments
878
+ .replace(/\/\*/g, '*'); // Remove comment open sequences
879
+ // Check if this is a separator line (like ----------) before processing
880
+ const trimmed = sanitizedComment.trim();
881
+ const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
882
+ if (isSeparatorLine) {
883
+ // For separator lines, preserve as-is (already sanitized above)
884
+ return `/* ${trimmed} */`;
885
+ }
886
+ // For multiline comments: convert newlines to spaces (security requirement)
887
+ sanitizedComment = sanitizedComment
888
+ .replace(/\r?\n/g, ' ') // Replace newlines with spaces
889
+ .replace(/\s+/g, ' ') // Collapse multiple spaces into single space
486
890
  .trim(); // Remove leading/trailing whitespace
487
891
  // Return empty string if comment becomes empty after sanitization
488
892
  if (!sanitizedComment) {
@@ -503,10 +907,12 @@ export class SqlPrintTokenParser {
503
907
  visitColumnReference(arg) {
504
908
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ColumnReference);
505
909
  token.innerTokens.push(arg.qualifiedName.accept(this));
910
+ this.addComponentComments(token, arg);
506
911
  return token;
507
912
  }
508
913
  visitFunctionCall(arg) {
509
914
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.FunctionCall);
915
+ this.addComponentComments(token, arg);
510
916
  token.innerTokens.push(arg.qualifiedName.accept(this));
511
917
  token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
512
918
  if (arg.argument) {
@@ -557,7 +963,13 @@ export class SqlPrintTokenParser {
557
963
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.BinaryExpression);
558
964
  token.innerTokens.push(this.visit(arg.left));
559
965
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
560
- token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.operator, arg.operator.value));
966
+ // Visit the operator to handle its comments properly
967
+ const operatorToken = this.visit(arg.operator);
968
+ const operatorLower = operatorToken.text.toLowerCase();
969
+ if (operatorLower === 'and' || operatorLower === 'or') {
970
+ operatorToken.type = SqlPrintTokenType.operator;
971
+ }
972
+ token.innerTokens.push(operatorToken);
561
973
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
562
974
  token.innerTokens.push(this.visit(arg.right));
563
975
  return token;
@@ -578,18 +990,30 @@ export class SqlPrintTokenParser {
578
990
  else {
579
991
  text = arg.value.toString();
580
992
  }
581
- return new SqlPrintToken(SqlPrintTokenType.value, text, SqlPrintTokenContainerType.LiteralValue);
993
+ const token = new SqlPrintToken(SqlPrintTokenType.value, text, SqlPrintTokenContainerType.LiteralValue);
994
+ // Handle positioned comments for LiteralValue
995
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
996
+ this.addPositionedCommentsToToken(token, arg);
997
+ // Clear positioned comments to prevent duplicate processing
998
+ arg.positionedComments = null;
999
+ }
1000
+ else if (arg.comments && arg.comments.length > 0) {
1001
+ this.addCommentsToToken(token, arg.comments);
1002
+ }
1003
+ return token;
582
1004
  }
583
1005
  visitParameterExpression(arg) {
584
1006
  // Create a parameter token and decorate it using the parameterDecorator
585
1007
  arg.index = this.index;
586
1008
  const text = this.parameterDecorator.decorate(arg.name.value, arg.index);
587
1009
  const token = new SqlPrintToken(SqlPrintTokenType.parameter, text);
1010
+ this.addComponentComments(token, arg);
588
1011
  this.index++;
589
1012
  return token;
590
1013
  }
591
1014
  visitSwitchCaseArgument(arg) {
592
1015
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SwitchCaseArgument);
1016
+ this.addComponentComments(token, arg);
593
1017
  // Add each WHEN/THEN clause
594
1018
  for (const kv of arg.cases) {
595
1019
  // Create a new line for each WHEN clause
@@ -599,14 +1023,27 @@ export class SqlPrintTokenParser {
599
1023
  // Add ELSE clause if present
600
1024
  if (arg.elseValue) {
601
1025
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
602
- token.innerTokens.push(this.createElseToken(arg.elseValue));
1026
+ token.innerTokens.push(this.createElseToken(arg.elseValue, arg.comments));
1027
+ }
1028
+ // Add SwitchCaseArgument comments (END keyword) if present and no elseValue
1029
+ else if (arg.comments && arg.comments.length > 0) {
1030
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1031
+ const commentTokens = this.createInlineCommentSequence(arg.comments);
1032
+ token.innerTokens.push(...commentTokens);
603
1033
  }
604
1034
  return token;
605
1035
  }
606
- createElseToken(elseValue) {
1036
+ createElseToken(elseValue, switchCaseComments) {
607
1037
  // Creates a token for the ELSE clause in a CASE expression.
608
1038
  const elseToken = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ElseClause); // Add the ELSE keyword
609
1039
  elseToken.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'else'));
1040
+ // Add ELSE and END keyword comments if present
1041
+ // The switchCaseComments contains both ELSE and END comments in order ['e1', 'end']
1042
+ if (switchCaseComments && switchCaseComments.length > 0) {
1043
+ elseToken.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1044
+ const commentTokens = this.createInlineCommentSequence(switchCaseComments);
1045
+ elseToken.innerTokens.push(...commentTokens);
1046
+ }
610
1047
  // Create a container for the ELSE value to enable proper indentation
611
1048
  elseToken.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
612
1049
  const elseValueContainer = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseElseValue);
@@ -616,12 +1053,24 @@ export class SqlPrintTokenParser {
616
1053
  }
617
1054
  visitCaseKeyValuePair(arg) {
618
1055
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseKeyValuePair);
1056
+ // Handle positioned comments for CaseKeyValuePair
1057
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1058
+ this.addPositionedCommentsToToken(token, arg);
1059
+ // Clear positioned comments to prevent duplicate processing
1060
+ arg.positionedComments = null;
1061
+ }
619
1062
  // Create WHEN clause
620
1063
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'when'));
621
1064
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
622
1065
  token.innerTokens.push(this.visit(arg.key)); // Create THEN clause
623
1066
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
624
1067
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'then'));
1068
+ // Add THEN keyword comments if present
1069
+ if (arg.comments && arg.comments.length > 0) {
1070
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1071
+ const commentTokens = this.createInlineCommentSequence(arg.comments);
1072
+ token.innerTokens.push(...commentTokens);
1073
+ }
625
1074
  // Create a container for the THEN value to enable proper indentation
626
1075
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
627
1076
  const thenValueContainer = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseThenValue);
@@ -636,14 +1085,71 @@ export class SqlPrintTokenParser {
636
1085
  visitIdentifierString(arg) {
637
1086
  // Create an identifier token and decorate it using the identifierDecorator
638
1087
  const text = arg.name === "*" ? arg.name : this.identifierDecorator.decorate(arg.name);
1088
+ // Handle positioned comments for IdentifierString
1089
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1090
+ const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.IdentifierString);
1091
+ // Add positioned comments
1092
+ this.addPositionedCommentsToToken(token, arg);
1093
+ // Clear positioned comments to prevent duplicate processing
1094
+ arg.positionedComments = null;
1095
+ // Add the identifier text as the main token
1096
+ const valueToken = new SqlPrintToken(SqlPrintTokenType.value, text);
1097
+ token.innerTokens.push(valueToken);
1098
+ return token;
1099
+ }
1100
+ // If there are legacy comments, create a container instead of a simple value token
1101
+ if (arg.comments && arg.comments.length > 0) {
1102
+ const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.IdentifierString);
1103
+ // Add the identifier text as the main token
1104
+ const valueToken = new SqlPrintToken(SqlPrintTokenType.value, text);
1105
+ token.innerTokens.push(valueToken);
1106
+ // Add legacy comments to the token
1107
+ this.addComponentComments(token, arg);
1108
+ return token;
1109
+ }
639
1110
  const token = new SqlPrintToken(SqlPrintTokenType.value, text, SqlPrintTokenContainerType.IdentifierString);
640
1111
  return token;
641
1112
  }
642
1113
  visitParenExpression(arg) {
643
1114
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ParenExpression);
1115
+ // Handle positioned comments for ParenExpression - check both self and inner expression
1116
+ const hasOwnComments = arg.positionedComments && arg.positionedComments.length > 0;
1117
+ const hasInnerComments = arg.expression.positionedComments && arg.expression.positionedComments.length > 0;
1118
+ // Store inner comments for later processing and clear to prevent duplicate processing
1119
+ let innerBeforeComments = [];
1120
+ let innerAfterComments = [];
1121
+ if (hasInnerComments) {
1122
+ innerBeforeComments = arg.expression.getPositionedComments('before');
1123
+ innerAfterComments = arg.expression.getPositionedComments('after');
1124
+ arg.expression.positionedComments = null;
1125
+ }
1126
+ // Build basic structure first
644
1127
  token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
645
1128
  token.innerTokens.push(this.visit(arg.expression));
646
1129
  token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
1130
+ // Now add positioned comments in the correct positions manually
1131
+ if (innerBeforeComments.length > 0) {
1132
+ const commentBlocks = this.createCommentBlocks(innerBeforeComments);
1133
+ // Insert after opening paren (index 1) without separator space
1134
+ let insertIndex = 1;
1135
+ for (const commentBlock of commentBlocks) {
1136
+ token.innerTokens.splice(insertIndex, 0, commentBlock);
1137
+ insertIndex++;
1138
+ }
1139
+ }
1140
+ if (innerAfterComments.length > 0) {
1141
+ const commentBlocks = this.createCommentBlocks(innerAfterComments);
1142
+ // Insert before closing paren (last position) without separator space
1143
+ const insertIndex = token.innerTokens.length;
1144
+ for (const commentBlock of commentBlocks) {
1145
+ token.innerTokens.splice(insertIndex - 1, 0, commentBlock);
1146
+ }
1147
+ }
1148
+ if (hasOwnComments) {
1149
+ this.addPositionedCommentsToParenExpression(token, arg);
1150
+ // Clear positioned comments to prevent duplicate processing in parent containers
1151
+ arg.positionedComments = null;
1152
+ }
647
1153
  return token;
648
1154
  }
649
1155
  visitCastExpression(arg) {
@@ -655,6 +1161,12 @@ export class SqlPrintTokenParser {
655
1161
  }
656
1162
  visitCaseExpression(arg) {
657
1163
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseExpression);
1164
+ // Handle positioned comments for CaseExpression (unified spec: positioned comments only)
1165
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1166
+ this.addPositionedCommentsToToken(token, arg);
1167
+ // Clear positioned comments to prevent duplicate processing
1168
+ arg.positionedComments = null;
1169
+ }
658
1170
  // Add the CASE keyword
659
1171
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'case'));
660
1172
  // Add the condition if exists
@@ -743,6 +1255,7 @@ export class SqlPrintTokenParser {
743
1255
  }
744
1256
  visitTypeValue(arg) {
745
1257
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.TypeValue);
1258
+ this.addComponentComments(token, arg);
746
1259
  token.innerTokens.push(arg.qualifiedName.accept(this));
747
1260
  if (arg.argument) {
748
1261
  token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
@@ -836,7 +1349,32 @@ export class SqlPrintTokenParser {
836
1349
  }
837
1350
  visitSelectItem(arg) {
838
1351
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SelectItem);
1352
+ // Preserve original positioned comments to avoid mutating the source object
1353
+ const originalSelectItemPositionedComments = arg.positionedComments;
1354
+ const originalValuePositionedComments = arg.value.positionedComments;
1355
+ // Clear positioned comments from the value to avoid duplication since SelectItem handles them
1356
+ arg.value.positionedComments = null;
1357
+ // Add 'before' positioned comments
1358
+ const beforeComments = arg.getPositionedComments('before');
1359
+ if (beforeComments.length > 0) {
1360
+ const commentTokens = this.createInlineCommentSequence(beforeComments);
1361
+ token.innerTokens.push(...commentTokens);
1362
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1363
+ }
1364
+ // Add the value (column name)
839
1365
  token.innerTokens.push(this.visit(arg.value));
1366
+ // Add 'after' positioned comments for the value
1367
+ // Skip after comments if the value is ParenExpression (already handled in ParenExpression processing)
1368
+ const afterComments = arg.getPositionedComments('after');
1369
+ const isParenExpression = arg.value.constructor.name === 'ParenExpression';
1370
+ if (afterComments.length > 0 && !isParenExpression) {
1371
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1372
+ const commentTokens = this.createInlineCommentSequence(afterComments);
1373
+ token.innerTokens.push(...commentTokens);
1374
+ }
1375
+ // Restore original positioned comments to avoid side effects
1376
+ arg.positionedComments = originalSelectItemPositionedComments;
1377
+ arg.value.positionedComments = originalValuePositionedComments;
840
1378
  if (!arg.identifier) {
841
1379
  return token;
842
1380
  }
@@ -849,13 +1387,70 @@ export class SqlPrintTokenParser {
849
1387
  }
850
1388
  // Add alias if it is different from the default name
851
1389
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1390
+ // Handle AS keyword positioned comments (before AS)
1391
+ const asKeywordPositionedComments = 'asKeywordPositionedComments' in arg ? arg.asKeywordPositionedComments : null;
1392
+ if (asKeywordPositionedComments) {
1393
+ const beforeComments = asKeywordPositionedComments.filter((pc) => pc.position === 'before');
1394
+ if (beforeComments.length > 0) {
1395
+ for (const posComment of beforeComments) {
1396
+ const commentTokens = this.createInlineCommentSequence(posComment.comments);
1397
+ token.innerTokens.push(...commentTokens);
1398
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1399
+ }
1400
+ }
1401
+ }
852
1402
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
1403
+ // Handle AS keyword positioned comments (after AS)
1404
+ if (asKeywordPositionedComments) {
1405
+ const afterComments = asKeywordPositionedComments.filter((pc) => pc.position === 'after');
1406
+ if (afterComments.length > 0) {
1407
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1408
+ for (const posComment of afterComments) {
1409
+ const commentTokens = this.createInlineCommentSequence(posComment.comments);
1410
+ token.innerTokens.push(...commentTokens);
1411
+ }
1412
+ }
1413
+ }
1414
+ // Fallback: Add AS keyword legacy comments if present
1415
+ const asKeywordComments = 'asKeywordComments' in arg ? arg.asKeywordComments : null;
1416
+ if (asKeywordComments && asKeywordComments.length > 0) {
1417
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1418
+ const commentTokens = this.createInlineCommentSequence(asKeywordComments);
1419
+ token.innerTokens.push(...commentTokens);
1420
+ }
853
1421
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
854
- token.innerTokens.push(this.visit(arg.identifier));
1422
+ // Visit identifier to get alias with proper spacing
1423
+ const identifierToken = this.visit(arg.identifier);
1424
+ token.innerTokens.push(identifierToken);
1425
+ // Handle alias positioned comments (after alias)
1426
+ const aliasPositionedComments = 'aliasPositionedComments' in arg ? arg.aliasPositionedComments : null;
1427
+ if (aliasPositionedComments) {
1428
+ const afterComments = aliasPositionedComments.filter((pc) => pc.position === 'after');
1429
+ if (afterComments.length > 0) {
1430
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1431
+ for (const posComment of afterComments) {
1432
+ const commentTokens = this.createInlineCommentSequence(posComment.comments);
1433
+ token.innerTokens.push(...commentTokens);
1434
+ }
1435
+ }
1436
+ }
1437
+ // Fallback: Add alias legacy comments if present
1438
+ const aliasComments = arg.aliasComments;
1439
+ if (aliasComments && aliasComments.length > 0) {
1440
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1441
+ const commentTokens = this.createInlineCommentSequence(aliasComments);
1442
+ token.innerTokens.push(...commentTokens);
1443
+ }
855
1444
  return token;
856
1445
  }
857
1446
  visitSelectClause(arg) {
858
1447
  const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'select', SqlPrintTokenContainerType.SelectClause);
1448
+ // Handle positioned comments for SelectClause (unified spec)
1449
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1450
+ this.addPositionedCommentsToToken(token, arg);
1451
+ // Clear positioned comments to prevent duplicate processing
1452
+ arg.positionedComments = null;
1453
+ }
859
1454
  // Handle hints and DISTINCT as part of the keyword line
860
1455
  let selectKeywordText = 'select';
861
1456
  // Add hint clauses immediately after SELECT (before DISTINCT)
@@ -903,6 +1498,12 @@ export class SqlPrintTokenParser {
903
1498
  }
904
1499
  visitDistinct(arg) {
905
1500
  const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'distinct');
1501
+ // Handle positioned comments for Distinct (unified spec)
1502
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1503
+ this.addPositionedCommentsToToken(token, arg);
1504
+ // Clear positioned comments to prevent duplicate processing
1505
+ arg.positionedComments = null;
1506
+ }
906
1507
  return token;
907
1508
  }
908
1509
  visitDistinctOn(arg) {
@@ -921,6 +1522,7 @@ export class SqlPrintTokenParser {
921
1522
  }
922
1523
  fullName += arg.table.accept(this).text;
923
1524
  const token = new SqlPrintToken(SqlPrintTokenType.value, fullName);
1525
+ this.addComponentComments(token, arg);
924
1526
  // alias (if present and different from table name)
925
1527
  if (arg.identifier && arg.identifier.name !== arg.table.name) {
926
1528
  }
@@ -971,8 +1573,38 @@ export class SqlPrintTokenParser {
971
1573
  visitJoinClause(arg) {
972
1574
  // Print join clause: [joinType] [lateral] [source] [on/using ...]
973
1575
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.JoinClause);
1576
+ // Handle JOIN keyword positioned comments (before JOIN)
1577
+ const joinKeywordPositionedComments = arg.joinKeywordPositionedComments;
1578
+ if (joinKeywordPositionedComments) {
1579
+ const beforeComments = joinKeywordPositionedComments.filter((pc) => pc.position === 'before');
1580
+ if (beforeComments.length > 0) {
1581
+ for (const posComment of beforeComments) {
1582
+ const commentTokens = this.createInlineCommentSequence(posComment.comments);
1583
+ token.innerTokens.push(...commentTokens);
1584
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1585
+ }
1586
+ }
1587
+ }
974
1588
  // join type (e.g. inner join, left join, etc)
975
1589
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.joinType.value));
1590
+ // Handle JOIN keyword positioned comments (after JOIN)
1591
+ if (joinKeywordPositionedComments) {
1592
+ const afterComments = joinKeywordPositionedComments.filter((pc) => pc.position === 'after');
1593
+ if (afterComments.length > 0) {
1594
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1595
+ for (const posComment of afterComments) {
1596
+ const commentTokens = this.createInlineCommentSequence(posComment.comments);
1597
+ token.innerTokens.push(...commentTokens);
1598
+ }
1599
+ }
1600
+ }
1601
+ // Fallback: Add JOIN keyword legacy comments if present
1602
+ const joinKeywordComments = arg.joinKeywordComments;
1603
+ if (joinKeywordComments && joinKeywordComments.length > 0) {
1604
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1605
+ const commentTokens = this.createInlineCommentSequence(joinKeywordComments);
1606
+ token.innerTokens.push(...commentTokens);
1607
+ }
976
1608
  if (arg.lateral) {
977
1609
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
978
1610
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'lateral'));
@@ -1025,10 +1657,20 @@ export class SqlPrintTokenParser {
1025
1657
  }
1026
1658
  token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
1027
1659
  }
1660
+ // Handle positioned comments for SourceAliasExpression (alias name comments)
1661
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1662
+ this.addPositionedCommentsToToken(token, arg);
1663
+ // Clear positioned comments to prevent duplicate processing
1664
+ arg.positionedComments = null;
1665
+ }
1666
+ else if (arg.comments && arg.comments.length > 0) {
1667
+ this.addCommentsToToken(token, arg.comments);
1668
+ }
1028
1669
  return token;
1029
1670
  }
1030
1671
  visitWhereClause(arg) {
1031
1672
  const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'where', SqlPrintTokenContainerType.WhereClause);
1673
+ this.addComponentComments(token, arg);
1032
1674
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1033
1675
  token.innerTokens.push(this.visit(arg.condition));
1034
1676
  return token;
@@ -1121,10 +1763,20 @@ export class SqlPrintTokenParser {
1121
1763
  token.innerTokens.push(arg.tables[i].accept(this));
1122
1764
  }
1123
1765
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1766
+ this.addComponentComments(token, arg);
1124
1767
  return token;
1125
1768
  }
1126
1769
  visitCommonTable(arg) {
1127
1770
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommonTable);
1771
+ // Handle positioned comments for CommonTable (avoid duplication)
1772
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1773
+ this.addPositionedCommentsToToken(token, arg);
1774
+ // Clear positioned comments to prevent duplicate processing
1775
+ arg.positionedComments = null;
1776
+ }
1777
+ else if (arg.comments && arg.comments.length > 0) {
1778
+ this.addCommentsToToken(token, arg.comments);
1779
+ }
1128
1780
  token.innerTokens.push(arg.aliasExpression.accept(this));
1129
1781
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1130
1782
  token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
@@ -1148,14 +1800,27 @@ export class SqlPrintTokenParser {
1148
1800
  // query
1149
1801
  visitSimpleQuery(arg) {
1150
1802
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SimpleSelectQuery);
1151
- // Add headerComments at the very beginning (before WITH clause)
1803
+ // Handle positioned comments for SimpleSelectQuery (unified spec)
1152
1804
  if (arg.headerComments && arg.headerComments.length > 0) {
1153
- const headerCommentBlocks = this.createCommentBlocks(arg.headerComments);
1154
- token.innerTokens.push(...headerCommentBlocks);
1805
+ // Fallback to legacy headerComments if no positioned comments
1806
+ // For smart comment style, treat headerComments as a single multi-line block
1807
+ if (this.commentStyle === 'smart' && arg.headerComments.length > 1) {
1808
+ const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
1809
+ token.innerTokens.push(mergedHeaderComment);
1810
+ }
1811
+ else {
1812
+ const headerCommentBlocks = this.createCommentBlocks(arg.headerComments);
1813
+ token.innerTokens.push(...headerCommentBlocks);
1814
+ }
1155
1815
  if (arg.withClause) {
1156
1816
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1157
1817
  }
1158
1818
  }
1819
+ if (arg.positionedComments && arg.positionedComments.length > 0) {
1820
+ this.addPositionedCommentsToToken(token, arg);
1821
+ // Clear positioned comments to prevent duplicate processing
1822
+ arg.positionedComments = null;
1823
+ }
1159
1824
  if (arg.withClause) {
1160
1825
  token.innerTokens.push(arg.withClause.accept(this));
1161
1826
  }
@@ -1371,4 +2036,7 @@ SqlPrintTokenParser.ARGUMENT_SPLIT_COMMA_TOKEN = new SqlPrintToken(SqlPrintToken
1371
2036
  SqlPrintTokenParser.PAREN_OPEN_TOKEN = new SqlPrintToken(SqlPrintTokenType.parenthesis, '(');
1372
2037
  SqlPrintTokenParser.PAREN_CLOSE_TOKEN = new SqlPrintToken(SqlPrintTokenType.parenthesis, ')');
1373
2038
  SqlPrintTokenParser.DOT_TOKEN = new SqlPrintToken(SqlPrintTokenType.dot, '.');
2039
+ // Set of component kinds that handle their own positioned comments
2040
+ // Note: Cannot use static readonly due to circular dependency issues with class initialization
2041
+ SqlPrintTokenParser._selfHandlingComponentTypes = null;
1374
2042
  //# sourceMappingURL=SqlPrintTokenParser.js.map