From 3db61067fe36f519e0ebdc1f3bd829d41df4d976 Mon Sep 17 00:00:00 2001 From: akulsr0 Date: Mon, 6 May 2024 12:05:19 +0530 Subject: [PATCH] [New] `no-danger`: add `customComponentNames` option --- CHANGELOG.md | 6 ++- docs/rules/no-danger.md | 14 ++++++ lib/rules/no-danger.js | 24 +++++++++- tests/lib/rules/no-danger.js | 86 ++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bccfaa25..1b031c0c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,20 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Added +* [`no-danger`]: add `customComponentNames` option ([#3748][] @akulsr0) + ### Fixed * [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv) [#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 +[#3748]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3748 ## [7.34.2] - 2024.05.24 ### 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 1cb0273739..3cb688b275 100644 --- a/lib/rules/no-danger.js +++ b/lib/rules/no-danger.js @@ -7,6 +7,7 @@ const has = require('object.hasown/polyfill')(); 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 a6f18630d5..86d054b64b 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' }, + }, + ], + }, ]), });