-
-
Notifications
You must be signed in to change notification settings - Fork 474
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
feat(lint): add rule noUselessLengthCheck
#4060
base: main
Are you sure you want to change the base?
feat(lint): add rule noUselessLengthCheck
#4060
Conversation
CodSpeed Performance ReportMerging #4060 will not alter performanceComparing Summary
|
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
…ck.rs Co-authored-by: Carson McManus <[email protected]>
…ck.rs Co-authored-by: Carson McManus <[email protected]>
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. The merge conflict can be fixed by rerunning codegen.
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
@GunseiKPaseri Could you add a changelog entry and resolve the conflicts? |
…ck.rs Co-authored-by: Carson McManus <[email protected]>
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
…ck.rs Co-authored-by: unvalley <[email protected]>
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
ErrorType::UselessLengthCheckWithEvery => markup! { | ||
"The empty check is useless as `Array#every()` returns `true` for an empty array." | ||
}, | ||
ErrorType::UselessLengthCheckWithSome => markup! { | ||
"The non-empty check is useless as `Array#some()` returns `false` for an empty 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.
We can use <Emphasis>
tag here to Array#every()
, true
, Array#some()
, false
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 overall, left nits suggestions
…ck.rs Co-authored-by: unvalley <[email protected]>
…ck.rs Co-authored-by: unvalley <[email protected]>
It was difficult to determine whether it was an unsafe fix, but is it okay to leave it as an unsafe fix? Is there any way to make it a safe fix? |
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 an early review
&& literal.to_string().trim() == "0" | ||
&& (operator == T![===] || operator == T![<]) | ||
{ | ||
return Some(target); | ||
} | ||
// .length !== 0 | ||
if matches!(expect_error, ErrorType::UselessLengthCheckWithSome) | ||
&& (literal.to_string().trim() == "0" && (operator == T![!==] || operator == T![>]) |
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.
Two things to change in all the code:
- don't allocate a string, so don't use
to_string
there's no need. You can usesyntax().text_trimmed()
- don't check the binary operations using the raw characters, use this utility https://github.com/biomejs/biome/blob/main/crates%2Fbiome_js_syntax%2Fsrc%2Fexpr_ext.rs#L259-L260
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.
Thank you for your contribution @GunseiKPaseri
This rule is quite complex, so we wouldn't want to urge the need to merge. There are a few concerns that should be addressed before:
- recursions: we should remove it and find an algorithm that puts less memory pressure
- complexity: I believe the code of this rule is too complex for what it should do, and it can be simplified. For example, I can't understand why we use a
Vec<>
for signals. - documentation: since this rule requires a lot of code, you should strive to provide as much documentation as possible. We are picky on this front
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
.map(|token| token.kind() == operator) | ||
.or(Some(false)); | ||
} | ||
(!JsParenthesizedExpression::can_cast(ancestor.kind())).then_some(false) |
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.
JsParenthesizedExpression
can be tough to deal with; for example, I don't think we are able to catch these cases:
if (((((array.length === 0 || array.every(Boolean))))));
The type AnyJsExpression
has a method called omit_parentheses
, which returns the expression inside all the parenthesis. You should use that, and see if you can catch cases like the one I shared
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 function returns whether the parent is a JsLogicalExpression
. When the parent is a JsParenthesizedExpression
, we want to continue searching up to its parent as well, so this function is called to check for that. Therefore, even with the current implementation, such cases can be detected. (However, since I have learned about the convenient omit_parentheses
function, I would like to rewrite some parts using it. Thank you.)
.unwrap_or(false) | ||
} | ||
|
||
/// Extract the expressions that perform length comparisons corresponding to the errors you want to check. |
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 find it difficult to understand the documentation; maybe you should provide an example. Perhaps because you call the second argument "error", you should see a better name, like the one I suggested.
Also, it would be nice to provide a code example in the docs.
crates/biome_js_analyze/src/lint/nursery/no_useless_length_check.rs
Outdated
Show resolved
Hide resolved
for err_type in [ | ||
ErrorType::UselessLengthCheckWithEvery, | ||
ErrorType::UselessLengthCheckWithSome, | ||
] { |
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 solution isn't maintainable, because if one day we add a new variant to ErrorType
, we need to tweak business logic.
The thing is, the rule knows it always needs to check for Array.every()
and Array.some()
, so I don't understand why we need to pass err_type
as a parameter.
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.
The naming might have been misleading. This actually refers to both &&
・||
and Some・Every, and it is intended to specify searching only one of them in the recursion. Since most of the code is the same, I consolidated it.
}; | ||
let left = logical_exp.left().ok()?; | ||
let left_replacer = (logical_exp.clone(), logical_exp.right().ok()?, left.range()); | ||
search_logical_exp( |
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 is a recursion, and when you want to avoid it. Usually, recursions are fine, but they aren't fit for working with trees, like an AST/CST.
You should refactor this code and try to use a simple loop, or maybe you could evaluate an iterator.
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 want to search through the entire logical expression. However, I couldn't think of a method other than recursion to extract A
, B
, C
from A || B || C
or A
, B
, C
, D
from (A || B) || (C || D)
. If it encounters something other than a logical expression or a different operator, the implementation is designed to terminate the process. Therefore, I believe the search will not become heavier than the logical expression being passed. If there is a good utility for this, I would like to use it.
any_exp: &AnyJsExpression, | ||
replacer: Option<Replacer>, | ||
expect_error: &ErrorType, | ||
comparing_zeros: &mut HashMap<String, Vec<Replacer>>, | ||
array_tokens_used_api: &mut HashSet<String>, |
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.
There are too many arguments here, I think. But it's fine. However, you should document them and explain what they are. What's any_exp
, for example? It's difficult to understand what it is, especially in relation to the name to search_local_exp
. Maybe you want to give a better name, like expression_to_search
.
Also, replacer
is a bit cryptic. Usually, you should try to give your parameters a name based on its business logic, not on its type.
|
||
impl Rule for NoUselessLengthCheck { | ||
type Query = Ast<JsLogicalExpression>; | ||
type State = (ErrorType, Replacer); |
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 believe it's best to use a struct
for the state, so you can document every field. As it is, it's difficult to understand what is it.
…ck.rs Co-authored-by: Emanuele Stoppa <[email protected]>
…ck.rs Co-authored-by: Emanuele Stoppa <[email protected]>
Here's a generic comment based on the responses I had from the other comments. I believe I understand more or less what you're trying to do; here's some advice based on my understanding of what you're trying to do. When you submit a PR that has some heavy business logic, you should at least provide a technical explanation of how you solve a problem. This is important because it reduces the cognitive load of the reviewers. I understand that the solution is in your head, but the solution isn't in the head of the reviewers, so you need to explain your solution to the others. This happened before, so please try to follow it. I still believe you don't need multiple signals, and still believe this rule can be simplified.
|
Summary
Implement
noUselessLengthCheck
(unicorn/no-useless-length-check)closes #3941
Test Plan