-
Notifications
You must be signed in to change notification settings - Fork 656
fix(rome_js_analyze): false negatives of noArrayIndexKey
#3681
Conversation
❌ Deploy Preview for docs-rometools failed.
|
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.
Thanks for your PR. I think there are some more issues with the rule (unrelated to your changes) and it may be possible to simplify the finding of the closest call expression.
crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs
Outdated
Show resolved
Hide resolved
crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs
Outdated
Show resolved
Hide resolved
crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs
Outdated
Show resolved
Hide resolved
@@ -7,8 +7,8 @@ use rome_js_semantic::SemanticModel; | |||
use rome_js_syntax::{ | |||
JsAnyCallArgument, JsArrowFunctionExpression, JsCallExpression, JsExpressionStatement, | |||
JsFunctionDeclaration, JsFunctionExpression, JsIdentifierBinding, JsMethodClassMember, | |||
JsMethodObjectMember, JsParameterList, JsPropertyObjectMember, JsReferenceIdentifier, | |||
JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, | |||
JsMethodObjectMember, JsParameterList, JsParenthesizedExpression, JsPropertyObjectMember, |
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'm sorry for commenting here but GitHub doesn't allow me to comment unchanged lines.
We should use the semantic model to resolve where the expression assigned to key
is declared. This replaces the find_function_parent
call on line 135
let Some(parameter) = model
.declaration(&reference)
.and_then(|declaration| declaration.syntax().parent())
.and_then(JsFormalParameter::cast);
let Some(function) = parameter
.parent::<JsParameterList>()
.and_then(|list| list.parent::<JsParameters>())
.and_then(|parameters| parameters.parent::<JsAnyFunction>()) else { return None; };
let Some(call_expression) = parameter
.parent::<JsCallArgumentList>()
.and_then(|arguments| arguments.parent::<JsCallArguments>())
.and_then(|arguments| arguments.parent::<JsCallExpression>()) else { return None };
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.
No worries and thanks @MichaReiser!
I was trying to refactor the rule by matching on JsxAttribute
and JsPropertyObjectMember
following suggestions in #3681 (comment) and #3681 (comment).
- Use the semantic model to find the declaration of index that is inside a
JsArrowFunctionExpression
orJsFunctionExpression
- From the function expression above, find the closest call expression that belongs to an array method
- Then traverse down to check if the declaration is the second argument of the function call to avoid false positive like the following
things.map((id) => {
return <Jsx key={id} />
})
- For
JsPropertyObjectMember
we need to navigate down further to check if theJsObjectExpression
is the second argument of aReact.cloneElement
call
React.Children.forEach(this.props.children, (_, index) => {
const obj = {key: index}
// we don't know if the key above is used as `JsxAttribute`
return notCloneElement(obj)
})
But I'm not sure if there is a better way to address the false positives without navigating up and then down like the above steps 🤔
Or should we address the more common use cases first without handling edge cases that are less likely to appear in userland?
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.
Then traverse down to check if the declaration is the second argument of the function call to avoid false positive like the following
You can probably use parameter.syntax().index()
. It gives you the index in the enclosing parameter list (you have to divide it by two because the list elements are parameter
, comma, parameter2`.
React.Children.forEach(this.props.children, (_, index) => {
const obj = {key: index}
// we don't know if the key above is used asJsxAttribute
return notCloneElement(obj)
})
I'm not sure if I understand the question/problem here. I believe it should be sufficient to only detect calls to cloneElement
where the object is the argument cloneElement { key: index }
.
The way I would expect cloneElement
to work if you match on object member is that:
- You test if the object member name is
key
- You navigate upwards from the object member until you find the enclosing call expression
- You test if it is a react API call
- If that's the case, do the same as for JSX elements by resolving the binding for
index
- then finding the enclosing call expression
- test if the call expression is an array method.
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.
bc7a03c now the rule is matching on JsxAttribute
and JsPropertyObjectMember
, and it will
- Find if it's a key property
- Retrieve the reference identifier from the key property,
- Use the semantic model to find the declaration of the reference is a parameter in a function expression
- Find the enclosing call expression
- Check if the call expression is an array method and the parameter is the array index of that method
- If it's matching on
JsPropertyObjectMember
, detect calls tocloneElement
Also, added tests for flatMap
and Array.from
How do we test if the current implementation is faster or slower than the previous 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.
Not sure if I can remove ReactCloneElementCall::from_call_expression
, it's no longer used because of this refactor
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.
ReactCloneElementCall
It's safe to remove the method as it is no longer used.
crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs
Outdated
Show resolved
Hide resolved
!bench_analyzer |
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.
Awesome work! Nice to see how you managed to shorten the implementation by 100 lines of code!
Analyzer Benchmark Results
|
Nice! |
And there are now errors that Rome detects in the playground because of your fixes :D Awesome job |
noArrayIndexKey
noArrayIndexKey
Summary
noArrayIndexKey
ignores valid cases #3670Test Plan
Added new test cases