diff --git a/README.md b/README.md index 46a9d595fb..163a8c71ea 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](# * [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes * [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties +* [react/no-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML * [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods * [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount` * [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate` diff --git a/docs/rules/no-danger-with-children.md b/docs/rules/no-danger-with-children.md new file mode 100644 index 0000000000..bb21a035dc --- /dev/null +++ b/docs/rules/no-danger-with-children.md @@ -0,0 +1,53 @@ +# Prevent problem with children and props.dangerouslySetInnerHTML (no-danger-with-children) + +This rule helps prevent problems caused by using children and the dangerouslySetInnerHTML prop at the same time. +React will throw a warning if this rule is ignored. + +## Rule Details + +The following patterns are considered warnings: + +```jsx +
+ Children +
+ + + Children + + +``` + +```js +React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } }, "Children"); + +React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } }, "Children"); +``` + +The following patterns are not considered warnings: + +```jsx +
+ + + +
+ Children +
+ + + Children + + +``` + +```js +React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } }); + +React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } }); + +React.createElement("div", {}, "Children"); + +React.createElement("Hello", {}, "Children"); +``` + diff --git a/index.js b/index.js index 0a25847325..88ab4dcb6c 100644 --- a/index.js +++ b/index.js @@ -53,7 +53,8 @@ var rules = { 'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'), 'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'), 'require-optimization': require('./lib/rules/require-optimization'), - 'no-find-dom-node': require('./lib/rules/no-find-dom-node') + 'no-find-dom-node': require('./lib/rules/no-find-dom-node'), + 'no-danger-with-children': require('./lib/rules/no-danger-with-children') }; var ruleNames = Object.keys(rules); diff --git a/lib/rules/no-danger-with-children.js b/lib/rules/no-danger-with-children.js new file mode 100644 index 0000000000..c9deb368cd --- /dev/null +++ b/lib/rules/no-danger-with-children.js @@ -0,0 +1,80 @@ +/** + * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML + * @author David Petersen + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ +module.exports = { + meta: { + docs: { + description: 'Report when a DOM element is using both children and dangerouslySetInnerHTML', + category: '', + recommended: false + }, + schema: [] // no options + }, + create: function(context) { + return { + JSXElement: function (node) { + var hasChildren = false; + var attributes = node.openingElement.attributes; + + if (node.children.length) { + hasChildren = true; + } else { + var childrenProp = attributes.find(function (attribute) { + return attribute.name.name === 'children'; + }); + if (childrenProp) { + hasChildren = true; + } + } + + if (attributes && hasChildren) { + var jsxElement = attributes.find(function (attribute) { + return attribute.name.name === 'dangerouslySetInnerHTML'; + }); + + + if (jsxElement) { + context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`'); + } + } + }, + CallExpression: function (node) { + if ( + node.callee + && node.callee.type === 'MemberExpression' + && node.callee.property.name === 'createElement' + && node.arguments.length > 1 + ) { + var hasChildren = false; + + var props = node.arguments[1].properties; + var dangerously = props.find(function(prop) { + return prop.key.name === 'dangerouslySetInnerHTML'; + }); + + + if (node.arguments.length === 2) { + var childrenProp = props.find(function(prop) { + return prop.key.name === 'children'; + }); + if (childrenProp) { + hasChildren = true; + } + } else { + hasChildren = true; + } + + if (dangerously && hasChildren) { + context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`'); + } + } + } + }; + } +}; diff --git a/tests/lib/rules/no-danger-with-children.js b/tests/lib/rules/no-danger-with-children.js new file mode 100644 index 0000000000..a44124bc50 --- /dev/null +++ b/tests/lib/rules/no-danger-with-children.js @@ -0,0 +1,139 @@ +/** + * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML + * @author David Petersen + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-danger-with-children'); +var RuleTester = require('eslint').RuleTester; + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('no-danger-with-children', rule, { + valid: [ + { + code: '
Children
', + parserOptions: parserOptions + }, + { + code: '
', + parserOptions: parserOptions + }, + { + code: 'Children', + parserOptions: parserOptions + }, + { + code: '', + parserOptions: parserOptions + }, + { + code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } });', + parserOptions: parserOptions + }, + { + code: 'React.createElement("div", {}, "Children");', + parserOptions: parserOptions + }, + { + code: 'React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } });', + parserOptions: parserOptions + }, + { + code: 'React.createElement("Hello", {}, "Children");', + parserOptions: parserOptions + } + ], + invalid: [ + { + code: [ + '
', + ' Children', + '
' + ].join('\n'), + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: '
', + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: [ + '', + ' Children', + '' + ].join('\n'), + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: '', + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: [ + 'React.createElement(', + ' "div",', + ' { dangerouslySetInnerHTML: { __html: "HTML" } },', + ' "Children"', + ');' + ].join('\n'), + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: [ + 'React.createElement(', + ' "div",', + ' {', + ' dangerouslySetInnerHTML: { __html: "HTML" },', + ' children: "Children",', + ' }', + ');' + ].join('\n'), + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: [ + 'React.createElement(', + ' "Hello",', + ' { dangerouslySetInnerHTML: { __html: "HTML" } },', + ' "Children"', + ');' + ].join('\n'), + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + }, + { + code: [ + 'React.createElement(', + ' "Hello",', + ' {', + ' dangerouslySetInnerHTML: { __html: "HTML" },', + ' children: "Children",', + ' }', + ');' + ].join('\n'), + errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}], + parserOptions: parserOptions + } + ] +});