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