-
Notifications
You must be signed in to change notification settings - Fork 657
feat(rome_js_analyze): lint rule to check if react hooks are at top level #3927
Conversation
✅ Deploy Preview for docs-rometools ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
Parser conformance results on ubuntu-latestjs/262
jsx/babel
symbols/microsoft
ts/babel
ts/microsoft
|
Comparing feat(rome_js_analyze): lint rule to check if react hooks are at top level Snapshot #6 to median since last deploy of rome.tools.
1 page tested
|
Chrome Desktop | iPhone, 4G LTE | Motorola Moto G Power, 3G connection |
---|---|---|
Most significant changes
15 other significant changes: Number of Requests on Chrome Desktop, Number of Requests on iPhone, 4G LTE, Number of Requests on Motorola Moto G Power, 3G connection, Total Page Size in Bytes on Chrome Desktop, Total Page Size in Bytes on iPhone, 4G LTE, Total Page Size in Bytes on Motorola Moto G Power, 3G connection, Total Image Size in Bytes on Chrome Desktop, Total Image Size in Bytes on iPhone, 4G LTE, Total Image Size in Bytes on Motorola Moto G Power, 3G connection, Total CSS Size in Bytes on Chrome Desktop, Total CSS Size in Bytes on iPhone, 4G LTE, Total CSS Size in Bytes on Motorola Moto G Power, 3G connection, Total HTML Size in Bytes on Chrome Desktop, Total HTML Size in Bytes on iPhone, 4G LTE, Total HTML Size in Bytes on Motorola Moto G Power, 3G connection
Calibre: Site dashboard | View this PR | Edit settings | View documentation
|
||
let call = ctx.query(); | ||
let hook_name_range = call.callee().ok()?.syntax().text_trimmed_range(); | ||
if react_hook_configuration(call, &options.hooks_config).is_some() { |
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.
While it makes sense to only inspect hooks that are present in the configuration for useExhaustiveDependencies
(since we need to know if it's a hook with a dependencies array), for useHookAtTopLevel
we could detect all function calls where the identifier of the callee starts with a lowercase "use" (if necessary we could narrow it down to only inspect "components functions", with a name starting with an uppercase letter)
Additionally since we're inspecting the call chain, we could enforce the "use*" naming convention on all intermediate functions between hooks and components. For instance here:
function Component() {
intermediate();
}
function intermediate() {
useEffect();
}
We could show a diagnostic on intermediate
suggesting to rename it to useIntermediate
(but that kind of analysis doesn't really suit the useHookAtTopLevel
name)
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.
While it makes sense to only inspect hooks that are present in the configuration for useExhaustiveDependencies (since we need to know if it's a hook with a dependencies array), for useHookAtTopLevel we could detect all function calls where the identifier of the callee starts with a lowercase "use" (if necessary we could narrow it down to only inspect "components functions", with a name starting with an uppercase letter)
Yeah. I have some plans to improve this.
Currently all react rules will need to query for JsCallExpression
and check if is a React call or not. Probably with 99% false rate. My plan is to create a service, or inside the semantic model or not, to flag react calls.
Then you would create something like
let calls = ctx.query().all_react_class();
or even
let hooks = ctx.query().all_hooks();
But for now, since we don't have this yet, I would keep this as it is.
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.
Additionally since we're inspecting the call chain, we could enforce the "use*" naming convention on all intermediate functions between hooks and components. For instance here:
We could show a diagnostic on intermediate suggesting to rename it to useIntermediate (but that kind of analysis doesn't really suit the useHookAtTopLevel name)
This is actually one of the new hooks rules we will create for vNext.
c60444a
to
9506ace
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.
I left some feedback.
Overall I think the PR is amazing, I think the diagnostics are even better than the ones of the eslint plugin! 🚀
I don't have huge concerns, although:
- we should review the wording of our diagnostics and try to make them beginner-friendly, the use of "Top Level" is not very known to beginners or even mid-level react developers;
- about
just
, we should update the contribution guidelines with the new commands and howjust
should be installed on the different platforms (even a link is sufficient). For example, I wouldn't know how to install and use it without some guidelines;
crates/rome_js_analyze/src/semantic_analyzers/nursery/use_hook_at_top_level.rs
Outdated
Show resolved
Hide resolved
crates/rome_js_analyze/src/semantic_analyzers/nursery/use_hook_at_top_level.rs
Outdated
Show resolved
Hide resolved
for call in model.all_calls(&f) { | ||
let node = call.tree(); | ||
let path = path.clone(); | ||
calls.push(CallPath { call: node, path }); | ||
} |
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.
Not sure if it makes a difference:
calls.extend(
model.all_calls(&f).into_inter().map(|call| {
let node = call.tree();
let path = path.clone();
CallPath { call: node, path }
})
)
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.
It is the same thing. But I tend to consider expressions with closures more complex, including for the compiler.
rule_category!(), | ||
hook_name_range, | ||
markup! { | ||
"This hook is not necessarily being called from the Top Level of the component function." |
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.
I wonder if we can align our wording with the one from the eslint plugin:
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
This would help the transition to our rules. I expect users to be accustomed to the wording provided by the plugin. What concerns me is the use of "Top level" which, I think. it's not a beginner-friendly concept.
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.
Done. What do you think now?
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.
Way better now! Thank you for looking into this!
3ef943c
to
4504d38
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.
Looks good to me! Could you please update the contribution guidelines or create an issue to track it? There's some places where we should update:
- https://github.com/rome/tools/blob/main/CONTRIBUTING.md#generated-files
- https://github.com/rome/tools/blob/main/crates/rome_analyze/CONTRIBUTING.md#code-generation
- https://github.com/rome/tools/blob/main/crates/rome_analyze/CONTRIBUTING.md#create-your-first-rule here maybe we could add your new
just
command
4504d38
to
64c6b34
Compare
Summary
Implements https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
Codegen and just
This PR also improve codegen around lint rules:
just new-lintrule
will create everything needed to start a rule from the scratch. The template can sure be improved, but as a first version I think it is fine.just test-lintrule
will run all the rule tests.just check-ready
check if the branch is ready to be merged.Test Plan