mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +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>`
|
||||
Spread(Box<'a, JSXSpreadChild<'a>>),
|
||||
}
|
||||
impl<'a> JSXChild<'a> {
|
||||
pub const fn is_expression_container(&self) -> bool {
|
||||
matches!(self, Self::ExpressionContainer(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// JSX Spread Child.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ mod jest {
|
|||
mod react {
|
||||
pub mod button_has_type;
|
||||
pub mod checked_requires_onchange_or_readonly;
|
||||
pub mod jsx_curly_brace_presence;
|
||||
pub mod jsx_key;
|
||||
pub mod jsx_no_comment_textnodes;
|
||||
pub mod jsx_no_duplicate_props;
|
||||
|
|
@ -716,6 +717,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
react::button_has_type,
|
||||
react::checked_requires_onchange_or_readonly,
|
||||
react::jsx_no_target_blank,
|
||||
react::jsx_curly_brace_presence,
|
||||
react::jsx_key,
|
||||
react::jsx_no_comment_textnodes,
|
||||
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