diff --git a/docs/rules/no-danger.md b/docs/rules/no-danger.md
index 04c664ba88..7c79000138 100644
--- a/docs/rules/no-danger.md
+++ b/docs/rules/no-danger.md
@@ -18,6 +18,10 @@ var React = require('react');
var Hello =
;
```
+```js
+React.createElement('div', { dangerouslySetInnerHTML: { __html: 'Hello World' } });
+```
+
Examples of **correct** code for this rule:
```jsx
@@ -26,6 +30,10 @@ var React = require('react');
var Hello = Hello World
;
```
+```js
+React.createElement('div', { className: 'bar' });
+```
+
## Rule Options
```js
diff --git a/lib/rules/no-danger.js b/lib/rules/no-danger.js
index 54dbed49cf..25d62a4a1a 100644
--- a/lib/rules/no-danger.js
+++ b/lib/rules/no-danger.js
@@ -12,6 +12,9 @@ const minimatch = require('minimatch');
const docsUrl = require('../util/docsUrl');
const jsxUtil = require('../util/jsx');
const report = require('../util/report');
+const variableUtil = require('../util/variable');
+const getText = require('../util/eslint').getText;
+const isCreateElement = require('../util/isCreateElement');
// ------------------------------------------------------------------------------
// Constants
@@ -75,6 +78,44 @@ module.exports = {
const configuration = context.options[0] || {};
const customComponentNames = configuration.customComponentNames || [];
+ function findSpreadVariable(node, name) {
+ return variableUtil.getVariableFromContext(context, node, name);
+ }
+
+ function findDangerousProperty(node, seenProps) {
+ if (!node) {
+ return false;
+ }
+ if (node.type === 'Identifier') {
+ const variable = findSpreadVariable(node, node.name);
+ if (variable && variable.defs.length && variable.defs[0].node.init) {
+ return findDangerousProperty(variable.defs[0].node.init, seenProps);
+ }
+ }
+ if (!node.properties) {
+ return false;
+ }
+ return node.properties.find((prop) => {
+ if (prop.type === 'Property') {
+ const name = prop.key.type === 'Identifier' ? prop.key.name : prop.key.value;
+ return prop.key && !prop.computed && isDangerous(name);
+ }
+ if (prop.type === 'ExperimentalSpreadProperty' || prop.type === 'SpreadElement') {
+ if (prop.argument && prop.argument.type === 'Identifier') {
+ const variable = findSpreadVariable(node, prop.argument.name);
+ if (variable && variable.defs.length && variable.defs[0].node.init) {
+ if (seenProps.indexOf(prop.argument.name) > -1) {
+ return false;
+ }
+ const newSeenProps = seenProps.concat(prop.argument.name || []);
+ return findDangerousProperty(variable.defs[0].node.init, newSeenProps);
+ }
+ }
+ }
+ return false;
+ });
+ }
+
return {
JSXAttribute(node) {
const nodeName = node.parent.name;
@@ -91,6 +132,40 @@ module.exports = {
});
}
},
+
+ CallExpression(node) {
+ if (
+ isCreateElement(context, node)
+ && node.arguments.length > 0
+ ) {
+ const typeNode = node.arguments[0];
+ let isDOMComponent = false;
+ let elementName = '';
+
+ if (typeNode.type === 'Literal' && typeof typeNode.value === 'string') {
+ elementName = typeNode.value;
+ isDOMComponent = /^[a-z]/.test(elementName);
+ } else if (typeNode.type === 'Identifier') {
+ elementName = typeNode.name;
+ } else if (typeNode.type === 'MemberExpression') {
+ elementName = getText(context, typeNode);
+ }
+
+ const enableCheckingCustomComponent = customComponentNames.some((name) => minimatch(elementName, name));
+
+ if (enableCheckingCustomComponent || isDOMComponent) {
+ const dangerously = findDangerousProperty(node.arguments[1], []);
+ if (dangerously) {
+ report(context, messages.dangerousProp, 'dangerousProp', {
+ node: dangerously,
+ data: {
+ name: dangerously.key.type === 'Identifier' ? dangerously.key.name : dangerously.key.value,
+ },
+ });
+ }
+ }
+ }
+ },
};
},
};
diff --git a/tests/lib/rules/no-danger.js b/tests/lib/rules/no-danger.js
index 9403a220bf..6cdceb96f4 100644
--- a/tests/lib/rules/no-danger.js
+++ b/tests/lib/rules/no-danger.js
@@ -52,6 +52,20 @@ ruleTester.run('no-danger', rule, {
`,
options: [{ customComponentNames: ['MUI*'] }],
},
+ {
+ code: 'React.createElement("div", { className: "bar" });',
+ },
+ {
+ code: 'React.createElement(App, { dangerouslySetInnerHTML: { __html: "" } });',
+ },
+ {
+ code: `
+ function App() {
+ return React.createElement(Title, { dangerouslySetInnerHTML: { __html: "hello" } });
+ }
+ `,
+ options: [{ customComponentNames: ['Home'] }],
+ },
]),
invalid: parsers.all([
{
@@ -173,5 +187,72 @@ ruleTester.run('no-danger', rule, {
},
],
},
+ {
+ code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "" } });',
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: 'React.createElement("div", { "dangerouslySetInnerHTML": { __html: "" } });',
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: 'React.createElement(App, { dangerouslySetInnerHTML: { __html: "" } });',
+ options: [{ customComponentNames: ['*'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ function App() {
+ return React.createElement(Title, { dangerouslySetInnerHTML: { __html: "hello" } });
+ }
+ `,
+ options: [{ customComponentNames: ['Title'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ const props = { dangerouslySetInnerHTML: { __html: "" } };
+ React.createElement("div", props);
+ `,
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
+ {
+ code: `
+ const props = { dangerouslySetInnerHTML: { __html: "" } };
+ React.createElement(App, props);
+ `,
+ options: [{ customComponentNames: ['*'] }],
+ errors: [
+ {
+ messageId: 'dangerousProp',
+ data: { name: 'dangerouslySetInnerHTML' },
+ },
+ ],
+ },
]),
});