From c7b0a6561d75ca6f5225d3fd35d9294aac665679 Mon Sep 17 00:00:00 2001
From: connorshea <2977353+connorshea@users.noreply.github.com>
Date: Sun, 1 Feb 2026 22:30:06 +0000
Subject: [PATCH] fix(linter): Fix config option docs for
`react/jsx-boolean-value` rule. (#18811)
Implement a proper tuple config so the resulting docs actually match the implemented rule. Also enforce the config options' validity by using TupleRuleConfig.
Also improve the documentation to make it easier to understand what the rule and config options actually do.
Part of #16023.
Generated docs:
````md
### Examples
Examples of **incorrect** code for this rule with default `"never"` mode:
```jsx
const Hello = ;
```
Examples of **correct** code for this rule with default `"never"` mode:
```jsx
const Hello = ;
const Foo = ;
```
Examples of **incorrect** code for this rule with `"always"` mode:
```jsx
const Hello = ;
```
Examples of **correct** code for this rule with `"always"` mode:
```jsx
const Hello = ;
```
## Configuration
### The 1st option
type: `"always" | "never"`
#### `"always"`
All boolean attributes must have explicit values.
#### `"never"`
All boolean attributes must omit values that are set to `true`.
### The 2nd option
This option is an object with the following properties:
#### always
type: `string[]`
default: `[]`
List of attribute names that should always have explicit boolean values.
Only necessary when main mode is `"never"`.
#### assumeUndefinedIsFalse
type: `boolean`
default: `false`
If `true`, treats `prop={false}` as equivalent to the prop being `undefined`.
When combined with `"never"` mode, this will enforce that the attribute is omitted entirely.
```jsx
// With "assumeUndefinedIsFalse": true
; // Incorrect
; // Correct
```
This option does nothing in `"always"` mode.
#### never
type: `string[]`
default: `[]`
List of attribute names that should never have explicit boolean values.
Only necessary when main mode is `"always"`.
````
AI Disclosure: Built with help from Claude Code + Opus 4.5, docs updates written manually by me.
My only real concern with this change is that the pre-calculation of "exceptions" no longer happens as part of the config parsing, and instead happens during the rule's code checks. This is going to be slower, but I'm not sure how much slower.
---
.../src/rules/react/jsx_boolean_value.rs | 153 +++++++++---------
1 file changed, 78 insertions(+), 75 deletions(-)
diff --git a/crates/oxc_linter/src/rules/react/jsx_boolean_value.rs b/crates/oxc_linter/src/rules/react/jsx_boolean_value.rs
index 95bcbeeb4c084..8e913c02f444f 100644
--- a/crates/oxc_linter/src/rules/react/jsx_boolean_value.rs
+++ b/crates/oxc_linter/src/rules/react/jsx_boolean_value.rs
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use crate::{
AstNode,
context::{ContextHost, LintContext},
- rule::Rule,
+ rule::{Rule, TupleRuleConfig},
utils::get_prop_value,
};
@@ -31,35 +31,44 @@ fn boolean_value_undefined_false_diagnostic(attr: &str, span: Span) -> OxcDiagno
.with_label(span)
}
-#[derive(Debug, Default, Clone)]
-pub struct JsxBooleanValue(Box);
-
#[derive(Debug, Default, Clone, JsonSchema, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum EnforceBooleanAttribute {
+ /// All boolean attributes must have explicit values.
Always,
+ /// All boolean attributes must omit values that are set to `true`.
#[default]
Never,
}
#[derive(Debug, Default, Clone, JsonSchema, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct JsxBooleanValueConfig {
- /// Enforce boolean attributes to always or never have a value.
- pub enforce_boolean_attribute: EnforceBooleanAttribute,
- /// List of attribute names to exclude from the rule.
- pub exceptions: FxHashSet,
- /// If true, treats `prop={false}` as equivalent to the prop being undefined
- pub assume_undefined_is_false: bool,
+#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
+pub struct JsxBooleanValueOptions {
+ /// List of attribute names that should always have explicit boolean values.
+ /// Only necessary when main mode is `"never"`.
+ always: FxHashSet,
+ /// List of attribute names that should never have explicit boolean values.
+ /// Only necessary when main mode is `"always"`.
+ never: FxHashSet,
+ /// If `true`, treats `prop={false}` as equivalent to the prop being `undefined`.
+ /// When combined with `"never"` mode, this will enforce that the attribute is omitted entirely.
+ ///
+ /// ```jsx
+ /// // With "assumeUndefinedIsFalse": true
+ /// ; // Incorrect
+ /// ; // Correct
+ /// ```
+ ///
+ /// This option does nothing in `"always"` mode.
+ assume_undefined_is_false: bool,
}
-impl std::ops::Deref for JsxBooleanValue {
- type Target = JsxBooleanValueConfig;
+#[derive(Debug, Default, Clone, JsonSchema, Deserialize, Serialize)]
+#[serde(default)]
+pub struct JsxBooleanValueConfig(EnforceBooleanAttribute, JsxBooleanValueOptions);
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
+#[derive(Debug, Default, Clone, Deserialize)]
+pub struct JsxBooleanValue(Box);
declare_oxc_lint!(
/// ### What it does
@@ -68,19 +77,32 @@ declare_oxc_lint!(
///
/// ### Why is this bad?
///
- /// In JSX, you can set a boolean attribute to `true` or omit it. This rule will enforce a consistent style for boolean attributes.
+ /// In JSX, you can set a boolean attribute to `true` or omit it.
+ /// This rule will enforce a consistent style for boolean attributes.
///
/// ### Examples
///
- /// Examples of **incorrect** code for this rule:
+ /// Examples of **incorrect** code for this rule with default `"never"` mode:
/// ```jsx
/// const Hello = ;
/// ```
///
- /// Examples of **correct** code for this rule:
+ /// Examples of **correct** code for this rule with default `"never"` mode:
+ /// ```jsx
+ /// const Hello = ;
+ ///
+ /// const Foo = ;
+ /// ```
+ ///
+ /// Examples of **incorrect** code for this rule with `"always"` mode:
/// ```jsx
/// const Hello = ;
/// ```
+ ///
+ /// Examples of **correct** code for this rule with `"always"` mode:
+ /// ```jsx
+ /// const Hello = ;
+ /// ```
JsxBooleanValue,
react,
style,
@@ -90,50 +112,21 @@ declare_oxc_lint!(
impl Rule for JsxBooleanValue {
fn from_configuration(value: serde_json::Value) -> Result {
- let enforce_boolean_attribute = value
- .get(0)
- .and_then(serde_json::Value::as_str)
- .map_or_else(EnforceBooleanAttribute::default, |value| match value {
- "always" => EnforceBooleanAttribute::Always,
- _ => EnforceBooleanAttribute::Never,
- });
-
- let config = value.get(1);
- let assume_undefined_is_false = config
- .and_then(|c| c.get("assumeUndefinedIsFalse"))
- .and_then(serde_json::Value::as_bool)
- .unwrap_or(false);
-
- // The exceptions are the inverse of the default, specifying both always and
- // never in the rule configuration is not allowed and ignored.
- let attribute_name = match enforce_boolean_attribute {
- EnforceBooleanAttribute::Never => "always",
- EnforceBooleanAttribute::Always => "never",
- };
-
- let exceptions = config
- .and_then(|c| c.get(attribute_name))
- .and_then(serde_json::Value::as_array)
- .map(|v| v.iter().filter_map(serde_json::Value::as_str).map(CompactStr::from).collect())
- .unwrap_or_default();
-
- Ok(Self(Box::new(JsxBooleanValueConfig {
- enforce_boolean_attribute,
- exceptions,
- assume_undefined_is_false,
- })))
+ serde_json::from_value::>(value).map(TupleRuleConfig::into_inner)
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::JSXOpeningElement(jsx_opening_elem) = node.kind() else { return };
+ let JsxBooleanValueConfig(mode, options) = &*self.0;
+
for attr in &jsx_opening_elem.attributes {
let JSXAttributeItem::Attribute(jsx_attr) = attr else { continue };
let JSXAttributeName::Identifier(ident) = &jsx_attr.name else { continue };
match get_prop_value(attr) {
None => {
- if self.is_always(ident.name.as_str()) {
+ if Self::is_always(mode, options, ident.name.as_str()) {
ctx.diagnostic_with_fix(
boolean_value_always_diagnostic(&ident.name, ident.span),
|fixer| fixer.insert_text_after(&ident.span, "={true}"),
@@ -144,7 +137,7 @@ impl Rule for JsxBooleanValue {
if let Some(expr) = container.expression.as_expression()
&& let Expression::BooleanLiteral(expr) = expr.without_parentheses()
{
- if expr.value && self.is_never(ident.name.as_str()) {
+ if expr.value && Self::is_never(mode, options, ident.name.as_str()) {
let span = Span::new(ident.span.end, jsx_attr.span.end);
ctx.diagnostic_with_fix(
boolean_value_diagnostic(&ident.name, span),
@@ -153,8 +146,8 @@ impl Rule for JsxBooleanValue {
}
if !expr.value
- && self.is_never(ident.name.as_str())
- && self.assume_undefined_is_false
+ && Self::is_never(mode, options, ident.name.as_str())
+ && options.assume_undefined_is_false
{
ctx.diagnostic_with_fix(
boolean_value_undefined_false_diagnostic(
@@ -177,20 +170,32 @@ impl Rule for JsxBooleanValue {
}
impl JsxBooleanValue {
- fn is_always(&self, prop_name: &str) -> bool {
- let is_exception = self.exceptions.contains(prop_name);
- if matches!(self.enforce_boolean_attribute, EnforceBooleanAttribute::Always) {
- return !is_exception;
+ /// Returns true if the attribute should always have an explicit boolean value.
+ fn is_always(
+ mode: &EnforceBooleanAttribute,
+ options: &JsxBooleanValueOptions,
+ prop_name: &str,
+ ) -> bool {
+ match mode {
+ // When mode is "always", all attributes should have explicit values except those in `never`
+ EnforceBooleanAttribute::Always => !options.never.contains(prop_name),
+ // When mode is "never", only attributes in `always` should have explicit values
+ EnforceBooleanAttribute::Never => options.always.contains(prop_name),
}
- is_exception
}
- fn is_never(&self, prop_name: &str) -> bool {
- let is_exception = self.exceptions.contains(prop_name);
- if matches!(self.enforce_boolean_attribute, EnforceBooleanAttribute::Never) {
- return !is_exception;
+ /// Returns true if the attribute should never have an explicit boolean value.
+ fn is_never(
+ mode: &EnforceBooleanAttribute,
+ options: &JsxBooleanValueOptions,
+ prop_name: &str,
+ ) -> bool {
+ match mode {
+ // When mode is "never", all attributes should omit values except those in `always`
+ EnforceBooleanAttribute::Never => !options.always.contains(prop_name),
+ // When mode is "always", only attributes in `never` should omit values
+ EnforceBooleanAttribute::Always => options.never.contains(prop_name),
}
- is_exception
}
}
@@ -229,10 +234,9 @@ fn test() {
),
(
";",
- Some(serde_json::json!([
- "always",
- { "assumeUndefinedIsFalse": true, "never": ["baz", "bak"] },
- ])),
+ Some(
+ serde_json::json!(["always", { "assumeUndefinedIsFalse": true, "never": ["baz", "bak"] }]),
+ ),
),
(
";",
@@ -255,10 +259,9 @@ fn test() {
(
";",
";",
- Some(serde_json::json!([
- "always",
- { "assumeUndefinedIsFalse": true, "never": ["baz", "bak"] },
- ])),
+ Some(
+ serde_json::json!(["always", { "assumeUndefinedIsFalse": true, "never": ["baz", "bak"] }]),
+ ),
),
("", "", Some(serde_json::json!(["always"]))),
];