Repository: yannickcr/eslint-plugin-react
Pull request #600 (2016-05-14 20:13:22)

Add option to the self-closing-comp
This pr adds an option to the self-closing-comp suggested #572, which is corresponding what type of tags (`{component: <boolean>, html: <boolean>}`) should be self-closed when this is possible. **Note**: this not changes current behavior, default option is `{component: true, html: false}`, but in the next major
release we can change it to `{component: true, html: true}` to enforce all tags to be self-closing when that is possible.




coveralls: 
[![Coverage Status](https://coveralls.io/builds/6183660/badge)](https://coveralls.io/builds/6183660)

Coverage increased (+0.005%) to 96.823% when pulling **700dab4418de356e10617fd4a098f52384ab414c on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/6183668/badge)](https://coveralls.io/builds/6183668)

Coverage increased (+0.005%) to 96.823% when pulling **52f0f8e2d9d14441fafe012ad64b1d787c04d4e1 on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/6183677/badge)](https://coveralls.io/builds/6183677)

Coverage increased (+0.005%) to 96.823% when pulling **702570d36b8a6da504052e1151bf39c0c1cacb79 on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/6186796/badge)](https://coveralls.io/builds/6186796)

Coverage increased (+0.005%) to 96.823% when pulling **2978369c6a57a45597aef0990bd6fe7f2c246974 on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/6186813/badge)](https://coveralls.io/builds/6186813)

Coverage increased (+0.005%) to 96.823% when pulling **d39238136dae53d866febf64183860ac00abdd8b on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/6188416/badge)](https://coveralls.io/builds/6188416)

Coverage increased (+0.005%) to 96.823% when pulling **74b103df9b007f712aa36b111d59617dd0a2b7f6 on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

ljharb: Just to be safe, could we also add tests with the options object omitted?
gitim: > Just to be safe, could we also add tests with the options object omitted?

Yes, sure. Just copied all tests without options and pasted with adding `options: []`.
coveralls: 
[![Coverage Status](https://coveralls.io/builds/6188722/badge)](https://coveralls.io/builds/6188722)

Coverage increased (+0.005%) to 96.823% when pulling **756008b654f5b70a38a2be663cc136bab7f10ebe on gitim:fix-self-closing-comp** into **750f9794f0c6b721cccd52e0c169971e183f7ac6 on yannickcr:master**.

ljharb: LGTM but i'll let @yannickcr do the merging since i'm pretty weak on parser code

Patch:
diff --git a/README.md b/README.md
index abf3161c..9c21a17b 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ The plugin has a [recommended configuration](#user-content-recommended-configura
 * [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state`
 * [no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted`
 * [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
+* [no-render-return-value](docs/rules/no-render-return-value.md): Prevent usage of the return value of `React.render`
 * [no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
 * [no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute.
 * [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable)
@@ -103,6 +104,7 @@ The plugin has a [recommended configuration](#user-content-recommended-configura
 * [jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md): Validate closing bracket location in JSX (fixable)
 * [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes (fixable)
 * [jsx-equals-spacing](docs/rules/jsx-equals-spacing.md): Enforce or disallow spaces around equal signs in JSX attributes (fixable)
+* [jsx-filename-extension](docs/rules/jsx-filename-extension.md): Restrict file extensions that may contain JSX
 * [jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md): Enforce position of the first prop in JSX
 * [jsx-handler-names](docs/rules/jsx-handler-names.md): Enforce event handler naming conventions in JSX
 * [jsx-indent](docs/rules/jsx-indent.md): Validate JSX indentation
diff --git a/docs/rules/jsx-filename-extension.md b/docs/rules/jsx-filename-extension.md
new file mode 100644
index 00000000..ec8c62ec
--- /dev/null
+++ b/docs/rules/jsx-filename-extension.md
@@ -0,0 +1,35 @@
+# Restrict file extensions that may contain JSX (jsx-filename-extension)
+
+## Rule Details
+
+The following pattern is considered a warning:
+
+```jsx
+// filename: MyComponent.js
+function MyComponent() {
+  return <div />;
+}
+```
+
+The following pattern is not considered a warning:
+
+```jsx
+// filename: MyComponent.jsx
+function MyComponent() {
+  return <div />;
+}
+```
+
+## Rule Options
+
+The set of allowed extensions is configurable. By default '.jsx' is allowed. If you wanted to allow both '.jsx' and '.js', the configuration would be:
+
+```js
+"rules": {
+  "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
+}
+```
+
+## When Not To Use It
+
+If you don't care about restricting the file extensions that may contain JSX.
diff --git a/docs/rules/no-render-return-value.md b/docs/rules/no-render-return-value.md
new file mode 100644
index 00000000..d028ec09
--- /dev/null
+++ b/docs/rules/no-render-return-value.md
@@ -0,0 +1,24 @@
+# Prevent usage of the return value of React.render (no-render-return-value)
+
+> `ReactDOM.render()` currently returns a reference to the root `ReactComponent` instance. However, using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the root `ReactComponent` instance, the preferred solution is to attach a [callback ref](http://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) to the root element.
+
+Source: [React Top-Level API documentation](http://facebook.github.io/react/docs/top-level-api.html#reactdom.render)
+
+## Rule Details
+
+This rule will warn you if you try to use the `ReactDOM.render()` return value.
+
+The following pattern is considered warning:
+
+```js
+const inst = ReactDOM.render(<App />, document.body);
+doSomethingWithInst(inst);
+```
+
+The following patterns are not considered warning:
+
+```js
+ReactDOM.render(<App ref={doSomethingWithInst} />, document.body);
+
+ReactDOM.render(<App />, document.body, doSomethingWithInst);
+```
diff --git a/docs/rules/self-closing-comp.md b/docs/rules/self-closing-comp.md
index 3f12ea53..41d1e9dd 100644
--- a/docs/rules/self-closing-comp.md
+++ b/docs/rules/self-closing-comp.md
@@ -17,5 +17,56 @@ var contentContainer = <div className="content"></div>;
 
 var HelloJohn = <Hello name="John" />;
 
-var Profile = <Hello name="John"><img src="picture.png" /></Hello>; 
+var Profile = <Hello name="John"><img src="picture.png" /></Hello>;
+```
+
+## Rule Options
+
+The rule can take one argument to select types of tags, which should be self-closed when this is possible. By default only custom components tags should be self-closed.
+
+```js
+...
+"self-closing-comp": ["error", {
+  "component": true,
+  "html": false
+}]
+...
+```
+
+### `component`
+
+When `true`, custom components tags should be self-closed.
+
+The following patterns are considered warnings:
+
+```js
+var HelloJohn = <Hello name="John"></Hello>;
+```
+
+The following patterns are not considered warnings:
+
+```js
+var contentContainer = <div className="content"></div>;
+
+var HelloJohn = <Hello name="John" />;
+
+var Profile = <Hello name="John"><img src="picture.png" /></Hello>;
+```
+
+### `html`
+
+When `true`, html components tags should be self-closed.
+
+The following patterns are considered warnings:
+
+```js
+var contentContainer = <div className="content"></div>;
+```
+
+The following patterns are not considered warnings:
+
+```js
+var contentContainer = <div className="content" />;
+
+var contentContainer = <div className="content"><div /></div>;
 ```
diff --git a/index.js b/index.js
index 19693475..b8c5cecc 100644
--- a/index.js
+++ b/index.js
@@ -14,6 +14,7 @@ module.exports = {
     'no-deprecated': require('./lib/rules/no-deprecated'),
     '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'),
+    'no-render-return-value': require('./lib/rules/no-render-return-value'),
     'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
     'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
     'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
@@ -44,7 +45,8 @@ module.exports = {
     'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
     'require-render-return': require('./lib/rules/require-render-return'),
     'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line'),
-    'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank')
+    'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
+    'jsx-filename-extension': require('./lib/rules/jsx-filename-extension')
   },
   configs: {
     recommended: {
@@ -66,6 +68,7 @@ module.exports = {
         'react/no-direct-mutation-state': 2,
         'react/no-is-mounted': 2,
         'react/no-unknown-property': 2,
+        'react/no-render-return-value': 2,
         'react/prop-types': 2,
         'react/react-in-jsx-scope': 2
       }
diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js
index 647420b0..f2abb00f 100644
--- a/lib/rules/display-name.js
+++ b/lib/rules/display-name.js
@@ -24,23 +24,25 @@ module.exports = Components.detect(function(context, components, utils) {
    * @returns {Boolean} True if we are declaring a display name, false if not.
    */
   function isDisplayNameDeclaration(node) {
-    // Special case for class properties
-    // (babel-eslint does not expose property name so we have to rely on tokens)
-    if (node.type === 'ClassProperty') {
-      var tokens = sourceCode.getFirstTokens(node, 2);
-      if (
-        tokens[0].value === 'displayName' ||
-        (tokens[1] && tokens[1].value === 'displayName')
-      ) {
-        return true;
-      }
-      return false;
+    switch (node.type) {
+      // Special case for class properties
+      // (babel-eslint does not expose property name so we have to rely on tokens)
+      case 'ClassProperty':
+        var tokens = sourceCode.getFirstTokens(node, 2);
+        if (
+          tokens[0].value === 'displayName' ||
+          (tokens[1] && tokens[1].value === 'displayName')
+        ) {
+          return true;
+        }
+        return false;
+      case 'Identifier':
+        return node.name === 'displayName';
+      case 'Literal':
+        return node.value === 'displayName';
+      default:
+        return false;
     }
-
-    return Boolean(
-      node &&
-      node.name === 'displayName'
-    );
   }
 
   /**
diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js
index 50a9e1f1..8a8f180d 100644
--- a/lib/rules/jsx-closing-bracket-location.js
+++ b/lib/rules/jsx-closing-bracket-location.js
@@ -57,7 +57,7 @@ module.exports = function(context) {
     if (typeof tokens.lastProp === 'undefined') {
       location = 'after-tag';
     // Is always after the last prop if this one is on the same line as the opening bracket
-    } else if (tokens.opening.line === tokens.lastProp.line) {
+    } else if (tokens.opening.line === tokens.lastProp.firstLine) {
       location = 'after-props';
     // Else use configuration dependent on selfClosing property
     } else {
@@ -97,7 +97,7 @@ module.exports = function(context) {
       case 'after-tag':
         return tokens.tag.line === tokens.closing.line;
       case 'after-props':
-        return tokens.lastProp.line === tokens.closing.line;
+        return tokens.lastProp.lastLine === tokens.closing.line;
       case 'props-aligned':
       case 'tag-aligned':
       case 'line-aligned':
@@ -124,7 +124,8 @@ module.exports = function(context) {
       lastProp = node.attributes[node.attributes.length - 1];
       lastProp = {
         column: sourceCode.getFirstToken(lastProp).loc.start.column,
-        line: sourceCode.getLastToken(lastProp).loc.end.line
+        firstLine: sourceCode.getFirstToken(lastProp).loc.start.line,
+        lastLine: sourceCode.getLastToken(lastProp).loc.end.line
       };
     }
     var openingLine = sourceCode.lines[opening.line - 1];
@@ -180,7 +181,7 @@ module.exports = function(context) {
 
       if (correctColumn !== null) {
         expectedNextLine = tokens.lastProp &&
-          (tokens.lastProp.line === tokens.closing.line);
+          (tokens.lastProp.lastLine === tokens.closing.line);
         data.details = ' (expected column ' + (correctColumn + 1) +
           (expectedNextLine ? ' on the next line)' : ')');
       }
diff --git a/lib/rules/jsx-filename-extension.js b/lib/rules/jsx-filename-extension.js
new file mode 100644
index 00000000..6fa7e1dc
--- /dev/null
+++ b/lib/rules/jsx-filename-extension.js
@@ -0,0 +1,68 @@
+/**
+ * @fileoverview Restrict file extensions that may contain JSX
+ * @author Joe Lencioni
+ */
+'use strict';
+
+var path = require('path');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var DEFAULTS = {
+  extensions: ['.jsx']
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+
+  function getExtensionsConfig() {
+    return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    JSXElement: function(node) {
+      var allowedExtensions = getExtensionsConfig();
+      var filename = context.getFilename();
+
+      var isAllowedExtension = allowedExtensions.some(function (extension) {
+        return filename.slice(-extension.length) === extension;
+      });
+
+      if (isAllowedExtension) {
+        return;
+      }
+
+      var extension = path.extname(filename);
+
+      context.report({
+        node: node,
+        message: 'JSX not allowed in files with extension \'' + extension + '\''
+      });
+    }
+  };
+
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    extensions: {
+      type: 'array',
+      items: {
+        type: 'string'
+      }
+    }
+  },
+  additionalProperties: false
+}];
diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js
index cbb0705a..bbf24d1f 100644
--- a/lib/rules/jsx-no-bind.js
+++ b/lib/rules/jsx-no-bind.js
@@ -13,6 +13,31 @@ module.exports = function(context) {
   var configuration = context.options[0] || {};
 
   return {
+    CallExpression: function(node) {
+      var callee = node.callee;
+      if (
+        !configuration.allowBind &&
+        callee.type !== 'MemberExpression' ||
+        callee.property.name !== 'bind'
+      ) {
+        return;
+      }
+      var ancestors = context.getAncestors(callee).reverse();
+      for (var i = 0, j = ancestors.length; i < j; i++) {
+        if (
+          !configuration.allowBind &&
+          (ancestors[i].type === 'MethodDefinition' && ancestors[i].key.name === 'render') ||
+          (ancestors[i].type === 'Property' && ancestors[i].key.name === 'render')
+        ) {
+          context.report({
+            node: callee,
+            message: 'JSX props should not use .bind()'
+          });
+          break;
+        }
+      }
+    },
+
     JSXAttribute: function(node) {
       var isRef = configuration.ignoreRefs && node.name.name === 'ref';
       if (isRef || !node.value || !node.value.expression) {
diff --git a/lib/rules/no-render-return-value.js b/lib/rules/no-render-return-value.js
new file mode 100644
index 00000000..cdc54ddb
--- /dev/null
+++ b/lib/rules/no-render-return-value.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Prevent usage of the return value of React.render
+ * @author Dustan Kasten
+ */
+'use strict';
+
+var versionUtil = require('../util/version');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    CallExpression: function(node) {
+      var callee = node.callee;
+      var parent = node.parent;
+      if (callee.type !== 'MemberExpression') {
+        return;
+      }
+
+      var calleeObjectName = /^ReactDOM$/;
+      if (versionUtil.test(context, '15.0.0')) {
+        calleeObjectName = /^ReactDOM$/;
+      } else if (versionUtil.test(context, '0.14.0')) {
+        calleeObjectName = /^React(DOM)?$/;
+      } else if (versionUtil.test(context, '0.13.0')) {
+        calleeObjectName = /^React$/;
+      }
+
+      if (
+        callee.object.type !== 'Identifier' ||
+        !calleeObjectName.test(callee.object.name) ||
+        callee.property.name !== 'render'
+      ) {
+        return;
+      }
+
+      if (
+        parent.type === 'VariableDeclarator' ||
+        parent.type === 'Property' ||
+        parent.type === 'ReturnStatement' ||
+        parent.type === 'ArrowFunctionExpression'
+      ) {
+        context.report({
+          node: callee,
+          message: 'Do not depend on the return value from ' + callee.object.name + '.render'
+        });
+      }
+    }
+  };
+
+};
+
+module.exports.schema = [];
+
diff --git a/lib/rules/self-closing-comp.js b/lib/rules/self-closing-comp.js
index 39719136..8e4f38c8 100644
--- a/lib/rules/self-closing-comp.js
+++ b/lib/rules/self-closing-comp.js
@@ -30,6 +30,14 @@ module.exports = function(context) {
     return true;
   }
 
+  function isShouldBeSelfClosed(node) {
+    var configuration = context.options[0] || {component: true};
+    return (
+      configuration.component && isComponent(node) ||
+      configuration.html && isTagName(node.name.name)
+    ) && !node.selfClosing && !hasChildren(node);
+  }
+
   // --------------------------------------------------------------------------
   // Public
   // --------------------------------------------------------------------------
@@ -37,7 +45,8 @@ module.exports = function(context) {
   return {
 
     JSXOpeningElement: function(node) {
-      if (!isComponent(node) || node.selfClosing || hasChildren(node)) {
+
+      if (!isShouldBeSelfClosed(node)) {
         return;
       }
       context.report({
@@ -49,4 +58,17 @@ module.exports = function(context) {
 
 };
 
-module.exports.schema = [];
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    component: {
+      default: true,
+      type: 'boolean'
+    },
+    html: {
+      default: false,
+      type: 'boolean'
+    }
+  },
+  additionalProperties: false
+}];
diff --git a/lib/util/Components.js b/lib/util/Components.js
index ca6e3460..f8692f17 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -5,6 +5,7 @@
 'use strict';
 
 var util = require('util');
+var doctrine = require('doctrine');
 var variableUtil = require('./variable');
 var pragmaUtil = require('./pragma');
 
@@ -160,12 +161,41 @@ function componentRule(rule, context) {
      * @returns {Boolean} True if the node is a React ES6 component, false if not
      */
     isES6Component: function(node) {
+      if (utils.isExplicitComponent(node)) {
+        return true;
+      }
+
       if (!node.superClass) {
         return false;
       }
       return new RegExp('^(' + pragma + '\\.)?Component$').test(sourceCode.getText(node.superClass));
     },
 
+    /**
+     * Check if the node is explicitly declared as a descendant of a React Component
+     *
+     * @param {ASTNode} node The AST node being checked (can be a ReturnStatement or an ArrowFunctionExpression).
+     * @returns {Boolean} True if the node is explicitly declared as a descendant of a React Component, false if not
+     */
+    isExplicitComponent: function(node) {
+      var comment = sourceCode.getJSDocComment(node);
+
+      if (comment === null) {
+        return false;
+      }
+
+      var commentAst = doctrine.parse(comment.value, {
+        unwrap: true,
+        tags: ['extends', 'augments']
+      });
+
+      var relevantTags = commentAst.tags.filter(function(tag) {
+        return tag.name === 'React.Component';
+      });
+
+      return relevantTags.length > 0;
+    },
+
     /**
      * Check if the node is returning JSX
      *
diff --git a/package.json b/package.json
index 1eb5b612..08091238 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,9 @@
   },
   "homepage": "https://github.com/yannickcr/eslint-plugin-react",
   "bugs": "https://github.com/yannickcr/eslint-plugin-react/issues",
+  "dependencies": {
+    "doctrine": "^1.2.0"
+  },
   "devDependencies": {
     "babel-eslint": "6.0.4",
     "coveralls": "2.11.9",


Test patch:
diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js
index f2fa89f7..1d831088 100644
--- a/tests/lib/rules/display-name.js
+++ b/tests/lib/rules/display-name.js
@@ -153,6 +153,16 @@ ruleTester.run('display-name', rule, {
       '});'
     ].join('\n'),
     parserOptions: parserOptions
+  }, {
+    code: [
+      'module.exports = React.createClass({',
+      '  "displayName": "Hello",',
+      '  "render": function() {',
+      '    return <div>Hello {this.props.name}</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
   }, {
     code: [
       'var Hello = React.createClass({',
diff --git a/tests/lib/rules/jsx-closing-bracket-location.js b/tests/lib/rules/jsx-closing-bracket-location.js
index e28f2505..1f126947 100644
--- a/tests/lib/rules/jsx-closing-bracket-location.js
+++ b/tests/lib/rules/jsx-closing-bracket-location.js
@@ -186,6 +186,38 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
     ].join('\n'),
     options: [{location: 'line-aligned'}],
     parserOptions: parserOptions
+  }, {
+    code: [
+      '<App foo={function() {',
+      '  console.log(\'bar\');',
+      '}}/>'
+    ].join('\n'),
+    options: [{location: 'after-props'}],
+    parserOptions: parserOptions
+  }, {
+    code: [
+      '<App foo={function() {',
+      '  console.log(\'bar\');',
+      '}}/>'
+    ].join('\n'),
+    options: [{location: 'props-aligned'}],
+    parserOptions: parserOptions
+  }, {
+    code: [
+      '<App foo={function() {',
+      '  console.log(\'bar\');',
+      '}}/>'
+    ].join('\n'),
+    options: [{location: 'tag-aligned'}],
+    parserOptions: parserOptions
+  }, {
+    code: [
+      '<App foo={function() {',
+      '  console.log(\'bar\');',
+      '}}/>'
+    ].join('\n'),
+    options: [{location: 'line-aligned'}],
+    parserOptions: parserOptions
   }, {
     code: [
       '<Provider store>',
diff --git a/tests/lib/rules/jsx-filename-extension.js b/tests/lib/rules/jsx-filename-extension.js
new file mode 100644
index 00000000..f40e3d5c
--- /dev/null
+++ b/tests/lib/rules/jsx-filename-extension.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Restrict file extensions that may contain JSX
+ * @author Joe Lencioni
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/jsx-filename-extension');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Code Snippets
+// ------------------------------------------------------------------------------
+
+var withJSX = 'module.exports = function MyComponent() { return <div />; }';
+var withoutJSX = 'module.exports = {}';
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('jsx-filename-extension', rule, {
+
+  valid: [
+    {
+      filename: 'MyComponent.jsx',
+      code: withJSX,
+      parserOptions: parserOptions
+    }, {
+      filename: 'MyComponent.js',
+      options: [{extensions: ['.js', '.jsx']}],
+      code: withJSX,
+      parserOptions: parserOptions
+    }, {
+      filename: 'notAComponent.js',
+      code: withoutJSX
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'MyComponent.js',
+      code: withJSX,
+      parserOptions: parserOptions,
+      errors: [{message: 'JSX not allowed in files with extension \'.js\''}]
+    }, {
+      filename: 'MyComponent.jsx',
+      code: withJSX,
+      parserOptions: parserOptions,
+      options: [{extensions: ['.js']}],
+      errors: [{message: 'JSX not allowed in files with extension \'.jsx\''}]
+    }
+  ]
+
+});
diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js
index e751ab32..f6e68637 100644
--- a/tests/lib/rules/jsx-no-bind.js
+++ b/tests/lib/rules/jsx-no-bind.js
@@ -83,6 +83,30 @@ ruleTester.run('jsx-no-bind', rule, {
       errors: [{message: 'JSX props should not use .bind()'}],
       parser: 'babel-eslint'
     },
+    {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    const click = this.someMethod.bind(this);',
+        '    return <div onClick={click}>Hello {this.state.name}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
+    {
+      code: [
+        'class Hello23 extends React.Component {',
+        '  render() {',
+        '    const click = this.someMethod.bind(this);',
+        '    return <div onClick={click}>Hello {this.state.name}</div>;',
+        '  }',
+        '};'
+      ].join('\n'),
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
 
     // Arrow functions
     {
diff --git a/tests/lib/rules/no-render-return-value.js b/tests/lib/rules/no-render-return-value.js
new file mode 100644
index 00000000..eb8a5c78
--- /dev/null
+++ b/tests/lib/rules/no-render-return-value.js
@@ -0,0 +1,134 @@
+/**
+ * @fileoverview Prevent usage of setState
+ * @author Mark Dalgleish
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-render-return-value');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-render-return-value', rule, {
+
+  valid: [{
+    code: [
+      'ReactDOM.render(<div />, document.body);'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'let node;',
+      'ReactDOM.render(<div ref={ref => node = ref}/>, document.body);'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: 'ReactDOM.render(<div ref={ref => this.node = ref}/>, document.body);',
+    parserOptions: parserOptions,
+    settings: {
+      react: {
+        version: '0.14.0'
+      }
+    }
+  }, {
+    code: 'React.render(<div ref={ref => this.node = ref}/>, document.body);',
+    parserOptions: parserOptions,
+    settings: {
+      react: {
+        version: '0.14.0'
+      }
+    }
+  }, {
+    code: 'React.render(<div ref={ref => this.node = ref}/>, document.body);',
+    parserOptions: parserOptions,
+    settings: {
+      react: {
+        version: '0.13.0'
+      }
+    }
+  }
+  ],
+
+  invalid: [{
+    code: [
+      'var Hello = ReactDOM.render(<div />, document.body);'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not depend on the return value from ReactDOM.render'
+    }]
+  }, {
+    code: [
+      'var o = {',
+      '  inst: ReactDOM.render(<div />, document.body)',
+      '};'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not depend on the return value from ReactDOM.render'
+    }]
+  }, {
+    code: [
+      'function render () {',
+      '  return ReactDOM.render(<div />, document.body)',
+      '}'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not depend on the return value from ReactDOM.render'
+    }]
+  }, {
+    code: 'var render = (a, b) => ReactDOM.render(a, b)',
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not depend on the return value from ReactDOM.render'
+    }]
+  }, {
+    code: 'var inst = React.render(<div />, document.body);',
+    parserOptions: parserOptions,
+    settings: {
+      react: {
+        version: '0.14.0'
+      }
+    },
+    errors: [{
+      message: 'Do not depend on the return value from React.render'
+    }]
+  }, {
+    code: 'var inst = ReactDOM.render(<div />, document.body);',
+    parserOptions: parserOptions,
+    settings: {
+      react: {
+        version: '0.14.0'
+      }
+    },
+    errors: [{
+      message: 'Do not depend on the return value from ReactDOM.render'
+    }]
+  }, {
+    code: 'var inst = React.render(<div />, document.body);',
+    parserOptions: parserOptions,
+    settings: {
+      react: {
+        version: '0.13.0'
+      }
+    },
+    errors: [{
+      message: 'Do not depend on the return value from React.render'
+    }]
+  }]
+});
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index 8c254e4c..8ab3292b 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -1274,6 +1274,22 @@ ruleTester.run('prop-types', rule, {
         column: 35,
         type: 'Identifier'
       }]
+    }, {
+      code: [
+        '/** @extends React.Component */',
+        'class Hello extends ChildComponent {',
+        '  render() {',
+        '    return <div>Hello {this.props.name}</div>;',
+        '  }',
+        '}'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [{
+        message: '\'name\' is missing in props validation',
+        line: 4,
+        column: 35,
+        type: 'Identifier'
+      }]
     }, {
       code: [
         'class Hello extends React.Component {',
diff --git a/tests/lib/rules/self-closing-comp.js b/tests/lib/rules/self-closing-comp.js
index df3a6828..b038fac8 100644
--- a/tests/lib/rules/self-closing-comp.js
+++ b/tests/lib/rules/self-closing-comp.js
@@ -50,6 +50,64 @@ ruleTester.run('self-closing-comp', rule, {
     }, {
       code: 'var HelloJohn = <Hello name="John">&nbsp;</Hello>;',
       parserOptions: parserOptions
+    }, {
+      code: 'var contentContainer = <div className="content"></div>;',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <Hello name="John" />;',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: 'var Profile = <Hello name="John"><img src="picture.png" /></Hello>;',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: '\
+      <Hello>\
+        <Hello name="John" />\
+      </Hello>',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <div>&nbsp;</div>;',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <div>{\' \'}</div>;',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <Hello name="John">&nbsp;</Hello>;',
+      options: [],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <Hello name="John"></Hello>;',
+      options: [{component: false}],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <Hello name="John">\n</Hello>;',
+      options: [{component: false}],
+      parserOptions: parserOptions
+    }, {
+      code: 'var HelloJohn = <Hello name="John"> </Hello>;',
+      options: [{component: false}],
+      parserOptions: parserOptions
+    }, {
+      code: 'var contentContainer = <div className="content" />;',
+      options: [{html: true}],
+      parserOptions: parserOptions
+    }, {
+      code: 'var contentContainer = <div className="content"><img src="picture.png" /></div>;',
+      options: [{html: true}],
+      parserOptions: parserOptions
+    }, {
+      code: '\
+      <div>\
+        <div className="content" />\
+      </div>',
+      options: [{html: true}],
+      parserOptions: parserOptions
     }
   ],
 
@@ -72,6 +130,49 @@ ruleTester.run('self-closing-comp', rule, {
       errors: [{
         message: 'Empty components are self-closing'
       }]
+    },
+    {
+      code: 'var HelloJohn = <Hello name="John"></Hello>;',
+      options: [],
+      parserOptions: parserOptions,
+      errors: [{
+        message: 'Empty components are self-closing'
+      }]
+    }, {
+      code: 'var HelloJohn = <Hello name="John">\n</Hello>;',
+      options: [],
+      parserOptions: parserOptions,
+      errors: [{
+        message: 'Empty components are self-closing'
+      }]
+    }, {
+      code: 'var HelloJohn = <Hello name="John"> </Hello>;',
+      options: [],
+      parserOptions: parserOptions,
+      errors: [{
+        message: 'Empty components are self-closing'
+      }]
+    }, {
+      code: 'var contentContainer = <div className="content"></div>;',
+      options: [{html: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: 'Empty components are self-closing'
+      }]
+    }, {
+      code: 'var contentContainer = <div className="content">\n</div>;',
+      options: [{html: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: 'Empty components are self-closing'
+      }]
+    }, {
+      code: 'var contentContainer = <div className="content"> </div>;',
+      options: [{html: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: 'Empty components are self-closing'
+      }]
     }
   ]
 });