Added jsx-key lint rule.
MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -68,6 +68,7 @@ export type DiagnosticCategory =
| 'lint/unsafeNegation'
| 'lint/unusedVariables'
| 'lint/react/jsxNoCommentText'
+ | 'lint/react/jsxKey'
| 'lsp/parse'
| 'parse/js'
| 'parse/json'

MODIFY
packages/@romejs/diagnostics/descriptions.ts
packages/@romejs/diagnostics/descriptions.ts
@@ -354,6 +354,10 @@ export const descriptions = createMessages({
category: 'lint/react/jsxNoCommentText',
message: 'Comments inside children should be placed in braces',
},
+ REACT_JSX_KEY: (origin: string) => ({
+ category: 'lint/react/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',

MODIFY
packages/@romejs/js-compiler/lint/rules/react/index.ts
packages/@romejs/js-compiler/lint/rules/react/index.ts
* LICENSE file in the root directory of this source tree.
*/
+import jsxKey from './jsxKey';
import jsxNoCommentText from './jsxNoCommentText';
// Add transforms in alphabetical order.
-export const reactLintTransforms = [jsxNoCommentText];
+export const reactLintTransforms = [jsxKey, jsxNoCommentText];

ADD
packages/@romejs/js-compiler/lint/rules/react/jsxKey.test.ts
+/**
+ * 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/react/jsxKey'},
+ );
+ },
+);

ADD
packages/@romejs/js-compiler/lint/rules/react/jsxKey.ts
+/**
+ * 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;
+ },
+};

Added jsx-key lint rule.