Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ const processDialectFormatOptions = ({
(options.tabularOnelineClauses ?? options.onelineClauses).map(name => [name, true])
),
identifierDashes: Boolean(tokenizerOptions.identChars?.dashes),
lineCommentTypes: tokenizerOptions.lineCommentTypes ?? ['--'],
});
4 changes: 4 additions & 0 deletions src/formatter/ExpressionFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export interface ProcessedDialectFormatOptions {
// In such dialects the "-" operator must keep its surrounding spaces,
// otherwise "a - b" densed to "a-b" would re-parse as a single identifier.
identifierDashes: boolean;
// Line comment markers of the dialect (e.g. "--", "//", "#"). Used by Layout
// to avoid gluing a layout item onto a preceding one in a way that would
// form a line comment and swallow the rest of the line.
lineCommentTypes: string[];
}

/** Formats a generic SQL expression */
Expand Down
5 changes: 4 additions & 1 deletion src/formatter/Formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ export default class Formatter {
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)),
this.dialect.formatOptions.lineCommentTypes
),
}).format(statement.children);

if (!statement.hasSemicolon) {
Expand Down
32 changes: 25 additions & 7 deletions src/formatter/Layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ 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,
// Line comment markers of the dialect. Any two adjacent layout items whose
// boundary would form one of these must stay separated.
private lineCommentTypes: string[] = ['--']
) {}

/**
* Appends token strings and whitespace modifications to SQL string.
Expand Down Expand Up @@ -57,20 +62,33 @@ export default class Layout {
this.items.push(WS.SINGLE_INDENT);
break;
default:
// Don't glue a layout item starting with "-" directly onto one ending with
// "-": that forms "--", which re-parses as a line comment and
// swallows the rest of the line (e.g. densing "a - -b" into "a--b").
if (item.startsWith('-') && this.lastItemEndsWith('-')) {
// Don't glue two layout items together when the boundary forms a line
// comment marker of this dialect: the result would re-parse as a
// comment and swallow the rest of the line (e.g. densing "a - -b"
// into "a--b", or a "/" operator directly before a block comment in
// Snowflake, where "//" starts a line comment).
if (this.wouldFormLineComment(item)) {
this.items.push(WS.SPACE);
}
this.items.push(item);
}
}
}

private lastItemEndsWith(suffix: string): boolean {
/**
* True when appending the given item directly onto the last one would form a
* line comment marker of this dialect, e.g. "-" after "-" in every dialect,
* or "/" after "/" in dialects where "//" starts a comment.
*/
private wouldFormLineComment(item: string): boolean {
const lastItem = last(this.items);
return typeof lastItem === 'string' && lastItem.endsWith(suffix);
if (typeof lastItem !== 'string') {
return false;
}
return this.lineCommentTypes.some(
marker =>
marker.length > 1 && lastItem.endsWith(marker[0]) && item.startsWith(marker.slice(1))
);
}

private trimHorizontalWhitespace() {
Expand Down
34 changes: 34 additions & 0 deletions test/snowflake.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,38 @@ describe('SnowflakeFormatter', () => {
CREATE TABLE identifier($foo);
`);
});
describe('Snowflake line comments', () => {
// Snowflake is the only dialect where "//" starts a line comment, so a "/"
// operator directly in front of a comment used to glue into "//" and turn
// the rest of the line into comment text.
it('does not glue a slash operator onto a following block comment', () => {
const result = format('SELECT a / /* c */ b FROM t;', { denseOperators: true });
expect(result).toBe(dedent`
SELECT
a/ /* c */ b
FROM
t;
`);
// The regression: "b" used to become part of the "//" comment.
expect(result).toContain('b');
expect(result).not.toContain('a//');
});

it('does not glue a slash operator onto a formatter-disable comment', () => {
const result = format('SELECT a / /* sql-formatter-disable */ b FROM t;', {
denseOperators: true,
});
expect(result).toContain('a/ /* sql-formatter-disable */ b');
expect(result).not.toContain('a//');
});

it('keeps densing a plain division', () => {
expect(format('SELECT a / b FROM t;', { denseOperators: true })).toBe(dedent`
SELECT
a/b
FROM
t;
`);
});
});
});