prettier: Allow inline method chaining if line length does not exceed `printWidth`
Just to give you some context on this, I’ve been working with @ljharb to bring prettier to @airbnb. We noticed a few cases where Prettier’s formatting could be adjusted to more closely align with our style guide.
cc. @vjeux
Prettier 1.7.4
If a method chain has more than 1 method, Prettier creates a linebreak after each chained method, increasing the indentation in the method body.
# Options (if any):
--single-quote
--print-width 100
--trailing-comma "all"
Input:
function HelloWorld() {
window.FooClient.setVars({
locale: getFooLocale({ page }),
authorizationToken: data.token,
}).initVerify('foo_container');
fejax.ajax({
url: '/verification/',
dataType: 'json',
}).then(
(data) => {
this.setState({ isLoading: false });
this.initWidget(data);
},
(data) => {
this.logImpression('foo_fetch_error', data);
Flash.error(I18n.t('offline_identity.foo_issue'));
},
);
}
Output:
function HelloWorld() {
window.FooClient
.setVars({
locale: getFooLocale({ page }),
authorizationToken: data.token,
})
.initVerify('foo_container');
fejax
.ajax({
url: '/verification/',
dataType: 'json',
})
.then(
data => {
this.setState({ isLoading: false });
this.initWidget(data);
},
data => {
this.logImpression('foo_fetch_error', data);
Flash.error(I18n.t('offline_identity.foo_issue'));
},
);
}
Desired behavior:
Creating a line break after chained methods can increase the indentation of multiline function bodies. It would help to allow chaining to be inline until it exceeds the printWidth
. This shouldn’t cause a readability issue since multiline blocks will still be encapsulated.
eg:
function HelloWorld() {
window.FooClient.setVars({
locale: getFooLocale({ page }),
authorizationToken: data.token,
}).initVerify('foo_container');
fejax.ajax({
url: '/verification/',
dataType: 'json',
}).then(
(data) => {
this.setState({ isLoading: false });
this.initWidget(data);
},
(data) => {
this.logImpression('foo_fetch_error', data);
Flash.error(I18n.t('offline_identity.foo_issue'));
},
);
}
Diff between Prettier & desired output:
*** prettier.js 2017-10-25 16:40:41.000000000 -0700
--- desired.js 2017-10-25 16:40:39.000000000 -0700
***************
*** 1,24 ****
function HelloWorld() {
! window.FooClient
! .setVars({
! locale: getFooLocale({ page }),
! authorizationToken: data.token,
! })
! .initVerify('foo_container');
! fejax
! .ajax({
! url: '/verification/',
! dataType: 'json',
! })
! .then(
! data => {
! this.setState({ isLoading: false });
! this.initWidget(data);
! },
! data => {
! this.logImpression('foo_fetch_error', data);
! Flash.error(I18n.t('offline_identity.foo_issue'));
! },
! );
}
--- 1,20 ----
function HelloWorld() {
! window.FooClient.setVars({
! locale: getFooLocale({ page }),
! authorizationToken: data.token,
! }).initVerify('foo_container');
! fejax.ajax({
! url: '/verification/',
! dataType: 'json',
! }).then(
! (data) => {
! this.setState({ isLoading: false });
! this.initWidget(data);
! },
! (data) => {
! this.logImpression('foo_fetch_error', data);
! Flash.error(I18n.t('offline_identity.foo_issue'));
! },
! );
}
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 48
- Comments: 21 (12 by maintainers)
Commits related to this issue
- Make the factory detection handle multiple elements This was only accounting for a single element in the first group. Now it handles arbitrary elements. Only the last element being capitalized matter... — committed to vjeux/prettier by vjeux 7 years ago
- Make the factory detection handle multiple elements (#3112) This was only accounting for a single element in the first group. Now it handles arbitrary elements. Only the last element being capitalize... — committed to prettier/prettier by vjeux 7 years ago
- examples from various issues about method chaining this commit includes no prettier functionality changes, so that diffs will be easily visible. specifically: * https://github.com/prettier/prettier... — committed to mmkal/prettier by mmkal 5 years ago
- examples from various issues about method chaining this commit includes no prettier functionality changes, so that diffs will be easily visible. specifically: * https://github.com/prettier/prettier... — committed to mmkal/prettier by mmkal 5 years ago
- examples from various issues about method chaining this commit includes no prettier functionality changes, so that diffs will be easily visible. specifically: * https://github.com/prettier/prettier... — committed to mmkal/prettier by mmkal 5 years ago
- examples from various issues about method chaining this commit includes no prettier functionality changes, so that diffs will be easily visible. specifically: * https://github.com/prettier/prettier... — committed to mmkal/prettier by mmkal 5 years ago
- method chain breaking heuristic (#6685) * examples from various issues about method chaining this commit includes no prettier functionality changes, so that diffs will be easily visible. specif... — committed to prettier/prettier by mmkal 4 years ago
There are many popular patterns chaining method calls using enzyme. Please find below an example when Prettier creates new lines for each chained method of wrapper. Would be really nice to have an option to inline at least the first call independent on the heuristics mentioned above.
Prettier 1.7.4 Playground link
Input:
Output:
fixed by #6685, not yet released
@j-f1 #4765 seems great - it’s saying “instead of always expanding to multiple lines, prefer one-line when it was originally one-line, where possible”.
However, what we want much more than that is “instead of always collapsing to one line when it fits, prefer multi-line when it was originally multi-line”.
Both seem possible (ie, they don’t conflict with each other). Is there any chance of also getting the second behavior?
@sharmilajesupaul @vjeux The destructuring thing is already tracked in #2550
Do you have examples where
Promise.resolve
orPromise.all
do not behave the way you want?I’d love to know how many times you hit this in practice. Adding configuration for small things like this has a really big cost. I believe that printing a slightly less optimal output for those is a good trade-off.
This is indeed how prettier is supposed to work. Looks like you found a bug where there’s a trailing comment in the first line: https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAzOArAhgDwHQDmcMAUgMoDyAcgBQDkA9KhBAwJYDOA+nADZsE2AI15w6ASjwwAFghoAdKAAIlYWWADWAQSgATAKJQsIuAGEsvXkKyaANEoYNFKmsCUcYWGAFcOAFTgcGHsAJzgOAAdoDjgAoKUAX3ElAF4APiVgZxUHBiUAEU5jUSUYCCUIsIA3BBglMIi4LzhdVQteDjxslV0ikyoAVQANMm8QmoBPGnEAbmyExVnFEFsQCAiYNmjkUCwPZFQLGNWhEJsNYjIImzYoAmQYEO84Vd0IMAOjl5AMDhwAITOmkuWAAtnAADK3OCfDrfCDeGARREAJlhxxA1xCMRCyBA1iEE140BWmJCtxgAHU2LoZMgABwABlWMVBbAeT2+HFuBFEAEVvBB4Ojvp4hNTadJkCjVo8sGx+HdTBBQaCsHj2qTfLFjBwRQkEkA
We do it for objects, but because objects can be used to mean so many different things that it’s hard to figure out why. For example
React.createClass({ ... })
should never be inlined, even if the content fits in one line: here object is being used as a class.For the others, I actually believe that this is a bad idea. The goal of prettier, and a reason why I believe it’s so successful, is that you can as a developer --not-- spend time formatting your code. You just write your code in a single ugly line, save and it’s going to look good. It affects the way you --write-- code.
With eslint, you write your code the same way as before and the compiler yells at you if you made a mistake. It is a safety guarantee but you still have to spend all the time formatting your code.
In order to achieve this result, letting people have the choice between different ways of formatting something is actually harmful. Now you won’t be sure if the input will be intentional or not. We’re doing it for objects and it’s a constant source of bug reports and people asking to get rid of it.
What would be great is if you could provide concrete examples where prettier didn’t do the thing you expected and we can figure out if there’s a way to make prettier print it best by default. What I found is that in practice, there’s very often one way that people are generally happy with: maybe not the way they would actually write it in practice, but one that doesn’t trigger a “omg, never in my codebase” reaction.
Note that I’m not against adding an option for it, but I’d love to know if we can find a way to fix it without having to use that giant hammer.
I sent #3112 to fix the case with
window.FooClient.setVars
I think that, when the first invocation can fit on one line, this would work well.
However I think the real conceptual difference is that in
window.FooClient.setVars(
or[].map(
, the invocation is conceptually a separate thing from the receiver - but inPromise.resolve(),
and$.ajax(
, the invocation and the receiver are part of the same conceptual thing and thus shouldn’t be split apart across lines.I’m not sure there’s a single good heuristic around this that can be programmatically applied while meeting our requirements.
@vjeux would it be possible to have an option to avoid altering method chains, while still styling their arguments and bodies?
You’re right, those diffs do look worse and the single heuristic: chain everything, until you hit the end of the line, is definitely not going to work. The leading and inline comments get completely displaced too.
What if Prettier only changed the first property invocation to be inline?
i.e. the first diff would look like:
and it would mirror the behavior of this Prettier output:
@ljharb do you agree?
How does #4765 look?
For the latest playground bug, turns out it was fixed a week ago, before you reported it 😃 https://github.com/prettier/prettier/pull/3079
@vjeux To answer your first question:
There are a few more issues we’ve come across internally and we’ve been trying to address some of them using eslint rules.
Would it be possible to make the list of exceptions like
_
and$
configurable? That way we can addfejax
and other cases to that list.as for this question:
This is going to take some digging on my part so I’ll get back to you with a more accurate answer soon. But afaik, this particular issue with method chaining is only affecting
fejax
,Promise.resolve
orPromise.all
andwindow.foo
(which you’ve already addressed).For reference, here’s the code that contains the heuristic: https://github.com/prettier/prettier/blob/6ccf5c0246970557426220008d2f950e67101181/src/printer.js#L3781-L3800
The entire function printMemberChain has a lot of comments that explain how it works if you are interested 😃