mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): add react/jsx-curly-brace-presence (#3949)
Note that this PR does not implement a fixer, but one is available.
This commit is contained in:
parent
eaf834f953
commit
85e8418a78
4 changed files with 1367 additions and 0 deletions
|
|
@ -370,6 +370,11 @@ pub enum JSXChild<'a> {
|
||||||
/// `<Foo>{...spread}</Foo>`
|
/// `<Foo>{...spread}</Foo>`
|
||||||
Spread(Box<'a, JSXSpreadChild<'a>>),
|
Spread(Box<'a, JSXSpreadChild<'a>>),
|
||||||
}
|
}
|
||||||
|
impl<'a> JSXChild<'a> {
|
||||||
|
pub const fn is_expression_container(&self) -> bool {
|
||||||
|
matches!(self, Self::ExpressionContainer(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// JSX Spread Child.
|
/// JSX Spread Child.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ mod jest {
|
||||||
mod react {
|
mod react {
|
||||||
pub mod button_has_type;
|
pub mod button_has_type;
|
||||||
pub mod checked_requires_onchange_or_readonly;
|
pub mod checked_requires_onchange_or_readonly;
|
||||||
|
pub mod jsx_curly_brace_presence;
|
||||||
pub mod jsx_key;
|
pub mod jsx_key;
|
||||||
pub mod jsx_no_comment_textnodes;
|
pub mod jsx_no_comment_textnodes;
|
||||||
pub mod jsx_no_duplicate_props;
|
pub mod jsx_no_duplicate_props;
|
||||||
|
|
@ -716,6 +717,7 @@ oxc_macros::declare_all_lint_rules! {
|
||||||
react::button_has_type,
|
react::button_has_type,
|
||||||
react::checked_requires_onchange_or_readonly,
|
react::checked_requires_onchange_or_readonly,
|
||||||
react::jsx_no_target_blank,
|
react::jsx_no_target_blank,
|
||||||
|
react::jsx_curly_brace_presence,
|
||||||
react::jsx_key,
|
react::jsx_key,
|
||||||
react::jsx_no_comment_textnodes,
|
react::jsx_no_comment_textnodes,
|
||||||
react::jsx_no_duplicate_props,
|
react::jsx_no_duplicate_props,
|
||||||
|
|
|
||||||
957
crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs
Normal file
957
crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs
Normal file
|
|
@ -0,0 +1,957 @@
|
||||||
|
use oxc_allocator::Vec;
|
||||||
|
use oxc_ast::{
|
||||||
|
ast::{
|
||||||
|
Expression, JSXAttributeItem, JSXAttributeValue, JSXChild, JSXElementName,
|
||||||
|
JSXExpressionContainer,
|
||||||
|
},
|
||||||
|
AstKind,
|
||||||
|
};
|
||||||
|
use oxc_diagnostics::{Error, LabeledSpan, OxcDiagnostic};
|
||||||
|
use oxc_macros::declare_oxc_lint;
|
||||||
|
use oxc_semantic::AstNodeId;
|
||||||
|
use oxc_span::{GetSpan as _, Span};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||||
|
|
||||||
|
fn jsx_curly_brace_presence_unnecessary_diagnostic(span: Span) -> OxcDiagnostic {
|
||||||
|
OxcDiagnostic::warn("Curly braces are unnecessary here.").with_label(span)
|
||||||
|
}
|
||||||
|
fn jsx_curly_brace_presence_necessary_diagnostic(span: Span) -> OxcDiagnostic {
|
||||||
|
OxcDiagnostic::warn("Curly braces are required here.")
|
||||||
|
.with_help("Wrap this value in curly braces")
|
||||||
|
.with_labels([LabeledSpan::new_primary_with_span(
|
||||||
|
Some("Wrap this value in curly braces".into()),
|
||||||
|
span,
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
enum Allowed {
|
||||||
|
Always,
|
||||||
|
Never,
|
||||||
|
#[default]
|
||||||
|
Ignore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Allowed {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
"always" => Ok(Self::Always),
|
||||||
|
"never" => Ok(Self::Never),
|
||||||
|
"ignore" => Ok(Self::Ignore),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Allowed {
|
||||||
|
pub fn is_never(self) -> bool {
|
||||||
|
matches!(self, Self::Never)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn is_always(self) -> bool {
|
||||||
|
matches!(self, Self::Always)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JsxCurlyBracePresence {
|
||||||
|
props: Allowed,
|
||||||
|
children: Allowed,
|
||||||
|
prop_element_values: Allowed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JsxCurlyBracePresence {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
props: Allowed::Never,
|
||||||
|
children: Allowed::Never,
|
||||||
|
prop_element_values: Allowed::Ignore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_oxc_lint!(
|
||||||
|
/// # 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`)
|
||||||
|
///
|
||||||
|
/// 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://oxc-project.github.io/docs/guide/usage/linter/cli.html#fix-problems).
|
||||||
|
///
|
||||||
|
/// 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)
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// By default, this rule will check for and warn about unnecessary curly
|
||||||
|
/// braces in both JSX props and children. For the sake of backwards
|
||||||
|
/// compatibility, prop values that are JSX elements are not considered by
|
||||||
|
/// default.
|
||||||
|
///
|
||||||
|
/// You can pass in options to enforce the presence of curly braces on JSX
|
||||||
|
/// props, children, JSX prop values that are JSX elements, or any
|
||||||
|
/// combination of the three. The same options are available for not
|
||||||
|
/// allowing unnecessary curly braces as well as ignoring the check.
|
||||||
|
///
|
||||||
|
/// **Note**: it is _highly recommended_ that you configure this rule with
|
||||||
|
/// an object, and 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.
|
||||||
|
///
|
||||||
|
/// ## Rule Options
|
||||||
|
///
|
||||||
|
/// ```js
|
||||||
|
/// ...
|
||||||
|
/// "react/jsx-curly-brace-presence": [<enabled>, { "props": <string>, "children": <string>, "propElementValues": <string> }]
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// or alternatively
|
||||||
|
///
|
||||||
|
/// ```js
|
||||||
|
/// ...
|
||||||
|
/// "react/jsx-curly-brace-presence": [<enabled>, <string>]
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### Valid options for `<string>`
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// If passed in the option to fix, this is how a style violation will get fixed
|
||||||
|
///
|
||||||
|
/// - `always`: wrap a JSX attribute in curly braces/JSX expression and/or a JSX child the same way but also with double quotes
|
||||||
|
/// - `never`: get rid of curly braces from a JSX attribute and/or a JSX child
|
||||||
|
///
|
||||||
|
/// - All fixing operations use double quotes.
|
||||||
|
///
|
||||||
|
/// For examples:
|
||||||
|
///
|
||||||
|
/// Examples of **incorrect** code for this rule, when configured with `{ props: "always", children: "always" }`:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App>Hello world</App>;
|
||||||
|
/// <App prop='Hello world'>{'Hello world'}</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// They can be fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App>{"Hello world"}</App>;
|
||||||
|
/// <App prop={"Hello world"}>{'Hello world'}</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Examples of **incorrect** code for this rule, when configured with `{ props: "never", children: "never" }`:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App>{'Hello world'}</App>;
|
||||||
|
/// <App prop={'Hello world'} attr={"foo"} />;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// They can be fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App>Hello world</App>;
|
||||||
|
/// <App prop="Hello world" attr="foo" />;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Examples of **incorrect** code for this rule, when configured with `{ props: "always", children: "always", "propElementValues": "always" }`:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop=<div /> />;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// They can be fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop={<div />} />;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Examples of **incorrect** code for this rule, when configured with `{ props: "never", children: "never", "propElementValues": "never" }`:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop={<div />} />;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// They can be fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop=<div /> />;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### 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
|
||||||
|
/// <App>Hello world</App>;
|
||||||
|
/// <App prop='Hello world' attr="foo">Hello world</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// They can be fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App>{"Hello world"}</App>;
|
||||||
|
/// <App prop={"Hello world"} attr={"foo"}>{"Hello world"}</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Examples of **incorrect** code for this rule, when configured with `"never"`:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop={'foo'} attr={"bar"}>{'Hello world'}</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// It can fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop="foo" attr="bar">Hello world</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Edge cases
|
||||||
|
///
|
||||||
|
/// The fix also deals with template literals, strings with quotes, and
|
||||||
|
/// strings with escapes characters.
|
||||||
|
///
|
||||||
|
/// - If the rule is set to get rid of unnecessary curly braces and the
|
||||||
|
/// template literal inside a JSX expression has no expression, it will
|
||||||
|
/// throw a warning and be fixed with double quotes. For example:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop={`Hello world`}>{`Hello world`}</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// will be warned and fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop="Hello world">Hello world</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - If the rule is set to enforce curly braces and the strings have
|
||||||
|
/// quotes, it will be fixed with double quotes for JSX children and the
|
||||||
|
/// normal way for JSX attributes. Also, double quotes will be escaped in
|
||||||
|
/// the fix.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop='Hello "foo" world'>Hello 'foo' "bar" world</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// will warned and fixed to:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <App prop={"Hello \"foo\" world"}>{"Hello 'foo' \"bar\" world"}</App>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - If the rule is set to get rid of unnecessary curly braces(JSX
|
||||||
|
/// expression) and there are characters that need to be escaped in its JSX
|
||||||
|
/// form, such as quote characters, [forbidden JSX text
|
||||||
|
/// characters](https://facebook.github.io/jsx/), escaped characters and
|
||||||
|
/// anything that looks like HTML entity names, the code will not be warned
|
||||||
|
/// because the fix may make the code less readable.
|
||||||
|
///
|
||||||
|
/// Examples of **correct** code for this rule, even when configured with `"never"`:
|
||||||
|
///
|
||||||
|
/// ```jsx
|
||||||
|
/// <Color text={"\u00a0"} />
|
||||||
|
/// <App>{"Hello \u00b7 world"}</App>;
|
||||||
|
/// <style type="text/css">{'.main { margin-top: 0; }'}</style>;
|
||||||
|
/// /**
|
||||||
|
/// * there's no way to inject a whitespace into jsx without a container so this
|
||||||
|
/// * will always be allowed.
|
||||||
|
/// */
|
||||||
|
/// <App>{' '}</App>
|
||||||
|
/// <App>{' '}</App>
|
||||||
|
/// <App>{/* comment */ <Bpp />}</App> // the comment makes the container necessary
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## 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
|
||||||
|
/// children as well as the use of unnecessary JSX expressions.
|
||||||
|
JsxCurlyBracePresence,
|
||||||
|
style,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Rule for JsxCurlyBracePresence {
|
||||||
|
fn from_configuration(value: Value) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
let value = if let Some(arr) = value.as_array() { &arr[0] } else { &value };
|
||||||
|
match value {
|
||||||
|
Value::String(s) => {
|
||||||
|
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();
|
||||||
|
Self { props: allowed, children: allowed, prop_element_values: allowed }
|
||||||
|
}
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let props = obj
|
||||||
|
.get("props")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.and_then(|props| Allowed::try_from(props).ok())
|
||||||
|
.unwrap_or(default.props);
|
||||||
|
let children = obj
|
||||||
|
.get("children")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.and_then(|children| Allowed::try_from(children).ok())
|
||||||
|
.unwrap_or(default.children);
|
||||||
|
let prop_element_values = obj
|
||||||
|
.get("propElementValues")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.and_then(|prop_element_values| Allowed::try_from(prop_element_values).ok())
|
||||||
|
.unwrap_or(default.prop_element_values);
|
||||||
|
|
||||||
|
Self { props, children, prop_element_values }
|
||||||
|
}
|
||||||
|
_ => default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||||
|
match node.kind() {
|
||||||
|
AstKind::JSXElement(el) => {
|
||||||
|
el.opening_element.attributes.iter().for_each(|attr| {
|
||||||
|
self.check_jsx_attribute(ctx, attr, node);
|
||||||
|
});
|
||||||
|
if self.children.is_never()
|
||||||
|
&& matches!(&el.opening_element.name, JSXElementName::Identifier(ident) if ident.name == "script")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.check_jsx_child(ctx, &el.children, node);
|
||||||
|
}
|
||||||
|
AstKind::JSXFragment(fragment) => {
|
||||||
|
self.check_jsx_child(ctx, &fragment.children, node);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_run(&self, ctx: &LintContext) -> bool {
|
||||||
|
ctx.source_type().is_jsx()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsxCurlyBracePresence {
|
||||||
|
fn check_jsx_child<'a>(
|
||||||
|
&self,
|
||||||
|
ctx: &LintContext<'a>,
|
||||||
|
children: &Vec<'a, JSXChild<'a>>,
|
||||||
|
node: &AstNode<'a>,
|
||||||
|
) {
|
||||||
|
for child in children {
|
||||||
|
match child {
|
||||||
|
JSXChild::ExpressionContainer(container) => {
|
||||||
|
self.check_expression_container(ctx, container, node, false);
|
||||||
|
}
|
||||||
|
JSXChild::Text(text) => {
|
||||||
|
if self.children.is_always()
|
||||||
|
&& children.len() == 1
|
||||||
|
&& !is_whitespace(&text.value)
|
||||||
|
{
|
||||||
|
ctx.diagnostic(jsx_curly_brace_presence_necessary_diagnostic(text.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_jsx_attribute<'a>(
|
||||||
|
&self,
|
||||||
|
ctx: &LintContext<'a>,
|
||||||
|
attr: &JSXAttributeItem<'a>,
|
||||||
|
node: &AstNode<'a>,
|
||||||
|
) {
|
||||||
|
let JSXAttributeItem::Attribute(attr) = attr else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(value) = attr.value.as_ref() else { return };
|
||||||
|
|
||||||
|
match value {
|
||||||
|
JSXAttributeValue::ExpressionContainer(container) => {
|
||||||
|
self.check_expression_container(ctx, container, node, true);
|
||||||
|
}
|
||||||
|
JSXAttributeValue::Element(el) => {
|
||||||
|
if self.prop_element_values.is_always() {
|
||||||
|
ctx.diagnostic(jsx_curly_brace_presence_necessary_diagnostic(el.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSXAttributeValue::Fragment(fragment) => {
|
||||||
|
if self.prop_element_values.is_always() {
|
||||||
|
ctx.diagnostic(jsx_curly_brace_presence_necessary_diagnostic(fragment.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSXAttributeValue::StringLiteral(string) => {
|
||||||
|
if self.props.is_always() {
|
||||||
|
ctx.diagnostic(jsx_curly_brace_presence_necessary_diagnostic(string.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_expression_container<'a>(
|
||||||
|
&self,
|
||||||
|
ctx: &LintContext<'a>,
|
||||||
|
container: &JSXExpressionContainer<'a>,
|
||||||
|
node: &AstNode<'a>,
|
||||||
|
// true for JSX props, false for JSX children
|
||||||
|
is_prop: bool,
|
||||||
|
) {
|
||||||
|
let Some(inner) = container.expression.as_expression() else { return };
|
||||||
|
let allowed = if is_prop { self.props } else { self.children };
|
||||||
|
match inner {
|
||||||
|
Expression::JSXFragment(_) => {
|
||||||
|
if !is_prop
|
||||||
|
&& self.children.is_never()
|
||||||
|
&& !has_adjacent_jsx_expression_containers(ctx, container, node.id())
|
||||||
|
{
|
||||||
|
report_unnecessary_curly(ctx, container, inner.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::JSXElement(el) => {
|
||||||
|
if is_prop {
|
||||||
|
if self.prop_element_values.is_never() && el.closing_element.is_none() {
|
||||||
|
report_unnecessary_curly(ctx, container, inner.span());
|
||||||
|
}
|
||||||
|
} else if self.children.is_never()
|
||||||
|
&& !has_adjacent_jsx_expression_containers(ctx, container, node.id())
|
||||||
|
{
|
||||||
|
report_unnecessary_curly(ctx, container, inner.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::StringLiteral(string) => {
|
||||||
|
if allowed.is_never() {
|
||||||
|
let raw = ctx.source_range(string.span().shrink_left(1).shrink_right(1));
|
||||||
|
if is_allowed_string_like(ctx, raw, container, node.id(), is_prop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
report_unnecessary_curly(ctx, container, string.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::TemplateLiteral(template) => {
|
||||||
|
if allowed.is_never() && template.is_no_substitution_template() {
|
||||||
|
let string = template.quasi().unwrap();
|
||||||
|
if is_allowed_string_like(ctx, string.as_str(), container, node.id(), is_prop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
report_unnecessary_curly(ctx, container, template.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_allowed_string_like<'a>(
|
||||||
|
ctx: &LintContext<'a>,
|
||||||
|
s: &'a str,
|
||||||
|
container: &JSXExpressionContainer<'a>,
|
||||||
|
node_id: AstNodeId,
|
||||||
|
is_prop: bool,
|
||||||
|
) -> bool {
|
||||||
|
is_whitespace(s)
|
||||||
|
|| is_line_break(s)
|
||||||
|
|| contains_html_entity(s)
|
||||||
|
|| !is_prop && contains_disallowed_jsx_text_chars(s)
|
||||||
|
|| s.trim() != s
|
||||||
|
|| contains_multiline_comment(s)
|
||||||
|
|| contains_line_break_literal(s)
|
||||||
|
|| contains_utf8_escape(s)
|
||||||
|
|| is_prop && contains_quote_characters(s)
|
||||||
|
|| has_adjacent_jsx_expression_containers(ctx, container, node_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_whitespace(s: &str) -> bool {
|
||||||
|
s.chars().all(char::is_whitespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_line_break(s: &str) -> bool {
|
||||||
|
s.chars().any(|c| matches!(c, '\n' | '\r')) || s.trim().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_line_break_literal(s: &str) -> bool {
|
||||||
|
s.chars().zip(s.chars().skip(1)).any(|tuple| matches!(tuple, ('\\', 'n' | 'r')))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_disallowed_jsx_text_chars(s: &str) -> bool {
|
||||||
|
s.chars().any(|c| matches!(c, '<' | '>' | '{' | '}' | '\\'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_multiline_comment(s: &str) -> bool {
|
||||||
|
s.contains("/*") || s.contains("*/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_quote_characters(s: &str) -> bool {
|
||||||
|
s.chars().any(|c| matches!(c, '"' | '\''))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_utf8_escape(s: &str) -> bool {
|
||||||
|
s.chars().zip(s.chars().skip(1)).any(|tuple| matches!(tuple, ('\\', 'u')))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_html_entity(s: &str) -> bool {
|
||||||
|
let and = s.find('&');
|
||||||
|
let semi = s.find(';');
|
||||||
|
matches!((and, semi), (Some(and), Some(semi)) if and < semi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_unnecessary_curly<'a>(
|
||||||
|
ctx: &LintContext<'a>,
|
||||||
|
_container: &JSXExpressionContainer<'a>,
|
||||||
|
inner_span: Span,
|
||||||
|
) {
|
||||||
|
ctx.diagnostic(jsx_curly_brace_presence_unnecessary_diagnostic(inner_span));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_adjacent_jsx_expression_containers<'a>(
|
||||||
|
ctx: &LintContext<'a>,
|
||||||
|
container: &JSXExpressionContainer<'a>,
|
||||||
|
node_id: AstNodeId,
|
||||||
|
// element: &JSXElement<'a>,
|
||||||
|
) -> bool {
|
||||||
|
let Some(parent) = ctx.semantic().nodes().parent_kind(node_id) else { return false };
|
||||||
|
let children = match parent {
|
||||||
|
AstKind::JSXElement(el) => &el.children,
|
||||||
|
AstKind::JSXFragment(fragment) => &fragment.children,
|
||||||
|
AstKind::ExpressionStatement(expr) => match &expr.expression {
|
||||||
|
Expression::JSXElement(el) => &el.children,
|
||||||
|
Expression::JSXFragment(fragment) => &fragment.children,
|
||||||
|
_ => {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(this_container_idx) = children.iter().position(|child| child.span() == container.span)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
[this_container_idx.checked_sub(1), this_container_idx.checked_add(1)]
|
||||||
|
.into_iter()
|
||||||
|
// [prev id, next id] -> [prev node, next node], removing out-of-bounds indices
|
||||||
|
.filter_map(|idx| idx.and_then(|idx| children.get(idx)))
|
||||||
|
.any(oxc_ast::ast::JSXChild::is_expression_container)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
use crate::tester::Tester;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
let pass = vec![
|
||||||
|
("<App {...props}>foo</App>", None),
|
||||||
|
("<>foo</>", None),
|
||||||
|
("<App {...props}>foo</App>", Some(json!([{ "props": "never" }]))),
|
||||||
|
("<App>{' '}</App>", None),
|
||||||
|
(
|
||||||
|
"<App>{' '}
|
||||||
|
</App>",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
("<App>{' '}</App>", None),
|
||||||
|
(
|
||||||
|
"<App>{' '}
|
||||||
|
</App>",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
("<App>{' '}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<App>{' '}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<App>{' '}</App>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<App>{' '}</App>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<App {...props}>foo</App>", Some(json!([{ "props": "always" }]))),
|
||||||
|
("<App>{`Hello ${word} World`}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<React.Fragment>
|
||||||
|
foo{' '}
|
||||||
|
<span>bar</span>
|
||||||
|
</React.Fragment>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<>
|
||||||
|
foo{' '}
|
||||||
|
<span>bar</span>
|
||||||
|
</>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
("<App>{`Hello \n World`}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<App>{`Hello ${word} World`}{`foo`}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<App prop={`foo ${word} bar`}>foo</App>", Some(json!([{ "props": "never" }]))),
|
||||||
|
("<App prop={`foo ${word} bar`} />", Some(json!([{ "props": "never" }]))),
|
||||||
|
("<App>{<myApp></myApp>}</App>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<App>{[]}</App>", None),
|
||||||
|
("<App>foo</App>", None),
|
||||||
|
(r#"<App>{"foo"}{<Component>bar</Component>}</App>"#, None),
|
||||||
|
("<App prop='bar'>foo</App>", None),
|
||||||
|
("<App prop={true}>foo</App>", None),
|
||||||
|
("<App prop>foo</App>", None),
|
||||||
|
(r"<App prop='bar'>{'foo \n bar'}</App>", None),
|
||||||
|
("<App prop={ ' ' }/>", None),
|
||||||
|
("<MyComponent prop='bar'>foo</MyComponent>", Some(json!([{ "props": "never" }]))),
|
||||||
|
(r#"<MyComponent prop="bar">foo</MyComponent>"#, Some(json!([{ "props": "never" }]))),
|
||||||
|
("<MyComponent>foo</MyComponent>", Some(json!([{ "children": "never" }]))),
|
||||||
|
(r#"<MyComponent>{<App/>}{"123"}</MyComponent>"#, Some(json!([{ "children": "never" }]))),
|
||||||
|
(r#"<App>{"foo 'bar' \"foo\" bar"}</App>"#, Some(json!([{ "children": "never" }]))),
|
||||||
|
("<MyComponent prop={'bar'}>foo</MyComponent>", Some(json!([{ "props": "always" }]))),
|
||||||
|
("<MyComponent>{'foo'}</MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
(r#"<MyComponent prop={"bar"}>foo</MyComponent>"#, Some(json!([{ "props": "always" }]))),
|
||||||
|
(r#"<MyComponent>{"foo"}</MyComponent>"#, Some(json!([{ "children": "always" }]))),
|
||||||
|
("<MyComponent>{'foo'}</MyComponent>", Some(json!([{ "children": "ignore" }]))),
|
||||||
|
("<MyComponent prop={'bar'}>foo</MyComponent>", Some(json!([{ "props": "ignore" }]))),
|
||||||
|
("<MyComponent>foo</MyComponent>", Some(json!([{ "children": "ignore" }]))),
|
||||||
|
("<MyComponent prop='bar'>foo</MyComponent>", Some(json!([{ "props": "ignore" }]))),
|
||||||
|
(r#"<MyComponent prop="bar">foo</MyComponent>"#, Some(json!([{ "props": "ignore" }]))),
|
||||||
|
(
|
||||||
|
"<MyComponent prop='bar'>{'foo'}</MyComponent>",
|
||||||
|
Some(json!([{ "children": "always", "props": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"<MyComponent prop={'bar'}>foo</MyComponent>",
|
||||||
|
Some(json!([{ "children": "never", "props": "always" }])),
|
||||||
|
),
|
||||||
|
("<MyComponent prop={'bar'}>{'foo'}</MyComponent>", Some(json!(["always"]))),
|
||||||
|
(r#"<MyComponent prop={"bar"}>{"foo"}</MyComponent>"#, Some(json!(["always"]))),
|
||||||
|
(r#"<MyComponent prop={"bar"} attr={'foo'} />"#, Some(json!(["always"]))),
|
||||||
|
(r#"<MyComponent prop="bar" attr='foo' />"#, Some(json!(["never"]))),
|
||||||
|
("<MyComponent prop='bar'>foo</MyComponent>", Some(json!(["never"]))),
|
||||||
|
(
|
||||||
|
"<MyComponent prop={`bar ${word} foo`}>{`foo ${word}`}</MyComponent>",
|
||||||
|
Some(json!(["never"])),
|
||||||
|
),
|
||||||
|
(r#"<MyComponent>{"div { margin-top: 0; }"}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent>{"<Foo />"}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent prop={"Hello \u1026 world"}>bar</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent>{"Hello \u1026 world"}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent prop={"Hello · world"}>bar</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent>{"Hello · world"}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent>{"Hello \n world"}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent>{"space after "}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent>{" space before"}</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
("<MyComponent>{`space after `}</MyComponent>", Some(json!(["never"]))),
|
||||||
|
("<MyComponent>{` space before`}</MyComponent>", Some(json!(["never"]))),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App prop={`
|
||||||
|
a
|
||||||
|
b
|
||||||
|
`} />
|
||||||
|
",
|
||||||
|
Some(json!(["never"])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App prop={`
|
||||||
|
a
|
||||||
|
b
|
||||||
|
`} />
|
||||||
|
",
|
||||||
|
Some(json!(["always"])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App>
|
||||||
|
{`
|
||||||
|
a
|
||||||
|
b
|
||||||
|
`}
|
||||||
|
</App>
|
||||||
|
",
|
||||||
|
Some(json!(["never"])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App>{`
|
||||||
|
a
|
||||||
|
b
|
||||||
|
`}</App>
|
||||||
|
",
|
||||||
|
Some(json!(["always"])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
%
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
{ 'space after ' }
|
||||||
|
<b>foo</b>
|
||||||
|
{ ' space before' }
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
{ `space after ` }
|
||||||
|
<b>foo</b>
|
||||||
|
{ ` space before` }
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
foo
|
||||||
|
<div>bar</div>
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent p={<Foo>Bar</Foo>}>
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
|
<MyComponent>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span>
|
||||||
|
{"foo"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</MyComponent>
|
||||||
|
"#,
|
||||||
|
Some(json!([{ "children": "always" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App>
|
||||||
|
<Component />
|
||||||
|
|
||||||
|
</App>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "always" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
const Component2 = () => {
|
||||||
|
return <span>/*</span>;
|
||||||
|
};
|
||||||
|
",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
const Component2 = () => {
|
||||||
|
return <span>/*</span>;
|
||||||
|
};
|
||||||
|
",
|
||||||
|
Some(json!([{ "props": "never", "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Component = () => {
|
||||||
|
return <span>{"/*"}</span>;
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
Some(json!([{ "props": "never", "children": "never" }])),
|
||||||
|
),
|
||||||
|
("<App>{/* comment */}</App>", None),
|
||||||
|
("<App horror=<div /> />", None),
|
||||||
|
("<App horror={<div />} />", None),
|
||||||
|
("<App horror=<div /> />", Some(json!([{ "propElementValues": "ignore" }]))),
|
||||||
|
("<App horror={<div />} />", Some(json!([{ "propElementValues": "ignore" }]))),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
|
<script>{`window.foo = "bar"`}</script>
|
||||||
|
"#,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
|
<CollapsibleTitle
|
||||||
|
extra={<span className="activity-type">{activity.type}</span>}
|
||||||
|
/>
|
||||||
|
"#,
|
||||||
|
Some(json!(["never"])),
|
||||||
|
),
|
||||||
|
("<App label={`${label}`} />", Some(json!(["never"]))),
|
||||||
|
("<App>{`${label}`}</App>", Some(json!(["never"]))),
|
||||||
|
];
|
||||||
|
|
||||||
|
let fail = vec![
|
||||||
|
("<App prop={`foo`} />", Some(json!([{ "props": "never" }]))),
|
||||||
|
("<App>{<myApp></myApp>}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<App>{<myApp></myApp>}</App>", None),
|
||||||
|
("<App prop={`foo`}>foo</App>", Some(json!([{ "props": "never" }]))),
|
||||||
|
("<App>{`foo`}</App>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<>{`foo`}</>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<MyComponent>{'foo'}</MyComponent>", None),
|
||||||
|
("<MyComponent prop={'bar'}>foo</MyComponent>", None),
|
||||||
|
("<MyComponent>{'foo'}</MyComponent>", Some(json!([{ "children": "never" }]))),
|
||||||
|
("<MyComponent prop={'bar'}>foo</MyComponent>", Some(json!([{ "props": "never" }]))),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
{'%'}
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
{'foo'}
|
||||||
|
<div>
|
||||||
|
{'bar'}
|
||||||
|
</div>
|
||||||
|
{'baz'}
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<MyComponent>
|
||||||
|
{'foo'}
|
||||||
|
<div>
|
||||||
|
{'bar'}
|
||||||
|
</div>
|
||||||
|
{'baz'}
|
||||||
|
{'some-complicated-exp'}
|
||||||
|
</MyComponent>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "never" }])),
|
||||||
|
),
|
||||||
|
("<MyComponent prop='bar'>foo</MyComponent>", Some(json!([{ "props": "always" }]))),
|
||||||
|
(
|
||||||
|
r#"<MyComponent prop="foo 'bar'">foo</MyComponent>"#,
|
||||||
|
Some(json!([{ "props": "always" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"<MyComponent prop='foo "bar"'>foo</MyComponent>"#,
|
||||||
|
Some(json!([{ "props": "always" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"<MyComponent prop="foo 'bar'">foo</MyComponent>"#,
|
||||||
|
Some(json!([{ "props": "always" }])),
|
||||||
|
),
|
||||||
|
("<MyComponent>foo bar </MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
(
|
||||||
|
r#"<MyComponent prop="foo 'bar' \n ">foo</MyComponent>"#,
|
||||||
|
Some(json!([{ "props": "always" }])),
|
||||||
|
),
|
||||||
|
("<MyComponent>foo bar \r </MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<MyComponent>foo bar 'foo'</MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
(r#"<MyComponent>foo bar "foo"</MyComponent>"#, Some(json!([{ "children": "always" }]))),
|
||||||
|
// NOTE: Not sure how to handle this case
|
||||||
|
// ("<MyComponent>foo bar <App/></MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<MyComponent>foo \n bar</MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<MyComponent>foo \\u1234 bar</MyComponent>", Some(json!([{ "children": "always" }]))),
|
||||||
|
("<MyComponent prop='foo \\u1234 bar' />", Some(json!([{ "props": "always" }]))),
|
||||||
|
("<MyComponent prop={'bar'}>{'foo'}</MyComponent>", Some(json!(["never"]))),
|
||||||
|
("<MyComponent prop='bar'>foo</MyComponent>", Some(json!(["always"]))),
|
||||||
|
(r#"<App prop={'foo'} attr={" foo "} />"#, Some(json!([{ "props": "never" }]))),
|
||||||
|
(r#"<App prop='foo' attr="bar" />"#, Some(json!([{ "props": "always" }]))),
|
||||||
|
(r#"<App prop='foo' attr={"bar"} />"#, Some(json!([{ "props": "always" }]))),
|
||||||
|
("<App prop={'foo'} attr='bar' />", Some(json!([{ "props": "always" }]))),
|
||||||
|
("<App prop='foo · bar' />", Some(json!([{ "props": "always" }]))),
|
||||||
|
("<App>foo · bar</App>", Some(json!([{ "children": "always" }]))),
|
||||||
|
(r#"<App>{'foo "bar"'}</App>"#, Some(json!([{ "children": "never" }]))),
|
||||||
|
(r#"<App>{"foo 'bar'"}</App>"#, Some(json!([{ "children": "never" }]))),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
|
<App prop=""#,
|
||||||
|
Some(json!(["always"])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App prop='",
|
||||||
|
Some(json!(["always"])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<App>
|
||||||
|
foo bar
|
||||||
|
<div>foo bar foo</div>
|
||||||
|
<span>
|
||||||
|
foo bar <i>foo bar</i>
|
||||||
|
<strong>
|
||||||
|
foo bar
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
</App>
|
||||||
|
",
|
||||||
|
Some(json!([{ "children": "always" }])),
|
||||||
|
),
|
||||||
|
// NOTE: Not sure how to handle this case
|
||||||
|
// (
|
||||||
|
// "
|
||||||
|
// <App>
|
||||||
|
// <Component>
|
||||||
|
// <Component />
|
||||||
|
//
|
||||||
|
// </App>
|
||||||
|
// ",
|
||||||
|
// Some(json!([{ "children": "always" }])),
|
||||||
|
// ),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<Box mb={'1rem'} />
|
||||||
|
",
|
||||||
|
Some(json!([{ "props": "never" }])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
<Box mb={'1rem {}'} />
|
||||||
|
",
|
||||||
|
Some(json!(["never"])),
|
||||||
|
),
|
||||||
|
(r#"<MyComponent prop={"{ style: true }"}>bar</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(r#"<MyComponent prop={"< style: true >"}>foo</MyComponent>"#, Some(json!(["never"]))),
|
||||||
|
(
|
||||||
|
"<App horror=<div /> />",
|
||||||
|
Some(
|
||||||
|
json!([{ "props": "always", "children": "always", "propElementValues": "always" }]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"<App horror={<div />} />",
|
||||||
|
Some(json!([{ "props": "never", "children": "never", "propElementValues": "never" }])),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Tester::new(JsxCurlyBracePresence::NAME, pass, fail).test_and_snapshot();
|
||||||
|
}
|
||||||
403
crates/oxc_linter/src/snapshots/jsx_curly_brace_presence.snap
Normal file
403
crates/oxc_linter/src/snapshots/jsx_curly_brace_presence.snap
Normal file
|
|
@ -0,0 +1,403 @@
|
||||||
|
---
|
||||||
|
source: crates/oxc_linter/src/tester.rs
|
||||||
|
---
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:12]
|
||||||
|
1 │ <App prop={`foo`} />
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:7]
|
||||||
|
1 │ <App>{<myApp></myApp>}</App>
|
||||||
|
· ───────────────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:7]
|
||||||
|
1 │ <App>{<myApp></myApp>}</App>
|
||||||
|
· ───────────────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:12]
|
||||||
|
1 │ <App prop={`foo`}>foo</App>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:7]
|
||||||
|
1 │ <App>{`foo`}</App>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:4]
|
||||||
|
1 │ <>{`foo`}</>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:15]
|
||||||
|
1 │ <MyComponent>{'foo'}</MyComponent>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:20]
|
||||||
|
1 │ <MyComponent prop={'bar'}>foo</MyComponent>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:15]
|
||||||
|
1 │ <MyComponent>{'foo'}</MyComponent>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:20]
|
||||||
|
1 │ <MyComponent prop={'bar'}>foo</MyComponent>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:3:15]
|
||||||
|
2 │ <MyComponent>
|
||||||
|
3 │ {'%'}
|
||||||
|
· ───
|
||||||
|
4 │ </MyComponent>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:3:15]
|
||||||
|
2 │ <MyComponent>
|
||||||
|
3 │ {'foo'}
|
||||||
|
· ─────
|
||||||
|
4 │ <div>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:7:15]
|
||||||
|
6 │ </div>
|
||||||
|
7 │ {'baz'}
|
||||||
|
· ─────
|
||||||
|
8 │ </MyComponent>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:5:17]
|
||||||
|
4 │ <div>
|
||||||
|
5 │ {'bar'}
|
||||||
|
· ─────
|
||||||
|
6 │ </div>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:3:15]
|
||||||
|
2 │ <MyComponent>
|
||||||
|
3 │ {'foo'}
|
||||||
|
· ─────
|
||||||
|
4 │ <div>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:7:15]
|
||||||
|
6 │ </div>
|
||||||
|
7 │ {'baz'}
|
||||||
|
· ─────
|
||||||
|
8 │ {'some-complicated-exp'}
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:8:15]
|
||||||
|
7 │ {'baz'}
|
||||||
|
8 │ {'some-complicated-exp'}
|
||||||
|
· ──────────────────────
|
||||||
|
9 │ </MyComponent>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:5:17]
|
||||||
|
4 │ <div>
|
||||||
|
5 │ {'bar'}
|
||||||
|
· ─────
|
||||||
|
6 │ </div>
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop='bar'>foo</MyComponent>
|
||||||
|
· ──┬──
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop="foo 'bar'">foo</MyComponent>
|
||||||
|
· ─────┬─────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop='foo "bar"'>foo</MyComponent>
|
||||||
|
· ─────┬─────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop="foo 'bar'">foo</MyComponent>
|
||||||
|
· ─────┬─────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ <MyComponent>foo bar </MyComponent>
|
||||||
|
· ────┬───
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop="foo 'bar' \n ">foo</MyComponent>
|
||||||
|
· ───────┬───────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ <MyComponent>foo bar
</MyComponent>
|
||||||
|
· ────┬────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ <MyComponent>foo bar 'foo'</MyComponent>
|
||||||
|
· ──────┬──────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ <MyComponent>foo bar "foo"</MyComponent>
|
||||||
|
· ──────┬──────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ ╭─▶ <MyComponent>foo
|
||||||
|
2 │ ├─▶ bar</MyComponent>
|
||||||
|
· ╰──── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ <MyComponent>foo \u1234 bar</MyComponent>
|
||||||
|
· ───────┬──────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop='foo \u1234 bar' />
|
||||||
|
· ────────┬───────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:20]
|
||||||
|
1 │ <MyComponent prop={'bar'}>{'foo'}</MyComponent>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:28]
|
||||||
|
1 │ <MyComponent prop={'bar'}>{'foo'}</MyComponent>
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:19]
|
||||||
|
1 │ <MyComponent prop='bar'>foo</MyComponent>
|
||||||
|
· ──┬──
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:25]
|
||||||
|
1 │ <MyComponent prop='bar'>foo</MyComponent>
|
||||||
|
· ─┬─
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:12]
|
||||||
|
1 │ <App prop={'foo'} attr={" foo "} />
|
||||||
|
· ─────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:11]
|
||||||
|
1 │ <App prop='foo' attr="bar" />
|
||||||
|
· ──┬──
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:22]
|
||||||
|
1 │ <App prop='foo' attr="bar" />
|
||||||
|
· ──┬──
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:11]
|
||||||
|
1 │ <App prop='foo' attr={"bar"} />
|
||||||
|
· ──┬──
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:24]
|
||||||
|
1 │ <App prop={'foo'} attr='bar' />
|
||||||
|
· ──┬──
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:11]
|
||||||
|
1 │ <App prop='foo · bar' />
|
||||||
|
· ─────────┬────────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:6]
|
||||||
|
1 │ <App>foo · bar</App>
|
||||||
|
· ────────┬───────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:7]
|
||||||
|
1 │ <App>{'foo "bar"'}</App>
|
||||||
|
· ───────────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:7]
|
||||||
|
1 │ <App>{"foo 'bar'"}</App>
|
||||||
|
· ───────────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
× Unterminated string
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:2:22]
|
||||||
|
1 │
|
||||||
|
2 │ <App prop="
|
||||||
|
· ─
|
||||||
|
╰────
|
||||||
|
|
||||||
|
× Unterminated string
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:2:22]
|
||||||
|
1 │
|
||||||
|
2 │ <App prop='
|
||||||
|
· ─
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:4:19]
|
||||||
|
3 │ foo bar
|
||||||
|
4 │ <div>foo bar foo</div>
|
||||||
|
· ─────┬─────
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
5 │ <span>
|
||||||
|
╰────
|
||||||
|
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]
|
||||||
|
5 │ <span>
|
||||||
|
6 │ foo bar <i>foo bar</i>
|
||||||
|
· ───┬───
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
7 │ <strong>
|
||||||
|
╰────
|
||||||
|
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]
|
||||||
|
6 │ foo bar <i>foo bar</i>
|
||||||
|
7 │ ╭─▶ <strong>
|
||||||
|
8 │ │ foo bar
|
||||||
|
9 │ ├─▶ </strong>
|
||||||
|
· ╰──── Wrap this value in curly braces
|
||||||
|
10 │ </span>
|
||||||
|
╰────
|
||||||
|
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]
|
||||||
|
1 │
|
||||||
|
2 │ <Box mb={'1rem'} />
|
||||||
|
· ──────
|
||||||
|
3 │
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:2:27]
|
||||||
|
1 │
|
||||||
|
2 │ <Box mb={'1rem {}'} />
|
||||||
|
· ─────────
|
||||||
|
3 │
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:20]
|
||||||
|
1 │ <MyComponent prop={"{ style: true }"}>bar</MyComponent>
|
||||||
|
· ─────────────────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:20]
|
||||||
|
1 │ <MyComponent prop={"< style: true >"}>foo</MyComponent>
|
||||||
|
· ─────────────────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are required here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:13]
|
||||||
|
1 │ <App horror=<div /> />
|
||||||
|
· ───┬───
|
||||||
|
· ╰── Wrap this value in curly braces
|
||||||
|
╰────
|
||||||
|
help: Wrap this value in curly braces
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(jsx-curly-brace-presence): Curly braces are unnecessary here.
|
||||||
|
╭─[jsx_curly_brace_presence.tsx:1:14]
|
||||||
|
1 │ <App horror={<div />} />
|
||||||
|
· ───────
|
||||||
|
╰────
|
||||||
Loading…
Reference in a new issue