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),
operatorsCombine: Boolean(options.operatorsCombine),
});
5 changes: 5 additions & 0 deletions src/formatter/ExpressionFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export interface DialectFormatOptions {
onelineClauses: string[];
// List of clauses that should be formatted on a single line in tabular style
tabularOnelineClauses?: string[];
// True in dialects that lex a run of operator characters as a single operator
// (PostgreSQL, Redshift), where two operators densed together re-parse as one.
operatorsCombine?: boolean;
}

// Contains the same data as DialectFormatOptions,
Expand All @@ -64,6 +67,8 @@ 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;
// See DialectFormatOptions.operatorsCombine.
operatorsCombine: boolean;
}

/** 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.operatorsCombine
),
}).format(statement.children);

if (!statement.hasSemicolon) {
Expand Down
31 changes: 26 additions & 5 deletions src/formatter/Layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 operatorsCombine = false) {}

/**
* Appends token strings and whitespace modifications to SQL string.
Expand Down Expand Up @@ -57,10 +57,13 @@ 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 an item starting with "-"/"+" onto a preceding operator when
// the two would re-lex as one token. "-" onto "-" forms "--" (a line
// comment that swallows the rest of the line) in every dialect. In dialects
// that lex a run of operator characters as a single operator (PostgreSQL,
// Redshift), a sign onto an operator containing ~!@#%^&|`? also merges
// (e.g. densing "5 % -2" into "5%-2", which re-parses as the operator "%-").
if (this.wouldMergeIntoOperator(item)) {
Comment on lines +60 to +66

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This long comment would really be better rewritten as a comment on the wouldMergeIntoOperator() method, describing what that method does.

this.items.push(WS.SPACE);
}
this.items.push(item);
Expand All @@ -73,6 +76,24 @@ export default class Layout {
return typeof lastItem === 'string' && lastItem.endsWith(suffix);
}

private wouldMergeIntoOperator(item: string): boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the name of this method to be kinda awkward. Definitely this code base doesn't contain any other function names starting with would- prefix.

I'd suggest inverting the boolean return value of this function and naming it something like isItemSafeToAppend().

if (!item.startsWith('-') && !item.startsWith('+')) {
return false;
}
const lastItem = last(this.items);
if (typeof lastItem !== 'string') {
return false;
}
const run = /[-+*/<>=~!@#%^&|`?]+$/u.exec(lastItem)?.[0];
if (!run) {
return false;
}
Comment on lines +87 to +90

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea what's going on in here. What does the run variable mean?

I guess it's some sort of run of characters. But that doesn't really help me in understanding its purpose.

if (item.startsWith('-') && run.endsWith('-')) {
return true;
}
return this.operatorsCombine && /[~!@#%^&|`?]/u.test(run);
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
private trimHorizontalWhitespace() {
while (isHorizontalWhitespace(last(this.items))) {
this.items.pop();
Expand Down
1 change: 1 addition & 0 deletions src/languages/postgresql/postgresql.formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,6 @@ export const postgresql: DialectOptions = {
alwaysDenseOperators: ['::', ':'],
onelineClauses: [...standardOnelineClauses, ...tabularOnelineClauses],
tabularOnelineClauses,
operatorsCombine: true,
},
};
1 change: 1 addition & 0 deletions src/languages/redshift/redshift.formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,6 @@ export const redshift: DialectOptions = {
alwaysDenseOperators: ['::'],
onelineClauses: [...standardOnelineClauses, ...tabularOnelineClauses],
tabularOnelineClauses,
operatorsCombine: true,
},
};
8 changes: 8 additions & 0 deletions test/mysql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,12 @@ describe('MySqlFormatter', () => {
DROP DEFAULT;
`);
});

it('does not space a sign after an operator in dense mode', () => {
expect(format('SELECT 5 % -2, 5 & -2', { denseOperators: true })).toBe(dedent`
SELECT
5%-2,
5&-2
`);
});
});
19 changes: 19 additions & 0 deletions test/postgresql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,25 @@ describe('PostgreSqlFormatter', () => {
`);
});

it('keeps a space between an operator and a following sign with denseOperators', () => {
expect(format('SELECT 5 % -2, 2 ^ -2, 8 # -1', { denseOperators: true })).toBe(dedent`
SELECT
5% -2,
2^ -2,
8# -1
`);
expect(format(`SELECT '[1,2]'::jsonb @> -1`, { denseOperators: true })).toBe(dedent`
SELECT
'[1,2]'::jsonb@> -1
`);
expect(format(`SELECT data ? -1 FROM t`, { denseOperators: true })).toBe(dedent`
SELECT
data? -1
FROM
t
Comment on lines +237 to +252

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I counted 17 special characters in the regular expression. In this test we're only checking a few of them.

`);
});

// Issue #813
it('supports OR REPLACE in CREATE FUNCTION', () => {
expect(format(`CREATE OR REPLACE FUNCTION foo ();`)).toBe(dedent`
Expand Down
Loading