diff --git a/package-lock.json b/package-lock.json
index cac62702..28a95bbf 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",
@@ -8058,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"
},
@@ -8316,7 +8685,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"
@@ -9383,9 +9751,10 @@
},
"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",
"@eslint/js": "^9.39.2",
"@lwc/eslint-plugin-lwc": "^3.3.0",
"@lwc/eslint-plugin-lwc-platform": "^6.3.0",
@@ -9400,6 +9769,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 +9833,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 +10023,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 +10047,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 +10106,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 +10425,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 +10500,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 +10609,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 +10643,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..91633aa5 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,19 +55,37 @@ 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();
- // 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;
- // 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;
+ // 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 originalBabelOptions = originalParserOptions.babelOptions || {};
+ configs[0].languageOptions = {
+ ...configs[0].languageOptions,
+ parserOptions: {
+ ...originalParserOptions,
+ // 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']
+ }
+ }
+ };
// Swap out eslintJs.configs.recommended with eslintJs.configs.all
configs[1] = eslintJs.configs.all;
@@ -115,7 +134,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 +186,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 +239,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..ba9f7da5 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,9 @@ export const LEGACY_ESLINT_IGNORE_FILE: string = '.eslintignore';
export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtractor): ESLintEngineConfig {
+ // 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']);
@@ -150,6 +160,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);
+}
+