diff --git a/src/formatter/Formatter.ts b/src/formatter/Formatter.ts index 8f10f87791..131c4d38de 100644 --- a/src/formatter/Formatter.ts +++ b/src/formatter/Formatter.ts @@ -39,16 +39,16 @@ export default class Formatter { private formatAst(statements: StatementNode[]): string { return statements - .map(stat => this.formatStatement(stat)) + .map((stat, i) => this.formatStatement(stat, i > 0)) .join('\n'.repeat(this.cfg.linesBetweenQueries + 1)); } - private formatStatement(statement: StatementNode): string { + private formatStatement(statement: StatementNode, startsOnNewLine: boolean): string { const layout = new ExpressionFormatter({ cfg: this.cfg, dialectCfg: this.dialect.formatOptions, params: this.params, - layout: new Layout(new Indentation(indentString(this.cfg))), + layout: new Layout(new Indentation(indentString(this.cfg)), startsOnNewLine), }).format(statement.children); if (!statement.hasSemicolon) { diff --git a/src/formatter/Layout.ts b/src/formatter/Layout.ts index 39fd4071b7..3cb49a5449 100644 --- a/src/formatter/Layout.ts +++ b/src/formatter/Layout.ts @@ -25,7 +25,7 @@ export type LayoutItem = WS.SPACE | WS.SINGLE_INDENT | WS.NEWLINE | WS.MANDATORY export default class Layout { private items: LayoutItem[] = []; - constructor(public indentation: Indentation) {} + constructor(public indentation: Indentation, private startsOnNewLine: boolean = false) {} /** * Appends token strings and whitespace modifications to SQL string. @@ -123,9 +123,11 @@ export default class Layout { } /** - * True when some content has already been added and nothing but indentation - * has been emitted since the last newline, meaning the next token would be - * placed at the start of a fresh (non-first) line. + * True when nothing but indentation has been emitted since the last newline, + * meaning the next token would be placed at the start of a fresh line. + * + * An empty layout counts when the statement itself is preceded by others, + * since the formatter joins statements with newlines. */ public isAtStartOfLine(): boolean { for (let i = this.items.length - 1; i >= 0; i--) { @@ -135,7 +137,7 @@ export default class Layout { } return item === WS.NEWLINE || item === WS.MANDATORY_NEWLINE; } - return false; + return this.startsOnNewLine; } private itemToString(item: LayoutItem): string { diff --git a/test/features/comments.ts b/test/features/comments.ts index 12e54c68a1..f8d65ea90c 100644 --- a/test/features/comments.ts +++ b/test/features/comments.ts @@ -90,6 +90,19 @@ export default function supportsComments(format: FormatFn, opts: CommentsConfig expect(format(result)).toBe(result); }); + it('keeps a block comment leading a later statement on its own line', () => { + const sql = 'SELECT 1; /* c */ COMMIT;'; + const result = dedent` + SELECT + 1; + + /* c */ + COMMIT; + `; + expect(format(sql)).toBe(result); + expect(format(result)).toBe(result); + }); + it('keeps block comments in various CREATE TABLE statement positions idempotent', () => { const sql = 'CREATE TABLE /* c */ tbl (/* c */ id INT, /* c */ first_name TEXT, last_name TEXT)';