Make JSX formatting inside ternary expressions consistent
I have this snippet (formatted by prettier):
const foo = cond ? (
<Foo>
<X />
</Foo>
) : (
<Bar />
);
which gets formatted like this:
const foo = cond ?
(
<Foo>
<X />
</Foo>
) :
<Bar />;
Or another example:
<Message>
{type === 'error' ? (
<Icon name="alert" />
) : type === 'warning' ? (
<Icon name="exclamation" />
) : (
<Icon name="info" />
)}
</Message>
gets formatted as:
<Message>
{type === 'error' ?
<Icon name="alert" /> :
type === 'warning' ?
<Icon name="exclamation" /> :
<Icon name="info" />}
</Message>;
I know formatting is subjective, but this looks quite bad. Previously both branches were consistently on the same indentation level. Now it adds another level of indentation just for the parentheses, instead of keeping it after the ?.
Would it be possible to make this behavior configurable? Subjective preferences aside, it would also help a lot for all those who want to get rid of prettier - nobody likes huge amounts of whitespace/indentation changes, they completely ruin git blame and give you very nasty merge/rebase conflicts in case you have lots of open PRs (and which large project doesn't...)
My config (mostly default, just a few changes):
{
"lineWidth": 100,
"indentWidth": 2,
"useTabs": false,
"semiColons": "always",
"quoteStyle": "preferSingle",
"jsx.quoteStyle": "preferDouble",
"quoteProps": "consistent",
"newLineKind": "lf",
"useBraces": "always",
"bracePosition": "sameLineUnlessHanging",
"singleBodyPosition": "nextLine",
"nextControlFlowPosition": "sameLine",
"trailingCommas": "onlyMultiLine",
"operatorPosition": "sameLine",
"preferHanging": false,
"preferSingleLine": false, // check behavior
"arrowFunction.useParentheses": "preferNone",
"binaryExpression.linePerExpression": true, // check behavior
"jsx.bracketPosition": "nextLine",
"jsx.forceNewLinesSurroundingContent": false,
"jsx.multiLineParens": "prefer",
"memberExpression.linePerExpression": true, // check behavior
"typeLiteral.separatorKind": "semiColon", // check with team
"enumDeclaration.memberSpacing": "newLine",
"spaceAround": false,
"spaceSurroundingProperties": false,
"binaryExpression.spaceSurroundingBitwiseAndArithmeticOperator": true,
"commentLine.forceSpaceAfterSlashes": true,
"constructor.spaceBeforeParentheses": false,
"constructorType.spaceAfterNewKeyword": false,
"constructSignature.spaceAfterNewKeyword": false,
"doWhileStatement.spaceAfterWhileKeyword": true,
"exportDeclaration.spaceSurroundingNamedExports": false,
"forInStatement.spaceAfterForKeyword": true,
"forOfStatement.spaceAfterForKeyword": true,
"forStatement.spaceAfterForKeyword": true,
"forStatement.spaceAfterSemiColons": true,
"functionDeclaration.spaceBeforeParentheses": false,
"functionExpression.spaceBeforeParentheses": false,
"functionExpression.spaceAfterFunctionKeyword": false,
"getAccessor.spaceBeforeParentheses": false,
"ifStatement.spaceAfterIfKeyword": true,
"importDeclaration.spaceSurroundingNamedImports": false,
"jsxSelfClosingElement.spaceBeforeSlash": true,
"jsxExpressionContainer.spaceSurroundingExpression": false,
"method.spaceBeforeParentheses": false,
"setAccessor.spaceBeforeParentheses": false,
"taggedTemplate.spaceBeforeLiteral": false,
"typeAnnotation.spaceBeforeColon": false,
"typeAssertion.spaceBeforeExpression": true,
"whileStatement.spaceAfterWhileKeyword": true,
"module.sortImportDeclarations": "maintain", // check codebase for impact of `caseInsensitive`
"module.sortExportDeclarations": "maintain", // check codebase for impact of `caseInsensitive`
"exportDeclaration.sortNamedExports": "maintain", // check codebase for impact of `caseInsensitive`
"exportDeclaration.sortTypeOnlyExports": "none",
"importDeclaration.sortNamedImports": "maintain", // check codebase for impact of `caseInsensitive`
"importDeclaration.sortTypeOnlyImports": "none",
"ignoreNodeCommentText": "dprint-ignore",
"ignoreFileCommentText": "dprint-ignore-file",
"exportDeclaration.forceSingleLine": false,
"importDeclaration.forceSingleLine": false,
"exportDeclaration.forceMultiLine": "never",
"importDeclaration.forceMultiLine": "never",
// compat with our existing prettier style
"arguments.trailingCommas": "never",
"conditionalExpression.operatorPosition": "maintain",
"conditionalExpression.preferSingleLine": true,
}
OK, so the first issue is partially avoided by setting conditionalExpression.linePerExpression to false. This is not documented BTW, I just found it while looking into the code.
With this change I get this:
const foo = cond ? (
<Foo>
<X />
</Foo>
) : <Bar />;
Ah, but this affects many more places. So not ideal. IMHO for the - pretty common - case of a conditional expression w/ JSX it would make more sense to not consider the parentheses part of the expression, but only their content...
Another case that looks quite weird (and IMHO deserves configurability):
<Foo
fixed="test"
short={test ? a : b}
long={test ? 'this is something longer' : 'foo'}
/>
is currently formatted like this (lineWidth=30):
<Foo
fixed="test"
short={test ? a : b}
long={test
? 'this is something longer'
: 'foo'}
/>;
however, not having anything in the line where the attribute name starts would be much better:
<Foo
fixed="test"
short={test ? a : b}
long={
test
? "this is something longer"
: "foo"
}
/>
FYI my current code that kind of works for me (it also breaks up stuff that would fit nicely in a single line, I could not figure out how to avoid this so far) is here: https://github.com/dprint/dprint-plugin-typescript/compare/main...ThiefMaster:dprint-plugin-typescript:jsx-ternary-improvements