diff --git a/crates/oxc_linter/src/rules/react/jsx_key.rs b/crates/oxc_linter/src/rules/react/jsx_key.rs
index c63abc780..c746c7521 100644
--- a/crates/oxc_linter/src/rules/react/jsx_key.rs
+++ b/crates/oxc_linter/src/rules/react/jsx_key.rs
@@ -1,13 +1,16 @@
use oxc_ast::{
- ast::{Expression, JSXAttributeItem, JSXAttributeName, JSXElement, JSXFragment},
+ ast::{
+ Expression, JSXAttributeItem, JSXAttributeName, JSXElement, JSXFragment, MemberExpression,
+ },
AstKind,
};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
+use oxc_span::{Atom, GetSpan, Span};
+
use oxc_macros::declare_oxc_lint;
-use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
@@ -18,8 +21,11 @@ enum JsxKeyDiagnostic {
MissingKeyPropForElementInArray(#[label] Span),
#[error("eslint-plugin-react(jsx-key): Missing \"key\" prop for element in iterator.")]
- #[diagnostic(severity(warning))]
- MissingKeyPropForElementInIterator(#[label] Span),
+ #[diagnostic(severity(warning), help("Add a \"key\" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key)."))]
+ MissingKeyPropForElementInIterator(
+ #[label("Iterator starts here")] Span,
+ #[label("Element generated here")] Span,
+ ),
#[error(
"eslint-plugin-react(jsx-key): \"key\" prop must be placed before any `{{...spread}}`"
@@ -68,7 +74,7 @@ impl Rule for JsxKey {
enum InsideArrayOrIterator {
Array,
- Iterator,
+ Iterator(Span),
}
fn is_in_array_or_iter<'a, 'b>(
@@ -97,9 +103,9 @@ fn is_in_array_or_iter<'a, 'b>(
let callee = &v.callee.without_parenthesized();
if let Expression::MemberExpression(v) = callee {
- if let Some(static_property_name) = v.static_property_name() {
- if TARGET_METHODS.contains(static_property_name) {
- return Some(InsideArrayOrIterator::Iterator);
+ if let Some((x, span)) = get_member_expression_name_and_span(v.0) {
+ if TARGET_METHODS.contains(x.as_str()) {
+ return Some(InsideArrayOrIterator::Iterator(span));
}
}
}
@@ -113,6 +119,28 @@ fn is_in_array_or_iter<'a, 'b>(
}
}
+fn get_member_expression_name_and_span<'a>(
+ member_expr: &'a MemberExpression<'a>,
+) -> Option<(&'a Atom, Span)> {
+ match member_expr {
+ MemberExpression::StaticMemberExpression(expr) => {
+ Some((&expr.property.name, expr.property.span))
+ }
+ MemberExpression::ComputedMemberExpression(expr) => match &expr.expression {
+ Expression::StringLiteral(lit) => Some((&lit.value, lit.span)),
+ Expression::TemplateLiteral(lit) => {
+ if lit.expressions.is_empty() && lit.quasis.len() == 1 {
+ Some((&lit.quasis[0].value.raw, lit.quasis[0].span))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ },
+ MemberExpression::PrivateFieldExpression(_) => None,
+ }
+}
+
fn check_jsx_element<'a>(node: &AstNode<'a>, jsx_elem: &JSXElement<'a>, ctx: &LintContext<'a>) {
if let Some(outer) = is_in_array_or_iter(node, ctx) {
if !jsx_elem.opening_element.attributes.iter().any(|attr| {
@@ -123,7 +151,7 @@ fn check_jsx_element<'a>(node: &AstNode<'a>, jsx_elem: &JSXElement<'a>, ctx: &Li
};
attr_ident.name == "key"
}) {
- ctx.diagnostic(gen_diagnostic(jsx_elem.span, &outer));
+ ctx.diagnostic(gen_diagnostic(jsx_elem.opening_element.name.span(), &outer));
}
}
}
@@ -156,15 +184,15 @@ fn check_jsx_element_is_key_before_spread<'a>(jsx_elem: &JSXElement<'a>, ctx: &L
fn check_jsx_fragment<'a>(node: &AstNode<'a>, fragment: &JSXFragment<'a>, ctx: &LintContext<'a>) {
if let Some(outer) = is_in_array_or_iter(node, ctx) {
- ctx.diagnostic(gen_diagnostic(fragment.span, &outer));
+ ctx.diagnostic(gen_diagnostic(fragment.opening_fragment.span, &outer));
}
}
fn gen_diagnostic(span: Span, outer: &InsideArrayOrIterator) -> JsxKeyDiagnostic {
match outer {
InsideArrayOrIterator::Array => JsxKeyDiagnostic::MissingKeyPropForElementInArray(span),
- InsideArrayOrIterator::Iterator => {
- JsxKeyDiagnostic::MissingKeyPropForElementInIterator(span)
+ InsideArrayOrIterator::Iterator(v) => {
+ JsxKeyDiagnostic::MissingKeyPropForElementInIterator(*v, span)
}
}
}
@@ -415,6 +443,38 @@ fn test() {
"#,
None,
),
+ (
+ r#"
+ const TestCase = () => {
+ const list = [1, 2, 3, 4, 5];
+
+ return (
+
+ {list.map(item => onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />)}
+
+ );
+ };
+ "#,
+ None,
+ ),
+ (
+ r#"
+ const TestCase = () => {
+ const list = [1, 2, 3, 4, 5];
+
+ return (
+
+ {list.map(item => (
+ onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />
+
)
+
+ )}
+
+ );
+ };
+ "#,
+ None,
+ ),
];
Tester::new(JsxKey::NAME, pass, fail).test_and_snapshot();
diff --git a/crates/oxc_linter/src/snapshots/jsx_key.snap b/crates/oxc_linter/src/snapshots/jsx_key.snap
index 3d814a8b8..9bf3a422f 100644
--- a/crates/oxc_linter/src/snapshots/jsx_key.snap
+++ b/crates/oxc_linter/src/snapshots/jsx_key.snap
@@ -5,109 +5,151 @@ expression: jsx_key
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in array.
╭─[jsx_key.tsx:1:1]
1 │ [];
- · ───────
+ · ───
╰────
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in array.
╭─[jsx_key.tsx:1:1]
1 │ [];
- · ────────────────
+ · ───
╰────
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in array.
╭─[jsx_key.tsx:1:1]
1 │ [, ];
- · ───────
+ · ───
╰────
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2 ,3].map(function(x) { return });
- · ───────
+ · ─┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2 ,3].map(x => );
- · ───────
+ · ─┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2 ,3].map(x => x && );
- · ─────────────
+ · ─┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2 ,3].map(x => x ? : );
- · ──────────────────
+ · ─┬─ ────┬───
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2 ,3].map(x => x ? : );
- · ─────────────
+ · ─┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2 ,3].map(x => { return });
- · ───────
+ · ─┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ Array.from([1, 2 ,3], function(x) { return });
- · ───────
+ · ──┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ Array.from([1, 2 ,3], (x => { return }));
- · ───────
+ · ──┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ Array.from([1, 2 ,3], (x => ));
- · ───────
+ · ──┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2, 3]?.map(x => )
- · ──────────────────
+ · ─┬─ ───────┬──────
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2, 3]?.map(x => )
- · ───────────────────────
+ · ─┬─ ─────────┬─────────
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2, 3]?.map(x => <>>)
- · ─────────────────────────
+ · ─┬─ ─┬
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2, 3]?.map(x => <>>)
- · ────────────────────
+ · ─┬─ ────────┬───────
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:1:1]
1 │ [1, 2, 3].map(x => <>{x}>);
- · ────────
+ · ─┬─ ─┬
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in array.
╭─[jsx_key.tsx:1:1]
1 │ [<>>];
- · ─────
+ · ──
╰────
⚠ eslint-plugin-react(jsx-key): "key" prop must be placed before any `{...spread}`
@@ -125,75 +167,163 @@ expression: jsx_key
help: To avoid conflicting with React's new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:8:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return
{item}
;
- · ─────────────────
+ · ─┬─
+ · ╰── Element generated here
10 │ }
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:11:1]
- 11 │
- 12 │ return
;
- · ───────
- 13 │ })}
- ╰────
-
- ⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:8:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return
{item}
;
- · ─────────────────
- 10 │ } else if (item < 5) {
+ 10 │ }
+ 11 │
+ 12 │ return
;
+ · ─┬─
+ · ╰── Element generated here
+ 13 │ })}
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:10:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
+ 8 │ if (item < 2) {
+ 9 │ return
{item}
;
+ · ─┬─
+ · ╰── Element generated here
+ 10 │ } else if (item < 5) {
+ ╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
+
+ ⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
+ 8 │ if (item < 2) {
+ 9 │ return
{item}
;
10 │ } else if (item < 5) {
11 │ return
- · ───────────
+ · ─┬─
+ · ╰── Element generated here
12 │ } else {
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:12:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
+ 8 │ if (item < 2) {
+ 9 │ return
{item}
;
+ 10 │ } else if (item < 5) {
+ 11 │ return
12 │ } else {
13 │ return
- · ───────────
+ · ─┬─
+ · ╰── Element generated here
14 │ }
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
+ 8 │ if (item < 2) {
+ ╰────
╭─[jsx_key.tsx:15:1]
15 │
16 │ return
;
- · ───────
+ · ─┬─
+ · ╰── Element generated here
17 │ })}
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:7:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
8 │ if (item < 2) return
{item}
;
- · ─────────────────
+ · ─┬─
+ · ╰── Element generated here
9 │ else if (item < 5) return
;
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:8:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
8 │ if (item < 2) return
{item}
;
9 │ else if (item < 5) return
;
- · ───────
+ · ─┬─
+ · ╰── Element generated here
10 │ else return
;
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
- ╭─[jsx_key.tsx:9:1]
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => {
+ · ─┬─
+ · ╰── Iterator starts here
+ 8 │ if (item < 2) return
{item}
;
9 │ else if (item < 5) return
;
10 │ else return
;
- · ───────
+ · ─┬─
+ · ╰── Element generated here
11 │ })}
╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
+
+ ⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />)}
+ · ─┬─ ──┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
+ 8 │
+ ╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).
+
+ ⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
+ ╭─[jsx_key.tsx:6:1]
+ 6 │
+ 7 │ {list.map(item => (
+ · ─┬─ ─┬─
+ · │ ╰── Element generated here
+ · ╰── Iterator starts here
+ 8 │ onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />
+ ╰────
+ help: Add a "key" prop to the element in the iterator (https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).