diff --git a/README.md b/README.md
index 89dab74847..d06bbb48ef 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ Finally, enable all of the rules that you would like to use.
* [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
* [jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
* [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
+* [jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components
* [jsx-quotes](docs/rules/jsx-quotes.md): Enforce quote style for JSX attributes
* [jsx-sort-prop-types](docs/rules/jsx-sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
* [jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting
diff --git a/docs/rules/jsx-pascal-case.md b/docs/rules/jsx-pascal-case.md
new file mode 100644
index 0000000000..e6a54c0f05
--- /dev/null
+++ b/docs/rules/jsx-pascal-case.md
@@ -0,0 +1,37 @@
+# Enforce PasalCase for user-defined JSX components (jsx-pascal-case)
+
+Enforces coding style that user-defined JSX components are defined and referenced in PascalCase.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+
+```
+
+```js
+
+
+
+```
+
+```js
+
+```
+
+The following patterns are not considered warnings:
+
+```js
+
+```
+
+```js
+
+
+
+```
+
+## When Not To Use It
+
+If you are not using JSX.
\ No newline at end of file
diff --git a/index.js b/index.js
index fdd87f7ed8..ab51959e1b 100644
--- a/index.js
+++ b/index.js
@@ -14,6 +14,7 @@ module.exports = {
'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
+ 'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
'jsx-quotes': require('./lib/rules/jsx-quotes'),
@@ -46,6 +47,7 @@ module.exports = {
'no-did-update-set-state': 0,
'react-in-jsx-scope': 0,
'jsx-uses-vars': 1,
+ 'jsx-pascal-case': 0,
'jsx-no-bind': 0,
'jsx-no-undef': 0,
'jsx-quotes': 0,
diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js
new file mode 100644
index 0000000000..c35bf7873c
--- /dev/null
+++ b/lib/rules/jsx-pascal-case.js
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Enforce PasalCase for user-defined JSX components
+ * @author Jake Marsh
+ */
+
+'use strict';
+
+var variableUtil = require('../util/variable');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var PASCAL_CASE_REGEX = /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ JSXOpeningElement: function(node) {
+ var variables = variableUtil.variablesInScope(context);
+
+ switch (node.name.type) {
+ case 'JSXIdentifier':
+ node = node.name;
+ break;
+ case 'JSXMemberExpression':
+ node = node.name.object;
+ break;
+ case 'JSXNamespacedName':
+ node = node.name.namespace;
+ break;
+ default:
+ break;
+ }
+
+ var isImportedVariable = variableUtil.findVariable(variables, node.name);
+ var isPascalCase = PASCAL_CASE_REGEX.test(node.name);
+
+ if (isImportedVariable && !isPascalCase) {
+ context.report(node, 'Imported JSX component ' + node.name + ' must be in PascalCase');
+ }
+ }
+ };
+
+};
diff --git a/tests/lib/rules/jsx-pascal-case.js b/tests/lib/rules/jsx-pascal-case.js
new file mode 100644
index 0000000000..446e34171c
--- /dev/null
+++ b/tests/lib/rules/jsx-pascal-case.js
@@ -0,0 +1,70 @@
+/**
+ * @fileoverview Tests for jsx-pascal-case
+ * @author Jake Marsh
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/jsx-pascal-case');
+var RuleTester = require('eslint').RuleTester;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('jsx-pascal-case', rule, {
+ valid: [{
+ code: [
+ 'var TestComponent;',
+ ''
+ ].join('\n'),
+ ecmaFeatures: {
+ jsx: true
+ }
+ }, {
+ code: [
+ 'var TestComponent;',
+ '',
+ ' ',
+ ''
+ ].join('\n'),
+ ecmaFeatures: {
+ jsx: true
+ }
+ }],
+
+ invalid: [{
+ code: [
+ 'var testComponent;',
+ ''
+ ].join('\n'),
+ ecmaFeatures: {
+ jsx: true
+ },
+ errors: [{message: 'Imported JSX component testComponent must be in PascalCase'}]
+ }, {
+ code: [
+ 'var test_component;',
+ ''
+ ].join('\n'),
+ ecmaFeatures: {
+ jsx: true
+ },
+ errors: [{message: 'Imported JSX component test_component must be in PascalCase'}]
+ }, {
+ code: [
+ 'var testComponent;',
+ '',
+ ' ',
+ ''
+ ].join('\n'),
+ ecmaFeatures: {
+ jsx: true
+ },
+ errors: [{message: 'Imported JSX component testComponent must be in PascalCase'}]
+ }]
+});