-
-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter) eslint-plugin-react: react-in-jsx-scope (#1025)
Co-authored-by: wenzhe <mysteryven@gmail.com>
- Loading branch information
1 parent
b425b73
commit 9bea278
Showing
3 changed files
with
144 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
crates/oxc_linter/src/rules/react/react_in_jsx_scope.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use oxc_ast::AstKind; | ||
use oxc_diagnostics::{ | ||
miette::{self, Diagnostic}, | ||
thiserror::Error, | ||
}; | ||
use oxc_macros::declare_oxc_lint; | ||
use oxc_span::{Atom, GetSpan, Span}; | ||
|
||
use crate::{context::LintContext, rule::Rule, AstNode}; | ||
|
||
#[derive(Debug, Error, Diagnostic)] | ||
#[error("eslint-plugin-react(react-in-jsx-scope): 'React' must be in scope when using JSX")] | ||
#[diagnostic(severity(warning), help("When using JSX, `<a />` expands to `React.createElement(\"a\")`. Therefore the `React` variable must be in scope."))] | ||
struct ReactInJsxScopeDiagnostic(#[label] pub Span); | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct ReactInJsxScope; | ||
|
||
declare_oxc_lint!( | ||
/// ### What it does | ||
/// | ||
/// Disallow missing React when using JSX | ||
/// | ||
/// ### Why is this bad? | ||
/// | ||
/// When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. | ||
/// | ||
/// ### Example | ||
/// ```javascript | ||
/// // Bad | ||
/// var a = <a />; | ||
/// | ||
/// // Good | ||
/// import React from "react"; | ||
/// var a = <a />; | ||
/// | ||
/// ``` | ||
ReactInJsxScope, | ||
suspicious | ||
); | ||
|
||
impl Rule for ReactInJsxScope { | ||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { | ||
let node_span = match node.kind() { | ||
AstKind::JSXOpeningElement(v) => v.name.span(), | ||
AstKind::JSXFragment(v) => v.opening_fragment.span, | ||
_ => return, | ||
}; | ||
let scope = ctx.scopes(); | ||
let react_name: &Atom = &Atom::from("React"); | ||
if scope.get_binding(scope.root_scope_id(), react_name).is_some() { | ||
return; | ||
} | ||
|
||
if !scope | ||
.ancestors(node.scope_id()) | ||
.any(|v| scope.get_bindings(v).iter().any(|(k, _)| k == react_name)) | ||
{ | ||
ctx.diagnostic(ReactInJsxScopeDiagnostic(node_span)); | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn test() { | ||
use crate::tester::Tester; | ||
|
||
let pass = vec![ | ||
("var React, App; <App />;", None), | ||
("var React; <img />;", None), | ||
("var React; <>fragment</>;", None), | ||
("var React; <x-gif />;", None), | ||
("var React, App, a=1; <App attr={a} />;", None), | ||
("var React, App, a=1; function elem() { return <App attr={a} />; }", None), | ||
("var React, App; <App />;", None), | ||
( | ||
" | ||
import React from 'react/addons'; | ||
const Button = createReactClass({ | ||
render() { | ||
return ( | ||
<button {...this.props}>{this.props.children}</button> | ||
) | ||
} | ||
}); | ||
export default Button; | ||
", | ||
None, | ||
), | ||
("var React, a = <img />;", None), | ||
]; | ||
|
||
let fail = vec![ | ||
("var App, a = <App />;", None), | ||
("var a = <App />;", None), | ||
("var a = <img />;", None), | ||
("var a = <>fragment</>;", None), | ||
("var Foo, a = <img />;", None), | ||
]; | ||
|
||
Tester::new(ReactInJsxScope::NAME, pass, fail).test_and_snapshot(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
source: crates/oxc_linter/src/tester.rs | ||
expression: react_in_jsx_scope | ||
--- | ||
⚠ eslint-plugin-react(react-in-jsx-scope): 'React' must be in scope when using JSX | ||
╭─[react_in_jsx_scope.tsx:1:1] | ||
1 │ var App, a = <App />; | ||
· ─── | ||
╰──── | ||
help: When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. | ||
|
||
⚠ eslint-plugin-react(react-in-jsx-scope): 'React' must be in scope when using JSX | ||
╭─[react_in_jsx_scope.tsx:1:1] | ||
1 │ var a = <App />; | ||
· ─── | ||
╰──── | ||
help: When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. | ||
|
||
⚠ eslint-plugin-react(react-in-jsx-scope): 'React' must be in scope when using JSX | ||
╭─[react_in_jsx_scope.tsx:1:1] | ||
1 │ var a = <img />; | ||
· ─── | ||
╰──── | ||
help: When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. | ||
|
||
⚠ eslint-plugin-react(react-in-jsx-scope): 'React' must be in scope when using JSX | ||
╭─[react_in_jsx_scope.tsx:1:1] | ||
1 │ var a = <>fragment</>; | ||
· ── | ||
╰──── | ||
help: When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. | ||
|
||
⚠ eslint-plugin-react(react-in-jsx-scope): 'React' must be in scope when using JSX | ||
╭─[react_in_jsx_scope.tsx:1:1] | ||
1 │ var Foo, a = <img />; | ||
· ─── | ||
╰──── | ||
help: When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. | ||
|
||
|