-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Further improve method chain breaking heuristic #7889
Conversation
edd226a
to
9e4b22d
Compare
oh playground link not found.... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good
Should we probably check the total amount of arguments instead (>= 6)? a = foo.bar("netlify", "what happened", "to previews?").baz().qux(); |
sounds good, but I think 6 is too much. a = foo.bar("netlify", "aaaaaaa").baz("fooaaoooo", "baraaaarr", "bar").qux(); |
"at least one call with more than two args" && "at least one other call with more than one arg"? Or maybe: "at least one call with more than two args, but this call shouldn't be first in the chain"? |
sounds good. if you push the implementation for it, I'll try it with various codes. |
pushed "at least one call with more than two args, but this call shouldn't be first in the chain" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about this? I found this when I ran Prettier 2 on a project in my company. It looks ugly for me.
Playground
Input:
cy.get(".ready")
.should("have.text", "FOO")
.should({ have, color, aaa });
Output:
cy.get(".ready").should("have.text", "FOO").should({ have, color, aaa });
Expected(same as input):
cy.get(".ready")
.should("have.text", "FOO")
.should({ have, color, aaa });
@sosukesuzuki My last take away from #7884 is that we probably should always break chains used as expression statements. What do you think about it? |
@thorn0 Sorry if I make any mistake. "always break chains used as expression statements" means it breaks this code, right? // Input
foo.bar().baz();
// Output?
foo
.bar()
.baz(); It looks overkill for me... |
@sosukesuzuki Do you have examples of real code that would look bad if we used this rule? |
@thorn0 hmm, I have no idea. I got it, such code may not be used much in the real world. |
393b45c
to
c099f5d
Compare
39bd9e7
to
8bb18db
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
const myScale = d3.scaleLinear() | ||
.domain([1950, 1980]) | ||
.range([0, width]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not keep the old one, add a new one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the old one was not real-life code. That's not how d3 is used. Our heuristics are developed to format real code. Ideally all the foo-bar-baz should be removed from that folder and replaced with more reasonable examples.
Realworld case of #7889 (comment) array.flat().reverse() Many methods on array can be called without arguments, |
@fisker In real code, the result of such array operations is usually used somehow, not discarded. E.g. |
e96e597
to
a9bad4c
Compare
@thorn0 After I commented on #7884, I kept mulling over what a heuristic would be that made me not care about intentional line breaks, and after 2 days I think I've found it: Never line-break inside a method chain before breaking the method chain itself. I have extracted and hopefully simplified a few examples here. First, line breaks inside an arrow function: const Profile = view.with({ name }).as((props) => (
<div>
<h1>Hello, {props.name}</h1>
</div>
))
// Better:
const Profile = view
.with({ name })
.as((props) => (
<div>
<h1>Hello, {props.name}</h1>
</div>
)) Second, line breaks inside an object definition: const fullName = RR.createQuery((o) => `${o.firstName} ${o.lastName}`).with({
firstName,
lastName,
})
// Better:
const fullName = RR
.createQuery((o) => `${o.firstName} ${o.lastName}`)
.with({ firstName, lastName }) Or most egregiously, line breaks inside an method call: const [firstName, setFirstName] = RR.name('user.profile.firstName').createState(
'John'
)
// Better
const [firstName, setFirstName] = RR
.name('user.profile.firstName')
.createState('John') Hopefully this is a useful heuristic for this scenario. |
@traviswatsonws A very good point, thanks. As for your first example (with a function), it's unlikely we'll change how it's formatted because the current output is intended (#469 (review), #469 (review)), but the other two indeed can be improved as you wrote. Implementation-wise, it might be challenging, and not in this PR maybe, but I'll look into it. For your second example, we also need to accept and implement #8055 first. |
a9bad4c
to
be71ef3
Compare
@thorn0 Would this discussion be better had in a different issue? As for the first function example, that case actually makes sense to me, and my example as provided is actually on the acceptable side, with the exception of indentation: EDIT: I don't know enough about this indentation stuff, I probably shouldn't have even mentioned it, but I'm going to leave it here for posterity. Maybe just... skip over it ;-) // This feels normal to not be indented
items.map(mapFunction).forEach((item) => {
// ...
})
// This feels awkward to not be indented, especially because if we add just one more step...
const output = items.map(mapFunction).map((item) => {
// ...
})
// Then we get this chain treatment, which is indented
const output = items
.map(mapFunction)
.map((item) => {
// ...
})
.map(otherMapFunction) Both are chains, just one more step, so the inconsistency feels "off". All that said, if I'm hypothesizing, that "break inside the lambda" treatment feels quite unique, and has some constraints:
And all this only matters when we are over the print width. I think these constraints are consistent with the link you provided. Here is an example that feels awkward/confusing to me for the reasons above: // Has 2 lambdas, and one of them is inside an object definition
const Profile = view.with({ name: (state) => state.name }).as((props) => (
<div>
<h1>Hello, {props.name}</h1>
</div>
)) |
@traviswatsonws Good catch. I'll fix this one in this PR. |
@traviswatsonws I've tried to implement your suggestion. Please play with this PR (playground) and tell me what you think. To install it locally, run |
I like it @thorn0. It's not as verbose as what I had in my head, but playing around with different argument amounts and lengths feels pretty good. 👍 |
It's not ready for merging yet. Committed it mostly to get feedback. I might move these last changes to another PR because they need refactoring and further tweaking. E.g. there is this idempotence issue: Prettier pr-8063 --parser babel Input: export default function theFunction(action$, store) {
return action$.ofType(THE_ACTION).switchMap(action => Observable
.webSocket({ url: THE_URL,
more: stuff(),
evenMore: stuff({ value1: true, value2: false, value3: false }),
})
.filter(data => theFilter(data))
.map(({ theType, ...data }) => theMap(theType, data))
.retryWhen(errors => errors));
} Output: export default function theFunction(action$, store) {
return action$.ofType(THE_ACTION).switchMap((action) =>
Observable
.webSocket({
url: THE_URL,
more: stuff(),
evenMore: stuff({ value1: true, value2: false, value3: false }),
})
.filter((data) => theFilter(data))
.map(({ theType, ...data }) => theMap(theType, data))
.retryWhen((errors) => errors)
);
} Second Output: export default function theFunction(action$, store) {
return action$.ofType(THE_ACTION).switchMap((action) =>
Observable.webSocket({
url: THE_URL,
more: stuff(),
evenMore: stuff({ value1: true, value2: false, value3: false }),
})
.filter((data) => theFilter(data))
.map(({ theType, ...data }) => theMap(theType, data))
.retryWhen((errors) => errors)
);
} |
I'm reverting these changes, moving them to a separate PR: #8063. I've reverted the current PR to the stage where it was approved + a fix for a bug described in #7889 (comment). Let's merge this first. |
e4ed1a8
to
14bb970
Compare
fixes #3594
fixes #3621
A continuation of the work started here: #6685
A method chain now is always split onto multiple lines if:
UPPERCASE_CONSTANT
s (the "fluent configuration" pattern),✨Try the playground for this PR✨
To install it locally,
run npm install thorn0/prettier#tweak-method-chains
docs/
directory)changelog_unreleased/*/pr-XXXX.md
file followingchangelog_unreleased/TEMPLATE.md
.