Apply various formatting fixes - #80
Conversation
| !expr.distinctKw && | ||
| !expr.nullHandlingKw && | ||
| !expr.orderBy && | ||
| !expr.limit && | ||
| !expr.having |
There was a problem hiding this comment.
Is there a way to avoid handling each case individually here?
There was a problem hiding this comment.
At the moment I think not much can be done here. This needs some changes on the parser side to make it easier to handle within the prettier plugin.
d26d012 to
120b664
Compare
nene
left a comment
There was a problem hiding this comment.
Thanks for the pull request. I've been busy with other stuff. Finally got to reviewing this.
In general it looks good:
- Break long WHEN/THEN clauses into separate lines
- Needs some more thought on how to actually indent these.
- Break long COMMENT ON clauses into separate lines
- Doesn't really match with the indentation suggested in #76
- Break long CREATE INDEX clauses into separate lines ✅
- Break long binary expressions into separate lines ✅
- Avoid line-break between empty parenthesis
- This is the trickiest change. The general logic looks sound. I do have some recommendations though.
| SELECT | ||
| CASE | ||
| WHEN column_name = 1 | ||
| THEN result_name | ||
| WHEN column_name = 2 | ||
| THEN other_result | ||
| ELSE foo |
There was a problem hiding this comment.
I find this not so easy to read, because WHEN and THEN blocks are indented the same amount. IMHO it's sort of like formatting if-else in some other language like so:
if x > 10
return 15
else if x < 10
return 20
It also doesn't match with how the procedural version of CASE expression gets formatted:
CASE
WHEN column_name = 1 THEN
SELECT \good'
WHEN columne_name = 2 THEN
SELECT 'bad'
ELSE
SELECT 'other'
END CASESee case.test.ts
I would go with similar indentation for the long WHEN..THEN blocks in general:
CASE
WHEN column_name = 1 THEN
result_name
WHEN columne_name = 2 THEN
other_result
ELSE
foo
END CASEThere was a problem hiding this comment.
Makes sense. So for multi-line WHEN/THEN clauses, will it be okay to format as follows?
CASE
WHEN
column_name = 1
AND (column_name = 2 OR column_name = 3)
AND column_name = 4 THEN
result_name
AND (result_name OR result_name)
WHEN columne_name = 5 THEN
other_result
ELSE
foo
ENDThere was a problem hiding this comment.
I think it would be better to treat the WHEN .. THEN pair as ( .. ) pair in C-style if statement, which in Prettier would get formatted like:
if (
column_name === 1
&& (column_name === 2 OR column_name === 3)
&& column_name === 4
) {
return result_name
} else if (columne_name === 5) {
return other_result;
}So in SQL this would be:
CASE
WHEN
column_name = 1
AND (column_name = 2 OR column_name = 3)
AND column_name = 4
THEN
result_name
WHEN columne_name = 5 THEN
other_result
ELSE
foo
ENDThis has the nice property, that the body of THEN is always indented the same amount. When the code between WHEN..THEN needs to wrap to multiple lines, the code coming after THEN won't need to be reformatted in any way.
| COMMENT ON | ||
| CONSTRAINT constraint_name ON DOMAIN domain_name | ||
| IS 'This is a really nice comment here.' |
There was a problem hiding this comment.
This indentation doesn't quite match with what was proposed in #76. According to that issue, it really should be:
COMMENT ON
CONSTRAINT constraint_name ON DOMAIN domain_name
IS 'This is a really nice comment here.'Though I admit that the version you implemented might better align with the current overall indentation style that the formatter produces. But on another hand I'm not really so happy with that overall style. I'd like to move it more towards a bit more indentation. For example I'm not too happy with the indentation of ALTER TABLE statements:
-- current
ALTER TABLE foo
ALTER COLUMN bar
SET DEFAULT 'hello';
-- I'd rather have it like:
ALTER TABLE foo
ALTER COLUMN bar
SET DEFAULT 'hello';Anyway... this COMMENT ON formatting is a minor thing. I don't mind if you leave it like this or change it.
There was a problem hiding this comment.
Yeah, my implementation was trying to align with the current indentation for multiline statetements, but I also agree (and prefer) the hanging indentation style. Though most SQL code samples I see online don't using hanging indentation, so this might be a candidate for a config -- I think I also saw something alluding to that in this repo's STYLE GUIDE.
Would the hanging indentation also extend to DML statements? In my experience it works well for INSERT, UPDATE and DELETE but is a little tricky for SELECT statements where the return-list either has to be at the same indentation as the other clauses or at an extra indentation level.
SELECT
col -- or one level deeper
FROM ..
WHERE ..;Wrt this PR, can we ship the current non-hanging style for COMMENT ON and CREATE INDEX so that hanging indentation can be worked on in a separate PR?
There was a problem hiding this comment.
Yep, as I said, no need to do anything about this in this PR.
In general the indentation of SQL is a tricky beast. It's hard to come up with a very good overall rule. Like yeah, that style for SELECT would be horrible.
I guess the main question to ask is does the current style cause confusion when reading it. With ALTER TABLE I see the problem that this single statement:
ALTER TABLE foo
ALTER COLUMN bar
SET DEFAULT 'hello';Is at first glance very similar to 3 separate statements (ignore that this is completely broken code):
ALTER TABLE foo;
ALTER COLUMN bar;
SET DEFAULT 'hello';...because each line starts with keywords that can be used to start a separate statement. This is not a case with SELECT where WHERE, FROM, etc aren't keywords that also start SQL statements.
There was a problem hiding this comment.
I guess the main question to ask is does the current style cause confusion when reading it.
Great point. Perhaps taking it case by case is a good approach for this that could also reduce adoption friction. I'm happy to help out with it, just let me know how I can help 😊
| const hasComments = (node: Node): boolean => | ||
| Boolean((node as Node & { comments?: unknown[] }).comments?.length); |
There was a problem hiding this comment.
A better way to implement this, would be:
const hasComments = (node: Node): boolean =>
(node.leading?.length ?? 0) > 0 || (node.trailing?.length ?? 0) > 0;The comments field is added to nodes by the Prettier engine. But the leading & trailing fields come directly from our parser.
Given that in here we really are interested from where the comments were in the original source code, we're better off using the leading & trailing. Plus we don't need the type-casts. Additionally it should be more resilient against changes in Prettier.
|
|
||
| const isCompactOp = (op: string) => op === "->" || op === "->>"; | ||
|
|
||
| const isEmptyParenContent = (expr: Node): boolean => { |
There was a problem hiding this comment.
I would turn this function into:
const isEmptyParenExpr = (expr: ParenExpr): boolean => {Then all that logic for determining whether it's an empty parenthesis would live in one place, and also the name of the function would IMHO be easier to understand.
|
Thanks for the review. Everything you write makes sense! I'm going to be AFK for the next 2 weeks so I'll respond/make updates after :) |
120b664 to
893a539
Compare
893a539 to
868b3f9
Compare
|
@nene this is ready for re-review :) |
| it(`breaks long WHEN/THEN into separate lines`, async () => { | ||
| await test( | ||
| dedent` | ||
| SELECT | ||
| CASE | ||
| WHEN column_name = 1 THEN | ||
| result_name | ||
| END | ||
| `, | ||
| { printWidth: 40 }, | ||
| ); | ||
| }); | ||
|
|
||
| it(`breaks multiple long WHEN/THEN clauses without blank lines between them`, async () => { | ||
| await test( | ||
| dedent` | ||
| SELECT | ||
| CASE | ||
| WHEN column_name = 1 THEN | ||
| result_name | ||
| WHEN column_name = 2 THEN | ||
| other_result | ||
| ELSE | ||
| foo | ||
| END | ||
| `, | ||
| { printWidth: 40 }, | ||
| ); | ||
| }); |
There was a problem hiding this comment.
I think these tests have become obsolete now that we always break. Best to remove
Fixes:
COMMENTstatements spanning multiple lines #76CREATE INDEXstatementsWHEN/THENclauses in CASE statements #78 - with no line-break between multiple WHEN clauses