fix(linter) Improve span for jsx key (#1040)

closes #1034
This commit is contained in:
Cameron 2023-10-23 14:45:57 +01:00 committed by GitHub
parent 854b55a3e6
commit 2483e5cbf1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 244 additions and 54 deletions

View file

@ -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 (
<div>
{list.map(item => <Text foo bar baz qux onClick={() => onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />)}
</div>
);
};
"#,
None,
),
(
r#"
const TestCase = () => {
const list = [1, 2, 3, 4, 5];
return (
<div>
{list.map(item => (<div>
<Text foo bar baz qux onClick={() => onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />
</div>)
)}
</div>
);
};
"#,
None,
),
];
Tester::new(JsxKey::NAME, pass, fail).test_and_snapshot();

View file

@ -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 │ [<App />];
· ───────
· ───
╰────
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in array.
╭─[jsx_key.tsx:1:1]
1 │ [<App {...key} />];
· ────────────────
· ───
╰────
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in array.
╭─[jsx_key.tsx:1:1]
1 │ [<App key={0}/>, <App />];
· ───────
· ───
╰────
⚠ 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 <App /> });
· ───────
· ─┬─ ─┬─
· │ ╰── 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 => <App />);
· ───────
· ─┬─ ─┬─
· │ ╰── 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 && <App 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 ? <App x={x} key="1" /> : <OtherApp 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 ? <App x={x} /> : <OtherApp x={x} key="2" />);
· ─────────────
· ─┬─ ─┬─
· │ ╰── 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 <App /> });
· ───────
· ─┬─ ─┬─
· │ ╰── 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 <App /> });
· ───────
· ──┬─ ─┬─
· │ ╰── 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 <App /> }));
· ───────
· ──┬─ ─┬─
· │ ╰── 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 => <App />));
· ───────
· ──┬─ ─┬─
· │ ╰── 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 => <BabelEslintApp />)
· ──────────────────
· ─┬─ ───────┬──────
· │ ╰── 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 => <TypescriptEslintApp />)
· ───────────────────────
· ─┬─ ─────────┬─────────
· │ ╰── 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 => <><OxcCompilerHello /></>)
· ─────────────────────────
· ─┬─ ─┬
· │ ╰── 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 => <><OxcCompilerHello /></>)
· ────────────────────
· ─┬─ ────────┬───────
· │ ╰── 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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return <div>{item}</div>;
· ─────────────────
· ─┬─
· ╰── 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 <div />;
· ───────
13 │ })}
╰────
⚠ eslint-plugin-react(jsx-key): Missing "key" prop for element in iterator.
╭─[jsx_key.tsx:8:1]
╭─[jsx_key.tsx:6:1]
6 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return <div>{item}</div>;
· ─────────────────
10 │ } else if (item < 5) {
10 │ }
11 │
12 │ return <div />;
· ─┬─
· ╰── 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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return <div>{item}</div>;
· ─┬─
· ╰── 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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return <div>{item}</div>;
10 │ } else if (item < 5) {
11 │ return <div></div>
· ───────────
· ─┬─
· ╰── 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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) {
9 │ return <div>{item}</div>;
10 │ } else if (item < 5) {
11 │ return <div></div>
12 │ } else {
13 │ return <div></div>
· ───────────
· ─┬─
· ╰── 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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) {
╰────
╭─[jsx_key.tsx:15:1]
15 │
16 │ return <div />;
· ───────
· ─┬─
· ╰── 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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) return <div>{item}</div>;
· ─────────────────
· ─┬─
· ╰── Element generated here
9 │ else if (item < 5) return <div />;
╰────
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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) return <div>{item}</div>;
9 │ else if (item < 5) return <div />;
· ───────
· ─┬─
· ╰── Element generated here
10 │ else return <div />;
╰────
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 │ <div>
7 │ {list.map(item => {
· ─┬─
· ╰── Iterator starts here
8 │ if (item < 2) return <div>{item}</div>;
9 │ else if (item < 5) return <div />;
10 │ else return <div />;
· ───────
· ─┬─
· ╰── 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 │ <div>
7 │ {list.map(item => <Text foo bar baz qux onClick={() => onClickHandler()} onPointerDown={() => onPointerDownHandler()} onMouseDown={() => onMouseDownHandler()} />)}
· ─┬─ ──┬─
· │ ╰── Element generated here
· ╰── Iterator starts here
8 │ </div>
╰────
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 │ <div>
7 │ {list.map(item => (<div>
· ─┬─ ─┬─
· │ ╰── Element generated here
· ╰── Iterator starts here
8 │ <Text foo bar baz qux onClick={() => 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).