Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
Merge pull request #377 from matvp91/jsx-key
Browse files Browse the repository at this point in the history
Added jsx-key lint rule.
  • Loading branch information
sebmck authored May 5, 2020
2 parents 6950982 + becef71 commit ed9ce57
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/@romejs/diagnostics/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type LintDiagnosticCategory =
| 'lint/emptyBlocks'
| 'lint/getterReturn'
| 'lint/inconsiderateLanguage'
| 'lint/jsxKey'
| 'lint/jsxNoCommentText'
| 'lint/noArguments'
| 'lint/noAsyncPromiseExecutor'
Expand Down
4 changes: 4 additions & 0 deletions packages/@romejs/diagnostics/descriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ export const descriptions = createMessages({
category: 'lint/jsxNoCommentText',
message: 'Comments inside children should be placed in braces',
},
REACT_JSX_KEY: (origin: string) => ({
category: 'lint/jsxKey',
message: markup`Missing the "key" prop for element in ${origin}`,
}),
UNSAFE_NEGATION: {
category: 'lint/unsafeNegation',
message: 'Unsafe usage of negation operator in left side of binary expression',
Expand Down
3 changes: 3 additions & 0 deletions packages/@romejs/js-compiler/lint/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

// EVERYTHING BELOW IS AUTOGENERATED. SEE SCRIPTS FOLDER FOR UPDATE SCRIPTS

import camelCase from './regular/camelCase';
import caseSingleStatement from './regular/caseSingleStatement';
import defaultExportSameBasename from './regular/defaultExportSameBasename';
Expand All @@ -14,6 +15,7 @@ import duplicateImportSource from './regular/duplicateImportSource';
import emptyBlocks from './regular/emptyBlocks';
import getterReturn from './regular/getterReturn';
import inconsiderateLanguage from './regular/inconsiderateLanguage';
import jsxKey from './react/jsxKey';
import jsxNoCommentText from './react/jsxNoCommentText';
import noArguments from './regular/noArguments';
import noAsyncPromiseExecutor from './regular/noAsyncPromiseExecutor';
Expand Down Expand Up @@ -63,6 +65,7 @@ export const lintTransforms = [
emptyBlocks,
getterReturn,
inconsiderateLanguage,
jsxKey,
jsxNoCommentText,
noArguments,
noAsyncPromiseExecutor,
Expand Down
176 changes: 176 additions & 0 deletions packages/@romejs/js-compiler/lint/rules/react/jsxKey.test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# `jsxKey.test.ts`

**DO NOT MODIFY**. This file has been autogenerated. Run `rome test packages/@romejs/js-compiler/lint/rules/react/jsxKey.test.ts --update-snapshots` to update.

## `jsx key`

### `0`

```
unknown:1:11 lint/jsxKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Missing the "key" prop for element in array
const a = [<div />, <div />]
^^^^^^^
unknown:1:20 lint/jsxKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Missing the "key" prop for element in array
const a = [<div />, <div />]
^^^^^^^
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Found 2 problems
```

### `0: formatted`

```
const a = [<div />, <div />];
```

### `1`

```
unknown:1:26 lint/jsxKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Missing the "key" prop for element in iterator
const a = [1, 2].map(x => <div>{x}</div>);
^^^^^^^^^^^^^^
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Found 1 problem
```

### `1: formatted`

```
const a = [1, 2].map((x) => <div>{x}</div>);
```

### `2`

```
unknown:2:9 lint/jsxKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Missing the "key" prop for element in iterator
1 │ const a = [1, 2].map(x => {
> 2 │ return <div>{x}</div>;
│ ^^^^^^^^^^^^^^
3 │ });
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Found 1 problem
```

### `2: formatted`

```
const a = [1, 2].map((x) => {
return <div>{x}</div>;
});
```

### `3`

```
unknown:2:9 lint/jsxKey ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Missing the "key" prop for element in iterator
1 │ const a = [1, 2].map(function(x) {
> 2 │ return <div>{x}</div>;
│ ^^^^^^^^^^^^^^
3 │ });
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Found 1 problem
```

### `3: formatted`

```
const a = [1, 2].map(function(x) {
return <div>{x}</div>;
});
```

### `4`

```
✔ No known problems!
```

### `4: formatted`

```
const a = [<div key="a" />, <div key={'b'} />];
```

### `5`

```
✔ No known problems!
```

### `5: formatted`

```
const a = [1, 2].map((x) => <div key={x}>{x}</div>);
```

### `6`

```
✔ No known problems!
```

### `6: formatted`

```
const a = [1, 2].map((x) => {
return <div key={x}>{x}</div>;
});
```

### `7`

```
✔ No known problems!
```

### `7: formatted`

```
const a = [1, 2].map(function(x) {
return <div key={x}>{x}</div>;
});
```
64 changes: 64 additions & 0 deletions packages/@romejs/js-compiler/lint/rules/react/jsxKey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {test} from 'rome';
import {dedent} from '@romejs/string-utils';
import {testLintMultiple} from '../testHelpers';

test(
'jsx key',
async (t) => {
await testLintMultiple(
t,
[
// INVALID
'const a = [<div />, <div />]',
dedent(
`
const a = [1, 2].map(x => <div>{x}</div>);
`,
),
dedent(
`
const a = [1, 2].map(x => {
return <div>{x}</div>;
});
`,
),
dedent(
`
const a = [1, 2].map(function(x) {
return <div>{x}</div>;
});
`,
),
// VALID
'const a = [<div key="a" />, <div key={"b"} />]',
dedent(
`
const a = [1, 2].map(x => <div key={x}>{x}</div>);
`,
),
dedent(
`
const a = [1, 2].map(x => {
return <div key={x}>{x}</div>;
});
`,
),
dedent(
`
const a = [1, 2].map(function(x) {
return <div key={x}>{x}</div>;
});
`,
),
],
{category: 'lint/jsxKey'},
);
},
);
78 changes: 78 additions & 0 deletions packages/@romejs/js-compiler/lint/rules/react/jsxKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {Path} from '@romejs/js-compiler';
import {AnyNode, JSXElement} from '@romejs/js-ast';
import {descriptions} from '@romejs/diagnostics';

function containsKeyAttr(node: JSXElement): boolean {
const ATTR_NAME = 'key';
return !!node.attributes.find((attr) =>
attr.type === 'JSXAttribute' && attr.name.name === ATTR_NAME
);
}

export default {
name: 'jsxKey',
enter(path: Path): AnyNode {
const {node, context} = path;

// JSXElement in array literal
if (
node.type === 'JSXElement' &&
!containsKeyAttr(node) &&
path.parentPath.node.type === 'ArrayExpression'
) {
context.addNodeDiagnostic(node, descriptions.LINT.REACT_JSX_KEY('array'));
}

// Array.prototype.map
if (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.value.type === 'Identifier' &&
node.callee.property.value.name === 'map'
) {
const fn = node.arguments[0];

// Short hand arrow function
if (
fn.type === 'ArrowFunctionExpression' &&
fn.body.type === 'JSXElement' &&
!containsKeyAttr(fn.body)
) {
context.addNodeDiagnostic(
fn.body,
descriptions.LINT.REACT_JSX_KEY('iterator'),
);
}

// Function or arrow function with block statement
if (
fn &&
(fn.type === 'FunctionExpression' ||
fn.type === 'ArrowFunctionExpression') &&
fn.body.type === 'BlockStatement'
) {
fn.body.body.forEach((statement) => {
if (
statement.type === 'ReturnStatement' &&
statement.argument?.type === 'JSXElement' &&
!containsKeyAttr(statement.argument)
) {
context.addNodeDiagnostic(
statement.argument,
descriptions.LINT.REACT_JSX_KEY('iterator'),
);
}
});
}
}

return node;
},
};

0 comments on commit ed9ce57

Please sign in to comment.