diff --git a/docs/rules/jsx-max-depth.md b/docs/rules/jsx-max-depth.md
new file mode 100644
index 0000000000..6440c0d445
--- /dev/null
+++ b/docs/rules/jsx-max-depth.md
@@ -0,0 +1,84 @@
+# Validate JSX maximum depth (react/jsx-max-depth)
+
+This option validates a specific depth for JSX.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+
+
+
+
+
+
+
+
+```
+
+## Rule Options
+
+It takes an option as the second parameter which can be a positive number for depth count.
+
+```js
+...
+"react/jsx-no-depth": [, { "max": }]
+...
+```
+
+The following patterns are considered warnings:
+
+```jsx
+// [2, { "max": 2 }]
+
+
+
+
+
+
+// [2, { "max": 2 }]
+const foobar = ;
+
+ {foobar}
+
+
+// [2, { "max": 3 }]
+
+
+
+
+
+
+
+```
+
+The following patterns are not warnings:
+
+```jsx
+
+// [2, { "max": 2 }]
+
+
+
+
+// [2,{ "max": 3 }]
+
+
+
+
+
+
+// [2, { "max": 4 }]
+
+
+
+
+
+
+
+```
+
+## When not to use
+
+If you are not using JSX then you can disable this rule.
diff --git a/index.js b/index.js
index d9c1df21d1..1cd39b6c8a 100644
--- a/index.js
+++ b/index.js
@@ -50,6 +50,7 @@ var allRules = {
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'),
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
'jsx-key': require('./lib/rules/jsx-key'),
+ 'jsx-max-depth': require('./lib/rules/jsx-max-depth'),
'no-string-refs': require('./lib/rules/no-string-refs'),
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
'require-render-return': require('./lib/rules/require-render-return'),
diff --git a/lib/rules/jsx-max-depth.js b/lib/rules/jsx-max-depth.js
new file mode 100644
index 0000000000..1a537194db
--- /dev/null
+++ b/lib/rules/jsx-max-depth.js
@@ -0,0 +1,146 @@
+/**
+ * @fileoverview Validate JSX maximum depth
+ * @author Chris
+ */
+'use strict';
+
+const has = require('has');
+const variableUtil = require('../util/variable');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Validate JSX maximum depth',
+ category: 'Stylistic Issues',
+ recommended: false
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ max: {
+ type: 'integer',
+ minimum: 1
+ }
+ },
+ additionalProperties: false
+ }
+ ]
+ },
+ create: function(context) {
+ const MESSAGE = 'Expected the depth of JSX Elements nested should be {{needed}} but found {{found}}.';
+ const DEFAULT_DEPTH = 3;
+
+ const option = context.options[0] || {};
+ const maxDepth = has(option, 'max') ? option.max : DEFAULT_DEPTH;
+
+ function isJSXElement(node) {
+ return node.type === 'JSXElement';
+ }
+
+ function isExpression(node) {
+ return node.type === 'JSXExpressionContainer';
+ }
+
+ function hasJSX(node) {
+ return isJSXElement(node) || isExpression(node) && isJSXElement(node.expression);
+ }
+
+ function isLeaf(node) {
+ const children = node.children;
+
+ return !children.length || !children.some(hasJSX);
+ }
+
+ function getDepth(node) {
+ let count = 1;
+
+ while (isJSXElement(node.parent) || isExpression(node.parent)) {
+ node = node.parent;
+ if (isJSXElement(node)) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+
+ function report(node, depth) {
+ context.report({
+ node: node,
+ message: MESSAGE,
+ data: {
+ found: depth,
+ needed: maxDepth
+ }
+ });
+ }
+
+ function findJSXElement(variables, name) {
+ function find(refs) {
+ let i = refs.length;
+
+ while (--i >= 0) {
+ if (has(refs[i], 'writeExpr')) {
+ const writeExpr = refs[i].writeExpr;
+
+ return isJSXElement(writeExpr)
+ && writeExpr
+ || writeExpr.type === 'Identifier'
+ && findJSXElement(variables, writeExpr.name);
+ }
+ }
+
+ return null;
+ }
+
+ const variable = variableUtil.getVariable(variables, name);
+ return variable && variable.references && find(variable.references);
+ }
+
+ function checkDescendant(baseDepth, children) {
+ children.forEach(function(node) {
+ if (!hasJSX(node)) {
+ return;
+ }
+
+ baseDepth++;
+ if (baseDepth > maxDepth) {
+ report(node, baseDepth);
+ } else if (!isLeaf(node)) {
+ checkDescendant(baseDepth, node.children);
+ }
+ });
+ }
+
+ return {
+ JSXElement: function(node) {
+ if (!isLeaf(node)) {
+ return;
+ }
+
+ const depth = getDepth(node);
+ if (depth > maxDepth) {
+ report(node, depth);
+ }
+ },
+ JSXExpressionContainer: function(node) {
+ if (node.expression.type !== 'Identifier') {
+ return;
+ }
+
+ const variables = variableUtil.variablesInScope(context);
+ const element = findJSXElement(variables, node.expression.name);
+
+ if (element) {
+ const baseDepth = getDepth(node);
+ checkDescendant(baseDepth, element.children);
+ }
+ }
+ };
+ }
+};
diff --git a/tests/lib/rules/jsx-max-depth.js b/tests/lib/rules/jsx-max-depth.js
new file mode 100644
index 0000000000..a36a03aaad
--- /dev/null
+++ b/tests/lib/rules/jsx-max-depth.js
@@ -0,0 +1,127 @@
+/**
+ * @fileoverview Validate JSX maximum depth
+ * @author Chris
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/jsx-max-depth');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true
+ }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester({parserOptions});
+ruleTester.run('jsx-max-depth', rule, {
+ valid: [{
+ code: [
+ ''
+ ].join('\n')
+ }, {
+ code: [
+ '',
+ ' ',
+ ''
+ ].join('\n'),
+ options: [{max: 2}]
+ }, {
+ code: [
+ '',
+ ' ',
+ ' ',
+ ' ',
+ ''
+ ].join('\n'),
+ options: [{}]
+ }, {
+ code: [
+ '',
+ ' ',
+ ' ',
+ ' ',
+ ''
+ ].join('\n'),
+ options: [{max: 3}]
+ }, {
+ code: [
+ 'const x = x
;',
+ '{x}
'
+ ].join('\n'),
+ options: [{max: 3}]
+ }, {
+ code: 'const foo = (x) => {x}
;',
+ options: [{max: 3}]
+ }],
+
+ invalid: [{
+ code: [
+ '',
+ ' ',
+ ''
+ ].join('\n'),
+ options: [{max: 1}],
+ errors: [{message: 'Expected the depth of JSX Elements nested should be 1 but found 2.'}]
+ }, {
+ code: [
+ '',
+ ' {bar}',
+ ''
+ ].join('\n'),
+ options: [{max: 1}],
+ errors: [{message: 'Expected the depth of JSX Elements nested should be 1 but found 2.'}]
+ }, {
+ code: [
+ '',
+ ' ',
+ ' ',
+ ' ',
+ ''
+ ].join('\n'),
+ options: [{max: 2}],
+ errors: [{message: 'Expected the depth of JSX Elements nested should be 2 but found 3.'}]
+ }, {
+ code: [
+ 'const x =
;',
+ '{x}
'
+ ].join('\n'),
+ options: [{max: 2}],
+ errors: [{message: 'Expected the depth of JSX Elements nested should be 2 but found 3.'}]
+ }, {
+ code: [
+ 'const x =
;',
+ 'let y = x;',
+ '{y}
'
+ ].join('\n'),
+ options: [{max: 2}],
+ errors: [{message: 'Expected the depth of JSX Elements nested should be 2 but found 3.'}]
+ }, {
+ code: [
+ 'const x =
;',
+ 'let y = x;',
+ '{x}-{y}
'
+ ].join('\n'),
+ options: [{max: 2}],
+ errors: [
+ {message: 'Expected the depth of JSX Elements nested should be 2 but found 3.'},
+ {message: 'Expected the depth of JSX Elements nested should be 2 but found 3.'}
+ ]
+ }, {
+ code: [
+ ''
+ ].join('\n'),
+ errors: [{message: 'Expected the depth of JSX Elements nested should be 3 but found 4.'}]
+ }]
+});