Repository: yannickcr/eslint-plugin-react
Pull request #138 (2015-07-01 18:30:08)

Prevent usage of dangerous JSX properties (no-danger)
Dangerous properties in React are those whose behavior is known to be a
common source of application vulnerabilities. The properties names
clearly indicate they are dangerous and should be avoided unless great
care is taken.

See https://facebook.github.io/react/tips/dangerously-set-inner-html.html

The following patterns are considered warnings:

```jsx
var Hello = <div dangerouslySetInnerHTML={{ __html: "Hello World" }}></div>;
```

The following patterns are not considered warnings:

```jsx
var Hello = <div>Hello World</div>;
```


Patch:
diff --git a/History.md b/History.md
index 55c2a1c4..43e1e1ef 100644
--- a/History.md
+++ b/History.md
@@ -1,10 +1,3 @@
-2.6.4 / 2015-07-02
-==================
-
-* fix simple destructuring handling ([#137][])
-
-[#137]: https://github.com/yannickcr/eslint-plugin-react/issues/137
-
 2.6.3 / 2015-06-30
 ==================
 
diff --git a/docs/rules/no-danger.md b/docs/rules/no-danger.md
new file mode 100644
index 00000000..3a9aa6f2
--- /dev/null
+++ b/docs/rules/no-danger.md
@@ -0,0 +1,27 @@
+# Prevent usage of dangerous JSX properties (no-danger)
+
+Dangerous properties in React are those whose behavior is known to be a common source of application vulnerabilities. The properties names clearly indicate they are dangerous and should be avoided unless great care is taken.
+
+See https://facebook.github.io/react/tips/dangerously-set-inner-html.html
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+var React = require('react');
+
+var Hello = <div dangerouslySetInnerHTML={{ __html: "Hello World" }}></div>;
+```
+
+The following patterns are not considered warnings:
+
+```js
+var React = require('react');
+
+var Hello = <div>Hello World</div>;
+```
+
+## 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/index.js b/index.js
index 71ee48e8..f642eca6 100644
--- a/index.js
+++ b/index.js
@@ -8,6 +8,7 @@ module.exports = {
     'display-name': require('./lib/rules/display-name'),
     'wrap-multilines': require('./lib/rules/wrap-multilines'),
     'self-closing-comp': require('./lib/rules/self-closing-comp'),
+    'no-danger': require('./lib/rules/no-danger'),
     'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
     'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
     'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
@@ -28,6 +29,7 @@ module.exports = {
     'display-name': 0,
     'wrap-multilines': 0,
     'self-closing-comp': 0,
+    'no-danger': 0,
     'no-did-mount-set-state': 0,
     'no-did-update-set-state': 0,
     'react-in-jsx-scope': 0,
diff --git a/lib/rules/no-danger.js b/lib/rules/no-danger.js
new file mode 100644
index 00000000..2f60dd85
--- /dev/null
+++ b/lib/rules/no-danger.js
@@ -0,0 +1,65 @@
+/**
+ * @fileoverview Prevent usage of dangerous JSX props
+ * @author Scott Andrews
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var DANGEROUS_MESSAGE = 'Dangerous property \'{{name}}\' found';
+
+var DANGEROUS_PROPERTY_NAMES = [
+  'dangerouslySetInnerHTML'
+];
+
+var DANGEROUS_PROPERTIES = DANGEROUS_PROPERTY_NAMES.reduce(function (props, prop) {
+  props[prop] = prop;
+  return props;
+}, Object.create(null));
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Checks if a node name match the JSX tag convention.
+ * @param {String} name - Name of the node to check.
+ * @returns {boolean} Whether or not the node name match the JSX tag convention.
+ */
+var tagConvention = /^[a-z]|\-/;
+function isTagName(name) {
+  return tagConvention.test(name);
+}
+
+/**
+ * Checks if a JSX attribute is dangerous.
+ * @param {String} name - Name of the attribute to check.
+ * @returns {boolean} Whether or not the attribute is dnagerous.
+ */
+function isDangerous(name) {
+  return name in DANGEROUS_PROPERTIES;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  return {
+
+    JSXAttribute: function(node) {
+      if (isTagName(node.parent.name.name) && isDangerous(node.name.name)) {
+        context.report(node, DANGEROUS_MESSAGE, {
+          name: node.name.name
+        });
+      }
+    }
+
+  };
+
+};
+
+module.exports.schema = [];
diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js
index 650f4752..658f50fa 100644
--- a/lib/rules/prop-types.js
+++ b/lib/rules/prop-types.js
@@ -385,6 +385,8 @@ module.exports = function(context) {
         }
         break;
       case 'VariableDeclarator':
+        type = 'destructuring';
+
         for (var i = 0, j = node.id.properties.length; i < j; i++) {
           if (
             (node.id.properties[i].key.name !== 'props' && node.id.properties[i].key.value !== 'props') ||
@@ -392,7 +394,6 @@ module.exports = function(context) {
           ) {
             continue;
           }
-          type = 'destructuring';
           properties = node.id.properties[i].value.properties;
           break;
         }
diff --git a/package.json b/package.json
index 3a6eba59..e41a3419 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-react",
-  "version": "2.6.4",
+  "version": "2.6.3",
   "author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
   "description": "React specific linting rules for ESLint",
   "main": "index.js",


Test patch:
diff --git a/tests/lib/rules/no-danger.js b/tests/lib/rules/no-danger.js
new file mode 100644
index 00000000..e76a666e
--- /dev/null
+++ b/tests/lib/rules/no-danger.js
@@ -0,0 +1,32 @@
+/**
+ * @fileoverview Tests for no-danger
+ * @author Scott Andrews
+ */
+
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint-tester');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+eslintTester.addRuleTest('lib/rules/no-danger', {
+  valid: [
+    {code: '<App />;', ecmaFeatures: {jsx: true}},
+    {code: '<App dangerouslySetInnerHTML={{ __html: "" }} />;', ecmaFeatures: {jsx: true}},
+    {code: '<div className="bar"></div>;', ecmaFeatures: {jsx: true}}
+  ],
+  invalid: [{
+    code: '<div dangerouslySetInnerHTML={{ __html: "" }}></div>;',
+    errors: [{message: 'Dangerous property \'dangerouslySetInnerHTML\' found'}],
+    ecmaFeatures: {jsx: true}
+   }
+  ]
+});
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index 0da4cc38..bdf90735 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -538,16 +538,6 @@ eslintTester.addRuleTest('lib/rules/prop-types', {
         '};'
       ].join('\n'),
       parser: 'babel-eslint'
-    }, {
-      code: [
-        'class Hello extends React.Component {',
-        '  render() {',
-        '    let {firstname} = this;',
-        '    return <div>{firstname}</div>;',
-        '  }',
-        '};'
-      ].join('\n'),
-      parser: 'babel-eslint'
     }
   ],