Repository: yannickcr/eslint-plugin-react
Pull request #5 (2015-03-01 12:47:14)

add react-in-jsx-scope rule
Hope this makes sense - I've included tests & docs as well.

See https://github.com/eslint/eslint/issues/1905 for some additional context.

I'm planning to follow up with a variant on `no-unused` that knows about JSX using `React.createElement`.

Sidenote: should there be a test which verifies all rules are exported in `index.js` ?


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

Coverage decreased (-1.92%) to 98.08% when pulling **de17bfeaedc1410fb276d5a0aba7cd298f81194c on glenjamin:react-in-jsx-scope** into **9c43de0350522c516312f086f44e9899afe8badd on yannickcr:master**.

glenjamin: Whoops, missed a test case for nested scopes - will add one to bump the coverage back up.

Patch:
diff --git a/docs/rules/react-in-jsx-scope.md b/docs/rules/react-in-jsx-scope.md
new file mode 100644
index 00000000..8de1297d
--- /dev/null
+++ b/docs/rules/react-in-jsx-scope.md
@@ -0,0 +1,23 @@
+# Prevent errors from not requiring React when using JSX (react-in-jsx-scope)
+
+When using JSX, `<a />` expands to `React.createElement("a")`. Therefore the
+`React` variable must be in scope.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+var Hello = <div>Hello {this.props.name}</div>;
+```
+
+The following patterns are not considered warnings:
+
+```js
+var React = require('react'); // or equivalent import
+var Hello = <div>Hello {this.props.name}</div>;
+```
+
+## When Not To Use It
+
+If you are setting `React` as a global variable, you will not need this rule.
diff --git a/index.js b/index.js
index 83b40b8a..dabec139 100644
--- a/index.js
+++ b/index.js
@@ -8,7 +8,8 @@ module.exports = {
     'wrap-multilines': require('./lib/rules/wrap-multilines'),
     'self-closing-comp': require('./lib/rules/self-closing-comp'),
     '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-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
+    'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope')
   },
   rulesConfig: {
     'no-multi-comp': 0,
@@ -17,6 +18,7 @@ module.exports = {
     'wrap-multilines': 0,
     'self-closing-comp': 0,
     'no-did-mount-set-state': 0,
-    'no-did-update-set-state': 0
+    'no-did-update-set-state': 0,
+    'react-in-jsx-scope': 0
   }
 };
diff --git a/lib/rules/react-in-jsx-scope.js b/lib/rules/react-in-jsx-scope.js
new file mode 100644
index 00000000..6f990ecc
--- /dev/null
+++ b/lib/rules/react-in-jsx-scope.js
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview Validate that React is in scope when using JSX.
+ * @author Glen Mailer
+ */
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+    var NOT_DEFINED_MESSAGE = "'React' must be in scope when using JSX";
+
+    function findVariable(variables, name) {
+        var i, len;
+        for (i = 0, len = variables.length; i < len; i++) {
+            if (variables[i].name === name) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    function variablesInScope() {
+        var scope = context.getScope(),
+            variables = scope.variables;
+
+        while (scope.type !== "global") {
+            scope = scope.upper;
+            variables = scope.variables.concat(variables);
+        }
+
+        return variables;
+    }
+
+    return {
+
+        "JSXOpeningElement": function(node) {
+            var variables = variablesInScope();
+            if (!findVariable(variables, 'React')) {
+                context.report(node, NOT_DEFINED_MESSAGE);
+            }
+        }
+
+    };
+
+};


Test patch:
diff --git a/tests/lib/rules/react-in-jsx-scope.js b/tests/lib/rules/react-in-jsx-scope.js
new file mode 100644
index 00000000..99be8fff
--- /dev/null
+++ b/tests/lib/rules/react-in-jsx-scope.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Tests for react-in-jsx-scope
+ * @author Glen Mailer
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint-tester');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+eslintTester.addRuleTest("lib/rules/react-in-jsx-scope", {
+    valid: [
+        {code: "var React, App; <App />;", args: [1, {vars: "all"}], ecmaFeatures: {jsx: true}},
+        {code: "var React; <img />;", args: [1, {vars: "all"}], ecmaFeatures: {jsx: true}},
+        {code: "var React; <x-gif />;", args: [1, {vars: "all"}], ecmaFeatures: {jsx: true}},
+        {code: "var React, App, a=1; <App attr={a} />;", ecmaFeatures: {jsx: true}},
+        {code: "var React, App, a=1; function elem() { return <App attr={a} />; }",
+         ecmaFeatures: {jsx: true}}
+    ],
+    invalid: [
+        {code: "var App, a = <App />;",
+         errors: [{message: "'React' must be in scope when using JSX"}], ecmaFeatures: {jsx: true}},
+        {code: "var a = <App />;",
+         errors: [{message: "'React' must be in scope when using JSX"}], ecmaFeatures: {jsx: true}},
+        {code: "var a = <img />;",
+         errors: [{message: "'React' must be in scope when using JSX"}], ecmaFeatures: {jsx: true}}
+    ]
+});