Skip to content

Commit 502224c

Browse files
committed
Merge branch 'fix-block-comment-idempotency'
2 parents a3bfeeb + f9304a6 commit 502224c

4 files changed

Lines changed: 77 additions & 3 deletions

File tree

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Sean Song <mail@seansong.dev>
6161
Sebastian Lyng Johansen <seblyng98@gmail.com>
6262
Sergei Egorov <sergei.egorov@zeroturnaround.com>
6363
Sai Asish Y <say.apm35@gmail.com>
64+
Sarath Francis <sarathfrancis90@gmail.com>
6465
Shub <shub1493biswas@gmail.com>
6566
Stanislav Germanovskii <s.germanovskiy@tinkoff.ru>
6667
Steven Yung <stevenyung@fastmail.com>

src/formatter/ExpressionFormatter.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export default class ExpressionFormatter {
378378
}
379379

380380
private formatBlockComment(node: BlockCommentNode | DisableCommentNode) {
381-
if (node.type === NodeType.block_comment && this.isMultilineBlockComment(node)) {
381+
if (node.type === NodeType.block_comment && this.isStandaloneBlockComment(node)) {
382382
this.splitBlockComment(node.text).forEach(line => {
383383
this.layout.add(WS.NEWLINE, WS.INDENT, line);
384384
});
@@ -388,8 +388,20 @@ export default class ExpressionFormatter {
388388
}
389389
}
390390

391-
private isMultilineBlockComment(node: BlockCommentNode): boolean {
392-
return isMultiline(node.text) || isMultiline(node.precedingWhitespace || '');
391+
// True when:
392+
//
393+
// - the source text had a newline before the comment
394+
// - the comment itself is multi-line
395+
// - we have already added a newline to output text (right before this to-be added comment)
396+
//
397+
// The last one will ensure the comment position stays idempotent - so that
398+
// re-formatting the same SQL won't result in comment position changing.
399+
private isStandaloneBlockComment(node: BlockCommentNode): boolean {
400+
return (
401+
isMultiline(node.text) ||
402+
isMultiline(node.precedingWhitespace || '') ||
403+
this.layout.isAtStartOfLine()
404+
);
393405
}
394406

395407
private isDocComment(comment: string): boolean {

src/formatter/Layout.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ export default class Layout {
111111
return this.items;
112112
}
113113

114+
/**
115+
* True when some content has already been added and nothing but indentation
116+
* has been emitted since the last newline, meaning the next token would be
117+
* placed at the start of a fresh (non-first) line.
118+
*/
119+
public isAtStartOfLine(): boolean {
120+
for (let i = this.items.length - 1; i >= 0; i--) {
121+
const item = this.items[i];
122+
if (item === WS.SINGLE_INDENT) {
123+
continue;
124+
}
125+
return item === WS.NEWLINE || item === WS.MANDATORY_NEWLINE;
126+
}
127+
return false;
128+
}
129+
114130
private itemToString(item: LayoutItem): string {
115131
switch (item) {
116132
case WS.SPACE:

test/features/comments.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,51 @@ export default function supportsComments(format: FormatFn, opts: CommentsConfig
6161
expect(format(sql)).toBe(sql);
6262
});
6363

64+
it('moves a leading block comment of SELECT clause onto its own line', () => {
65+
const result = format('SELECT /* comment */ foo FROM tbl');
66+
expect(result).toBe(dedent`
67+
SELECT
68+
/* comment */
69+
foo
70+
FROM
71+
tbl
72+
`);
73+
// Formatting must be idempotent: re-formatting the result must not change it.
74+
expect(format(result)).toBe(result);
75+
});
76+
77+
it('keeps block comments in various SELECT statement positions idempotent', () => {
78+
const sql = 'SELECT foo /* c */, /* c */ bar FROM /* c */ tbl1 JOIN /* c */ tbl2';
79+
const result = dedent`
80+
SELECT
81+
foo /* c */,
82+
/* c */
83+
bar
84+
FROM
85+
/* c */
86+
tbl1
87+
JOIN /* c */ tbl2
88+
`;
89+
expect(format(sql)).toBe(result);
90+
expect(format(result)).toBe(result);
91+
});
92+
93+
it('keeps block comments in various CREATE TABLE statement positions idempotent', () => {
94+
const sql =
95+
'CREATE TABLE /* c */ tbl (/* c */ id INT, /* c */ first_name TEXT, last_name TEXT)';
96+
const result = dedent`
97+
CREATE TABLE /* c */ tbl (
98+
/* c */
99+
id INT,
100+
/* c */
101+
first_name TEXT,
102+
last_name TEXT
103+
)
104+
`;
105+
expect(format(sql)).toBe(result);
106+
expect(format(result)).toBe(result);
107+
});
108+
64109
it('formats tricky line comments', () => {
65110
expect(format('SELECT a--comment, here\nFROM b--comment')).toBe(dedent`
66111
SELECT

0 commit comments

Comments
 (0)