diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4cd79ece1..57d22ee42c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,16 +13,18 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`jsx-handler-names`]: support ignoring component names ([#3772][] @akulsr0)
* version settings: Allow react defaultVersion to be configurable ([#3771][] @onlywei)
* [`jsx-closing-tag-location`]: add `line-aligned` option ([#3777] @kimtaejin3)
+* [`no-danger`]: add `customComponentNames` option ([#3748][] @akulsr0)
### Changed
* [Refactor] `variableUtil`: Avoid creating a single flat variable scope for each lookup ([#3782][] @DanielRosenwasser)
-e[#3782]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3782
+[#3782]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3782
[#3777]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3777
[#3774]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3774
[#3772]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3772
[#3771]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3771
[#3759]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3759
+[#3748]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3748
[#3724]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3724
[#3694]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3694
@@ -60,7 +62,7 @@ e[#3782]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3782
### Fixed
* [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi)
-* [`jsx-no-leaked-render`]: invalid report if left side is boolean ([#3746][] @akulsr0)
+* [`jsx-no-leaked-render`]: invalid report if left eside is boolean ([#3746][] @akulsr0)
* [`jsx-closing-bracket-location`]: message shows `{{details}}` when there are no details ([#3759][] @mdjermanovic)
* [`no-invalid-html-attribute`]: ensure error messages are correct ([#3759][] @mdjermanovic, @ljharb)
diff --git a/docs/rules/no-danger.md b/docs/rules/no-danger.md
index 417989e0b2..6ffe8b9a93 100644
--- a/docs/rules/no-danger.md
+++ b/docs/rules/no-danger.md
@@ -24,6 +24,20 @@ var React = require('react');
var Hello =
Hello World
;
```
+## Rule Options
+
+```js
+...
+"react/no-danger": [, {
+ "customComponentNames": Array,
+}]
+...
+```
+
+### customComponentNames
+
+Defaults to `[]`, if you want to enable this rule for all custom components you can pass `customComponentNames` as `['*']`, or else you can pass specific components name to the array.
+
## When Not To Use It
If you are certain the content passed to dangerouslySetInnerHTML is sanitized HTML you can disable this rule.
diff --git a/lib/rules/no-danger.js b/lib/rules/no-danger.js
index eecdd87e23..be5c961e61 100644
--- a/lib/rules/no-danger.js
+++ b/lib/rules/no-danger.js
@@ -7,6 +7,7 @@
const has = require('hasown');
const fromEntries = require('object.fromentries/polyfill')();
+const minimatch = require('minimatch');
const docsUrl = require('../util/docsUrl');
const jsxUtil = require('../util/jsx');
@@ -55,13 +56,32 @@ module.exports = {
messages,
- schema: [],
+ schema: [{
+ type: 'object',
+ properties: {
+ customComponentNames: {
+ items: {
+ type: 'string',
+ },
+ minItems: 0,
+ type: 'array',
+ uniqueItems: true,
+ },
+ },
+ }],
},
create(context) {
+ const configuration = context.options[0] || {};
+ const customComponentNames = configuration.customComponentNames || [];
+
return {
JSXAttribute(node) {
- if (jsxUtil.isDOMComponent(node.parent) && isDangerous(node.name.name)) {
+ const functionName = node.parent.name.name;
+
+ const enableCheckingCustomComponent = customComponentNames.some((name) => minimatch(functionName, name));
+
+ if ((enableCheckingCustomComponent || jsxUtil.isDOMComponent(node.parent)) && isDangerous(node.name.name)) {
report(context, messages.dangerousProp, 'dangerousProp', {
node,
data: {
diff --git a/tests/lib/rules/no-danger.js b/tests/lib/rules/no-danger.js
index 11618c77f7..e354ad28bd 100644
--- a/tests/lib/rules/no-danger.js
+++ b/tests/lib/rules/no-danger.js
@@ -32,6 +32,26 @@ ruleTester.run('no-danger', rule, {
{ code: ';' },
{ code: ';' },
{ code: ';' },
+ {
+ code: ';',
+ options: [{ customComponentNames: ['*'] }],
+ },
+ {
+ code: `
+ function App() {
+ return hello" }} />;
+ }
+ `,
+ options: [{ customComponentNames: ['Home'] }],
+ },
+ {
+ code: `
+ function App() {
+ return hello" }} />;
+ }
+ `,
+ options: [{ customComponentNames: ['MUI*'] }],
+ },
]),
invalid: parsers.all([
{
@@ -43,5 +63,71 @@ ruleTester.run('no-danger', rule, {
},
],
},
+ {
+ code: 'hello" }} />;',
+ options: [{ customComponentNames: ['*'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ function App() {
+ return hello" }} />;
+ }
+ `,
+ options: [{ customComponentNames: ['Title'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ function App() {
+ return hello" }} />;
+ }
+ `,
+ options: [{ customComponentNames: ['*Foo'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ function App() {
+ return hello" }} />;
+ }
+ `,
+ options: [{ customComponentNames: ['Foo*'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ function App() {
+ return hello" }} />;
+ }
+ `,
+ options: [{ customComponentNames: ['*MUI'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
]),
});