From 70f077a02d5f3348ca65f6f16aef48daee60b4f2 Mon Sep 17 00:00:00 2001 From: Namrata Gupta Date: Wed, 24 Dec 2025 15:48:54 +0530 Subject: [PATCH 1/2] Adding changes to support react in .jsx files --- package-lock.json | 413 ++++- .../code-analyzer-eslint-engine/package.json | 4 +- .../src/base-config.ts | 72 +- .../code-analyzer-eslint-engine/src/config.ts | 16 +- .../src/messages.ts | 5 + .../src/missing-types-for-dependencies.d.ts | 21 + .../src/rule-mappings.ts | 411 +++++ .../test/engine.test.ts | 116 +- .../test/misc.test.ts | 9 +- .../test/plugin.test.ts | 5 +- .../test/rule-mappings.test.ts | 6 +- .../test-data/rules_ReactConfig.goldfile.json | 1437 +++++++++++++++++ .../test-data/workspaceWithReactFiles/App.jsx | 18 + .../workspaceWithReactFiles/Button.jsx | 12 + .../workspaceWithReactFiles/utils.js | 9 + 15 files changed, 2495 insertions(+), 59 deletions(-) create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/rules_ReactConfig.goldfile.json create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/App.jsx create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/Button.jsx create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/utils.js diff --git a/package-lock.json b/package-lock.json index cac62702..03cd16a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -98,7 +97,6 @@ "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.5.tgz", "integrity": "sha512-HLkYQfRICudzcOtjGwkPvGc5nF1b4ljLZh1IRDj50lRZ718NAKVgQpIAUX8bfg6u/yuSKY3L7E0YzIV+OxrB8Q==", "license": "MIT", - "peer": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", @@ -137,6 +135,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", @@ -205,7 +215,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -367,7 +376,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -505,6 +513,91 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -1815,7 +1908,6 @@ "resolved": "https://registry.npmjs.org/@salesforce/eslint-plugin-lightning/-/eslint-plugin-lightning-1.0.1.tgz", "integrity": "sha512-oyUVSNUA0WkkQr3BRtcAYhYotzIpqZtfMpUVMhROPN8YjDGu6CzCoC3/1i4ySIevgmH3J83KypwoqvRfoQf8Ww==", "license": "MIT", - "peer": true, "peerDependencies": { "eslint": "^7 || ^8" } @@ -2044,7 +2136,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.0", @@ -2073,7 +2164,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", @@ -2543,7 +2633,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2692,6 +2781,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -2749,6 +2858,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -2985,7 +3110,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -3574,9 +3698,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -3665,6 +3789,33 @@ "integrity": "sha512-7zQHIugusEuMWjWafkdSwzDWGZ5EbRjSBp5mzfa8kwZoCS1zeiKLNV2SM7vbtPCo8xztWrukg5xZ7OGkYEoEbQ==", "license": "MIT" }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3778,7 +3929,6 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3880,7 +4030,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -3965,7 +4114,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.5.0.tgz", "integrity": "sha512-DAi9H8xN/TUuNOt+xDP1RqpCJLsSxBb5u1zXSpCyp0VAWGL8MBAg5t7/Dk+76iX7d1LhWu4DDH77IQNUolLDyg==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/utils": "^8.0.0" }, @@ -3986,6 +4134,107 @@ } } }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-restricted-globals": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.2.0.tgz", @@ -5544,6 +5793,23 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -5566,7 +5832,6 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -6234,6 +6499,21 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6301,6 +6581,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6533,6 +6825,15 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -6574,6 +6875,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -7000,6 +7316,23 @@ "dev": true, "license": "MIT" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -7787,6 +8120,43 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -8316,7 +8686,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9386,6 +9755,7 @@ "version": "0.38.0", "license": "BSD-3-Clause", "dependencies": { + "@babel/preset-react": "^7.27.1", "@eslint/js": "^9.39.2", "@lwc/eslint-plugin-lwc": "^3.3.0", "@lwc/eslint-plugin-lwc-platform": "^6.3.0", @@ -9400,6 +9770,7 @@ "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jest": "^29.5.0", + "eslint-plugin-react": "^7.37.2", "globals": "^16.5.0", "semver": "^7.7.3", "typescript": "^5.9.3", @@ -9463,7 +9834,6 @@ "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.9.tgz", "integrity": "sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==", "license": "MIT", - "peer": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", @@ -9654,7 +10024,6 @@ "resolved": "https://registry.npmjs.org/@salesforce/eslint-plugin-lightning/-/eslint-plugin-lightning-2.0.0.tgz", "integrity": "sha512-lC3GL2j6B2wAGeTFWT0h47BFg+0R7naqqlQW+ANvNSaIC/qEB+tNSRcdAZ8DRTojsI3GRdpgq3FTB1llbrFBng==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -9679,7 +10048,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9739,7 +10107,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -10059,7 +10426,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.30.1", @@ -10135,7 +10501,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.30.1", "@typescript-eslint/types": "8.30.1", @@ -10245,7 +10610,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.10.0.tgz", "integrity": "sha512-hyMWUxkBH99HpXT3p8hc7REbEZK3D+nk8vHXGgpB+XXsi0gO4PxMSP+pjfUzb67GnV9yawV9a53eUmcde1CCZA==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" }, @@ -10280,7 +10644,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index 41be91da..3c4d0e36 100644 --- a/packages/code-analyzer-eslint-engine/package.json +++ b/packages/code-analyzer-eslint-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-eslint-engine", "description": "Plugin package that adds 'eslint' as an engine into Salesforce Code Analyzer", - "version": "0.38.0", + "version": "0.39.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,6 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { + "@babel/preset-react": "^7.27.1", "@eslint/js": "^9.39.2", "@lwc/eslint-plugin-lwc": "^3.3.0", "@lwc/eslint-plugin-lwc-platform": "^6.3.0", @@ -27,6 +28,7 @@ "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jest": "^29.5.0", + "eslint-plugin-react": "^7.37.2", "globals": "^16.5.0", "semver": "^7.7.3", "typescript": "^5.9.3", diff --git a/packages/code-analyzer-eslint-engine/src/base-config.ts b/packages/code-analyzer-eslint-engine/src/base-config.ts index 8bd164ff..141898b2 100644 --- a/packages/code-analyzer-eslint-engine/src/base-config.ts +++ b/packages/code-analyzer-eslint-engine/src/base-config.ts @@ -4,6 +4,7 @@ import eslintTs from "typescript-eslint"; import lwcEslintPluginLwcPlatform from "@lwc/eslint-plugin-lwc-platform"; import salesforceEslintConfigLwc from "@salesforce/eslint-config-lwc"; import sldsEslintPlugin from "@salesforce-ux/eslint-plugin-slds"; +import eslintPluginReact from "eslint-plugin-react"; import {ESLintEngineConfig} from "./config"; import globals from "globals"; @@ -54,20 +55,39 @@ export class BaseConfigFactory { if (this.useTsBaseConfig()) { configArray.push(...this.createTypescriptConfigArray()); } + // Add React plugin config for JSX files + if (this.useReactBaseConfig()) { + configArray.push(...this.createReactConfigArray()); + } return configArray; } private createJavascriptPlusLwcConfigArray(): Linter.Config[] { let configs: Linter.Config[] = validateAndGetRawLwcConfigArray(); + // Deep clone the languageOptions to avoid mutating the original shared config from the LWC package + const originalParserOptions = configs[0].languageOptions!.parserOptions as Linter.ParserOptions; + const clonedBabelOptions = JSON.parse(JSON.stringify(originalParserOptions.babelOptions)); + configs[0].languageOptions = { + ...configs[0].languageOptions, + parserOptions: { + ...originalParserOptions, + babelOptions: clonedBabelOptions + } + }; + // TODO: Remove the For the following 2 updates when https://github.com/salesforce/eslint-config-lwc/issues/158 is fixed // 1) Turn off the babel parser's configFile option from the lwc base plugin - (configs[0].languageOptions!.parserOptions as Linter.ParserOptions).babelOptions.configFile = false; + clonedBabelOptions.configFile = false; // 2) For some reason babel doesn't like .cjs files unless we explicitly set this to undefined because I think // ESLint 9 is setting it to "commonjs" automatically when the field doesn't exist in the parserOptions (and for // babel "commonjs" isn't a valid option) (configs[0].languageOptions!.parserOptions as Linter.ParserOptions).sourceType = undefined; + // 3) Add @babel/preset-react to enable JSX parsing for React/JSX files + const existingPresets = clonedBabelOptions.presets || []; + clonedBabelOptions.presets = [...existingPresets, '@babel/preset-react']; + // Swap out eslintJs.configs.recommended with eslintJs.configs.all configs[1] = eslintJs.configs.all; @@ -115,7 +135,14 @@ export class BaseConfigFactory { private createJavascriptConfigArray(): Linter.Config[] { return [{ ... eslintJs.configs.all, - files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`) + files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`), + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true // Enable JSX parsing for React/JSX files + } + } + } }]; } @@ -160,6 +187,40 @@ export class BaseConfigFactory { return configs; } + /** + * Creates React plugin config for JavaScript files. + * + * React rules are applied to all JS files (.js, .jsx, .cjs, .mjs) - if a file + * doesn't contain React code, the rules simply won't report any violations. + * + * Note: TypeScript React support (.tsx) is planned for the next iteration. + */ + private createReactConfigArray(): Linter.Config[] { + // Apply React rules to all JavaScript files + const jsExtensions = this.engineConfig.file_extensions.javascript; + + if (jsExtensions.length === 0) { + return []; + } + + // Get all rules from eslint-plugin-react's flat config + const reactAllConfig = eslintPluginReact.configs.flat.all; + + return [{ + ...reactAllConfig, + files: jsExtensions.map(ext => `**/*${ext}`), + settings: { + ...reactAllConfig.settings, + react: { + // React version - "detect" automatically picks the installed version, falls back to latest + version: 'detect', + // Pragma is the function JSX compiles to (e.g.,
→ React.createElement('div')) + pragma: 'React' + } + } + }]; + } + private useJsBaseConfig(): boolean { return !this.engineConfig.disable_javascript_base_config && this.engineConfig.file_extensions.javascript.length > 0; } @@ -179,6 +240,13 @@ export class BaseConfigFactory { private useTsBaseConfig(): boolean { return !this.engineConfig.disable_typescript_base_config && this.engineConfig.file_extensions.typescript.length > 0; } + + private useReactBaseConfig(): boolean { + // React config is independently controlled by disable_react_base_config + // React rules apply to all JS files - no harm if file has no React code + return !this.engineConfig.disable_react_base_config && + this.engineConfig.file_extensions.javascript.length > 0; + } } // In order to supply all the eslint rules (instead of just the recommended ones) to be selectable, and in order to diff --git a/packages/code-analyzer-eslint-engine/src/config.ts b/packages/code-analyzer-eslint-engine/src/config.ts index 62530054..1603abc7 100644 --- a/packages/code-analyzer-eslint-engine/src/config.ts +++ b/packages/code-analyzer-eslint-engine/src/config.ts @@ -36,6 +36,10 @@ export type ESLintEngineConfig = { // Default: false disable_typescript_base_config: boolean + // If true then the base configuration that supplies the React/JSX rules will not be applied. + // Default: true (React support is currently gated; will change to false when released) + disable_react_base_config: boolean + // Extensions of the files in your workspace that will be used to discover rules. // To associate file extensions to the standard ESLint JavaScript rules, LWC rules, or custom JavaScript-based // rules, add them under the 'javascript' language. To associate file extensions to the standard TypeScript @@ -66,9 +70,10 @@ export const DEFAULT_CONFIG: ESLintEngineConfig = { disable_lwc_base_config: false, disable_slds_base_config: false, disable_typescript_base_config: false, + disable_react_base_config: true, // Gated for now - will change to false when released file_extensions: { - javascript: ['.js', '.cjs', '.mjs'], - typescript: ['.ts'], + javascript: ['.js', '.cjs', '.mjs', '.jsx'], + typescript: ['.ts'], // Note: .tsx support planned for next iteration html: ['.html', '.htm', '.cmp'], css: ['.css', '.scss'], other: [] @@ -114,6 +119,8 @@ export const ESLINT_ENGINE_CONFIG_DESCRIPTION: ConfigDescription = { valueType: "boolean", defaultValue: DEFAULT_CONFIG.disable_typescript_base_config }, + // Note: disable_react_base_config is gated and not user-configurable yet + // TODO: Add to fieldDescriptions when React support is released file_extensions: { descriptionText: getMessage('ConfigFieldDescription_file_extensions'), valueType: "object", @@ -136,6 +143,8 @@ export const LEGACY_ESLINT_IGNORE_FILE: string = '.eslintignore'; export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtractor): ESLintEngineConfig { + // Note: disable_react_base_config is intentionally excluded - React support is gated + // TODO: Add 'disable_react_base_config' when React support is released configValueExtractor.validateContainsOnlySpecifiedKeys(['eslint_config_file', 'eslint_ignore_file', 'auto_discover_eslint_config', 'disable_javascript_base_config', 'disable_lwc_base_config', 'disable_slds_base_config', 'disable_typescript_base_config', 'file_extensions']); @@ -150,6 +159,9 @@ export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtr disable_lwc_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_lwc_base_config'), disable_slds_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_slds_base_config'), disable_typescript_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_typescript_base_config'), + // React support is gated - always force to true regardless of customer config + // TODO: Change to eslintConfigValueExtractor.extractBooleanValue('disable_react_base_config') when released + disable_react_base_config: true, file_extensions: eslintConfigValueExtractor.extractFileExtensionsValue(), }; } diff --git a/packages/code-analyzer-eslint-engine/src/messages.ts b/packages/code-analyzer-eslint-engine/src/messages.ts index a5c1e025..0778a35d 100644 --- a/packages/code-analyzer-eslint-engine/src/messages.ts +++ b/packages/code-analyzer-eslint-engine/src/messages.ts @@ -45,6 +45,11 @@ const MESSAGE_CATALOG : { [key: string]: string } = { `The base configuration for TypeScript files adds the rules from the "plugin:@typescript-eslint:all" configuration to Code Analyzer.\n` + `See https://typescript-eslint.io/rules and https://eslint.org/docs/latest/rules for the lists of rules.`, + ConfigFieldDescription_disable_react_base_config: + `Whether to turn off the default base configuration that supplies the React/JSX rules for .jsx and .tsx files\n` + + `The base configuration for React adds the rules from the "eslint-plugin-react" configuration to Code Analyzer.\n` + + `See https://www.npmjs.com/package/eslint-plugin-react for the list of rules.`, + ConfigFieldDescription_file_extensions: `Extensions of the files in your workspace that will be used to discover rules.\n` + `To associate file extensions to the standard ESLint JavaScript rules, LWC rules, or custom JavaScript-based\n` + diff --git a/packages/code-analyzer-eslint-engine/src/missing-types-for-dependencies.d.ts b/packages/code-analyzer-eslint-engine/src/missing-types-for-dependencies.d.ts index 47dd4f42..66fdc688 100644 --- a/packages/code-analyzer-eslint-engine/src/missing-types-for-dependencies.d.ts +++ b/packages/code-analyzer-eslint-engine/src/missing-types-for-dependencies.d.ts @@ -51,3 +51,24 @@ declare module '@salesforce-ux/eslint-plugin-slds' { }; export = plugin; } + +// This declaration adds in the missing types for "eslint-plugin-react" +declare module 'eslint-plugin-react' { + import type { ESLint, Linter } from 'eslint'; + import type { RuleDefinition } from "@eslint/core"; + + const plugin: ESLint.Plugin & { + readonly rules: Record; + readonly configs: { + readonly recommended: Linter.Config; + readonly all: Linter.Config; + readonly "jsx-runtime": Linter.Config; + readonly flat: { + readonly recommended: Linter.Config; + readonly all: Linter.Config; + readonly "jsx-runtime": Linter.Config; + }; + }; + }; + export = plugin; +} diff --git a/packages/code-analyzer-eslint-engine/src/rule-mappings.ts b/packages/code-analyzer-eslint-engine/src/rule-mappings.ts index fd524b9a..1b2c989e 100644 --- a/packages/code-analyzer-eslint-engine/src/rule-mappings.ts +++ b/packages/code-analyzer-eslint-engine/src/rule-mappings.ts @@ -3,6 +3,7 @@ import {COMMON_TAGS, SeverityLevel} from "@salesforce/code-analyzer-engine-api"; // Convenience tag to apply to framework specific rules const LWC = "LWC"; const SLDS = "SLDS"; +const REACT = "React"; /** * The following is a list of the base rules that we have reviewed where we have designated the rule tags and @@ -1692,4 +1693,414 @@ export const RULE_MAPPINGS: Record { @@ -60,8 +63,10 @@ describe('Tests for the describeRules method of ESLintEngine', () => { const TS_CONFIG_RULES: RuleDescription[] = loadRuleDescriptions('rules_OnlyTypeScriptBaseConfig.goldfile.json'); const CSS_CONFIG_RULES: RuleDescription[] = loadRuleDescriptions('rules_OnlySldsCssBaseConfig.goldfile.json'); const HTML_CONFIG_RULES: RuleDescription[] = loadRuleDescriptions('rules_OnlySldsHtmlBaseConfig.goldfile.json'); + const REACT_CONFIG_RULES: RuleDescription[] = loadRuleDescriptions('rules_ReactConfig.goldfile.json'); const SLDS_CONFIG_RULES: RuleDescription[] = makeUniqueAndSorted([...CSS_CONFIG_RULES, ...HTML_CONFIG_RULES]); - const DEFAULT_RULES: RuleDescription[] = makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES]); + // React rules apply to all JS files (not just .jsx) - if no React code, rules simply don't report violations + const DEFAULT_RULES: RuleDescription[] = makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES, ...REACT_CONFIG_RULES]); const CUSTOM_RULES: RuleDescription[] = loadRuleDescriptions('rules_OnlyCustomConfigWithNewRules.goldfile.json'); // The config for workspaceThatHasCustomConfigModifyingExistingRules modifies some rule properties. This does not @@ -73,6 +78,7 @@ describe('Tests for the describeRules method of ESLintEngine', () => { r => ["array-callback-return", "no-unused-vars", "no-useless-backreference"].includes(r.name)); + // React rules now apply to all JS files, so all tests with JS files get React rules type TEST_SCENARIO = {description: string, folder: string, expectationRuleDescriptions: RuleDescription[]}; const testScenarios: TEST_SCENARIO[] = [ { @@ -135,6 +141,7 @@ describe('Tests for the describeRules method of ESLintEngine', () => { path.join(workspaceWithNoCustomConfig, 'dummy3.txt'), path.join(workspaceWithNoCustomConfig, 'dummy2.ts') ]))); + // No JS files means no JS rules and no React rules (React applies to JS files for now, we will add TS support in next iteration) expect(ruleDescriptions).toEqual(TS_CONFIG_RULES); }); @@ -143,7 +150,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions(new Workspace('id', [ path.join(workspaceWithNoCustomConfig, 'dummy1.js'), path.join(workspaceWithNoCustomConfig, 'dummy3.txt')]))); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES])); + // React rules included - applies to .js files + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When describing rules from a workspace with no javascript or typescript files, then no rules should return', async () => { @@ -164,7 +172,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_javascript_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When disable_lwc_base_config=true, then the lwc rules are removed but javascript rules remain', async() => { @@ -172,7 +181,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_lwc_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When disable_slds_base_config=true, then the slds rules are removed but other rules remain', async() => { @@ -180,7 +190,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_slds_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When disable_typescript_base_config=true, then the typescript rules are removed', async() => { @@ -188,47 +199,80 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_typescript_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...SLDS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); - it('When disable_lwc_base_config=true, disable_typescript_base_config=true, and disable_slds_base_config=true, then only base javascript rules remain', async() => { + it('When disable_lwc_base_config=true, disable_typescript_base_config=true, and disable_slds_base_config=true, then only base javascript and react rules remain', async() => { const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, disable_typescript_base_config: true, disable_lwc_base_config: true, disable_slds_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(JS_CONFIG_RULES); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...JS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); - it('When disable_javascript_base_config=true, disable_lwc_base_config=true, and disable_slds_base_config=true, then only base typescript rules remain', async() => { + it('When disable_javascript_base_config=true, disable_lwc_base_config=true, and disable_slds_base_config=true, then typescript and react rules remain', async() => { const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, disable_javascript_base_config: true, disable_lwc_base_config: true, disable_slds_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(TS_CONFIG_RULES); + // React rules included - React is independently controlled, not dependent on JS/LWC config + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...TS_CONFIG_RULES, ...REACT_CONFIG_RULES])); + }); + + it('When disable_javascript_base_config=true, disable_typescript_base_config=true, and and disable_slds_base_config=true, then only base lwc and react rules remain', async() => { + const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, + disable_javascript_base_config: true, + disable_typescript_base_config: true, + disable_slds_base_config: true + }); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...REACT_CONFIG_RULES])); + }); + + it('When disable_react_base_config=true, then the react rules are removed but other rules remain', async() => { + const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, + disable_react_base_config: true + }); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); + }); + + it('When describing rules from a workspace with .jsx files, then React rules are included', async() => { + const engine: Engine = await createEngineFromPlugin(DEFAULT_CONFIG_FOR_TESTING); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions( + new Workspace('id', [workspaceWithReactFiles]))); + // Should include LWC, JS, and React rules (for .js and .jsx files) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); - it('When disable_javascript_base_config=true, disable_typescript_base_config=true, and and disable_slds_base_config=true, then only base lwc rules remain', async() => { + it('When all base configs except react are disabled, then only react rules remain', async() => { const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, disable_javascript_base_config: true, + disable_lwc_base_config: true, disable_typescript_base_config: true, disable_slds_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(LWC_CONFIG_RULES); + // React rules included - React is independently controlled + expect(ruleDescriptions).toEqual(REACT_CONFIG_RULES); }); - it('When disable_javascript_base_config=true, disable_typescript_base_config=true, and and disable_lwc_base_config=true, then only base slds rules remain', async() => { + it('When disable_javascript_base_config=true, disable_typescript_base_config=true, and and disable_lwc_base_config=true, then only slds and react rules remain', async() => { const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, disable_javascript_base_config: true, disable_typescript_base_config: true, disable_lwc_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(SLDS_CONFIG_RULES); + // React rules included - React is independently controlled + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...SLDS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When all *_base_config equal true and no custom config exists, then no rules should exist', async() => { @@ -236,7 +280,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_javascript_base_config: true, disable_typescript_base_config: true, disable_lwc_base_config: true, - disable_slds_base_config: true + disable_slds_base_config: true, + disable_react_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); expect(ruleDescriptions).toHaveLength(0); @@ -250,10 +295,11 @@ describe('Tests for the describeRules method of ESLintEngine', () => { } }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); + // React rules NOT included - React only applies to .jsx which is a JS extension expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); }); - it('When file_extensions.typescript is empty, then javascript rules do not get picked up', async () => { + it('When file_extensions.typescript is empty, then typescript rules do not get picked up', async () => { const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, file_extensions: { ... DEFAULT_CONFIG_FOR_TESTING.file_extensions, @@ -261,7 +307,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { } }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...SLDS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When file_extensions.html is empty, then html rules do not get picked up', async () => { @@ -272,7 +319,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { } }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...CSS_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...CSS_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When file_extensions.css is empty, then css rules do not get picked up', async () => { @@ -283,7 +331,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { } }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); - expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...HTML_CONFIG_RULES])); + // React rules included - no workspace provided means placeholder files used (includes .jsx) + expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...LWC_CONFIG_RULES, ...JS_CONFIG_RULES, ...TS_CONFIG_RULES, ...HTML_CONFIG_RULES, ...REACT_CONFIG_RULES])); }); it('When file_extensions are all empty, then no rules are returned', async () => { @@ -376,7 +425,8 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_javascript_base_config: true, disable_typescript_base_config: true, disable_lwc_base_config: true, - disable_slds_base_config: true + disable_slds_base_config: true, + disable_react_base_config: true }); const logEvents: LogEvent[] = []; engine.onEvent(EventType.LogEvent, (event: LogEvent) => logEvents.push(event)); @@ -390,7 +440,9 @@ describe('Tests for the describeRules method of ESLintEngine', () => { auto_discover_eslint_config: true, disable_javascript_base_config: true, disable_typescript_base_config: true, - disable_lwc_base_config: true + disable_lwc_base_config: true, + disable_slds_base_config: true, + disable_react_base_config: true }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions( new Workspace('id', [path.join(testDataFolder, 'workspaceWithFlatConfigJs', 'dummy1.js')]))); @@ -403,6 +455,7 @@ describe('Tests for the describeRules method of ESLintEngine', () => { }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions( new Workspace('id', [path.join(testDataFolder, 'workspaceWithFlatConfigJs')]))); + // No JS files after ignores means no JS rules and no React rules (React applies to JS files) expect(ruleDescriptions).toEqual(makeUniqueAndSorted([...TS_CONFIG_RULES, ...SLDS_CONFIG_RULES])); }); @@ -429,6 +482,7 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_javascript_base_config: true, disable_typescript_base_config: true, disable_slds_base_config: true, + disable_react_base_config: true, eslint_config_file: path.join(workspaceThatHasCustomConfigWithNewRules, 'eslint-config-only-for-other-files.js') }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions( @@ -443,6 +497,7 @@ describe('Tests for the describeRules method of ESLintEngine', () => { disable_javascript_base_config: true, disable_typescript_base_config: true, disable_slds_base_config: true, + disable_react_base_config: true, eslint_config_file: path.join(workspaceThatHasCustomConfigWithNewRules, 'eslint-config-only-for-other-files.js'), file_extensions:{ ... DEFAULT_CONFIG.file_extensions, @@ -762,6 +817,20 @@ describe('Typical tests for the runRules method of ESLintEngine', () => { message: getMessage('ViolationFoundFromUnregisteredRule', 'oops-rule', JSON.stringify(filteredViolation, null, 2)) }); }); + + it('When runRules is called on workspace with React .jsx files, then React rules report violations', async () => { + const engine: Engine = await createEngineFromPlugin(DEFAULT_CONFIG_FOR_TESTING); + const runOptions: RunOptions = createRunOptions(new Workspace('id', [workspaceWithReactFiles])); + const results: EngineRunResults = await engine.runRules(['react/prop-types'], runOptions); + + // The Button.jsx and App.jsx files should have prop-types violations + expect(results.violations.length).toBeGreaterThan(0); + expect(results.violations.every(v => v.ruleName === 'react/prop-types')).toBe(true); + // Violations should be in .jsx files + const jsxViolations = results.violations.filter(v => + v.codeLocations[0].file.endsWith('.jsx')); + expect(jsxViolations.length).toBeGreaterThan(0); + }); }); describe('Tests for the getEngineVersion method of ESLint Engine', () => { @@ -809,8 +878,9 @@ describe('Tests for emitting events', () => { it('When describeRules is called, then it emits correct progress events', async () => { await engine.describeRules(createDescribeOptions()); // TODO: We should make our DescribeRulesProgressEvents more refined while calculating the eslint context information + // Note: These percentages depend on the number of file extensions configured (includes .jsx) expect(describeRulesProgressEvents.map(e => e.percentComplete)).toEqual( - [0, 10, 14, 18, 24.67, 31.33, 38, 44.67, 51.33, 58, 64.67, 71.33, 78, 82, 86, 90, 95, 100]); + [0, 10, 14, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 82, 86, 90, 95, 100]); }); it('When runRules is called, then it emits correct progress events', async () => { diff --git a/packages/code-analyzer-eslint-engine/test/misc.test.ts b/packages/code-analyzer-eslint-engine/test/misc.test.ts index ea28f040..674fca45 100644 --- a/packages/code-analyzer-eslint-engine/test/misc.test.ts +++ b/packages/code-analyzer-eslint-engine/test/misc.test.ts @@ -8,19 +8,22 @@ import {ESLintWorkspace} from "../src/workspace"; const DEFAULT_CONFIG_FOR_TESTING: ESLintEngineConfig = { ...DEFAULT_CONFIG, - config_root: __dirname + config_root: __dirname, + // React is gated in production (default: true). For testing, enable it (eventual default: false) + disable_react_base_config: false } const testDataFolder: string = path.join(__dirname, 'test-data'); describe("Miscellaneous tests that test sensitive implementation details more directly", () => { - it("Make sure that the ESLint.Options can be stringified to an output that is no larger than 1000 lines", async () => { + it("Make sure that the ESLint.Options can be stringified to an output that is no larger than 1500 lines", async () => { const eslintOptionsFactory: ESLintOptionsFactory = new ESLintOptionsFactory(); const eslintOptions: ESLint.Options = await eslintOptionsFactory.createESLintOptions( DEFAULT_CONFIG_FOR_TESTING, __dirname, undefined); eslintOptions.ruleFilter = () => true; // Doesn't matter const optionsString: string = stringifyESLintOptions(eslintOptions); const numLines: number = optionsString.split('\n').length; - expect(numLines).toBeLessThanOrEqual(1000); + // Increased from 1000 to 1500 to accommodate React config rules + expect(numLines).toBeLessThanOrEqual(1500); // Checking a few substrings as sanity checks: expect(optionsString).toContain('"baseConfig":'); diff --git a/packages/code-analyzer-eslint-engine/test/plugin.test.ts b/packages/code-analyzer-eslint-engine/test/plugin.test.ts index f6654d46..fd653d3b 100644 --- a/packages/code-analyzer-eslint-engine/test/plugin.test.ts +++ b/packages/code-analyzer-eslint-engine/test/plugin.test.ts @@ -47,10 +47,11 @@ describe('Tests for the ESLintEnginePlugin', () => { it('When createEngineConfig is called with an object containing an invalid key, then we error', async () => { const userProvidedOverrides: ConfigObject = {dummy: 3}; await expect(callCreateEngineConfig(plugin, userProvidedOverrides)).rejects.toThrow( + // Note: disable_react_base_config is intentionally excluded - React support is gated getMessageFromCatalog(SHARED_MESSAGE_CATALOG, 'ConfigObjectContainsInvalidKey', 'engines.eslint', 'dummy', '["auto_discover_eslint_config","disable_javascript_base_config","disable_lwc_base_config",' + - '"disable_slds_base_config","disable_typescript_base_config","eslint_config_file","eslint_ignore_file",' + - '"file_extensions"]')); + '"disable_slds_base_config","disable_typescript_base_config",' + + '"eslint_config_file","eslint_ignore_file","file_extensions"]')); }); it('When a valid legacy eslint_config_file is passed to createEngineConfig, then it is set on the config', async () => { diff --git a/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts b/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts index c6673d1e..1e7f5b15 100644 --- a/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts +++ b/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts @@ -7,7 +7,11 @@ import { createDescribeOptions } from "./test-helpers"; describe('Tests for the rule-mappings', () => { it('Test that the list of all bundled rules matches our RULE_MAPPINGS list', async () => { const enginePlugin: ESLintEnginePlugin = new ESLintEnginePlugin(); - const engine: Engine = await enginePlugin.createEngine('eslint', DEFAULT_CONFIG); + // React is gated in production (default: true). For testing, enable it (eventual default: false) + const engine: Engine = await enginePlugin.createEngine('eslint', { + ...DEFAULT_CONFIG, + disable_react_base_config: false + }); const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); const actualRuleNames: Set = new Set(ruleDescriptions.map(rd => rd.name)); const ruleNamesInRuleMappings: Set = new Set(Object.keys(RULE_MAPPINGS)); diff --git a/packages/code-analyzer-eslint-engine/test/test-data/rules_ReactConfig.goldfile.json b/packages/code-analyzer-eslint-engine/test/test-data/rules_ReactConfig.goldfile.json new file mode 100644 index 00000000..f411767e --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/rules_ReactConfig.goldfile.json @@ -0,0 +1,1437 @@ +[ + { + "description": "Enforces consistent naming for boolean props", + "name": "react/boolean-prop-naming", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/boolean-prop-naming.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of `button` elements without an explicit `type` attribute", + "name": "react/button-has-type", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/button-has-type.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce using `onChange` or `readonly` attribute when `checked` is used", + "name": "react/checked-requires-onchange-or-readonly", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/checked-requires-onchange-or-readonly.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce all defaultProps have a corresponding non-required PropType", + "name": "react/default-props-match-prop-types", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/default-props-match-prop-types.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce consistent usage of destructuring assignment of props, state, and context", + "name": "react/destructuring-assignment", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/destructuring-assignment.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow missing displayName in a React component definition", + "name": "react/display-name", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/display-name.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow certain props on components", + "name": "react/forbid-component-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/forbid-component-props.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow certain props on DOM Nodes", + "name": "react/forbid-dom-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/forbid-dom-props.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow certain elements", + "name": "react/forbid-elements", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/forbid-elements.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow using another component's propTypes", + "name": "react/forbid-foreign-prop-types", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/forbid-foreign-prop-types.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow certain propTypes", + "name": "react/forbid-prop-types", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/forbid-prop-types.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Require all forwardRef components include a ref parameter", + "name": "react/forward-ref-uses-ref", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/forward-ref-uses-ref.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce a specific function type for function components", + "name": "react/function-component-definition", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/function-component-definition.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Ensure destructuring and symmetric naming of useState hook value and setter variables", + "name": "react/hook-use-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/hook-use-state.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce sandbox attribute on iframe elements", + "name": "react/iframe-missing-sandbox", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/iframe-missing-sandbox.md" + ], + "severityLevel": 2, + "tags": [ + "React", + "Security", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce boolean attributes notation in JSX", + "name": "react/jsx-boolean-value", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-boolean-value.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions", + "name": "react/jsx-child-element-spacing", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-child-element-spacing.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce closing bracket location in JSX", + "name": "react/jsx-closing-bracket-location", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-closing-bracket-location.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce closing tag location for multiline JSX", + "name": "react/jsx-closing-tag-location", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-closing-tag-location.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes", + "name": "react/jsx-curly-brace-presence", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-curly-brace-presence.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce consistent linebreaks in curly braces in JSX attributes and expressions", + "name": "react/jsx-curly-newline", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-curly-newline.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions", + "name": "react/jsx-curly-spacing", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-curly-spacing.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce or disallow spaces around equal signs in JSX attributes", + "name": "react/jsx-equals-spacing", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-equals-spacing.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow file extensions that may contain JSX", + "name": "react/jsx-filename-extension", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-filename-extension.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce proper position of the first property in JSX", + "name": "react/jsx-first-prop-new-line", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-first-prop-new-line.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce shorthand or standard form for React fragments", + "name": "react/jsx-fragments", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-fragments.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce event handler naming conventions in JSX", + "name": "react/jsx-handler-names", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-handler-names.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce JSX indentation", + "name": "react/jsx-indent", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-indent.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce props indentation in JSX", + "name": "react/jsx-indent-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-indent-props.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow missing `key` props in iterators/collection literals", + "name": "react/jsx-key", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-key.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce JSX maximum depth", + "name": "react/jsx-max-depth", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-max-depth.md" + ], + "severityLevel": 2, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce maximum of props on a single line in JSX", + "name": "react/jsx-max-props-per-line", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-max-props-per-line.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Require or prevent a new line after jsx elements and expressions.", + "name": "react/jsx-newline", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-newline.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow `.bind()` or arrow functions in JSX props", + "name": "react/jsx-no-bind", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-bind.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow comments from being inserted as text nodes", + "name": "react/jsx-no-comment-textnodes", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-comment-textnodes.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallows JSX context provider values from taking values that will cause needless rerenders", + "name": "react/jsx-no-constructed-context-values", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-constructed-context-values.md" + ], + "severityLevel": 2, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow duplicate properties in JSX", + "name": "react/jsx-no-duplicate-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-duplicate-props.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow problematic leaked values from being rendered", + "name": "react/jsx-no-leaked-render", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-leaked-render.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of string literals in JSX", + "name": "react/jsx-no-literals", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-literals.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of `javascript:` URLs", + "name": "react/jsx-no-script-url", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-script-url.md" + ], + "severityLevel": 2, + "tags": [ + "React", + "Security", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow `target=\"_blank\"` attribute without `rel=\"noreferrer\"`", + "name": "react/jsx-no-target-blank", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-target-blank.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "Security", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow undeclared variables in JSX", + "name": "react/jsx-no-undef", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-undef.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow unnecessary fragments", + "name": "react/jsx-no-useless-fragment", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-useless-fragment.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Require one JSX element per line", + "name": "react/jsx-one-expression-per-line", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-one-expression-per-line.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce PascalCase for user-defined JSX components", + "name": "react/jsx-pascal-case", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-pascal-case.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow multiple spaces between inline JSX props", + "name": "react/jsx-props-no-multi-spaces", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-props-no-multi-spaces.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow JSX prop spreading the same identifier multiple times", + "name": "react/jsx-props-no-spread-multi", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-props-no-spread-multi.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow JSX prop spreading", + "name": "react/jsx-props-no-spreading", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-props-no-spreading.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce props alphabetical sorting", + "name": "react/jsx-sort-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-sort-props.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce whitespace in and around the JSX opening and closing brackets", + "name": "react/jsx-tag-spacing", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-tag-spacing.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow React to be incorrectly marked as unused", + "name": "react/jsx-uses-react", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-react.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow variables used in JSX to be incorrectly marked as unused", + "name": "react/jsx-uses-vars", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-vars.md" + ], + "severityLevel": 4, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow missing parentheses around multiline JSX", + "name": "react/jsx-wrap-multilines", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-wrap-multilines.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow when this.state is accessed within setState", + "name": "react/no-access-state-in-setstate", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-access-state-in-setstate.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow adjacent inline elements not separated by whitespace.", + "name": "react/no-adjacent-inline-elements", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-adjacent-inline-elements.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of Array index in keys", + "name": "react/no-array-index-key", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-array-index-key.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Lifecycle methods should be methods on the prototype, not class fields", + "name": "react/no-arrow-function-lifecycle", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-arrow-function-lifecycle.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow passing of children as props", + "name": "react/no-children-prop", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-children-prop.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of dangerous JSX properties", + "name": "react/no-danger", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-danger.md" + ], + "severityLevel": 2, + "tags": [ + "React", + "Security", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow when a DOM element is using both children and dangerouslySetInnerHTML", + "name": "react/no-danger-with-children", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-danger-with-children.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "Security", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of deprecated methods", + "name": "react/no-deprecated", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-deprecated.md" + ], + "severityLevel": 4, + "tags": [ + "Recommended", + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of setState in componentDidMount", + "name": "react/no-did-mount-set-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-did-mount-set-state.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of setState in componentDidUpdate", + "name": "react/no-did-update-set-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-did-update-set-state.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow direct mutation of this.state", + "name": "react/no-direct-mutation-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-direct-mutation-state.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of findDOMNode", + "name": "react/no-find-dom-node", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-find-dom-node.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of invalid attributes", + "name": "react/no-invalid-html-attribute", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-invalid-html-attribute.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of isMounted", + "name": "react/no-is-mounted", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-is-mounted.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow multiple component definition per file", + "name": "react/no-multi-comp", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-multi-comp.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce that namespaces are not used in React elements", + "name": "react/no-namespace", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-namespace.md" + ], + "severityLevel": 2, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of referential-type variables as default param in functional component", + "name": "react/no-object-type-as-default-prop", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-object-type-as-default-prop.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of shouldComponentUpdate when extending React.PureComponent", + "name": "react/no-redundant-should-component-update", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-redundant-should-component-update.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of the return value of ReactDOM.render", + "name": "react/no-render-return-value", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-render-return-value.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of setState", + "name": "react/no-set-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-set-state.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow using string references", + "name": "react/no-string-refs", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-string-refs.md" + ], + "severityLevel": 4, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow `this` from being used in stateless functional components", + "name": "react/no-this-in-sfc", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-this-in-sfc.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow common typos", + "name": "react/no-typos", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-typos.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow unescaped HTML entities from appearing in markup", + "name": "react/no-unescaped-entities", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unescaped-entities.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of unknown DOM property", + "name": "react/no-unknown-property", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unknown-property.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of unsafe lifecycle methods", + "name": "react/no-unsafe", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unsafe.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow creating unstable components inside components", + "name": "react/no-unstable-nested-components", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unstable-nested-components.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow declaring unused methods of component class", + "name": "react/no-unused-class-component-methods", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unused-class-component-methods.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow definitions of unused propTypes", + "name": "react/no-unused-prop-types", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unused-prop-types.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow definitions of unused state", + "name": "react/no-unused-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unused-state.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow usage of setState in componentWillUpdate", + "name": "react/no-will-update-set-state", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-will-update-set-state.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce ES5 or ES6 class for React Components", + "name": "react/prefer-es6-class", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prefer-es6-class.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Prefer exact proptype definitions", + "name": "react/prefer-exact-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prefer-exact-props.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce that props are read-only", + "name": "react/prefer-read-only-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prefer-read-only-props.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce stateless components to be written as a pure function", + "name": "react/prefer-stateless-function", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prefer-stateless-function.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow missing props validation in a React component definition", + "name": "react/prop-types", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prop-types.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow missing React when using JSX", + "name": "react/react-in-jsx-scope", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/react-in-jsx-scope.md" + ], + "severityLevel": 3, + "tags": [ + "Recommended", + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce a defaultProps definition for every prop that is not a required prop", + "name": "react/require-default-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/require-default-props.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce React components to have a shouldComponentUpdate method", + "name": "react/require-optimization", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/require-optimization.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "Performance", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce ES5 or ES6 class for returning value in render function", + "name": "react/require-render-return", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/require-render-return.md" + ], + "severityLevel": 2, + "tags": [ + "Recommended", + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow extra closing tags for components without children", + "name": "react/self-closing-comp", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/self-closing-comp.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce component methods order", + "name": "react/sort-comp", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/sort-comp.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce defaultProps declarations alphabetical sorting", + "name": "react/sort-default-props", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/sort-default-props.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce propTypes declarations alphabetical sorting", + "name": "react/sort-prop-types", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/sort-prop-types.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce class component state initialization style", + "name": "react/state-in-constructor", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/state-in-constructor.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "CodeStyle", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforces where React component static properties should be positioned.", + "name": "react/static-property-placement", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/static-property-placement.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "Design", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Enforce style prop value is an object", + "name": "react/style-prop-object", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/style-prop-object.md" + ], + "severityLevel": 4, + "tags": [ + "React", + "BestPractices", + "JavaScript", + "TypeScript" + ] + }, + { + "description": "Disallow void DOM elements (e.g. ``, `
`) from receiving children", + "name": "react/void-dom-elements-no-children", + "resourceUrls": [ + "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/void-dom-elements-no-children.md" + ], + "severityLevel": 3, + "tags": [ + "React", + "ErrorProne", + "JavaScript", + "TypeScript" + ] + } +] diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/App.jsx b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/App.jsx new file mode 100644 index 00000000..db264c8d --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/App.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import Button from './Button'; + +function App() { + const handleClick = () => { + console.log('Button clicked!'); + }; + + return ( +
+

React App

+
+ ); +} + +export default App; + diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/Button.jsx b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/Button.jsx new file mode 100644 index 00000000..3eca5537 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/Button.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +function Button({ label, onClick }) { + return ( + + ); +} + +export default Button; + diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/utils.js b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/utils.js new file mode 100644 index 00000000..dc02bfdc --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithReactFiles/utils.js @@ -0,0 +1,9 @@ +// Plain JavaScript utility file (no React) +export function formatDate(date) { + return date.toISOString(); +} + +export function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + From a0ed3ae857b0641ced2b17b95cd8e448416eb468 Mon Sep 17 00:00:00 2001 From: Namrata Gupta Date: Tue, 30 Dec 2025 11:46:34 +0530 Subject: [PATCH 2/2] Addressing comments for gating the react enable/disable variable --- package-lock.json | 3 +- .../src/base-config.ts | 29 +++++++++---------- .../code-analyzer-eslint-engine/src/config.ts | 5 ++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 03cd16a4..28a95bbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8428,7 +8428,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9752,7 +9751,7 @@ }, "packages/code-analyzer-eslint-engine": { "name": "@salesforce/code-analyzer-eslint-engine", - "version": "0.38.0", + "version": "0.39.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@babel/preset-react": "^7.27.1", diff --git a/packages/code-analyzer-eslint-engine/src/base-config.ts b/packages/code-analyzer-eslint-engine/src/base-config.ts index 141898b2..91633aa5 100644 --- a/packages/code-analyzer-eslint-engine/src/base-config.ts +++ b/packages/code-analyzer-eslint-engine/src/base-config.ts @@ -65,29 +65,28 @@ export class BaseConfigFactory { private createJavascriptPlusLwcConfigArray(): Linter.Config[] { let configs: Linter.Config[] = validateAndGetRawLwcConfigArray(); - // Deep clone the languageOptions to avoid mutating the original shared config from the LWC package + // Reconstruct languageOptions to avoid mutating the original shared config from the LWC package + // TODO: Remove configFile and sourceType overrides when https://github.com/salesforce/eslint-config-lwc/issues/158 is fixed const originalParserOptions = configs[0].languageOptions!.parserOptions as Linter.ParserOptions; - const clonedBabelOptions = JSON.parse(JSON.stringify(originalParserOptions.babelOptions)); + const originalBabelOptions = originalParserOptions.babelOptions || {}; configs[0].languageOptions = { ...configs[0].languageOptions, parserOptions: { ...originalParserOptions, - babelOptions: clonedBabelOptions + // For some reason babel doesn't like .cjs files unless we explicitly set this to undefined + // because ESLint 9 is setting it to "commonjs" automatically when the field doesn't exist + // in the parserOptions (and for babel "commonjs" isn't a valid option) + sourceType: undefined, + babelOptions: { + ...originalBabelOptions, + // Turn off the babel parser's configFile option from the lwc base plugin + configFile: false, + // Add @babel/preset-react to enable JSX parsing for React/JSX files + presets: [...(originalBabelOptions.presets || []), '@babel/preset-react'] + } } }; - // TODO: Remove the For the following 2 updates when https://github.com/salesforce/eslint-config-lwc/issues/158 is fixed - // 1) Turn off the babel parser's configFile option from the lwc base plugin - clonedBabelOptions.configFile = false; - // 2) For some reason babel doesn't like .cjs files unless we explicitly set this to undefined because I think - // ESLint 9 is setting it to "commonjs" automatically when the field doesn't exist in the parserOptions (and for - // babel "commonjs" isn't a valid option) - (configs[0].languageOptions!.parserOptions as Linter.ParserOptions).sourceType = undefined; - - // 3) Add @babel/preset-react to enable JSX parsing for React/JSX files - const existingPresets = clonedBabelOptions.presets || []; - clonedBabelOptions.presets = [...existingPresets, '@babel/preset-react']; - // Swap out eslintJs.configs.recommended with eslintJs.configs.all configs[1] = eslintJs.configs.all; diff --git a/packages/code-analyzer-eslint-engine/src/config.ts b/packages/code-analyzer-eslint-engine/src/config.ts index 1603abc7..ba9f7da5 100644 --- a/packages/code-analyzer-eslint-engine/src/config.ts +++ b/packages/code-analyzer-eslint-engine/src/config.ts @@ -143,8 +143,9 @@ export const LEGACY_ESLINT_IGNORE_FILE: string = '.eslintignore'; export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtractor): ESLintEngineConfig { - // Note: disable_react_base_config is intentionally excluded - React support is gated - // TODO: Add 'disable_react_base_config' when React support is released + // disable_react_base_config bypasses validation - React support is gated but we need it for internal testing + // TODO: Move 'disable_react_base_config' to validateContainsOnlySpecifiedKeys when React support is released + configValueExtractor.addKeysThatBypassValidation(['disable_react_base_config']); configValueExtractor.validateContainsOnlySpecifiedKeys(['eslint_config_file', 'eslint_ignore_file', 'auto_discover_eslint_config', 'disable_javascript_base_config', 'disable_lwc_base_config', 'disable_slds_base_config', 'disable_typescript_base_config', 'file_extensions']);