Skip to content

Commit

Permalink
feat(linter): implement useExplicitLengthCheck (#2631)
Browse files Browse the repository at this point in the history
  • Loading branch information
minht11 authored May 4, 2024
1 parent f3a651d commit f77ab54
Show file tree
Hide file tree
Showing 16 changed files with 1,724 additions and 90 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

### Linter

#### New features

- Add [nursery/useExplicitLengthCheck](https://biomejs.dev/linter/rules/use-explicit-length-check/).

#### Bug fixes

- [noBlankTarget](https://biomejs.dev/linter/rules/no-blank-target/) no longer hangs when applying a code fix ([#2675](https://github.com/biomejs/biome/issues/2675)).
Expand Down
14 changes: 14 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 26 additions & 7 deletions crates/biome_configuration/src/linter/rules.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ define_categories! {
"lint/nursery/noUselessUndefinedInitialization": "https://biomejs.dev/linter/rules/no-useless-undefined-initialization",
"lint/nursery/useArrayLiterals": "https://biomejs.dev/linter/rules/use-array-literals",
"lint/nursery/useBiomeSuppressionComment": "https://biomejs.dev/linter/rules/use-biome-suppression-comment",
"lint/nursery/useExplicitLengthCheck": "https://biomejs.dev/linter/rules/use-explicit-length-check",
"lint/nursery/useConsistentBuiltinInstantiation": "https://biomejs.dev/linter/rules/use-consistent-new-builtin",
"lint/nursery/useDefaultSwitchClause": "https://biomejs.dev/linter/rules/use-default-switch-clause",
"lint/nursery/useGenericFontNames": "https://biomejs.dev/linter/rules/use-generic-font-names",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ use biome_analyze::{
use biome_console::markup;
use biome_diagnostics::Applicability;
use biome_js_syntax::{
AnyJsExpression, JsCallArgumentList, JsCallArguments, JsCallExpression,
JsConditionalExpression, JsDoWhileStatement, JsForStatement, JsIfStatement, JsNewExpression,
JsSyntaxKind, JsSyntaxNode, JsUnaryExpression, JsUnaryOperator, JsWhileStatement,
is_in_boolean_context, is_negation, AnyJsExpression, JsCallArgumentList, JsCallArguments,
JsCallExpression, JsNewExpression, JsSyntaxNode, JsUnaryOperator,
};
use biome_rowan::{AstNode, AstSeparatedList, BatchMutationExt, SyntaxNodeCast};
use biome_rowan::{AstNode, AstSeparatedList, BatchMutationExt};

use crate::JsRuleAction;

Expand Down Expand Up @@ -65,52 +64,17 @@ declare_rule! {
}
}

/// Check if this node is in the position of `test` slot of parent syntax node.
/// ## Example
/// ```js
/// if (!!x) {
/// ^^^ this is a boolean context
/// }
/// ```
fn is_in_boolean_context(node: &JsSyntaxNode) -> Option<bool> {
let parent = node.parent()?;
match parent.kind() {
JsSyntaxKind::JS_IF_STATEMENT => {
Some(parent.cast::<JsIfStatement>()?.test().ok()?.syntax() == node)
}
JsSyntaxKind::JS_DO_WHILE_STATEMENT => {
Some(parent.cast::<JsDoWhileStatement>()?.test().ok()?.syntax() == node)
}
JsSyntaxKind::JS_WHILE_STATEMENT => {
Some(parent.cast::<JsWhileStatement>()?.test().ok()?.syntax() == node)
}
JsSyntaxKind::JS_FOR_STATEMENT => {
Some(parent.cast::<JsForStatement>()?.test()?.syntax() == node)
}
JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => Some(
parent
.cast::<JsConditionalExpression>()?
.test()
.ok()?
.syntax()
== node,
),
_ => None,
}
}

/// Checks if the node is a `Boolean` Constructor Call
/// # Example
/// ```js
/// new Boolean(x);
/// ```
/// The checking algorithm of [JsNewExpression] is a little different from [JsCallExpression] due to
/// two nodes have different shapes
fn is_boolean_constructor_call(node: &JsSyntaxNode) -> Option<bool> {
pub fn is_boolean_constructor_call(node: &JsSyntaxNode) -> Option<JsNewExpression> {
let expr = JsCallArgumentList::cast_ref(node)?
.parent::<JsCallArguments>()?
.parent::<JsNewExpression>()?;
Some(expr.has_callee("Boolean"))

expr.has_callee("Boolean").then_some(expr)
}

/// Check if the SyntaxNode is a `Boolean` Call Expression
Expand All @@ -123,20 +87,6 @@ fn is_boolean_call(node: &JsSyntaxNode) -> Option<bool> {
Some(expr.has_callee("Boolean"))
}

/// Check if the SyntaxNode is a Negate Unary Expression
/// ## Example
/// ```js
/// !!x
/// ```
fn is_negation(node: &JsSyntaxNode) -> Option<JsUnaryExpression> {
let unary_expr = JsUnaryExpression::cast_ref(node)?;
if unary_expr.operator().ok()? == JsUnaryOperator::LogicalNot {
Some(unary_expr)
} else {
None
}
}

impl Rule for NoExtraBooleanCast {
type Query = Ast<AnyJsExpression>;
type State = (AnyJsExpression, ExtraBooleanCastType);
Expand All @@ -151,7 +101,7 @@ impl Rule for NoExtraBooleanCast {
// reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
let parent_node_in_boolean_cast_context = is_in_boolean_context(n.syntax())
.unwrap_or(false)
|| is_boolean_constructor_call(&parent).unwrap_or(false)
|| is_boolean_constructor_call(&parent).is_some()
|| is_negation(&parent).is_some()
|| is_boolean_call(&parent).unwrap_or(false);
// Convert `!!x` -> `x` if parent `SyntaxNode` in any boolean `Type Coercion` context
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f77ab54

Please sign in to comment.