diff --git a/README.md b/README.md
index 5928a627cf..06f97b7b75 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
+* [react/forbid-elements](docs/rules/forbid-elements.md): Forbid certain elements
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
* [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md): Forbid foreign propTypes
* [react/no-array-index-key](docs/rules/no-array-index-key.md): Prevent using Array index in `key` props
diff --git a/docs/rules/forbid-elements.md b/docs/rules/forbid-elements.md
new file mode 100644
index 0000000000..16c4c62ddd
--- /dev/null
+++ b/docs/rules/forbid-elements.md
@@ -0,0 +1,58 @@
+# Forbid certain elements (forbid-elements)
+
+You may want to forbid usage of certain elements in favor of others, (e.g. forbid all `
` and use ` ` instead). This rule allows you to configure a list of forbidden elements and to specify their desired replacements.
+
+## Rule Details
+
+This rule checks all JSX elements and `React.createElement` calls and verifies that no forbidden elements are used. This rule is off by default. If on, no elements are forbidden by default.
+
+## Rule Options
+
+```js
+...
+"forbid-elements": [, { "forbid": [] }]
+...
+```
+
+### `forbid`
+
+An array of strings and/or objects. An object in this array may have the following properties:
+
+* `element` (required): the name of the forbidden element (e.g. `'button'`, `'Modal'`)
+* `message`: additional message that gets reported
+
+A string item in the array is a shorthand for `{ element: string }`.
+
+The following patterns are not considered warnings:
+
+```jsx
+// [1, { "forbid": ["button"] }]
+
+
+// [1, { "forbid": [{ "element": "button" }] }]
+
+```
+
+The following patterns are considered warnings:
+
+```jsx
+// [1, { "forbid": ["button"] }]
+
+React.createElement('button');
+
+// [1, { "forbid": ["Modal"] }]
+
+React.createElement(Modal);
+
+// [1, { "forbid": ["Namespaced.Element"] }]
+
+React.createElement(Namespaced.Element);
+
+// [1, { "forbid": [{ "element": "button", "message": "use instead" }, "input"] }]
+
+React.createElement('div', {}, React.createElemet('button', {}, React.createElement('input')));
+```
+
+## When not to use
+
+If you don't want to forbid any elements.
diff --git a/index.js b/index.js
index 36317d95de..914e4433f3 100644
--- a/index.js
+++ b/index.js
@@ -42,6 +42,7 @@ var allRules = {
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
'forbid-component-props': require('./lib/rules/forbid-component-props'),
+ 'forbid-elements': require('./lib/rules/forbid-elements'),
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'),
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js
new file mode 100644
index 0000000000..8cce882fb4
--- /dev/null
+++ b/lib/rules/forbid-elements.js
@@ -0,0 +1,112 @@
+/**
+ * @fileoverview Forbid certain elements
+ * @author Kenneth Chung
+ */
+'use strict';
+
+var has = require('has');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Forbid certain elements',
+ category: 'Best Practices',
+ recommended: false
+ },
+
+ schema: [{
+ type: 'object',
+ properties: {
+ forbid: {
+ type: 'array',
+ items: {
+ anyOf: [
+ {type: 'string'},
+ {
+ type: 'object',
+ properties: {
+ element: {type: 'string'},
+ message: {type: 'string'}
+ },
+ required: ['element'],
+ additionalProperties: false
+ }
+ ]
+ }
+ }
+ },
+ additionalProperties: false
+ }]
+ },
+
+ create: function(context) {
+ var sourceCode = context.getSourceCode();
+ var configuration = context.options[0] || {};
+ var forbidConfiguration = configuration.forbid || [];
+
+ var indexedForbidConfigs = {};
+
+ forbidConfiguration.forEach(function(item) {
+ if (typeof item === 'string') {
+ indexedForbidConfigs[item] = {element: item};
+ } else {
+ indexedForbidConfigs[item.element] = item;
+ }
+ });
+
+ function errorMessageForElement(name) {
+ var message = '<' + name + '> is forbidden';
+ var additionalMessage = indexedForbidConfigs[name].message;
+
+ if (additionalMessage) {
+ message = message + ', ' + additionalMessage;
+ }
+
+ return message;
+ }
+
+ function isValidCreateElement(node) {
+ return node.callee
+ && node.callee.type === 'MemberExpression'
+ && node.callee.object.name === 'React'
+ && node.callee.property.name === 'createElement'
+ && node.arguments.length > 0;
+ }
+
+ function reportIfForbidden(element, node) {
+ if (has(indexedForbidConfigs, element)) {
+ context.report({
+ node: node,
+ message: errorMessageForElement(element)
+ });
+ }
+ }
+
+ return {
+ JSXOpeningElement: function(node) {
+ reportIfForbidden(sourceCode.getText(node.name), node.name);
+ },
+
+ CallExpression: function(node) {
+ if (!isValidCreateElement(node)) {
+ return;
+ }
+
+ var argument = node.arguments[0];
+ var argType = argument.type;
+
+ if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
+ reportIfForbidden(argument.name, argument);
+ } else if (argType === 'Literal' && /^[a-z][^\.]*$/.test(argument.value)) {
+ reportIfForbidden(argument.value, argument);
+ } else if (argType === 'MemberExpression') {
+ reportIfForbidden(sourceCode.getText(argument), argument);
+ }
+ }
+ };
+ }
+};
diff --git a/tests/lib/rules/forbid-elements.js b/tests/lib/rules/forbid-elements.js
new file mode 100644
index 0000000000..0ab7fcdc1f
--- /dev/null
+++ b/tests/lib/rules/forbid-elements.js
@@ -0,0 +1,230 @@
+/**
+ * @fileoverview Tests for forbid-elements
+ */
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/forbid-elements');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+ ecmaVersion: 6,
+ ecmaFeatures: {
+ experimentalObjectRestSpread: true,
+ jsx: true
+ }
+};
+
+require('babel-eslint');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('forbid-elements', rule, {
+ valid: [
+ {
+ code: ' ',
+ options: [],
+ parserOptions: parserOptions
+ },
+ {
+ code: ' ',
+ options: [{forbid: []}],
+ parserOptions: parserOptions
+ },
+ {
+ code: ' ',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: ' ',
+ options: [{forbid: [{element: 'button'}]}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement(button)',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'createElement("button")',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'NotReact.createElement("button")',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("_thing")',
+ options: [{forbid: ['_thing']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("Modal")',
+ options: [{forbid: ['Modal']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("dotted.component")',
+ options: [{forbid: ['dotted.component']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement(function() {})',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement({})',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement(1)',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions
+ }
+ ],
+
+ invalid: [
+ {
+ code: ' ',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden'}]
+ },
+ {
+ code: '[ , ]',
+ options: [{forbid: ['button', 'Modal']}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'},
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: ' ',
+ options: [{forbid: ['dotted.component']}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: ' ',
+ options: [{forbid: [
+ {element: 'dotted.Component', message: 'that ain\'t cool'}
+ ]}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden, that ain\'t cool'}]
+ },
+ {
+ code: ' ',
+ options: [{forbid: [
+ {element: 'button', message: 'use instead'}
+ ]}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden, use instead'}]
+ },
+ {
+ code: ' ',
+ options: [{forbid: [{element: 'button'}, {element: 'input'}]}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'},
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: ' ',
+ options: [{forbid: [{element: 'button'}, 'input']}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'},
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: ' ',
+ options: [{forbid: ['input', {element: 'button'}]}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'},
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: ' ',
+ options: [{forbid: [
+ {element: 'button', message: 'use instead'},
+ {element: 'button', message: 'use instead'}
+ ]}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden, use instead'}]
+ },
+ {
+ code: 'React.createElement("button", {}, child)',
+ options: [{forbid: ['button']}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden'}]
+ },
+ {
+ code: '[React.createElement(Modal), React.createElement("button")]',
+ options: [{forbid: ['button', 'Modal']}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'},
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: 'React.createElement(dotted.Component)',
+ options: [{forbid: [
+ {element: 'dotted.Component', message: 'that ain\'t cool'}
+ ]}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden, that ain\'t cool'}]
+ },
+ {
+ code: 'React.createElement(dotted.component)',
+ options: [{forbid: ['dotted.component']}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'}
+ ]
+ },
+ {
+ code: 'React.createElement(_comp)',
+ options: [{forbid: ['_comp']}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: '<_comp> is forbidden'}
+ ]
+ },
+ {
+ code: 'React.createElement("button")',
+ options: [{forbid: [
+ {element: 'button', message: 'use instead'}
+ ]}],
+ parserOptions: parserOptions,
+ errors: [{message: ' is forbidden, use instead'}]
+ },
+ {
+ code: 'React.createElement("button", {}, React.createElement("input"))',
+ options: [{forbid: [{element: 'button'}, {element: 'input'}]}],
+ parserOptions: parserOptions,
+ errors: [
+ {message: ' is forbidden'},
+ {message: ' is forbidden'}
+ ]
+ }
+ ]
+});