diff --git a/crates/oxc_linter/src/rules/react/jsx_fragments.rs b/crates/oxc_linter/src/rules/react/jsx_fragments.rs
index 9eadeac0ef20b..ae20b2cf8113b 100644
--- a/crates/oxc_linter/src/rules/react/jsx_fragments.rs
+++ b/crates/oxc_linter/src/rules/react/jsx_fragments.rs
@@ -6,27 +6,52 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
-use crate::{AstNode, context::LintContext, rule::Rule, utils::is_jsx_fragment};
+use crate::{
+ AstNode,
+ context::LintContext,
+ rule::{DefaultRuleConfig, Rule},
+ utils::is_jsx_fragment,
+};
fn jsx_fragments_diagnostic(span: Span, mode: FragmentMode) -> OxcDiagnostic {
let msg = if mode == FragmentMode::Element {
- "Standard form for React fragments is preferred"
+ "Standard form for React fragments is preferred."
} else {
- "Shorthand form for React fragments is preferred"
+ "Shorthand form for React fragments is preferred."
};
let help = if mode == FragmentMode::Element {
- "Use instead of <>>"
+ "Use `` instead of `<>>`."
} else {
- "Use <>> instead of "
+ "Use `<>>` instead of ``."
};
OxcDiagnostic::warn(msg).with_help(help).with_label(span)
}
-#[derive(Debug, Default, Clone, JsonSchema, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct JsxFragments {
- /// `syntax` mode:
- ///
+#[derive(Debug, Clone, JsonSchema, Deserialize)]
+#[serde(untagged)]
+pub enum JsxFragments {
+ Mode(FragmentMode),
+ Object { mode: FragmentMode },
+}
+
+impl Default for JsxFragments {
+ fn default() -> Self {
+ JsxFragments::Mode(FragmentMode::Syntax)
+ }
+}
+
+impl JsxFragments {
+ fn mode(&self) -> FragmentMode {
+ match self {
+ JsxFragments::Mode(m) => *m,
+ JsxFragments::Object { mode } => *mode,
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, JsonSchema, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum FragmentMode {
/// This is the default mode. It will enforce the shorthand syntax for React fragments, with one exception.
/// Keys or attributes are not supported by the shorthand syntax, so the rule will not warn on standard-form fragments that use those.
///
@@ -43,8 +68,8 @@ pub struct JsxFragments {
/// ```jsx
///
/// ```
- ///
- /// `element` mode:
+ #[default]
+ Syntax,
/// This mode enforces the standard form for React fragments.
///
/// Examples of **incorrect** code for this rule:
@@ -60,23 +85,9 @@ pub struct JsxFragments {
/// ```jsx
///
/// ```
- mode: FragmentMode,
-}
-
-#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, JsonSchema, Deserialize, Serialize)]
-#[serde(rename_all = "kebab-case")]
-pub enum FragmentMode {
- #[default]
- Syntax,
Element,
}
-impl From<&str> for FragmentMode {
- fn from(value: &str) -> Self {
- if value == "element" { Self::Element } else { Self::Syntax }
- }
-}
-
declare_oxc_lint!(
/// ### What it does
///
@@ -89,24 +100,21 @@ declare_oxc_lint!(
react,
style,
fix,
- config = JsxFragments,
+ config = FragmentMode,
);
impl Rule for JsxFragments {
+ // Generally we should prefer the string-only syntax for compatibility with the original ESLint rule,
+ // but we originally implemented the rule with only the object syntax, so we support both now.
fn from_configuration(value: Value) -> Self {
- let obj = value.get(0);
- Self {
- mode: obj
- .and_then(|v| v.get("mode"))
- .and_then(Value::as_str)
- .map(FragmentMode::from)
- .unwrap_or_default(),
- }
+ serde_json::from_value::>(value)
+ .unwrap_or_default()
+ .into_inner()
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
- AstKind::JSXElement(jsx_elem) if self.mode == FragmentMode::Syntax => {
+ AstKind::JSXElement(jsx_elem) if self.mode() == FragmentMode::Syntax => {
let Some(closing_element) = &jsx_elem.closing_element else {
return;
};
@@ -116,7 +124,7 @@ impl Rule for JsxFragments {
return;
}
ctx.diagnostic_with_fix(
- jsx_fragments_diagnostic(jsx_elem.opening_element.name.span(), self.mode),
+ jsx_fragments_diagnostic(jsx_elem.opening_element.name.span(), self.mode()),
|fixer| {
let before_opening_tag = ctx.source_range(Span::new(
jsx_elem.span().start,
@@ -140,9 +148,9 @@ impl Rule for JsxFragments {
},
);
}
- AstKind::JSXFragment(jsx_frag) if self.mode == FragmentMode::Element => {
+ AstKind::JSXFragment(jsx_frag) if self.mode() == FragmentMode::Element => {
ctx.diagnostic_with_fix(
- jsx_fragments_diagnostic(jsx_frag.opening_fragment.span(), self.mode),
+ jsx_fragments_diagnostic(jsx_frag.opening_fragment.span(), self.mode()),
|fixer| {
let before_opening_tag = ctx.source_range(Span::new(
jsx_frag.span().start,
@@ -186,24 +194,31 @@ fn test() {
(r#""#, None),
("", None),
("", None),
+ // Configuration can be done via a string directly, or an object with the `mode` field.
+ ("<>>", Some(json!(["syntax"]))),
+ ("<>>", Some(json!([{"mode": "syntax"}]))),
+ ("", Some(json!(["element"]))),
("", Some(json!([{"mode": "element"}]))),
];
let fail = vec![
("", None),
("", None),
+ ("<>>", Some(json!(["element"]))),
("<>>", Some(json!([{"mode": "element"}]))),
];
let fix = vec![
("", "<>>", None),
("", "<>>", None),
+ ("<>>", "", Some(json!(["element"]))),
(
"<>>",
"",
Some(json!([{"mode": "element"}])),
),
];
+
Tester::new(JsxFragments::NAME, JsxFragments::PLUGIN, pass, fail)
.expect_fix(fix)
.test_and_snapshot();
diff --git a/crates/oxc_linter/src/snapshots/react_jsx_fragments.snap b/crates/oxc_linter/src/snapshots/react_jsx_fragments.snap
index 30dc51f0877d5..d693f8a83f6d5 100644
--- a/crates/oxc_linter/src/snapshots/react_jsx_fragments.snap
+++ b/crates/oxc_linter/src/snapshots/react_jsx_fragments.snap
@@ -1,23 +1,30 @@
---
source: crates/oxc_linter/src/tester.rs
---
- ⚠ eslint-plugin-react(jsx-fragments): Shorthand form for React fragments is preferred
+ ⚠ eslint-plugin-react(jsx-fragments): Shorthand form for React fragments is preferred.
╭─[jsx_fragments.tsx:1:2]
1 │
· ────────
╰────
- help: Use <>> instead of
+ help: Use `<>>` instead of ``.
- ⚠ eslint-plugin-react(jsx-fragments): Shorthand form for React fragments is preferred
+ ⚠ eslint-plugin-react(jsx-fragments): Shorthand form for React fragments is preferred.
╭─[jsx_fragments.tsx:1:2]
1 │
· ──────────────
╰────
- help: Use <>> instead of
+ help: Use `<>>` instead of ``.
- ⚠ eslint-plugin-react(jsx-fragments): Standard form for React fragments is preferred
+ ⚠ eslint-plugin-react(jsx-fragments): Standard form for React fragments is preferred.
╭─[jsx_fragments.tsx:1:1]
1 │ <>>
· ──
╰────
- help: Use instead of <>>
+ help: Use `` instead of `<>>`.
+
+ ⚠ eslint-plugin-react(jsx-fragments): Standard form for React fragments is preferred.
+ ╭─[jsx_fragments.tsx:1:1]
+ 1 │ <>>
+ · ──
+ ╰────
+ help: Use `` instead of `<>>`.