-
Notifications
You must be signed in to change notification settings - Fork 47.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
[ESLint] Handle optional member chains #19275
Conversation
This disambiguates "optional"/"required" because that terminology is taken by optional chaining.
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 916b82b:
|
desc: | ||
'Update the dependencies array to be: [pizza.crust, pizza.toppings]', | ||
'Update the dependencies array to be: [pizza.crust, pizza?.toppings]', |
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.
Note: technically this could be further improved to not add ?
at all because another dep didn't use it. However, I don't think it matters because at least this doesn't cause a crash. Additionally, this points your attention to unnecessary ?
inside the function — since presumably if data is immutable, you shouldn't need it anyway.
* foo.(bar) -> 'foo.bar' | ||
* foo.bar.(baz) -> 'foo.bar.baz' | ||
* foo(.)bar -> 'foo.bar' | ||
* foo.bar(.)baz -> 'foo.bar.baz' |
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.
This comment didn't make sense so I fixed it.
Details of bundled changes.Comparing: 7ca1d86...916b82b eslint-plugin-react-hooks
Size changes (stable) |
That's not how optional chaining works, if I'm reading the OP right. |
Details of bundled changes.Comparing: 7ca1d86...916b82b eslint-plugin-react-hooks
Size changes (experimental) |
Hmm. Maybe I wasn't clear or maybe I'm missing something. I'm referring to this scenario: useEffect(() => {
console.log(a?.b.x)
console.log(a?.b.x)
}) If we were to auto-fill dependencies, we'd want to suggest However, with this scenario: useEffect(() => {
console.log(a?.b.x)
console.log(a.b.x)
}) In this case, suggesting Similarly, if the user code has |
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 great
errors: [ | ||
{ | ||
message: | ||
"React Hook useEffect has a missing dependency: 'pizza.crust'. " + |
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.
We may want this to explain why only one is included with something like:
React Hook useEffect has a missing dependencies: 'pizza?.crust' and 'pizza.crust' (which can be reduced to a dependency on 'pizza.crust'). Either include it or remove the dependency array.
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.
Meh. I don't think this is useful enough to clutter the message.
@gaearon as long as there's no getters in the chain that might change the nullishness of properties on later evaluation, and no assignments or mutations in between the two statements, you're right :-) |
Yeah. This rule in general assumes that render-scope objects you read inside Hook callbacks are immutable POJOs or treated as such. Otherwise dependencies don't work as a concept in general. |
Co-authored-by: Ricky <rickhanlonii@gmail.com>
* Rename internal variables This disambiguates "optional"/"required" because that terminology is taken by optional chaining. * Handle optional member chains * Update comment Co-authored-by: Ricky <rickhanlonii@gmail.com> Co-authored-by: Ricky <rickhanlonii@gmail.com>
Follow-up to #19273.
Our algorithm treats
a.b.c.d
as unique paths in a many places and those parts should work the same way regardless of whether these are optional or required members. So in #19273, I removed?.
from the internal representation.However, ideally we still want to print
?.
where needed if all usages of that member in the user code used optional chaining. For example, if we usepizza?.crust
andpizza?.crust.density
, then we should suggestpizza?.crust
in deps, but if one of them didn’t have?
, then we should suggestpizza.crust
instead. Because then, at least one of them is required anyway. None of this should have any effects on the dependency logic — both variations, if typed by user, should satisfy the constraint.The implementation is split in two stages.
It intentionally doesn’t interfere with the main algorithm that checks the dependencies. Instead, when we initially generate member paths, we also fill a Map. In that Map, we mark for each deep path whether it was a required or an optional one. By the time we have collected all dependencies, the Map will contain a mapping from the key path to a Boolean:
true
if all usages were optional, andfalse
if some of them were required.Finally, when we're ready to print our suggestions and/or fix the code, we will use that Map to pretty-print our paths. We will consult it to see if each segment should be concatenated using
?.
or.
. If at least one usage was required, or if that path was not found in the code, then we will default to.
as a default path separator. So if you don't use?.
you won’t see it.This adds a few tests and also fixes a bunch of existing tests to use better output.
This is split in two commits. The first one is just a bunch of renames because "optional" clashed with another concept we previously called the same way. Review commits individually.