From 584a9bc53ccc55a64e5171276f69baf9795ae4ce Mon Sep 17 00:00:00 2001 From: XantreGodlike Date: Sun, 31 Dec 2023 02:04:24 +0100 Subject: [PATCH 1/4] initial implementation of react_jsx_no_undef --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/react/jsx_no_undef.rs | 131 ++++++++++++++++++ .../src/snapshots/jsx_no_undef.snap | 41 ++++++ 3 files changed, 174 insertions(+) create mode 100644 crates/oxc_linter/src/rules/react/jsx_no_undef.rs create mode 100644 crates/oxc_linter/src/snapshots/jsx_no_undef.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 934febe8a1f60..34bc03c3532ca 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -141,6 +141,7 @@ mod react { pub mod jsx_key; pub mod jsx_no_comment_text_nodes; pub mod jsx_no_duplicate_props; + pub mod jsx_no_undef; pub mod jsx_no_useless_fragment; pub mod no_children_prop; pub mod no_dangerously_set_inner_html; @@ -453,6 +454,7 @@ oxc_macros::declare_all_lint_rules! { react::jsx_no_comment_text_nodes, react::jsx_no_duplicate_props, react::jsx_no_useless_fragment, + react::jsx_no_undef, react::react_in_jsx_scope, react::no_children_prop, react::no_dangerously_set_inner_html, diff --git a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs new file mode 100644 index 0000000000000..58828d6a300f8 --- /dev/null +++ b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs @@ -0,0 +1,131 @@ +use once_cell::sync::Lazy; +use oxc_ast::{ + ast::{ + JSXElementName, JSXIdentifier, JSXMemberExpression, JSXMemberExpressionObject, + JSXOpeningElement, + }, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use regex::Regex; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-react(jsx-no-undef):")] +#[diagnostic(severity(warning), help(""))] +struct JsxNoUndefDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct JsxNoUndef; + +declare_oxc_lint!( + /// ### What it does + /// This rule helps locate potential ReferenceErrors resulting from misspellings or missing components. + /// + /// ### Why is this bad? + /// + /// + /// ### Example + /// ```javascript + /// ``` + JsxNoUndef, + correctness +); + +fn get_member_ident<'a>(expr: &'a JSXMemberExpression<'a>) -> &'a JSXIdentifier { + match expr.object { + JSXMemberExpressionObject::Identifier(ref ident) => ident, + JSXMemberExpressionObject::MemberExpression(ref next_expr) => get_member_ident(next_expr), + } +} +fn get_resolvable_ident<'a>(node: &'a JSXElementName<'a>) -> Option<&'a JSXIdentifier> { + static STRING_ELEMENT_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[a-z]").unwrap()); + + match node { + JSXElementName::Identifier(ref ident) + if !STRING_ELEMENT_REGEX.is_match(ident.name.as_str()) => + { + Some(ident) + } + JSXElementName::Identifier(_) | JSXElementName::NamespacedName(_) => None, + JSXElementName::MemberExpression(expr) => Some(get_member_ident(expr)), + } +} + +impl Rule for JsxNoUndef { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::JSXOpeningElement(JSXOpeningElement { name: el_name, .. }) = &node.kind() { + if let Some(ident) = get_resolvable_ident(el_name) { + let has_binding = ctx + .symbols() + .get_scope_id_from_name(&ident.name) + .map_or(false, |scope_id| ctx.scopes().has_binding(scope_id, &ident.name)); + + if !has_binding { + ctx.diagnostic(JsxNoUndefDiagnostic(ident.span)); + } + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var React, App; React.render();", None), + ("var React; React.render();", None), + ("var React; React.render();", None), + ("var React, app; React.render();", None), + ("var React, app; React.render();", None), + ("var React; React.render();", None), + /*( + r" + var React; + class Hello extends React.Component { + render() { + return + } + } + ", + None, + ),*/ + // TODO: globals ("var React; React.render();", None), + ( + r#" + import Text from "cool-module"; + const TextWrapper = function (props) { + return ( + + ); + }; + "#, + None, + ), + ]; + + let fail = vec![ + ("var React; React.render();", None), + ("var React; React.render();", None), + ("var React; React.render();", None), + ("var React; React.render();", None), + /* TODO: Something about allow global (r#" + const TextWrapper = function (props) { + return ( + + ); + }; + export default TextWrapper; + "#, None), */ + ("var React; React.render();", None), + ]; + + Tester::new(JsxNoUndef::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/jsx_no_undef.snap b/crates/oxc_linter/src/snapshots/jsx_no_undef.snap new file mode 100644 index 0000000000000..48f7616bb0094 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/jsx_no_undef.snap @@ -0,0 +1,41 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 144 +expression: jsx_no_undef +--- + ⚠ eslint-plugin-react(jsx-no-undef): + ╭─[jsx_no_undef.tsx:1:1] + 1 │ var React; React.render(); + · ─── + ╰──── + help: + + ⚠ eslint-plugin-react(jsx-no-undef): + ╭─[jsx_no_undef.tsx:1:1] + 1 │ var React; React.render(); + · ──── + ╰──── + help: + + ⚠ eslint-plugin-react(jsx-no-undef): + ╭─[jsx_no_undef.tsx:1:1] + 1 │ var React; React.render(); + · ──── + ╰──── + help: + + ⚠ eslint-plugin-react(jsx-no-undef): + ╭─[jsx_no_undef.tsx:1:1] + 1 │ var React; React.render(); + · ──── + ╰──── + help: + + ⚠ eslint-plugin-react(jsx-no-undef): + ╭─[jsx_no_undef.tsx:1:1] + 1 │ var React; React.render(); + · ─── + ╰──── + help: + + From 682606b8372cb0ad5d77356c04a42898bfb77b45 Mon Sep 17 00:00:00 2001 From: XantreGodlike Date: Sun, 31 Dec 2023 12:25:08 +0100 Subject: [PATCH 2/4] added plugin descripton and updated snapshot --- .../src/rules/react/jsx_no_undef.rs | 18 +++++++++-------- .../src/snapshots/jsx_no_undef.snap | 20 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs index 58828d6a300f8..dd6f7e9465d3e 100644 --- a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs +++ b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs @@ -11,28 +11,30 @@ use oxc_diagnostics::{ thiserror::Error, }; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{Atom, Span}; use regex::Regex; use crate::{context::LintContext, rule::Rule, AstNode}; #[derive(Debug, Error, Diagnostic)] -#[error("eslint-plugin-react(jsx-no-undef):")] -#[diagnostic(severity(warning), help(""))] -struct JsxNoUndefDiagnostic(#[label] pub Span); +#[error("eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX")] +#[diagnostic(severity(warning), help("'{0}' is not defined."))] +struct JsxNoUndefDiagnostic(Atom, #[label] pub Span); #[derive(Debug, Default, Clone)] pub struct JsxNoUndef; declare_oxc_lint!( /// ### What it does - /// This rule helps locate potential ReferenceErrors resulting from misspellings or missing components. + /// Disallow undeclared variables in JSX /// /// ### Why is this bad? - /// + /// It is most likely a potential ReferenceError caused by a misspelling of a variable or parameter name. /// /// ### Example - /// ```javascript + /// ```jsx + /// const A = () => + /// const C = /// ``` JsxNoUndef, correctness @@ -68,7 +70,7 @@ impl Rule for JsxNoUndef { .map_or(false, |scope_id| ctx.scopes().has_binding(scope_id, &ident.name)); if !has_binding { - ctx.diagnostic(JsxNoUndefDiagnostic(ident.span)); + ctx.diagnostic(JsxNoUndefDiagnostic(ident.name.clone(), ident.span)); } } } diff --git a/crates/oxc_linter/src/snapshots/jsx_no_undef.snap b/crates/oxc_linter/src/snapshots/jsx_no_undef.snap index 48f7616bb0094..5d66317176a91 100644 --- a/crates/oxc_linter/src/snapshots/jsx_no_undef.snap +++ b/crates/oxc_linter/src/snapshots/jsx_no_undef.snap @@ -3,39 +3,39 @@ source: crates/oxc_linter/src/tester.rs assertion_line: 144 expression: jsx_no_undef --- - ⚠ eslint-plugin-react(jsx-no-undef): + ⚠ eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX ╭─[jsx_no_undef.tsx:1:1] 1 │ var React; React.render(); · ─── ╰──── - help: + help: 'App' is not defined. - ⚠ eslint-plugin-react(jsx-no-undef): + ⚠ eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX ╭─[jsx_no_undef.tsx:1:1] 1 │ var React; React.render(); · ──── ╰──── - help: + help: 'Appp' is not defined. - ⚠ eslint-plugin-react(jsx-no-undef): + ⚠ eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX ╭─[jsx_no_undef.tsx:1:1] 1 │ var React; React.render(); · ──── ╰──── - help: + help: 'appp' is not defined. - ⚠ eslint-plugin-react(jsx-no-undef): + ⚠ eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX ╭─[jsx_no_undef.tsx:1:1] 1 │ var React; React.render(); · ──── ╰──── - help: + help: 'appp' is not defined. - ⚠ eslint-plugin-react(jsx-no-undef): + ⚠ eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX ╭─[jsx_no_undef.tsx:1:1] 1 │ var React; React.render(); · ─── ╰──── - help: + help: 'Foo' is not defined. From 402cd2d766f77382df3e431c763928dec8d0fa0d Mon Sep 17 00:00:00 2001 From: XantreGodlike Date: Sun, 31 Dec 2023 12:39:33 +0100 Subject: [PATCH 3/4] fix: handled this case --- .../oxc_linter/src/rules/react/jsx_no_undef.rs | 18 +++++++----------- .../oxc_linter/src/snapshots/jsx_no_undef.snap | 7 +++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs index dd6f7e9465d3e..aafbbd7a59bc0 100644 --- a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs +++ b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs @@ -64,6 +64,9 @@ impl Rule for JsxNoUndef { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXOpeningElement(JSXOpeningElement { name: el_name, .. }) = &node.kind() { if let Some(ident) = get_resolvable_ident(el_name) { + if ident.name.as_str() == "this" { + return; + } let has_binding = ctx .symbols() .get_scope_id_from_name(&ident.name) @@ -88,7 +91,7 @@ fn test() { ("var React, app; React.render();", None), ("var React, app; React.render();", None), ("var React; React.render();", None), - /*( + ( r" var React; class Hello extends React.Component { @@ -98,8 +101,8 @@ fn test() { } ", None, - ),*/ - // TODO: globals ("var React; React.render();", None), + ), + // TODO: Text should be declared in globals ("var React; React.render();", None), ( r#" import Text from "cool-module"; @@ -118,15 +121,8 @@ fn test() { ("var React; React.render();", None), ("var React; React.render();", None), ("var React; React.render();", None), - /* TODO: Something about allow global (r#" - const TextWrapper = function (props) { - return ( - - ); - }; - export default TextWrapper; - "#, None), */ ("var React; React.render();", None), + ("var React; Unknown; React.render()", None), ]; Tester::new(JsxNoUndef::NAME, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/jsx_no_undef.snap b/crates/oxc_linter/src/snapshots/jsx_no_undef.snap index 5d66317176a91..3b3467c3c53fc 100644 --- a/crates/oxc_linter/src/snapshots/jsx_no_undef.snap +++ b/crates/oxc_linter/src/snapshots/jsx_no_undef.snap @@ -38,4 +38,11 @@ expression: jsx_no_undef ╰──── help: 'Foo' is not defined. + ⚠ eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX + ╭─[jsx_no_undef.tsx:1:1] + 1 │ var React; Unknown; React.render() + · ─────── + ╰──── + help: 'Unknown' is not defined. + From 65b9f0f28ac302eddf28f64ac80ecd7b3a907e0b Mon Sep 17 00:00:00 2001 From: XantreGodlike Date: Sun, 31 Dec 2023 12:53:47 +0100 Subject: [PATCH 4/4] removed unnecessary regex --- crates/oxc_linter/src/rules/react/jsx_no_undef.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs index aafbbd7a59bc0..d1ca94db66976 100644 --- a/crates/oxc_linter/src/rules/react/jsx_no_undef.rs +++ b/crates/oxc_linter/src/rules/react/jsx_no_undef.rs @@ -1,4 +1,3 @@ -use once_cell::sync::Lazy; use oxc_ast::{ ast::{ JSXElementName, JSXIdentifier, JSXMemberExpression, JSXMemberExpressionObject, @@ -12,7 +11,6 @@ use oxc_diagnostics::{ }; use oxc_macros::declare_oxc_lint; use oxc_span::{Atom, Span}; -use regex::Regex; use crate::{context::LintContext, rule::Rule, AstNode}; @@ -47,11 +45,9 @@ fn get_member_ident<'a>(expr: &'a JSXMemberExpression<'a>) -> &'a JSXIdentifier } } fn get_resolvable_ident<'a>(node: &'a JSXElementName<'a>) -> Option<&'a JSXIdentifier> { - static STRING_ELEMENT_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[a-z]").unwrap()); - match node { JSXElementName::Identifier(ref ident) - if !STRING_ELEMENT_REGEX.is_match(ident.name.as_str()) => + if !(ident.name.as_str().starts_with(char::is_lowercase)) => { Some(ident) }