From 861eb07a8cbe2e6aa9d6e360aa244c9dc29fede1 Mon Sep 17 00:00:00 2001 From: Lucas Weng <30640930+lucasweng@users.noreply.github.com> Date: Fri, 18 Nov 2022 22:10:58 +0800 Subject: [PATCH] fix(rome_js_analyze): false negatives of `noArrayIndexKey` (#3681) Co-authored-by: Micha Reiser Resolves undefined --- crates/rome_js_analyze/src/react.rs | 101 ---- .../correctness/no_array_index_key.rs | 315 ++++------- .../specs/correctness/noArrayIndexKey.jsx | 123 +++++ .../correctness/noArrayIndexKey.jsx.snap | 494 +++++++++++++++++- .../playground/tabs/DiagnosticsListTab.tsx | 1 + website/src/playground/tabs/SettingsTab.tsx | 4 +- 6 files changed, 712 insertions(+), 326 deletions(-) diff --git a/crates/rome_js_analyze/src/react.rs b/crates/rome_js_analyze/src/react.rs index 90a195ded8c..42d24763289 100644 --- a/crates/rome_js_analyze/src/react.rs +++ b/crates/rome_js_analyze/src/react.rs @@ -117,107 +117,6 @@ impl ReactApiCall for ReactCreateElementCall { } } -/// A convenient data structure that returns the three arguments of the [React.cloneElement] call -/// -///[React.cloneElement]: https://reactjs.org/docs/react-api.html#cloneelement -pub(crate) struct ReactCloneElementCall { - /// The type of the react element - #[allow(dead_code)] - pub(crate) element_type: JsAnyCallArgument, - /// Optional props - pub(crate) new_props: Option, - /// Optional children - #[allow(dead_code)] - pub(crate) children: Option, -} - -impl ReactCloneElementCall { - /// Checks if the current node is a possible `cloneElement` call. - /// - /// There are two cases: - /// - /// First case - /// ```js - /// React.cloneElement() - /// ``` - /// We check if the node is a static member expression with the specific members. Also, if `React` - /// has been imported in the current scope, we make sure that the binding `React` has been imported - /// from the `"react"` module. - /// - /// Second case - /// - /// ```js - /// cloneElement() - /// ``` - /// - /// The logic of this second case is very similar to the previous one, simply the node that we have - /// to inspect is different. - pub(crate) fn from_call_expression( - call_expression: &JsCallExpression, - model: &SemanticModel, - ) -> Option { - let callee = call_expression.callee().ok()?; - let is_react_clone_element = - is_react_call_api(callee, model, ReactLibrary::React, "cloneElement"); - - if is_react_clone_element { - let arguments = call_expression.arguments().ok()?.args(); - // React.cloneElement() should not be processed - if !arguments.is_empty() { - let mut iter = arguments.iter(); - let first_argument = if let Some(first_argument) = iter.next() { - first_argument.ok()? - } else { - return None; - }; - let second_argument = - iter.next() - .and_then(|argument| argument.ok()) - .and_then(|argument| { - argument - .as_js_any_expression()? - .as_js_object_expression() - .cloned() - }); - let third_argument = iter - .next() - .and_then(|argument| argument.ok()) - .and_then(|argument| argument.as_js_any_expression().cloned()); - - Some(ReactCloneElementCall { - element_type: first_argument, - new_props: second_argument, - children: third_argument, - }) - } else { - None - } - } else { - None - } - } -} - -impl ReactApiCall for ReactCloneElementCall { - fn find_prop_by_name(&self, prop_name: &str) -> Option { - self.new_props.as_ref().and_then(|props| { - let members = props.members(); - members.iter().find_map(|member| { - let member = member.ok()?; - let property = member.as_js_property_object_member()?; - let property_name = property.name().ok()?; - - let property_name = property_name.as_js_literal_member_name()?; - if property_name.name().ok()? == prop_name { - Some(property.clone()) - } else { - None - } - }) - }) - } -} - #[derive(Debug, Clone, Copy)] pub(crate) enum ReactLibrary { React, diff --git a/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs b/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs index be1741f6ab6..5c710cd3192 100644 --- a/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs +++ b/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_array_index_key.rs @@ -1,16 +1,14 @@ -use crate::react::{is_react_call_api, ReactApiCall, ReactCloneElementCall, ReactLibrary}; +use crate::react::{is_react_call_api, ReactLibrary}; use crate::semantic_services::Semantic; use rome_analyze::context::RuleContext; use rome_analyze::{declare_rule, Rule, RuleDiagnostic}; use rome_console::markup; -use rome_js_semantic::SemanticModel; use rome_js_syntax::{ - JsAnyCallArgument, JsArrowFunctionExpression, JsCallExpression, JsExpressionStatement, - JsFunctionDeclaration, JsFunctionExpression, JsIdentifierBinding, JsMethodClassMember, - JsMethodObjectMember, JsParameterList, JsPropertyObjectMember, JsReferenceIdentifier, - JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, + JsAnyFunction, JsCallArgumentList, JsCallArguments, JsCallExpression, JsFormalParameter, + JsIdentifierBinding, JsObjectExpression, JsObjectMemberList, JsParameterList, JsParameters, + JsPropertyObjectMember, JsReferenceIdentifier, JsxAttribute, }; -use rome_rowan::{declare_node_union, AstNode, AstSeparatedList}; +use rome_rowan::{declare_node_union, AstNode}; declare_rule! { /// Discourage the usage of Array index in keys. @@ -46,18 +44,35 @@ declare_rule! { } declare_node_union! { - pub(crate) NoArrayIndexKeyQuery = JsxOpeningElement | JsxSelfClosingElement | JsCallExpression + pub(crate) NoArrayIndexKeyQuery = JsxAttribute | JsPropertyObjectMember } -declare_node_union! { - pub(crate) KeyPropWithArrayIndex = JsxAttribute | JsPropertyObjectMember -} +impl NoArrayIndexKeyQuery { + const fn is_property_object_member(&self) -> bool { + matches!(self, NoArrayIndexKeyQuery::JsPropertyObjectMember(_)) + } + + fn is_key_property(&self) -> Option { + Some(match self { + NoArrayIndexKeyQuery::JsxAttribute(attribute) => { + let attribute_name = attribute.name().ok()?; + let name = attribute_name.as_jsx_name()?; + let name_token = name.value_token().ok()?; + name_token.text_trimmed() == "key" + } + NoArrayIndexKeyQuery::JsPropertyObjectMember(object_member) => { + let object_member_name = object_member.name().ok()?; + let name = object_member_name.as_js_literal_member_name()?; + let name = name.value().ok()?; + name.text_trimmed() == "key" + } + }) + } -impl KeyPropWithArrayIndex { /// Extracts the reference from the possible invalid prop fn as_js_reference_identifier(&self) -> Option { match self { - KeyPropWithArrayIndex::JsxAttribute(attribute) => attribute + NoArrayIndexKeyQuery::JsxAttribute(attribute) => attribute .initializer()? .value() .ok()? @@ -67,7 +82,7 @@ impl KeyPropWithArrayIndex { .as_js_identifier_expression()? .name() .ok(), - KeyPropWithArrayIndex::JsPropertyObjectMember(object_member) => object_member + NoArrayIndexKeyQuery::JsPropertyObjectMember(object_member) => object_member .value() .ok()? .as_js_identifier_expression()? @@ -77,41 +92,6 @@ impl KeyPropWithArrayIndex { } } -impl NoArrayIndexKeyQuery { - const fn is_call_expression(&self) -> bool { - matches!(self, NoArrayIndexKeyQuery::JsCallExpression(_)) - } - - fn find_key_attribute(&self, model: &SemanticModel) -> Option { - match self { - NoArrayIndexKeyQuery::JsxOpeningElement(element) => element - .find_attribute_by_name("key") - .ok()? - .map(KeyPropWithArrayIndex::from), - NoArrayIndexKeyQuery::JsxSelfClosingElement(element) => element - .find_attribute_by_name("key") - .ok()? - .map(KeyPropWithArrayIndex::from), - NoArrayIndexKeyQuery::JsCallExpression(expression) => { - let create_clone_element = - ReactCloneElementCall::from_call_expression(expression, model)?; - create_clone_element - .find_prop_by_name("key") - .map(KeyPropWithArrayIndex::from) - } - } - } - - fn find_function_like_ancestor(&self) -> Option { - let element = match self { - NoArrayIndexKeyQuery::JsxOpeningElement(element) => element.syntax(), - NoArrayIndexKeyQuery::JsxSelfClosingElement(element) => element.syntax(), - NoArrayIndexKeyQuery::JsCallExpression(expression) => expression.syntax(), - }; - element.ancestors().find_map(FunctionLike::cast) - } -} - pub(crate) struct NoArrayIndexKeyState { /// The incorrect prop incorrect_prop: JsReferenceIdentifier, @@ -127,40 +107,66 @@ impl Rule for NoArrayIndexKey { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); - let model = ctx.model(); - let attribute_key = node.find_key_attribute(model)?; - let reference = attribute_key.as_js_reference_identifier()?; - let value = reference.value_token().ok()?; - let function_parent = node.find_function_like_ancestor()?; - let key_value = value.text_trimmed(); + if !node.is_key_property()? { + return None; + } - let is_inside_array_method = is_inside_array_method(&function_parent)?; + let model = ctx.model(); + let reference = node.as_js_reference_identifier()?; + + // Given the reference identifier retrieved from the key property, + // find the declaration and ensure it resolves to the parameter of a function, + // and navigate up to the closest call expression + let parameter = model + .declaration(&reference) + .and_then(|declaration| declaration.syntax().parent()) + .and_then(JsFormalParameter::cast)?; + let function = parameter + .parent::() + .and_then(|list| list.parent::()) + .and_then(|parameters| parameters.parent::())?; + let call_expression = function + .parent::() + .and_then(|arguments| arguments.parent::()) + .and_then(|arguments| arguments.parent::())?; + + // Check if the caller is an array method and the parameter is the array index of that method + let is_array_method_index = is_array_method_index(¶meter, &call_expression)?; + + if !is_array_method_index { + return None; + } - if is_inside_array_method { - if node.is_call_expression() { - // 1. We scan the parent and look for a potential `React.Children` method call - // 2. If found, then we retrieve the incorrect `index` - // 3. If we fail to find a `React.Children` method call, then we try to find and `Array.` method call - let binding_origin = find_react_children_function_argument(&function_parent, model) - .and_then(|function_argument| { - find_array_index_key(key_value, &function_argument) - }) - .or_else(|| find_array_index_key(key_value, &function_parent))?; + if node.is_property_object_member() { + let object_expression = node + .parent::() + .and_then(|list| list.parent::())?; + + // Check if the object expression is passed to a `React.cloneElement` call + let call_expression = object_expression + .parent::() + .and_then(|list| list.parent::()) + .and_then(|arguments| arguments.parent::())?; + let callee = call_expression.callee().ok()?; + + if is_react_call_api(callee, model, ReactLibrary::React, "cloneElement") { + let binding = parameter.binding().ok()?; + let binding_origin = binding.as_js_any_binding()?.as_js_identifier_binding()?; Some(NoArrayIndexKeyState { - binding_origin, + binding_origin: binding_origin.clone(), incorrect_prop: reference, }) } else { - let binding_origin = find_array_index_key(key_value, &function_parent)?; - - Some(NoArrayIndexKeyState { - binding_origin, - incorrect_prop: reference, - }) + None } } else { - None + let binding = parameter.binding().ok()?; + let binding_origin = binding.as_js_any_binding()?.as_js_identifier_binding()?; + Some(NoArrayIndexKeyState { + binding_origin: binding_origin.clone(), + incorrect_prop: reference, + }) } } @@ -189,27 +195,23 @@ impl Rule for NoArrayIndexKey { } } -/// Given a function like node, it navigates the `callee` of its parent +/// Given a parameter and a call expression, it navigates the `callee` of the call /// and check if the method called by this function belongs to an array method +/// and if the parameter is an array index /// /// ```js -/// Array.map(() => { -/// return +/// Array.map((_, index) => { +/// return /// }) /// ``` /// -/// Given this example, the input node is the arrow function and we navigate to -/// retrieve the name `map` call and we check if it belongs to an `Array.prototype` method. -fn is_inside_array_method(function_like_node: &FunctionLike) -> Option { - let function_parent = function_like_node - .syntax() - .ancestors() - .find_map(|ancestor| JsExpressionStatement::cast_ref(&ancestor))?; - - let name = function_parent - .expression() - .ok()? - .as_js_call_expression()? +/// Given this example, the input node is the `index` and `Array.map(...)` call and we navigate to +/// retrieve the name `map` and we check if it belongs to an `Array.prototype` method. +fn is_array_method_index( + parameter: &JsFormalParameter, + call_expression: &JsCallExpression, +) -> Option { + let name = call_expression .callee() .ok()? .as_js_static_member_expression()? @@ -218,132 +220,15 @@ fn is_inside_array_method(function_like_node: &FunctionLike) -> Option { .as_js_name()? .value_token() .ok()?; - - Some(matches!( - name.text_trimmed(), - "map" - | "forEach" - | "filter" - | "some" - | "every" - | "find" - | "findIndex" - | "reduce" - | "reduceRight" - )) -} - -declare_node_union! { - pub(crate) FunctionLike = JsFunctionDeclaration - | JsFunctionExpression - | JsArrowFunctionExpression - | JsMethodClassMember - | JsMethodObjectMember -} - -impl FunctionLike { - fn parameters(&self) -> Option { - let list = match self { - FunctionLike::JsFunctionDeclaration(node) => node.parameters().ok()?.items(), - FunctionLike::JsFunctionExpression(node) => node.parameters().ok()?.items(), - FunctionLike::JsArrowFunctionExpression(node) => { - node.parameters().ok()?.as_js_parameters()?.items() - } - FunctionLike::JsMethodClassMember(node) => node.parameters().ok()?.items(), - FunctionLike::JsMethodObjectMember(node) => node.parameters().ok()?.items(), - }; - Some(list) - } -} -/// It checks if the index binding comes from an array function and has the same name -/// used inside the `"key"` prop. -/// -/// ```js -/// Array.forEach((element, index, array) => { -/// -/// }); -/// ``` -/// -/// We scan all the parameters and we check if its name -fn find_array_index_key( - key_name: &str, - function_like_node: &FunctionLike, -) -> Option { - let parameters = function_like_node.parameters()?; - parameters.iter().find_map(|parameter| { - let parameter = parameter - .ok()? - .as_js_any_formal_parameter()? - .as_js_formal_parameter()? - .binding() - .ok()?; - let last_parameter = parameter.as_js_any_binding()?.as_js_identifier_binding()?; - - if last_parameter.name_token().ok()?.text_trimmed() == key_name { - Some(last_parameter.clone()) - } else { - None - } - }) -} - -/// This function initially checks if the [JsCallExpression] matches the following cases; -/// -/// ```js -/// React.Children.map(); -/// React.Children.forEach(); -/// Children.map(); -/// Children.forEach() -/// ``` -/// Then, it navigates the arguments and return the second argument, only if it's -/// -/// For example -/// -/// ```js -/// React.Children.map(this.props.children); // not correct -/// React.Children.map(this.props.children, (child, index) => {}) // correct, returns arrow function expression -/// React.Children.map(this.props.children, function(child, index) {}) // correct, returns function expression -/// ``` -/// -fn find_react_children_function_argument( - call_expression: &FunctionLike, - model: &SemanticModel, -) -> Option { - let function_parent = call_expression - .syntax() - .ancestors() - .find_map(|ancestor| JsExpressionStatement::cast_ref(&ancestor))?; - - let call_expression = function_parent.expression().ok()?; - let call_expression = call_expression.as_js_call_expression()?; - - let member_expression = call_expression.callee().ok()?; - let member_expression = member_expression.as_js_static_member_expression()?; - - let member = member_expression.member().ok()?; - let array_call = matches!( - member.as_js_name()?.value_token().ok()?.text_trimmed(), - "forEach" | "map" - ); - - if !array_call { - return None; - } - - let object = member_expression.object().ok()?; - - // React.Children.forEach/map or Children.forEach/map - if is_react_call_api(object, model, ReactLibrary::React, "Children") { - let arguments = call_expression.arguments().ok()?; - let arguments = arguments.args(); - let mut arguments = arguments.into_iter(); - - match (arguments.next(), arguments.next(), arguments.next()) { - (Some(_), Some(Ok(JsAnyCallArgument::JsAnyExpression(second_argument))), None) => { - FunctionLike::cast(second_argument.into_syntax()) - } - _ => None, - } + let name = name.text_trimmed(); + + if matches!( + name, + "map" | "flatMap" | "from" | "forEach" | "filter" | "some" | "every" | "find" | "findIndex" + ) { + Some(parameter.syntax().index() == 2) + } else if matches!(name, "reduce" | "reduceRight") { + Some(parameter.syntax().index() == 4) } else { None } diff --git a/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx b/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx index efa70cb986b..f6212e2d538 100644 --- a/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx +++ b/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx @@ -46,11 +46,75 @@ Children.forEach(this.props.children, function (child, index) { return foo; }) +function Test(props) { + return Children.map(props.children, function (child, index) { + return cloneElement(child, { key: index }); + }); +} things.map((thing, index) => ( React.cloneElement(thing, { key: index }) )); +things.flatMap((thing, index) => { + return ; +}) + +Array.from(things, (thing, index) => { + return ; +}) + +const mapping = { + foo: () => ( + things.map((_, index) => ) + ), +} + +class A extends React.Component { + renderThings = () => ( + things.map((_, index) => ) + ) +} + +const Component1 = () => things.map((_, index) => ); + +const Component2 = () => ( + things.map((_, index) => ) +); + +function Component3() { + return things.map((_, index) => ); +} + +function Component4() { + let elements = things.map((_, index) => ); + if (condition) { + elements = others.map((_, index) => ); + } + return elements; +} + +function Component5({things}) { + const elements = useMemo(() => things.map((_, index) => ), [things]); + return elements; +} + +function Component6({things}) { + const elements = useMemo(() => ( + things.map((_, index) => ) + ), [things]); + return elements; +} + +function Component7() { + return ( + + {({things}) => ( + things.map((_, index) => ) + )} + + ) +} // valid something.forEach((element, index) => { @@ -60,3 +124,62 @@ something.forEach((element, index) => { }); + + +const mapping = { + foo: () => ( + things.map((_, index) => ) + ), +} + +class A extends React.Component { + renderThings = () => ( + things.map((_, index) => ) + ) +} + +const Component8 = () => things.map((_, index) => ); + +const Component9 = () => ( + things.map((_, index) => ) +); + +function Component10() { + return things.map((_, index) => ); +} + +function Component11() { + let elements = things.map((_, index) => ); + if (condition) { + elements = others.map((_, index) => ); + } + return elements; +} + +function Component12({things}) { + const elements = useMemo(() => things.map((_, index) => ), [things]); + return elements; +} + +function Component13({things}) { + const elements = useMemo(() => ( + things.map((_, index) => ) + ), [things]); + return elements; +} + +function Component14() { + return ( + + {({things}) => ( + things.map((_, index) => ) + )} + + ) +} + +function Component15() { + return ids.map((id) => { + return + } +}) diff --git a/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx.snap b/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx.snap index 682b1d94dec..8e565d70c6f 100644 --- a/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx.snap +++ b/crates/rome_js_analyze/tests/specs/correctness/noArrayIndexKey.jsx.snap @@ -52,11 +52,75 @@ Children.forEach(this.props.children, function (child, index) { return foo; }) +function Test(props) { + return Children.map(props.children, function (child, index) { + return cloneElement(child, { key: index }); + }); +} things.map((thing, index) => ( React.cloneElement(thing, { key: index }) )); +things.flatMap((thing, index) => { + return ; +}) + +Array.from(things, (thing, index) => { + return ; +}) + +const mapping = { + foo: () => ( + things.map((_, index) => ) + ), +} + +class A extends React.Component { + renderThings = () => ( + things.map((_, index) => ) + ) +} + +const Component1 = () => things.map((_, index) => ); + +const Component2 = () => ( + things.map((_, index) => ) +); + +function Component3() { + return things.map((_, index) => ); +} + +function Component4() { + let elements = things.map((_, index) => ); + if (condition) { + elements = others.map((_, index) => ); + } + return elements; +} + +function Component5({things}) { + const elements = useMemo(() => things.map((_, index) => ), [things]); + return elements; +} + +function Component6({things}) { + const elements = useMemo(() => ( + things.map((_, index) => ) + ), [things]); + return elements; +} + +function Component7() { + return ( + + {({things}) => ( + things.map((_, index) => ) + )} + + ) +} // valid something.forEach((element, index) => { @@ -67,6 +131,65 @@ something.forEach((element, index) => { }); + +const mapping = { + foo: () => ( + things.map((_, index) => ) + ), +} + +class A extends React.Component { + renderThings = () => ( + things.map((_, index) => ) + ) +} + +const Component8 = () => things.map((_, index) => ); + +const Component9 = () => ( + things.map((_, index) => ) +); + +function Component10() { + return things.map((_, index) => ); +} + +function Component11() { + let elements = things.map((_, index) => ); + if (condition) { + elements = others.map((_, index) => ); + } + return elements; +} + +function Component12({things}) { + const elements = useMemo(() => things.map((_, index) => ), [things]); + return elements; +} + +function Component13({things}) { + const elements = useMemo(() => ( + things.map((_, index) => ) + ), [things]); + return elements; +} + +function Component14() { + return ( + + {({things}) => ( + things.map((_, index) => ) + )} + + ) +} + +function Component15() { + return ids.map((id) => { + return + } +}) + ``` # Diagnostics @@ -398,22 +521,377 @@ noArrayIndexKey.jsx:45:44 lint/correctness/noArrayIndexKey ━━━━━━━ ``` ``` -noArrayIndexKey.jsx:51:38 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +noArrayIndexKey.jsx:51:43 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 49 │ function Test(props) { + 50 │ return Children.map(props.children, function (child, index) { + > 51 │ return cloneElement(child, { key: index }); + │ ^^^^^ + 52 │ }); + 53 │ } + + i This is the source of the key value. + + 49 │ function Test(props) { + > 50 │ return Children.map(props.children, function (child, index) { + │ ^^^^^ + 51 │ return cloneElement(child, { key: index }); + 52 │ }); + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:56:38 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Avoid using the index of an array as key property in an element. - 50 │ things.map((thing, index) => ( - > 51 │ React.cloneElement(thing, { key: index }) + 55 │ things.map((thing, index) => ( + > 56 │ React.cloneElement(thing, { key: index }) │ ^^^^^ - 52 │ )); - 53 │ + 57 │ )); + 58 │ + + i This is the source of the key value. + + 53 │ } + 54 │ + > 55 │ things.map((thing, index) => ( + │ ^^^^^ + 56 │ React.cloneElement(thing, { key: index }) + 57 │ )); + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:60:28 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 59 │ things.flatMap((thing, index) => { + > 60 │ return ; + │ ^^^^^ + 61 │ }) + 62 │ + + i This is the source of the key value. + + 57 │ )); + 58 │ + > 59 │ things.flatMap((thing, index) => { + │ ^^^^^ + 60 │ return ; + 61 │ }) + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:64:28 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 63 │ Array.from(things, (thing, index) => { + > 64 │ return ; + │ ^^^^^ + 65 │ }) + 66 │ + + i This is the source of the key value. + + 61 │ }) + 62 │ + > 63 │ Array.from(things, (thing, index) => { + │ ^^^^^ + 64 │ return ; + 65 │ }) + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:69:50 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 67 │ const mapping = { + 68 │ foo: () => ( + > 69 │ things.map((_, index) => ) + │ ^^^^^ + 70 │ ), + 71 │ } + + i This is the source of the key value. + + 67 │ const mapping = { + 68 │ foo: () => ( + > 69 │ things.map((_, index) => ) + │ ^^^^^ + 70 │ ), + 71 │ } + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:75:50 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 73 │ class A extends React.Component { + 74 │ renderThings = () => ( + > 75 │ things.map((_, index) => ) + │ ^^^^^ + 76 │ ) + 77 │ } + + i This is the source of the key value. + + 73 │ class A extends React.Component { + 74 │ renderThings = () => ( + > 75 │ things.map((_, index) => ) + │ ^^^^^ + 76 │ ) + 77 │ } + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:79:67 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 77 │ } + 78 │ + > 79 │ const Component1 = () => things.map((_, index) => ); + │ ^^^^^ + 80 │ + 81 │ const Component2 = () => ( + + i This is the source of the key value. + + 77 │ } + 78 │ + > 79 │ const Component1 = () => things.map((_, index) => ); + │ ^^^^^ + 80 │ + 81 │ const Component2 = () => ( + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:82:46 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 81 │ const Component2 = () => ( + > 82 │ things.map((_, index) => ) + │ ^^^^^ + 83 │ ); + 84 │ i This is the source of the key value. - > 50 │ things.map((thing, index) => ( + 81 │ const Component2 = () => ( + > 82 │ things.map((_, index) => ) │ ^^^^^ - 51 │ React.cloneElement(thing, { key: index }) - 52 │ )); + 83 │ ); + 84 │ + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:86:53 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 85 │ function Component3() { + > 86 │ return things.map((_, index) => ); + │ ^^^^^ + 87 │ } + 88 │ + + i This is the source of the key value. + + 85 │ function Component3() { + > 86 │ return things.map((_, index) => ); + │ ^^^^^ + 87 │ } + 88 │ + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:90:61 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 89 │ function Component4() { + > 90 │ let elements = things.map((_, index) => ); + │ ^^^^^ + 91 │ if (condition) { + 92 │ elements = others.map((_, index) => ); + + i This is the source of the key value. + + 89 │ function Component4() { + > 90 │ let elements = things.map((_, index) => ); + │ ^^^^^ + 91 │ if (condition) { + 92 │ elements = others.map((_, index) => ); + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:92:61 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 90 │ let elements = things.map((_, index) => ); + 91 │ if (condition) { + > 92 │ elements = others.map((_, index) => ); + │ ^^^^^ + 93 │ } + 94 │ return elements; + + i This is the source of the key value. + + 90 │ let elements = things.map((_, index) => ); + 91 │ if (condition) { + > 92 │ elements = others.map((_, index) => ); + │ ^^^^^ + 93 │ } + 94 │ return elements; + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:98:77 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 97 │ function Component5({things}) { + > 98 │ const elements = useMemo(() => things.map((_, index) => ), [things]); + │ ^^^^^ + 99 │ return elements; + 100 │ } + + i This is the source of the key value. + + 97 │ function Component5({things}) { + > 98 │ const elements = useMemo(() => things.map((_, index) => ), [things]); + │ ^^^^^ + 99 │ return elements; + 100 │ } + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:104:50 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 102 │ function Component6({things}) { + 103 │ const elements = useMemo(() => ( + > 104 │ things.map((_, index) => ) + │ ^^^^^ + 105 │ ), [things]); + 106 │ return elements; + + i This is the source of the key value. + + 102 │ function Component6({things}) { + 103 │ const elements = useMemo(() => ( + > 104 │ things.map((_, index) => ) + │ ^^^^^ + 105 │ ), [things]); + 106 │ return elements; + + i The order of the items may change, and this also affects performances and component state. + + i Check the React documentation. + + +``` + +``` +noArrayIndexKey.jsx:113:58 lint/correctness/noArrayIndexKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the index of an array as key property in an element. + + 111 │ + 112 │ {({things}) => ( + > 113 │ things.map((_, index) => ) + │ ^^^^^ + 114 │ )} + 115 │ + + i This is the source of the key value. + + 111 │ + 112 │ {({things}) => ( + > 113 │ things.map((_, index) => ) + │ ^^^^^ + 114 │ )} + 115 │ i The order of the items may change, and this also affects performances and component state. diff --git a/website/src/playground/tabs/DiagnosticsListTab.tsx b/website/src/playground/tabs/DiagnosticsListTab.tsx index 1267802289b..b4d115a6922 100644 --- a/website/src/playground/tabs/DiagnosticsListTab.tsx +++ b/website/src/playground/tabs/DiagnosticsListTab.tsx @@ -99,6 +99,7 @@ export default function DiagnosticsListTab({ editorRef, diagnostics }: Props) {
    {diagnostics.map((diag, i) => { return ( + // rome-ignore lint(correctness/noArrayIndexKey): Diagnostic has no stable id. ); })} diff --git a/website/src/playground/tabs/SettingsTab.tsx b/website/src/playground/tabs/SettingsTab.tsx index e058b2906e4..76743677bc3 100644 --- a/website/src/playground/tabs/SettingsTab.tsx +++ b/website/src/playground/tabs/SettingsTab.tsx @@ -248,10 +248,10 @@ function FileView({
      - {files.map((filename, i) => { + {files.map((filename) => { return ( 1}