Repository: yannickcr/eslint-plugin-react
Pull request #6 (2015-03-01 16:47:19)

Add jsx-uses-react rule
See eslint/eslint#1905 for additional context.

This rule somewhat unusual, in that it modifies the behaviour of a core eslint rule.

I'm not hugely keen on this, but it does work. It is possible for the rule to be broken by changes in eslint core.

The alternative would be to copy-paste the original rule and modify it.
That would require users to disable the core rule and replace it with this one - and then any improvements to the core rule would have to be tracked here and updated.

Of those two choices, I think the first one is the most practical, even if it breaks the usual clean separation of rules.


Patch:
diff --git a/docs/rules/jsx-uses-react.md b/docs/rules/jsx-uses-react.md
new file mode 100644
index 00000000..1002eda8
--- /dev/null
+++ b/docs/rules/jsx-uses-react.md
@@ -0,0 +1,29 @@
+# Make JSX count towards use of a declared variable (jsx-uses-react)
+
+JSX expands to a call to `React.createElement`, a file which includes `React`
+but only uses JSX should still consider the `React` variable used.
+
+This rule has no effect if the `no-unused-vars` rule is not enabled.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+var React = require('react'); // and other equivalent imports
+
+// nothing to do with React
+```
+
+The following patterns are not considered warnings:
+
+```js
+var React = require('react');
+
+var elem = <div>Some Stuff</div>;
+```
+
+## When Not To Use It
+
+If you are not using JSX, or React is delcared as global variable, this rule
+will not be useful.
diff --git a/index.js b/index.js
index 83b40b8a..969126b1 100644
--- a/index.js
+++ b/index.js
@@ -2,6 +2,7 @@
 
 module.exports = {
   rules: {
+    'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
     'no-multi-comp': require('./lib/rules/no-multi-comp'),
     'prop-types': require('./lib/rules/prop-types'),
     'display-name': require('./lib/rules/display-name'),
@@ -11,6 +12,7 @@ module.exports = {
     'no-did-update-set-state': require('./lib/rules/no-did-update-set-state')
   },
   rulesConfig: {
+    'jsx-uses-react': 0,
     'no-multi-comp': 0,
     'prop-types': 0,
     'display-name': 0,
diff --git a/lib/rules/jsx-uses-react.js b/lib/rules/jsx-uses-react.js
new file mode 100644
index 00000000..9202c724
--- /dev/null
+++ b/lib/rules/jsx-uses-react.js
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Count <Jsx /> as use of the React variable
+ * @author Glen Mailer
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  function flagReactAsUsedInJSX() {
+    var scope = context.getScope(),
+      variables = scope.variables,
+      i,
+      len;
+
+    while (scope.type !== "global") {
+      scope = scope.upper;
+      variables = [].concat.apply(scope.variables, variables);
+    }
+
+    // mark first React found with the same special flag used by no-unused-vars
+    for (i = 0, len = variables.length; i < len; i++) {
+      if (variables[i].name === 'React') {
+        variables[i].eslintJSXUsed = true;
+        return;
+      }
+    }
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    'JSXOpeningElement': function() {
+      flagReactAsUsedInJSX();
+    }
+  };
+
+};


Test patch:
diff --git a/tests/lib/rules/jsx-uses-react.js b/tests/lib/rules/jsx-uses-react.js
new file mode 100644
index 00000000..cc424bcb
--- /dev/null
+++ b/tests/lib/rules/jsx-uses-react.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Tests for jsx-uses-react
+ * @author Glen Mailer
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint-tester');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+eslint.defineRule('jsx-uses-react', require('../../../lib/rules/jsx-uses-react'));
+eslintTester.addRuleTest("node_modules/eslint/lib/rules/no-unused-vars", {
+    valid: [
+        {code: "/*eslint jsx-uses-react:1*/ var App, React; <App />;", ecmaFeatures: {jsx: true}},
+        {code: "/*eslint jsx-uses-react:1*/ var React; <div />;", ecmaFeatures: {jsx: true}},
+        {code: "/*eslint jsx-uses-react:1*/ var React; (function () { <div /> })();", ecmaFeatures: {jsx: true}}
+    ],
+    invalid: [
+        {code: "/*eslint jsx-uses-react:1*/ var React;",
+         errors: [{message: "React is defined but never used"}], ecmaFeatures: {jsx: true}}
+    ]
+});