Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: noArrayIndexKey false negative in template literals #1586

Merged
merged 33 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cb1438e
refactor(linter): UseAnchorContent code action
vasucp1207 Oct 7, 2023
364f018
refactor(linter): UseAnchorContent code action
vasucp1207 Oct 7, 2023
d94d9e1
refactor(linter): UseAnchorContent code action
vasucp1207 Oct 7, 2023
5517a6b
Merge branch 'biomejs:main' into main
vasucp1207 Oct 8, 2023
0d46365
Merge branch 'biomejs:main' into main
vasucp1207 Oct 12, 2023
4d11c7c
Merge branch 'biomejs:main' into main
vasucp1207 Oct 15, 2023
aa3d3a5
Merge branch 'biomejs:main' into main
vasucp1207 Oct 19, 2023
a39dad7
Merge branch 'biomejs:main' into main
vasucp1207 Oct 22, 2023
281c6a2
Merge branch 'biomejs:main' into main
vasucp1207 Oct 25, 2023
f3fb5d1
Merge branch 'biomejs:main' into main
vasucp1207 Oct 29, 2023
76b3f81
Merge branch 'biomejs:main' into main
vasucp1207 Oct 29, 2023
103bc72
Merge branch 'biomejs:main' into main
vasucp1207 Oct 31, 2023
63186c9
Merge branch 'biomejs:main' into main
vasucp1207 Nov 3, 2023
8138132
Merge branch 'biomejs:main' into main
vasucp1207 Nov 4, 2023
f276c68
Merge branch 'biomejs:main' into main
vasucp1207 Nov 16, 2023
5e968fc
Merge branch 'biomejs:main' into main
vasucp1207 Nov 17, 2023
736d2f1
Merge branch 'biomejs:main' into main
vasucp1207 Nov 21, 2023
4d6153a
Merge branch 'biomejs:main' into main
vasucp1207 Nov 21, 2023
517cac3
Merge branch 'biomejs:main' into main
vasucp1207 Nov 21, 2023
e833968
Merge branch 'biomejs:main' into main
vasucp1207 Nov 23, 2023
86ed25f
Merge branch 'biomejs:main' into main
vasucp1207 Nov 25, 2023
9c1cf36
Merge branch 'biomejs:main' into main
vasucp1207 Nov 28, 2023
31aa757
Merge branch 'biomejs:main' into main
vasucp1207 Nov 29, 2023
302a0b4
Merge branch 'biomejs:main' into main
vasucp1207 Dec 2, 2023
9d7af14
Merge branch 'biomejs:main' into main
vasucp1207 Dec 7, 2023
2787dd4
Merge branch 'biomejs:main' into main
vasucp1207 Dec 11, 2023
68d833d
Merge branch 'biomejs:main' into main
vasucp1207 Dec 20, 2023
8ea685f
Merge branch 'biomejs:main' into main
vasucp1207 Jan 9, 2024
dff5253
Merge branch 'biomejs:main' into main
vasucp1207 Jan 11, 2024
15d00db
Merge branch 'biomejs:main' into main
vasucp1207 Jan 11, 2024
aeb89a7
Merge branch 'biomejs:main' into main
vasucp1207 Jan 16, 2024
bd6c880
fix noArrayIndexKey false negative in template literals
vasucp1207 Jan 17, 2024
ee9aa93
fix noArrayIndexKey false negative in template literals
vasucp1207 Jan 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom

#### Bug fixes

- Fix [#1575](https://github.com/biomejs/biome/issues/1575). [noArrayIndexKey](https://biomejs.dev/linter/rules/no-array-index-key/) now captures array index value inside template literals and with string concatination. Contributed by @vasucp1207

### Parser


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use biome_analyze::context::RuleContext;
use biome_analyze::{declare_rule, Rule, RuleDiagnostic, RuleSource};
use biome_console::markup;
use biome_js_syntax::{
AnyJsFunction, AnyJsMemberExpression, JsCallArgumentList, JsCallArguments, JsCallExpression,
JsFormalParameter, JsObjectExpression, JsObjectMemberList, JsParameterList, JsParameters,
JsPropertyObjectMember, JsReferenceIdentifier, JsxAttribute,
AnyJsExpression, AnyJsFunction, AnyJsMemberExpression, AnyJsTemplateElement,
JsBinaryExpression, JsCallArgumentList, JsCallArguments, JsCallExpression, JsFormalParameter,
JsObjectExpression, JsObjectMemberList, JsParameterList, JsParameters, JsPropertyObjectMember,
JsReferenceIdentifier, JsxAttribute,
};
use biome_rowan::{declare_node_union, AstNode, TextRange};

Expand Down Expand Up @@ -36,6 +37,32 @@ declare_rule! {
/// React.cloneElement(child, { key: index })
/// ))
/// ```
///
/// ```jsx,expect_diagnostic
/// something.forEach((Element, index) => {
/// <Component key={`test-key-${index}`} >foo</Component>
/// });
/// ```
///
/// ```jsx,expect_diagnostic
/// something.forEach((Element, index) => {
/// <Component key={"test" + index} >foo</Component>
/// });
/// ```
///
/// ### Valid
/// ```jsx
/// something.forEach((item) => {
/// <Component key={item.id} >foo</Component>
/// });
/// ```
///
/// ```jsx
/// something.forEach((item) => {
/// <Component key={item.baz.foo} >foo</Component>
/// });
/// ```
///
pub(crate) NoArrayIndexKey {
version: "1.0.0",
name: "noArrayIndexKey",
Expand Down Expand Up @@ -71,24 +98,18 @@ impl NoArrayIndexKeyQuery {
}

/// Extracts the reference from the possible invalid prop
fn as_js_reference_identifier(&self) -> Option<JsReferenceIdentifier> {
fn as_js_expression(&self) -> Option<AnyJsExpression> {
match self {
NoArrayIndexKeyQuery::JsxAttribute(attribute) => attribute
.initializer()?
.value()
.ok()?
.as_jsx_expression_attribute_value()?
.expression()
.ok()?
.as_js_identifier_expression()?
.name()
.ok(),
NoArrayIndexKeyQuery::JsPropertyObjectMember(object_member) => object_member
.value()
.ok()?
.as_js_identifier_expression()?
.name()
.ok(),
NoArrayIndexKeyQuery::JsPropertyObjectMember(object_member) => {
object_member.value().ok()
}
}
}
}
Expand All @@ -114,7 +135,35 @@ impl Rule for NoArrayIndexKey {
}

let model = ctx.model();
let reference = node.as_js_reference_identifier()?;
let reference = node.as_js_expression()?;

let mut capture_array_index = None;

match reference {
AnyJsExpression::JsIdentifierExpression(identifier_expression) => {
capture_array_index = Some(identifier_expression.name().ok()?);
}
AnyJsExpression::JsTemplateExpression(template_expression) => {
let template_elements = template_expression.elements();
for element in template_elements {
if let AnyJsTemplateElement::JsTemplateElement(template_element) = element {
let cap_index_value = template_element
.expression()
.ok()?
.as_js_identifier_expression()?
.name()
.ok();
capture_array_index = cap_index_value;
}
}
}
AnyJsExpression::JsBinaryExpression(binary_expression) => {
let _ = cap_array_index_value(&binary_expression, &mut capture_array_index);
}
_ => {}
};

let reference = capture_array_index?;

// Given the reference identifier retrieved from the key property,
// find the declaration and ensure it resolves to the parameter of a function,
Expand Down Expand Up @@ -227,3 +276,30 @@ fn is_array_method_index(
None
}
}

fn cap_array_index_value(
binary_expression: &JsBinaryExpression,
capture_array_index: &mut Option<JsReferenceIdentifier>,
) -> Option<()> {
let left = binary_expression.left().ok()?;
let right = binary_expression.right().ok()?;

// recursive call if left or right again are binary_expressions
if let Some(left_binary) = left.as_js_binary_expression() {
cap_array_index_value(left_binary, capture_array_index);
};

if let Some(right_binary) = right.as_js_binary_expression() {
cap_array_index_value(right_binary, capture_array_index);
};

if let Some(left_expression) = left.as_js_identifier_expression() {
*capture_array_index = left_expression.name().ok();
};

if let Some(right_expression) = right.as_js_identifier_expression() {
*capture_array_index = right_expression.name().ok();
};

Some(())
}
Original file line number Diff line number Diff line change
@@ -1,110 +1,134 @@
import { Children, cloneElement } from "react";

something.forEach((Element, index) => {
<Component key={index}>foo</Component>;
<Component key={index}>foo</Component>;
});
something.forEach((element, index, array) => {
<Component key={index}>foo</Component>;
<Component key={index}>foo</Component>;
});
things.filter((thing, index) => {
otherThings.push(<Hello key={index}>foo</Hello>);
otherThings.push(<Hello key={index}>foo</Hello>);
});

something.forEach((Element, index) => {
<Component key={index} />;
<Component key={index} />;
});
something.forEach((element, index, array) => {
<Component key={index} />;
<Component key={index} />;
});
things.filter((thing, index) => {
otherThings.push(<Hello key={index} />);
otherThings.push(<Hello key={index} />);
});
things.reduce(
(collection, thing, index) => collection.concat(<Hello key={index} />),
[]
(collection, thing, index) => collection.concat(<Hello key={index} />),
[]
);

React.Children.map(this.props.children, (child, index) =>
React.cloneElement(child, { key: index })
React.cloneElement(child, { key: index })
);

React.Children.forEach(this.props.children, function (child, index) {
return React.cloneElement(child, { key: index });
return React.cloneElement(child, { key: index });
});

Children.map(this.props.children, (child, index) =>
cloneElement(child, { key: index })
cloneElement(child, { key: index })
);

Children.forEach(this.props.children, function (child, index) {
return cloneElement(child, { key: index });
return cloneElement(child, { key: index });
});

Children.forEach(this.props.children, function (child, index) {
const foo = cloneElement(child, { key: index });
return foo;
const foo = cloneElement(child, { key: index });
return foo;
});

function Test(props) {
return Children.map(props.children, function (child, index) {
return cloneElement(child, { key: index });
});
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 <Component key={index} />;
return <Component key={index} />;
});

Array.from(things, (thing, index) => {
return <Component key={index} />;
return <Component key={index} />;
});

const mapping = {
foo: () => things.map((_, index) => <Component key={index} />),
foo: () => things.map((_, index) => <Component key={index} />),
};

class A extends React.Component {
renderThings = () => things.map((_, index) => <Component key={index} />);
renderThings = () => things.map((_, index) => <Component key={index} />);
}

const Component1 = () => things.map((_, index) => <Component key={index} />);

const Component2 = () => things.map((_, index) => <Component key={index} />);

function Component3() {
return things.map((_, index) => <Component key={index} />);
return things.map((_, index) => <Component key={index} />);
}

function Component4() {
let elements = things.map((_, index) => <Component key={index} />);
if (condition) {
elements = others.map((_, index) => <Component key={index} />);
}
return elements;
let elements = things.map((_, index) => <Component key={index} />);
if (condition) {
elements = others.map((_, index) => <Component key={index} />);
}
return elements;
}

function Component5({ things }) {
const elements = useMemo(
() => things.map((_, index) => <Component key={index} />),
[things]
);
return elements;
const elements = useMemo(
() => things.map((_, index) => <Component key={index} />),
[things]
);
return elements;
}

function Component6({ things }) {
const elements = useMemo(
() => things.map((_, index) => <Component key={index} />),
[things]
);
return elements;
const elements = useMemo(
() => things.map((_, index) => <Component key={index} />),
[things]
);
return elements;
}

function Component7() {
return (
<HoC>
{({ things }) => things.map((_, index) => <Component key={index} />)}
</HoC>
);
return (
<HoC>
{({ things }) => things.map((_, index) => <Component key={index} />)}
</HoC>
);
}

function Component8() {
return (
<HoC>
{({ things }) => things.map((_, index) => <Component key={`test-key-${index}`} />)}
</HoC>
);
}

function Component9() {
return (
<HoC>
{({ things }) => things.map((_, index) => <Component key={"test" + index} />)}
</HoC>
);
}

function Component10() {
return (
<HoC>
{({ things }) => things.map((_, index) => <Component key={"test" + index + "test"} />)}
</HoC>
);
}
Loading