Repository: yannickcr/eslint-plugin-react
Pull request #117 (2015-06-17 22:08:06)

Allow blocking of '.jsx' extensions with `require()`
Created a new rule, 'require-extension', which inspects the required
module ID and warns if the file extension is .jsx.

The particular disallowed file extensions are configurable, defaulting to
just '.jsx'. If you wanted to also disallow '.js', the rule can be
configured as:

    "rules": {
      "react/require-extension": [1, { "extensions": [".js", ".jsx"] }],
    }

Note: this rule does not prevent required files from containing forbidden
extensions, it merely prevents the extension from being included in the
`require()` statement.

This rule is inspired by the Airbnb react style guide:
https://github.com/airbnb/javascript/tree/master/react#naming


Patch:
diff --git a/docs/rules/require-extenion.md b/docs/rules/require-extenion.md
new file mode 100644
index 00000000..37c8fb2d
--- /dev/null
+++ b/docs/rules/require-extenion.md
@@ -0,0 +1,38 @@
+# Restrict file extensions that may be required (require-extension)
+
+`require()` statements should generally not include a file extension as there is a well defined mechanism for resolving a module ID to a specific file. This rule inspects the module ID being required and creates a warning if the ID contains a '.jsx' file extension.
+
+Note: this rule does not prevent required files from containing these extensions, it merely prevents the extension from being included in the `require()` statement.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+var index = require('./index.jsx');
+
+// When [1, {extensions: ['.js']}]
+var index = require('./index.js');
+```
+
+The following patterns are not considered warnings:
+
+```js
+var index = require('./index');
+
+var eslint = require('eslint');
+```
+
+## Rule Options
+
+The set of forbidden extensions is configurable. By default '.jsx' is blocked. If you wanted to forbid both '.jsx' and '.js', the configuration would be:
+
+```js
+"rules": {
+  "react/require-extension": [1, { "extensions": [".js", ".jsx"] }],
+}
+```
+
+## When Not To Use It
+
+If you have file in your project with a '.jsx' file extension and do not have `require()` configured to automatically resolve '.jsx' files.
diff --git a/index.js b/index.js
index c467477b..71ee48e8 100644
--- a/index.js
+++ b/index.js
@@ -18,7 +18,8 @@ module.exports = {
     'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
     'jsx-sort-prop-types': require('./lib/rules/jsx-sort-prop-types'),
     'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
-    'sort-comp': require('./lib/rules/sort-comp')
+    'sort-comp': require('./lib/rules/sort-comp'),
+    'require-extension': require('./lib/rules/require-extension')
   },
   rulesConfig: {
     'jsx-uses-react': 0,
@@ -37,6 +38,7 @@ module.exports = {
     'jsx-sort-props': 0,
     'jsx-sort-prop-types': 0,
     'jsx-boolean-value': 0,
-    'sort-comp': 0
+    'sort-comp': 0,
+    'require-extension': 0
   }
 };
diff --git a/lib/rules/require-extension.js b/lib/rules/require-extension.js
new file mode 100644
index 00000000..2a5f7c67
--- /dev/null
+++ b/lib/rules/require-extension.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Restrict file extensions that may be required
+ * @author Scott Andrews
+ */
+'use strict';
+
+var path = require('path');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var DEFAULTS = {
+  extentions: ['.jsx']
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  function isRequire(expression) {
+    return expression.callee.name === 'require';
+  }
+
+  function getId(expression) {
+    return expression.arguments[0] && expression.arguments[0].value;
+  }
+
+  function getExtension(id) {
+    return path.extname(id || '');
+  }
+
+  function getExtentionsConfig() {
+    return context.options[0] && context.options[0].extensions || DEFAULTS.extentions;
+  }
+
+  var forbiddenExtensions = getExtentionsConfig().reduce(function (extensions, extension) {
+    extensions[extension] = true;
+    return extensions;
+  }, Object.create(null));
+
+  function isForbiddenExtension(ext) {
+    return ext in forbiddenExtensions;
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    CallExpression: function(node) {
+      if (isRequire(node)) {
+        var ext = getExtension(getId(node));
+        if (isForbiddenExtension(ext)) {
+          context.report(node, 'Unable to require module with extension \'' + ext + '\'');
+        }
+      }
+    }
+
+  };
+
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    extensions: {type: 'array'}
+  }
+}];


Test patch:
diff --git a/tests/lib/rules/require-extension.js b/tests/lib/rules/require-extension.js
new file mode 100644
index 00000000..3e48542a
--- /dev/null
+++ b/tests/lib/rules/require-extension.js
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview Restrict file extensions that may be required
+ * @author Scott Andrews
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint-tester');
+
+// ------------------------------------------------------------------------------
+// Code Snippets
+// ------------------------------------------------------------------------------
+
+var REQUIRE_PACKAGE = 'require(\'eslint\')';
+
+var REQUIRE_JS = 'require(\'./index.js\')';
+
+var REQUIRE_JSX = 'require(\'./index.jsx\')';
+
+var REQUIRE_JSON = 'require(\'./index.json\')';
+
+var REQUIRE_EMPTY = 'require()';
+
+var REQUIRE_OBJECT = 'require({})';
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+eslintTester.addRuleTest('lib/rules/require-extension', {
+
+    valid: [
+        {
+            code: REQUIRE_PACKAGE
+        }, {
+            code: REQUIRE_JS
+        }, {
+            code: REQUIRE_JSON
+        }, {
+            code: REQUIRE_EMPTY
+        }, {
+            code: REQUIRE_OBJECT
+        }, {
+            code: REQUIRE_PACKAGE,
+            args: [1]
+        }, {
+            code: REQUIRE_JS,
+            args: [1]
+        }, {
+            code: REQUIRE_JSON,
+            args: [1]
+        }, {
+            code: REQUIRE_EMPTY,
+            args: [1]
+        }, {
+            code: REQUIRE_OBJECT,
+            args: [1]
+        }, {
+            code: REQUIRE_JSON,
+            args: [1, {extensions: ['.js']}]
+        }, {
+            code: REQUIRE_JSX,
+            args: [1, {extensions: ['.js']}]
+        }
+    ],
+
+    invalid: [
+        {
+            code: REQUIRE_JSX,
+            errors: [{message: 'Unable to require module with extension \'.jsx\''}]
+        }, {
+            code: REQUIRE_JSX,
+            args: [1],
+            errors: [{message: 'Unable to require module with extension \'.jsx\''}]
+        }, {
+            code: REQUIRE_JS,
+            args: [1, {extensions: ['.js']}],
+            errors: [{message: 'Unable to require module with extension \'.js\''}]
+        }, {
+            code: REQUIRE_JS,
+            args: [1, {extensions: ['.js', '.jsx']}],
+            errors: [{message: 'Unable to require module with extension \'.js\''}]
+        }, {
+            code: REQUIRE_JSX,
+            args: [1, {extensions: ['.js', '.jsx']}],
+            errors: [{message: 'Unable to require module with extension \'.jsx\''}]
+        }
+    ]
+
+});