diff --git a/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs b/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs index b3deda8580b30..297d52ecd69dc 100644 --- a/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs +++ b/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs @@ -70,8 +70,27 @@ impl Allowed { #[derive(Debug, Clone, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "camelCase", default)] pub struct JsxCurlyBracePresence { + /// Whether to enforce or disallow curly braces for props on JSX elements. + /// + /// - `never` will disallow unnecessary curly braces, e.g. this will be preferred: `` + /// - `always` will force the usage of curly braces like this, in all cases: `` + /// - `ignore` will allow either style for prop values. props: Allowed, + /// Whether to enforce or disallow curly braces for child content of a JSX element. + /// + /// - `never` will disallow unnecessary curly braces, e.g. this will be preferred: `I love oxlint` + /// - `always` will force the usage of curly braces like this, in all cases: `{'I love oxlint'}` + /// - `ignore` will allow either style for child content. children: Allowed, + /// When set to `ignore` or `never`, this JSX code is allowed (or enforced): + /// ` />;` + /// + /// When set to `always`, the curly braces are required for prop values that are + /// JSX elements: `} />;` + /// + /// **Note**: it is _highly_ recommended that you set `propElementValues` to `always`. + /// The ability to omit curly braces around prop values that are JSX elements is obscure, and + /// intentionally undocumented, and should not be relied upon. prop_element_values: Allowed, } @@ -86,19 +105,30 @@ impl Default for JsxCurlyBracePresence { } declare_oxc_lint!( + /// ### What it does + /// /// Disallow unnecessary JSX expressions when literals alone are /// sufficient or enforce JSX expressions on literals in JSX children or - /// attributes (`react/jsx-curly-brace-presence`) + /// attributes. /// /// This rule allows you to enforce curly braces or disallow unnecessary /// curly braces in JSX props and/or children. /// /// For situations where JSX expressions are unnecessary, please refer to - /// [the React doc](https://facebook.github.io/react/docs/jsx-in-depth.html) + /// [the React doc](https://react.dev/learn/writing-markup-with-jsx) /// and [this page about JSX /// gotchas](https://github.com/facebook/react/blob/v15.4.0-rc.3/docs/docs/02.3-jsx-gotchas.md#html-entities). /// - /// ## Rule Details + /// ### Why is this bad? + /// + /// Using different styles for your JSX code can make it harder to read and + /// less consistent. + /// + /// Code consistency improves readability. By enforcing or disallowing + /// curly braces in JSX props and/or children, this rule helps maintain + /// consistent patterns across your application. + /// + /// ### Rule Details /// /// By default, this rule will check for and warn about unnecessary curly /// braces in both JSX props and children. For the sake of backwards @@ -115,30 +145,27 @@ declare_oxc_lint!( /// to omit curly braces around prop values that are JSX elements is /// obscure, and intentionally undocumented, and should not be relied upon. /// - /// ## Rule Options + /// #### Example Configurations /// - /// ```js - /// ... - /// "react/jsx-curly-brace-presence": [, { "props": , "children": , "propElementValues": }] - /// ... + /// ```jsonc + /// { + /// "rules": { + /// "react/jsx-curly-brace-presence": ["error", { "props": , "children": , "propElementValues": }] + /// } + /// } /// ``` /// /// or alternatively /// - /// ```js - /// ... - /// "react/jsx-curly-brace-presence": [, ] - /// ... + /// ```jsonc + /// { + /// "rules": { + /// "react/jsx-curly-brace-presence": ["error", "always"] // or "never" or "ignore" + /// } + /// } /// ``` /// - /// ### Valid options for `` - /// - /// They are `always`, `never` and `ignore` for checking on JSX props and - /// children. - /// - /// - `always`: always enforce curly braces inside JSX props, children, and/or JSX prop values that are JSX Elements - /// - `never`: never allow unnecessary curly braces inside JSX props, children, and/or JSX prop values that are JSX Elements - /// - `ignore`: ignore the rule for JSX props, children, and/or JSX prop values that are JSX Elements + /// ### Fix Details /// /// If passed in the option to fix, this is how a style violation will get fixed /// @@ -147,7 +174,7 @@ declare_oxc_lint!( /// /// - All fixing operations use double quotes. /// - /// For examples: + /// ### Examples /// /// Examples of **incorrect** code for this rule, when configured with `{ props: "always", children: "always" }`: /// @@ -201,15 +228,6 @@ declare_oxc_lint!( /// />; /// ``` /// - /// ### Alternative syntax - /// - /// The options are also `always`, `never`, and `ignore` for the same meanings. - /// - /// In this syntax, only a string is provided and the default will be set to - /// that option for checking on both JSX props and children. - /// - /// For examples: - /// /// Examples of **incorrect** code for this rule, when configured with `"always"`: /// /// ```jsx @@ -236,7 +254,7 @@ declare_oxc_lint!( /// Hello world; /// ``` /// - /// ## Edge cases + /// ### Edge cases /// /// The fix also deals with template literals, strings with quotes, and /// strings with escapes characters. @@ -294,7 +312,7 @@ declare_oxc_lint!( /// {/* comment */ } // the comment makes the container necessary /// ``` /// - /// ## When Not To Use It + /// ### When Not To Use It /// /// You should turn this rule off if you are not concerned about maintaining /// consistency regarding the use of curly braces in JSX props and/or @@ -315,10 +333,11 @@ impl Rule for JsxCurlyBracePresence { }; match value { Value::String(s) => { + // TODO: Replace this with a proper DefaultRuleConfig implementation and handle errors with that. let allowed = Allowed::try_from(s.as_str()) - .map_err(|()| Error::msg( - r#"Invalid string config for eslint-plugin-react/jsx-curly-brace-presence: only "always", "never", or "ignored" are allowed. "# - )).unwrap(); + .map_err(|()| Error::msg( + r#"Invalid string config for react/jsx-curly-brace-presence: only "always", "never", or "ignore" are allowed. "# + )).unwrap(); Ok(Self { props: allowed, children: allowed, prop_element_values: allowed }) } Value::Object(obj) => { @@ -805,13 +824,13 @@ fn test() { ("{' '}", None), ( "{' '} - ", + ", None, ), ("{' '}", None), ( "{' '} - ", + ", None, ), ("{' '}", Some(json!([{ "children": "never" }]))), @@ -822,20 +841,20 @@ fn test() { ("{`Hello ${word} World`}", Some(json!([{ "children": "never" }]))), ( " - - foo{' '} - bar - - ", + + foo{' '} + bar + + ", Some(json!([{ "children": "never" }])), ), ( " - <> - foo{' '} - bar - - ", + <> + foo{' '} + bar + + ", Some(json!([{ "children": "never" }])), ), ("{`Hello \n World`}", Some(json!([{ "children": "never" }]))), @@ -895,133 +914,133 @@ fn test() { ("{` space before`}", Some(json!(["never"]))), ( " - - ", + + ", Some(json!(["never"])), ), ( " - - ", + + ", Some(json!(["always"])), ), ( " - - {` - a - b - `} - - ", + + {` + a + b + `} + + ", Some(json!(["never"])), ), ( " - {` - a - b - `} - ", + {` + a + b + `} + ", Some(json!(["always"])), ), ( " - - % - - ", + + % + + ", Some(json!([{ "children": "never" }])), ), ( " - - { 'space after ' } - foo - { ' space before' } - - ", + + { 'space after ' } + foo + { ' space before' } + + ", Some(json!([{ "children": "never" }])), ), ( " - - { `space after ` } - foo - { ` space before` } - - ", + + { `space after ` } + foo + { ` space before` } + + ", Some(json!([{ "children": "never" }])), ), ( " - - foo -
bar
-
- ", + + foo +
bar
+
+ ", Some(json!([{ "children": "never" }])), ), ( " - Bar
}> - - ", + Bar
}> + + ", None, ), ( r#" - -
-

- - {"foo"} - -

-
-
- "#, + +
+

+ + {"foo"} + +

+
+
+ "#, Some(json!([{ "children": "always" }])), ), ( " - -   -   - - ", + +   +   + + ", Some(json!([{ "children": "always" }])), ), ( " - const Component2 = () => { - return /*; - }; - ", + const Component2 = () => { + return /*; + }; + ", None, ), ( " - const Component2 = () => { - return /*; - }; - ", + const Component2 = () => { + return /*; + }; + ", Some(json!([{ "props": "never", "children": "never" }])), ), ( r#" - import React from "react"; + import React from "react"; - const Component = () => { - return {"/*"}; - }; - "#, + const Component = () => { + return {"/*"}; + }; + "#, Some(json!([{ "props": "never", "children": "never" }])), ), ("{/* comment */}", None), @@ -1031,16 +1050,16 @@ fn test() { ("} />", Some(json!([{ "propElementValues": "ignore" }]))), ( r#" - - "#, + + "#, None, ), ( r#" - {activity.type}} - /> - "#, + {activity.type}} + /> + "#, Some(json!(["never"])), ), ("", Some(json!(["never"]))), @@ -1061,35 +1080,35 @@ fn test() { ("foo", Some(json!([{ "props": "never" }]))), ( " - - {'%'} - - ", + + {'%'} + + ", Some(json!([{ "children": "never" }])), ), ( " - - {'foo'} -
- {'bar'} -
- {'baz'} -
- ", + + {'foo'} +
+ {'bar'} +
+ {'baz'} +
+ ", Some(json!([{ "children": "never" }])), ), ( " - - {'foo'} -
- {'bar'} -
- {'baz'} - {'some-complicated-exp'} -
- ", + + {'foo'} +
+ {'bar'} +
+ {'baz'} + {'some-complicated-exp'} +
+ ", Some(json!([{ "children": "never" }])), ), ("foo", Some(json!([{ "props": "always" }]))), @@ -1129,49 +1148,49 @@ fn test() { (r#"{"foo 'bar'"}"#, Some(json!([{ "children": "never" }]))), ( r#" - "#, + "#, Some(json!(["always"])), ), ( " - ", + ", Some(json!(["always"])), ), ( " - - foo bar -
foo bar foo
- - foo bar foo bar - - foo bar - - -
- ", + + foo bar +
foo bar foo
+ + foo bar foo bar + + foo bar + + +
+ ", Some(json!([{ "children": "always" }])), ), ( " - - <Component> -    -   - - ", + + <Component> +    +   + + ", Some(json!([{ "children": "always" }])), ), ( " - - ", + + ", Some(json!([{ "props": "never" }])), ), ( " - - ", + + ", Some(json!(["never"])), ), (r#"bar"#, Some(json!(["never"]))), @@ -1192,8 +1211,8 @@ fn test() { ), ( r#" - - "#, + + "#, Some(json!(["never"])), ), ]; @@ -1231,59 +1250,59 @@ fn test() { ), ( " - - {'%'} - - ", + + {'%'} + + ", " - - % - - ", + + % + + ", Some(json!([{ "children": "never" }])), ), ( " - - {'foo'} -
- {'bar'} -
- {'baz'} -
- ", + + {'foo'} +
+ {'bar'} +
+ {'baz'} +
+ ", " - - foo -
- bar -
- baz -
- ", + + foo +
+ bar +
+ baz +
+ ", Some(json!([{ "children": "never" }])), ), ( " - - {'foo'} -
- {'bar'} -
- {'baz'} - {'some-complicated-exp'} -
- ", + + {'foo'} +
+ {'bar'} +
+ {'baz'} + {'some-complicated-exp'} +
+ ", " - - foo -
- bar -
- baz - some-complicated-exp -
- ", + + foo +
+ bar +
+ baz + some-complicated-exp +
+ ", Some(json!([{ "children": "never" }])), ), ( @@ -1405,78 +1424,78 @@ fn test() { ( // Note: this case does not exist in the eslint tests cases r#" - "#, + "#, r#" - "#, + "#, Some(json!(["always"])), ), ( " - - foo bar -
foo bar foo
- - foo bar foo bar - - foo bar - - -
- ", + + foo bar +
foo bar foo
+ + foo bar foo bar + + foo bar + + +
+ ", r#" - - {"foo bar"} -
{"foo bar foo"}
- - {"foo bar "}{"foo bar"} - - {"foo bar"} - - -
- "#, + + {"foo bar"} +
{"foo bar foo"}
+ + {"foo bar "}{"foo bar"} + + {"foo bar"} + + +
+ "#, Some(json!([{ "children": "always" }])), ), ( " - - <Component> -    -   - - ", + + <Component> +    +   + + ", r#" - - <{"Component"}> -    -   - - "#, + + <{"Component"}> +    +   + + "#, Some(json!([{ "children": "always" }])), ), ( " - - ", + + ", r#" - - "#, + + "#, Some(json!([{ "props": "never" }])), ), ( " - - ", + + ", r#" - - "#, + + "#, Some(json!(["never"])), ), ( @@ -1508,11 +1527,11 @@ fn test() { ), ( r#" - - "#, + + "#, r#" - - "#, + + "#, Some(json!(["never"])), ), ( @@ -1532,6 +1551,7 @@ fn test() { ), (r#""#, r#""#, Some(json!(["never"]))), ]; + Tester::new(JsxCurlyBracePresence::NAME, JsxCurlyBracePresence::PLUGIN, pass, fail) .expect_fix(fix) .test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/react_jsx_curly_brace_presence.snap b/crates/oxc_linter/src/snapshots/react_jsx_curly_brace_presence.snap index f102779c519db..e17e53304a147 100644 --- a/crates/oxc_linter/src/snapshots/react_jsx_curly_brace_presence.snap +++ b/crates/oxc_linter/src/snapshots/react_jsx_curly_brace_presence.snap @@ -72,7 +72,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'bar'}` with `"bar"`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:3:15] + ╭─[jsx_curly_brace_presence.tsx:3:24] 2 │ 3 │ {'%'} · ─── @@ -81,7 +81,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'%'}` with `%`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:3:15] + ╭─[jsx_curly_brace_presence.tsx:3:24] 2 │ 3 │ {'foo'} · ───── @@ -90,7 +90,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'foo'}` with `foo`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:7:15] + ╭─[jsx_curly_brace_presence.tsx:7:24] 6 │ 7 │ {'baz'} · ───── @@ -99,7 +99,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'baz'}` with `baz`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:5:17] + ╭─[jsx_curly_brace_presence.tsx:5:26] 4 │
5 │ {'bar'} · ───── @@ -108,7 +108,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'bar'}` with `bar`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:3:15] + ╭─[jsx_curly_brace_presence.tsx:3:24] 2 │ 3 │ {'foo'} · ───── @@ -117,7 +117,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'foo'}` with `foo`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:7:15] + ╭─[jsx_curly_brace_presence.tsx:7:24] 6 │
7 │ {'baz'} · ───── @@ -126,7 +126,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'baz'}` with `baz`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:8:15] + ╭─[jsx_curly_brace_presence.tsx:8:24] 7 │ {'baz'} 8 │ {'some-complicated-exp'} · ────────────────────── @@ -135,7 +135,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'some-complicated-exp'}` with `some-complicated-exp`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:5:17] + ╭─[jsx_curly_brace_presence.tsx:5:26] 4 │
5 │ {'bar'} · ───── @@ -354,7 +354,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{"foo 'bar'"}` with `foo 'bar'`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:2:22] + ╭─[jsx_curly_brace_presence.tsx:2:31] 1 │ 2 │ · ─┬ @@ -363,7 +363,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:2:22] + ╭─[jsx_curly_brace_presence.tsx:2:31] 1 │ 2 │ · ─┬ @@ -372,7 +372,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:2:17] + ╭─[jsx_curly_brace_presence.tsx:2:26] 1 │ 2 │ ╭─▶ 3 │ │ foo bar @@ -383,7 +383,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:4:19] + ╭─[jsx_curly_brace_presence.tsx:4:28] 3 │ foo bar 4 │
foo bar foo
· ─────┬───── @@ -393,7 +393,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:5:20] + ╭─[jsx_curly_brace_presence.tsx:5:29] 4 │
foo bar foo
5 │ ╭─▶ 6 │ ├─▶ foo bar foo bar @@ -403,7 +403,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:6:27] + ╭─[jsx_curly_brace_presence.tsx:6:36] 5 │ 6 │ foo bar foo bar · ───┬─── @@ -413,7 +413,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:7:24] + ╭─[jsx_curly_brace_presence.tsx:7:33] 6 │ foo bar foo bar 7 │ ╭─▶ 8 │ │ foo bar @@ -424,7 +424,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here. - ╭─[jsx_curly_brace_presence.tsx:2:23] + ╭─[jsx_curly_brace_presence.tsx:2:26] 1 │ 2 │ ╭─▶ 3 │ │ <Component> @@ -435,7 +435,7 @@ source: crates/oxc_linter/src/tester.rs help: Wrap this value in curly braces ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:2:21] + ╭─[jsx_curly_brace_presence.tsx:2:30] 1 │ 2 │ · ────── @@ -444,7 +444,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{'1rem'}` with `"1rem"`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:2:21] + ╭─[jsx_curly_brace_presence.tsx:2:30] 1 │ 2 │ · ───────── @@ -489,7 +489,7 @@ source: crates/oxc_linter/src/tester.rs help: Replace `{"'"}` with `"'"`. ⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here. - ╭─[jsx_curly_brace_presence.tsx:2:29] + ╭─[jsx_curly_brace_presence.tsx:2:32] 1 │ 2 │ · ──────────────────────────────────────────────────────────────────────────────────────