rawsql-ts 0.11.42-beta → 0.11.43-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 (32) hide show
  1. package/README.md +0 -1
  2. package/dist/esm/index.min.js +15 -6
  3. package/dist/esm/index.min.js.map +2 -2
  4. package/dist/esm/src/parsers/SqlPrintTokenParser.d.ts +11 -18
  5. package/dist/esm/src/parsers/SqlPrintTokenParser.js +126 -218
  6. package/dist/esm/src/parsers/SqlPrintTokenParser.js.map +1 -1
  7. package/dist/esm/src/transformers/LinePrinter.d.ts +1 -0
  8. package/dist/esm/src/transformers/LinePrinter.js +22 -1
  9. package/dist/esm/src/transformers/LinePrinter.js.map +1 -1
  10. package/dist/esm/src/transformers/SqlFormatter.d.ts +0 -2
  11. package/dist/esm/src/transformers/SqlFormatter.js +1 -1
  12. package/dist/esm/src/transformers/SqlFormatter.js.map +1 -1
  13. package/dist/esm/src/transformers/SqlPrinter.d.ts +17 -2
  14. package/dist/esm/src/transformers/SqlPrinter.js +383 -18
  15. package/dist/esm/src/transformers/SqlPrinter.js.map +1 -1
  16. package/dist/esm/tsconfig.browser.tsbuildinfo +1 -1
  17. package/dist/index.min.js +15 -6
  18. package/dist/index.min.js.map +2 -2
  19. package/dist/src/parsers/SqlPrintTokenParser.d.ts +11 -18
  20. package/dist/src/parsers/SqlPrintTokenParser.js +126 -218
  21. package/dist/src/parsers/SqlPrintTokenParser.js.map +1 -1
  22. package/dist/src/transformers/LinePrinter.d.ts +1 -0
  23. package/dist/src/transformers/LinePrinter.js +22 -1
  24. package/dist/src/transformers/LinePrinter.js.map +1 -1
  25. package/dist/src/transformers/SqlFormatter.d.ts +0 -2
  26. package/dist/src/transformers/SqlFormatter.js +0 -1
  27. package/dist/src/transformers/SqlFormatter.js.map +1 -1
  28. package/dist/src/transformers/SqlPrinter.d.ts +17 -2
  29. package/dist/src/transformers/SqlPrinter.js +383 -18
  30. package/dist/src/transformers/SqlPrinter.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +1 -1
@@ -39,7 +39,6 @@ export declare class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrint
39
39
  parameterDecorator: ParameterDecorator;
40
40
  identifierDecorator: IdentifierDecorator;
41
41
  index: number;
42
- private commentStyle;
43
42
  constructor(options?: {
44
43
  preset?: FormatterConfig;
45
44
  identifierEscape?: {
@@ -51,7 +50,6 @@ export declare class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrint
51
50
  end: string;
52
51
  };
53
52
  parameterStyle?: 'anonymous' | 'indexed' | 'named';
54
- commentStyle?: 'block' | 'smart';
55
53
  });
56
54
  /**
57
55
  * Pretty-prints a BinarySelectQuery (e.g., UNION, INTERSECT, EXCEPT).
@@ -111,10 +109,6 @@ export declare class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrint
111
109
  * This structure supports both oneliner and multiline formatting modes.
112
110
  */
113
111
  private createCommentBlocks;
114
- /**
115
- * Creates smart comment blocks by merging consecutive block comments into multi-line format
116
- */
117
- private createSmartCommentBlocks;
118
112
  /**
119
113
  * Determines if a comment should be merged with consecutive comments
120
114
  */
@@ -123,12 +117,6 @@ export declare class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrint
123
117
  * Creates a multi-line block comment structure from consecutive comments
124
118
  * Returns a CommentBlock containing multiple comment lines for proper LinePrinter integration
125
119
  */
126
- private createMultiLineCommentBlock;
127
- /**
128
- * Creates a multi-line comment block specifically for headerComments
129
- * headerComments come as pre-split lines, so we handle them differently
130
- */
131
- private createHeaderMultiLineCommentBlock;
132
120
  /**
133
121
  * Creates a single CommentBlock with the standard structure:
134
122
  * Comment -> CommentNewline -> Space
@@ -143,12 +131,6 @@ export declare class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrint
143
131
  * and converting others to block format for safety
144
132
  */
145
133
  private formatComment;
146
- /**
147
- * Formats comments using smart style rules:
148
- * - Only multi-line block comment merging is supported
149
- * - Single-line comments remain as block comments (no dash conversion)
150
- */
151
- private formatCommentSmart;
152
134
  /**
153
135
  * Inserts comment blocks into a token and handles spacing logic.
154
136
  * Adds separator spaces for clause-level containers and manages duplicate space removal.
@@ -180,6 +162,17 @@ export declare class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrint
180
162
  * Prevents SQL injection by removing dangerous comment sequences.
181
163
  */
182
164
  private formatBlockComment;
165
+ private shouldMergeHeaderComments;
166
+ private createHeaderMultiLineCommentBlock;
167
+ /**
168
+ * Formats text as a single-line comment while sanitizing unsafe sequences.
169
+ */
170
+ private formatLineComment;
171
+ /**
172
+ * Sanitizes content intended for a single-line comment.
173
+ */
174
+ private sanitizeLineCommentContent;
175
+ private escapeCommentDelimiters;
183
176
  private visitValueList;
184
177
  private visitColumnReference;
185
178
  private visitFunctionCall;
@@ -139,10 +139,9 @@ export class SqlPrintTokenParser {
139
139
  return this._selfHandlingComponentTypes;
140
140
  }
141
141
  constructor(options) {
142
- var _a, _b, _c, _d, _e, _f, _g, _h;
142
+ var _a, _b, _c, _d, _e, _f, _g;
143
143
  this.handlers = new Map();
144
144
  this.index = 1;
145
- this.commentStyle = 'block';
146
145
  if (options === null || options === void 0 ? void 0 : options.preset) {
147
146
  const preset = options.preset;
148
147
  options = Object.assign(Object.assign({}, preset), options);
@@ -156,7 +155,6 @@ export class SqlPrintTokenParser {
156
155
  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 : '"',
157
156
  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 : '"'
158
157
  });
159
- this.commentStyle = (_h = options === null || options === void 0 ? void 0 : options.commentStyle) !== null && _h !== void 0 ? _h : 'block';
160
158
  this.handlers.set(ValueList.kind, (expr) => this.visitValueList(expr));
161
159
  this.handlers.set(ColumnReference.kind, (expr) => this.visitColumnReference(expr));
162
160
  this.handlers.set(QualifiedName.kind, (expr) => this.visitQualifiedName(expr));
@@ -247,9 +245,7 @@ export class SqlPrintTokenParser {
247
245
  arg.positionedComments = null;
248
246
  }
249
247
  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) {
248
+ if (this.shouldMergeHeaderComments(arg.headerComments)) {
253
249
  const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
254
250
  token.innerTokens.push(mergedHeaderComment);
255
251
  }
@@ -438,14 +434,9 @@ export class SqlPrintTokenParser {
438
434
  const beforeComments = component.getPositionedComments('before');
439
435
  if (beforeComments.length > 0) {
440
436
  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, ' '));
437
+ for (let i = commentBlocks.length - 1; i >= 0; i--) {
438
+ token.innerTokens.unshift(commentBlocks[i]);
446
439
  }
447
- // Insert before the existing content
448
- token.innerTokens.unshift(...beforeTokens);
449
440
  }
450
441
  // Handle 'after' comments - add inline after the main content
451
442
  const afterComments = component.getPositionedComments('after');
@@ -453,7 +444,7 @@ export class SqlPrintTokenParser {
453
444
  const commentBlocks = this.createCommentBlocks(afterComments);
454
445
  // Append after comments with spaces for inline formatting
455
446
  for (const commentBlock of commentBlocks) {
456
- token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
447
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
457
448
  token.innerTokens.push(commentBlock);
458
449
  }
459
450
  }
@@ -482,16 +473,8 @@ export class SqlPrintTokenParser {
482
473
  if (!(comments === null || comments === void 0 ? void 0 : comments.length)) {
483
474
  return;
484
475
  }
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);
493
- this.insertCommentBlocksWithSpacing(token, commentBlocks);
494
- }
476
+ const commentBlocks = this.createCommentBlocks(comments);
477
+ this.insertCommentBlocksWithSpacing(token, commentBlocks);
495
478
  }
496
479
  /**
497
480
  * Creates inline comment sequence for multiple comments without newlines
@@ -502,7 +485,7 @@ export class SqlPrintTokenParser {
502
485
  const comment = comments[i];
503
486
  if (comment.trim()) {
504
487
  // Add comment token directly
505
- const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatBlockComment(comment));
488
+ const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatComment(comment));
506
489
  commentTokens.push(commentToken);
507
490
  // Add space between comments (except after last comment)
508
491
  if (i < comments.length - 1) {
@@ -519,10 +502,7 @@ export class SqlPrintTokenParser {
519
502
  * This structure supports both oneliner and multiline formatting modes.
520
503
  */
521
504
  createCommentBlocks(comments) {
522
- if (this.commentStyle === 'smart') {
523
- return this.createSmartCommentBlocks(comments);
524
- }
525
- // Block style (default) - each comment gets its own block
505
+ // Create individual comment blocks for each comment entry
526
506
  const commentBlocks = [];
527
507
  for (const comment of comments) {
528
508
  // Accept comments that have content after trim OR are separator lines OR are empty (for structure preservation)
@@ -534,46 +514,6 @@ export class SqlPrintTokenParser {
534
514
  }
535
515
  return commentBlocks;
536
516
  }
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
517
  /**
578
518
  * Determines if a comment should be merged with consecutive comments
579
519
  */
@@ -584,8 +524,14 @@ export class SqlPrintTokenParser {
584
524
  return false;
585
525
  }
586
526
  // 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;
527
+ if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
528
+ const inner = trimmed.slice(2, -2).trim();
529
+ if (!inner) {
530
+ return false;
531
+ }
532
+ if (trimmed.includes('\n')) {
533
+ return false;
534
+ }
589
535
  }
590
536
  // Merge all other content including separator lines, plain text, and single-line block comments
591
537
  // Separator lines within comment blocks should be merged together
@@ -595,98 +541,6 @@ export class SqlPrintTokenParser {
595
541
  * Creates a multi-line block comment structure from consecutive comments
596
542
  * Returns a CommentBlock containing multiple comment lines for proper LinePrinter integration
597
543
  */
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
- }
690
544
  /**
691
545
  * Creates a single CommentBlock with the standard structure:
692
546
  * Comment -> CommentNewline -> Space
@@ -714,42 +568,28 @@ export class SqlPrintTokenParser {
714
568
  */
715
569
  formatComment(comment) {
716
570
  const trimmed = comment.trim();
717
- // Smart style processing
718
- if (this.commentStyle === 'smart') {
719
- return this.formatCommentSmart(trimmed);
571
+ if (!trimmed) {
572
+ return '/* */';
573
+ }
574
+ const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
575
+ if (isSeparatorLine) {
576
+ return this.formatBlockComment(trimmed);
720
577
  }
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;
578
+ if (trimmed.startsWith('--')) {
579
+ return this.formatLineComment(trimmed.slice(2));
726
580
  }
727
- // If it's already a block comment, preserve it (but sanitize)
728
581
  if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
729
- // Pass the entire comment including /* and */ for proper sanitization
730
582
  return this.formatBlockComment(trimmed);
731
583
  }
732
- // For plain text comments, convert to block format
733
584
  return this.formatBlockComment(trimmed);
734
585
  }
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
- }
745
586
  /**
746
587
  * Inserts comment blocks into a token and handles spacing logic.
747
588
  * Adds separator spaces for clause-level containers and manages duplicate space removal.
748
589
  */
749
590
  insertCommentBlocksWithSpacing(token, commentBlocks) {
750
- // For SelectItem, append comments at the end with proper spacing
591
+ // For SelectItem, append comment blocks after ensuring spacing
751
592
  if (token.containerType === SqlPrintTokenContainerType.SelectItem) {
752
- // Add space before comment if not already present
753
593
  if (token.innerTokens.length > 0) {
754
594
  const lastToken = token.innerTokens[token.innerTokens.length - 1];
755
595
  if (lastToken.type !== SqlPrintTokenType.space) {
@@ -769,7 +609,6 @@ export class SqlPrintTokenParser {
769
609
  }
770
610
  // Special handling for IdentifierString to add space before comment
771
611
  if (token.containerType === SqlPrintTokenContainerType.IdentifierString) {
772
- // Add space before comment if not already present
773
612
  if (token.innerTokens.length > 0) {
774
613
  const lastToken = token.innerTokens[token.innerTokens.length - 1];
775
614
  if (lastToken.type !== SqlPrintTokenType.space) {
@@ -872,27 +711,94 @@ export class SqlPrintTokenParser {
872
711
  * Prevents SQL injection by removing dangerous comment sequences.
873
712
  */
874
713
  formatBlockComment(comment) {
875
- // Sanitize dangerous comment sequences to prevent SQL injection
876
- let sanitizedComment = comment
877
- .replace(/\*\//g, '*') // Remove comment close sequences
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} */`;
714
+ const hasDelimiters = comment.startsWith('/*') && comment.endsWith('*/');
715
+ const rawContent = hasDelimiters ? comment.slice(2, -2) : comment;
716
+ const escapedContent = this.escapeCommentDelimiters(rawContent);
717
+ const normalized = escapedContent.replace(/\r?\n/g, '\n');
718
+ const lines = normalized
719
+ .split('\n')
720
+ .map(line => line.replace(/\s+/g, ' ').trim())
721
+ .filter(line => line.length > 0);
722
+ if (lines.length === 0) {
723
+ return '/* */';
724
+ }
725
+ const isSeparatorLine = lines.length === 1 && /^[-=_+*#]+$/.test(lines[0]);
726
+ if (!hasDelimiters) {
727
+ // Flatten free-form comments to a single block to avoid leaking multi-line structures.
728
+ if (isSeparatorLine) {
729
+ return `/* ${lines[0]} */`;
730
+ }
731
+ const flattened = lines.join(' ');
732
+ return `/* ${flattened} */`;
733
+ }
734
+ if (isSeparatorLine || lines.length === 1) {
735
+ return `/* ${lines[0]} */`;
736
+ }
737
+ const body = lines.map(line => ` ${line}`).join('\n');
738
+ return `/*\n${body}\n*/`;
739
+ }
740
+ shouldMergeHeaderComments(comments) {
741
+ if (comments.length <= 1) {
742
+ return false;
743
+ }
744
+ return comments.some(comment => {
745
+ const trimmed = comment.trim();
746
+ return /^[-=_+*#]{3,}$/.test(trimmed) || trimmed.startsWith('- ') || trimmed.startsWith('* ');
747
+ });
748
+ }
749
+ createHeaderMultiLineCommentBlock(headerComments) {
750
+ const commentBlock = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommentBlock);
751
+ if (headerComments.length === 0) {
752
+ const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, '/* */');
753
+ commentBlock.innerTokens.push(commentToken);
885
754
  }
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
890
- .trim(); // Remove leading/trailing whitespace
891
- // Return empty string if comment becomes empty after sanitization
892
- if (!sanitizedComment) {
893
- return '';
755
+ else {
756
+ const openToken = new SqlPrintToken(SqlPrintTokenType.comment, '/*');
757
+ commentBlock.innerTokens.push(openToken);
758
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
759
+ for (const line of headerComments) {
760
+ const sanitized = this.escapeCommentDelimiters(line);
761
+ const lineToken = new SqlPrintToken(SqlPrintTokenType.comment, ` ${sanitized}`);
762
+ commentBlock.innerTokens.push(lineToken);
763
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
764
+ }
765
+ const closeToken = new SqlPrintToken(SqlPrintTokenType.comment, '*/');
766
+ commentBlock.innerTokens.push(closeToken);
894
767
  }
895
- return `/* ${sanitizedComment} */`;
768
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
769
+ commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
770
+ return commentBlock;
771
+ }
772
+ /**
773
+ * Formats text as a single-line comment while sanitizing unsafe sequences.
774
+ */
775
+ formatLineComment(content) {
776
+ // Normalize content to a single line and remove dangerous sequences
777
+ const sanitized = this.sanitizeLineCommentContent(content);
778
+ if (!sanitized) {
779
+ return '--';
780
+ }
781
+ return `-- ${sanitized}`;
782
+ }
783
+ /**
784
+ * Sanitizes content intended for a single-line comment.
785
+ */
786
+ sanitizeLineCommentContent(content) {
787
+ // Replace comment delimiters to avoid nested comment injection
788
+ let sanitized = this.escapeCommentDelimiters(content)
789
+ .replace(/\r?\n/g, ' ')
790
+ .replace(/\u2028|\u2029/g, ' ')
791
+ .replace(/\s+/g, ' ')
792
+ .trim();
793
+ if (sanitized.startsWith('--')) {
794
+ sanitized = sanitized.slice(2).trimStart();
795
+ }
796
+ return sanitized;
797
+ }
798
+ escapeCommentDelimiters(content) {
799
+ return content
800
+ .replace(/\/\*/g, '\\/\\*')
801
+ .replace(/\*\//g, '*\\/');
896
802
  }
897
803
  visitValueList(arg) {
898
804
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
@@ -1355,19 +1261,16 @@ export class SqlPrintTokenParser {
1355
1261
  const originalValuePositionedComments = arg.value.positionedComments;
1356
1262
  // Clear positioned comments from the value to avoid duplication since SelectItem handles them
1357
1263
  arg.value.positionedComments = null;
1358
- // Add 'before' positioned comments
1264
+ // Add positioned comments in recorded order
1359
1265
  const beforeComments = arg.getPositionedComments('before');
1266
+ const afterComments = arg.getPositionedComments('after');
1267
+ const isParenExpression = arg.value.constructor.name === 'ParenExpression';
1360
1268
  if (beforeComments.length > 0) {
1361
1269
  const commentTokens = this.createInlineCommentSequence(beforeComments);
1362
1270
  token.innerTokens.push(...commentTokens);
1363
1271
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1364
1272
  }
1365
- // Add the value (column name)
1366
1273
  token.innerTokens.push(this.visit(arg.value));
1367
- // Add 'after' positioned comments for the value
1368
- // Skip after comments if the value is ParenExpression (already handled in ParenExpression processing)
1369
- const afterComments = arg.getPositionedComments('after');
1370
- const isParenExpression = arg.value.constructor.name === 'ParenExpression';
1371
1274
  if (afterComments.length > 0 && !isParenExpression) {
1372
1275
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1373
1276
  const commentTokens = this.createInlineCommentSequence(afterComments);
@@ -1803,9 +1706,7 @@ export class SqlPrintTokenParser {
1803
1706
  const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SimpleSelectQuery);
1804
1707
  // Handle positioned comments for SimpleSelectQuery (unified spec)
1805
1708
  if (arg.headerComments && arg.headerComments.length > 0) {
1806
- // Fallback to legacy headerComments if no positioned comments
1807
- // For smart comment style, treat headerComments as a single multi-line block
1808
- if (this.commentStyle === 'smart' && arg.headerComments.length > 1) {
1709
+ if (this.shouldMergeHeaderComments(arg.headerComments)) {
1809
1710
  const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
1810
1711
  token.innerTokens.push(mergedHeaderComment);
1811
1712
  }
@@ -1891,11 +1792,18 @@ export class SqlPrintTokenParser {
1891
1792
  const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'values', SqlPrintTokenContainerType.ValuesQuery);
1892
1793
  // Add headerComments before VALUES keyword
1893
1794
  if (arg.headerComments && arg.headerComments.length > 0) {
1894
- const headerCommentBlocks = this.createCommentBlocks(arg.headerComments);
1895
- for (const commentBlock of headerCommentBlocks) {
1896
- token.innerTokens.push(commentBlock);
1795
+ if (this.shouldMergeHeaderComments(arg.headerComments)) {
1796
+ const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
1797
+ token.innerTokens.push(mergedHeaderComment);
1897
1798
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1898
1799
  }
1800
+ else {
1801
+ const headerCommentBlocks = this.createCommentBlocks(arg.headerComments);
1802
+ for (const commentBlock of headerCommentBlocks) {
1803
+ token.innerTokens.push(commentBlock);
1804
+ token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1805
+ }
1806
+ }
1899
1807
  }
1900
1808
  token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
1901
1809
  const values = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.Values);