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</span>" }} />; + } + `, + options: [{ customComponentNames: ['Home'] }], + }, + { + code: ` + function App() { + return <TextMUI dangerouslySetInnerHTML={{ __html: "<span>hello</span>" }} />; + } + `, + options: [{ customComponentNames: ['MUI*'] }], + }, ]), invalid: parsers.all([ { @@ -43,5 +63,71 @@ ruleTester.run('no-danger', rule, { }, ], }, + { + code: '<App dangerouslySetInnerHTML={{ __html: "<span>hello</span>" }} />;', + options: [{ customComponentNames: ['*'] }], + errors: [ + { + messageId: 'dangerousProp', + data: { name: 'dangerouslySetInnerHTML' }, + }, + ], + }, + { + code: ` + function App() { + return <Title dangerouslySetInnerHTML={{ __html: "<span>hello</span>" }} />; + } + `, + options: [{ customComponentNames: ['Title'] }], + errors: [ + { + messageId: 'dangerousProp', + data: { name: 'dangerouslySetInnerHTML' }, + }, + ], + }, + { + code: ` + function App() { + return <TextFoo dangerouslySetInnerHTML={{ __html: "<span>hello</span>" }} />; + } + `, + options: [{ customComponentNames: ['*Foo'] }], + errors: [ + { + messageId: 'dangerousProp', + data: { name: 'dangerouslySetInnerHTML' }, + }, + ], + }, + { + code: ` + function App() { + return <FooText dangerouslySetInnerHTML={{ __html: "<span>hello</span>" }} />; + } + `, + options: [{ customComponentNames: ['Foo*'] }], + errors: [ + { + messageId: 'dangerousProp', + data: { name: 'dangerouslySetInnerHTML' }, + }, + ], + }, + { + code: ` + function App() { + return <TextMUI dangerouslySetInnerHTML={{ __html: "<span>hello</span>" }} />; + } + `, + options: [{ customComponentNames: ['*MUI'] }], + errors: [ + { + messageId: 'dangerousProp', + data: { name: 'dangerouslySetInnerHTML' }, + }, + ], + }, ]), });