Skip to content

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

crates/oxc_linter/src/rules.rs

+2
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ mod react {
141141
pub mod jsx_key;
142142
pub mod jsx_no_comment_text_nodes;
143143
pub mod jsx_no_duplicate_props;
144+
pub mod jsx_no_undef;
144145
pub mod jsx_no_useless_fragment;
145146
pub mod no_children_prop;
146147
pub mod no_dangerously_set_inner_html;
@@ -453,6 +454,7 @@ oxc_macros::declare_all_lint_rules! {
453454
react::jsx_no_comment_text_nodes,
454455
react::jsx_no_duplicate_props,
455456
react::jsx_no_useless_fragment,
457+
react::jsx_no_undef,
456458
react::react_in_jsx_scope,
457459
react::no_children_prop,
458460
react::no_dangerously_set_inner_html,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use oxc_ast::{
2+
ast::{
3+
JSXElementName, JSXIdentifier, JSXMemberExpression, JSXMemberExpressionObject,
4+
JSXOpeningElement,
5+
},
6+
AstKind,
7+
};
8+
use oxc_diagnostics::{
9+
miette::{self, Diagnostic},
10+
thiserror::Error,
11+
};
12+
use oxc_macros::declare_oxc_lint;
13+
use oxc_span::{Atom, Span};
14+
15+
use crate::{context::LintContext, rule::Rule, AstNode};
16+
17+
#[derive(Debug, Error, Diagnostic)]
18+
#[error("eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX")]
19+
#[diagnostic(severity(warning), help("'{0}' is not defined."))]
20+
struct JsxNoUndefDiagnostic(Atom, #[label] pub Span);
21+
22+
#[derive(Debug, Default, Clone)]
23+
pub struct JsxNoUndef;
24+
25+
declare_oxc_lint!(
26+
/// ### What it does
27+
/// Disallow undeclared variables in JSX
28+
///
29+
/// ### Why is this bad?
30+
/// It is most likely a potential ReferenceError caused by a misspelling of a variable or parameter name.
31+
///
32+
/// ### Example
33+
/// ```jsx
34+
/// const A = () => <App />
35+
/// const C = <B />
36+
/// ```
37+
JsxNoUndef,
38+
correctness
39+
);
40+
41+
fn get_member_ident<'a>(expr: &'a JSXMemberExpression<'a>) -> &'a JSXIdentifier {
42+
match expr.object {
43+
JSXMemberExpressionObject::Identifier(ref ident) => ident,
44+
JSXMemberExpressionObject::MemberExpression(ref next_expr) => get_member_ident(next_expr),
45+
}
46+
}
47+
fn get_resolvable_ident<'a>(node: &'a JSXElementName<'a>) -> Option<&'a JSXIdentifier> {
48+
match node {
49+
JSXElementName::Identifier(ref ident)
50+
if !(ident.name.as_str().starts_with(char::is_lowercase)) =>
51+
{
52+
Some(ident)
53+
}
54+
JSXElementName::Identifier(_) | JSXElementName::NamespacedName(_) => None,
55+
JSXElementName::MemberExpression(expr) => Some(get_member_ident(expr)),
56+
}
57+
}
58+
59+
impl Rule for JsxNoUndef {
60+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
61+
if let AstKind::JSXOpeningElement(JSXOpeningElement { name: el_name, .. }) = &node.kind() {
62+
if let Some(ident) = get_resolvable_ident(el_name) {
63+
if ident.name.as_str() == "this" {
64+
return;
65+
}
66+
let has_binding = ctx
67+
.symbols()
68+
.get_scope_id_from_name(&ident.name)
69+
.map_or(false, |scope_id| ctx.scopes().has_binding(scope_id, &ident.name));
70+
71+
if !has_binding {
72+
ctx.diagnostic(JsxNoUndefDiagnostic(ident.name.clone(), ident.span));
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
#[test]
80+
fn test() {
81+
use crate::tester::Tester;
82+
83+
let pass = vec![
84+
("var React, App; React.render(<App />);", None),
85+
("var React; React.render(<img />);", None),
86+
("var React; React.render(<x-gif />);", None),
87+
("var React, app; React.render(<app.Foo />);", None),
88+
("var React, app; React.render(<app.foo.Bar />);", None),
89+
("var React; React.render(<Apppp:Foo />);", None),
90+
(
91+
r"
92+
var React;
93+
class Hello extends React.Component {
94+
render() {
95+
return <this.props.tag />
96+
}
97+
}
98+
",
99+
None,
100+
),
101+
// TODO: Text should be declared in globals ("var React; React.render(<Text />);", None),
102+
(
103+
r#"
104+
import Text from "cool-module";
105+
const TextWrapper = function (props) {
106+
return (
107+
<Text />
108+
);
109+
};
110+
"#,
111+
None,
112+
),
113+
];
114+
115+
let fail = vec![
116+
("var React; React.render(<App />);", None),
117+
("var React; React.render(<Appp.Foo />);", None),
118+
("var React; React.render(<appp.Foo />);", None),
119+
("var React; React.render(<appp.foo.Bar />);", None),
120+
("var React; React.render(<Foo />);", None),
121+
("var React; Unknown; React.render(<Unknown />)", None),
122+
];
123+
124+
Tester::new(JsxNoUndef::NAME, pass, fail).test_and_snapshot();
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
assertion_line: 144
4+
expression: jsx_no_undef
5+
---
6+
eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX
7+
╭─[jsx_no_undef.tsx:1:1]
8+
1var React; React.render(<App />);
9+
· ───
10+
╰────
11+
help: 'App' is not defined.
12+
13+
eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX
14+
╭─[jsx_no_undef.tsx:1:1]
15+
1var React; React.render(<Appp.Foo />);
16+
· ────
17+
╰────
18+
help: 'Appp' is not defined.
19+
20+
eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX
21+
╭─[jsx_no_undef.tsx:1:1]
22+
1var React; React.render(<appp.Foo />);
23+
· ────
24+
╰────
25+
help: 'appp' is not defined.
26+
27+
eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX
28+
╭─[jsx_no_undef.tsx:1:1]
29+
1var React; React.render(<appp.foo.Bar />);
30+
· ────
31+
╰────
32+
help: 'appp' is not defined.
33+
34+
eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX
35+
╭─[jsx_no_undef.tsx:1:1]
36+
1var React; React.render(<Foo />);
37+
· ───
38+
╰────
39+
help: 'Foo' is not defined.
40+
41+
eslint-plugin-react(jsx-no-undef): Disallow undeclared variables in JSX
42+
╭─[jsx_no_undef.tsx:1:1]
43+
1var React; Unknown; React.render(<Unknown />)
44+
· ───────
45+
╰────
46+
help: 'Unknown' is not defined.
47+
48+

0 commit comments

Comments
 (0)