Repository: yannickcr/eslint-plugin-react
Pull request #911 (2016-10-14 19:06:11)

Mark deprecated rules as deprecated in their metadata
Over on [eslint-find-rules](https://github.com/sarbbottam/eslint-find-rules), there has been some discussion about being able to detect deprecated rules in order to [not report them as unused](https://github.com/sarbbottam/eslint-find-rules/issues/172) and to [report when they are still being used](https://github.com/sarbbottam/eslint-find-rules/issues/188).

In order to implement those features, there needs to be a way to detect that a rule has been deprecated.  Core ESLint rules that are deprecated are marked as such in their metadata.

This PR adds the same `deprecated` flag to the metadata of the deprecated rules in this plugin.

It might be possible to make use of this metadata in `index.js` to automatically determine which rules are deprecated, rather than duplicating the information in two places.  However, my attempts at this were not that clean and didn’t seem worth it.  I could take another look if you think it worth it.



Patch:
diff --git a/docs/rules/prefer-stateless-function.md b/docs/rules/prefer-stateless-function.md
index 3efee0ca..aa8285e5 100644
--- a/docs/rules/prefer-stateless-function.md
+++ b/docs/rules/prefer-stateless-function.md
@@ -8,7 +8,7 @@ This rule will check your class based React components for
 
 * methods/properties other than `displayName`, `propTypes`, `render` and useless constructor (same detection as ESLint [no-useless-constructor rule](http://eslint.org/docs/rules/no-useless-constructor))
 * instance property other than `this.props` and `this.context`
-* extension of `React.PureComponent` ()
+* extension of `React.PureComponent` (if the `ignorePureComponents` flag is true)
 * presence of `ref` attribute in JSX
 * `render` method that return anything but JSX: `undefined`, `null`, etc. (only in React <15.0.0, see [shared settings](https://github.com/yannickcr/eslint-plugin-react/blob/master/README.md#configuration) for React version configuration)
 
diff --git a/index.js b/index.js
index 6bc26713..758e8d84 100644
--- a/index.js
+++ b/index.js
@@ -1,12 +1,6 @@
 'use strict';
 
-var deprecatedRules = {
-  'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
-  'require-extension': require('./lib/rules/require-extension'),
-  'wrap-multilines': require('./lib/rules/wrap-multilines')
-};
-
-var rules = {
+var allRules = {
   'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
   'no-multi-comp': require('./lib/rules/no-multi-comp'),
   'prop-types': require('./lib/rules/prop-types'),
@@ -58,34 +52,50 @@ var rules = {
   'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
   'style-prop-object': require('./lib/rules/style-prop-object'),
   'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
-  'no-children-prop': require('./lib/rules/no-children-prop')
+  'no-children-prop': require('./lib/rules/no-children-prop'),
+  'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
+  'require-extension': require('./lib/rules/require-extension'),
+  'wrap-multilines': require('./lib/rules/wrap-multilines')
 };
 
-var ruleNames = Object.keys(rules);
-var allRules = {};
-for (var i = 0; i < ruleNames.length; i++) {
-  allRules['react/' + ruleNames[i]] = 2;
-}
-
-var exportedRules = {};
-for (var key in rules) {
-  if (!rules.hasOwnProperty(key)) {
-    continue;
+function filterRules(rules, predicate) {
+  var result = {};
+  for (var key in rules) {
+    if (rules.hasOwnProperty(key) && predicate(rules[key])) {
+      result[key] = rules[key];
+    }
   }
-  exportedRules[key] = rules[key];
+  return result;
 }
-for (var deprecatedKey in deprecatedRules) {
-  if (!deprecatedRules.hasOwnProperty(deprecatedKey)) {
-    continue;
+
+function configureAsError(rules) {
+  var result = {};
+  for (var key in rules) {
+    if (!rules.hasOwnProperty(key)) {
+      continue;
+    }
+    result['react/' + key] = 2;
   }
-  exportedRules[deprecatedKey] = deprecatedRules[deprecatedKey];
+  return result;
 }
 
+var activeRules = filterRules(allRules, function(rule) {
+  return !rule.meta.deprecated;
+});
+var activeRulesConfig = configureAsError(activeRules);
+
+var deprecatedRules = filterRules(allRules, function(rule) {
+  return rule.meta.deprecated;
+});
+
 module.exports = {
   deprecatedRules: deprecatedRules,
-  rules: exportedRules,
+  rules: allRules,
   configs: {
     recommended: {
+      plugin: [
+        'react'
+      ],
       parserOptions: {
         ecmaFeatures: {
           jsx: true
@@ -109,12 +119,15 @@ module.exports = {
       }
     },
     all: {
+      plugin: [
+        'react'
+      ],
       parserOptions: {
         ecmaFeatures: {
           jsx: true
         }
       },
-      rules: allRules
+      rules: activeRulesConfig
     }
   }
 };
diff --git a/lib/rules/forbid-component-props.js b/lib/rules/forbid-component-props.js
index 4218112c..8d6ba9f9 100644
--- a/lib/rules/forbid-component-props.js
+++ b/lib/rules/forbid-component-props.js
@@ -48,7 +48,7 @@ module.exports = {
     return {
       JSXAttribute: function(node) {
         var tag = node.parent.name.name;
-        if (tag[0] !== tag[0].toUpperCase()) {
+        if (tag && tag[0] !== tag[0].toUpperCase()) {
           // This is a DOM node, not a Component, so exit.
           return;
         }
diff --git a/lib/rules/no-comment-textnodes.js b/lib/rules/no-comment-textnodes.js
index 92e7f482..83ae7450 100644
--- a/lib/rules/no-comment-textnodes.js
+++ b/lib/rules/no-comment-textnodes.js
@@ -15,6 +15,7 @@ var isWarnedForDeprecation = false;
 
 module.exports = {
   meta: {
+    deprecated: true,
     docs: {
       description: 'Comments inside children section of tag should be placed inside braces',
       category: 'Possible Errors',
diff --git a/lib/rules/require-extension.js b/lib/rules/require-extension.js
index 1a8a2ed2..273d3563 100644
--- a/lib/rules/require-extension.js
+++ b/lib/rules/require-extension.js
@@ -24,6 +24,7 @@ var PKG_REGEX = /^[^\.]((?!\/).)*$/;
 
 module.exports = {
   meta: {
+    deprecated: true,
     docs: {
       description: 'Restrict file extensions that may be required',
       category: 'Stylistic Issues',
diff --git a/lib/rules/wrap-multilines.js b/lib/rules/wrap-multilines.js
index 2b6900ee..439acbbe 100644
--- a/lib/rules/wrap-multilines.js
+++ b/lib/rules/wrap-multilines.js
@@ -15,6 +15,7 @@ var isWarnedForDeprecation = false;
 
 module.exports = {
   meta: {
+    deprecated: true,
     docs: {
       description: 'Prevent missing parentheses around multilines JSX',
       category: 'Stylistic Issues',


Test patch:
diff --git a/tests/index.js b/tests/index.js
index a85b4cb6..8e058037 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -23,6 +23,20 @@ describe('all rule files should be exported by the plugin', function() {
   });
 });
 
+describe('deprecated rules', function() {
+  it('marks all deprecated rules as deprecated', function() {
+    ruleFiles.forEach(function(ruleName) {
+      var inDeprecatedRules = Boolean(plugin.deprecatedRules[ruleName]);
+      var isDeprecated = plugin.rules[ruleName].meta.deprecated;
+      if (inDeprecatedRules) {
+        assert(isDeprecated, ruleName + ' metadata should mark it as deprecated');
+      } else {
+        assert(!isDeprecated, ruleName + ' metadata should not mark it as deprecated');
+      }
+    });
+  });
+});
+
 describe('configurations', function() {
   it('should export a \'recommended\' configuration', function() {
     assert(plugin.configs.recommended);
diff --git a/tests/lib/rules/forbid-component-props.js b/tests/lib/rules/forbid-component-props.js
index 9483f80e..227e4e90 100644
--- a/tests/lib/rules/forbid-component-props.js
+++ b/tests/lib/rules/forbid-component-props.js
@@ -81,6 +81,33 @@ ruleTester.run('forbid-component-props', rule, {
     ].join('\n'),
     options: [{forbid: ['style', 'foo']}],
     parserOptions: parserOptions
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <this.Foo bar="baz" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'class First extends React.createClass {',
+      '  render() {',
+      '    return <this.foo className="bar" />;',
+      '  }',
+      '}'
+    ].join('\n'),
+    options: [{forbid: ['style']}],
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'const First = (props) => (',
+      '  <this.Foo {...props} />',
+      ');'
+    ].join('\n'),
+    parserOptions: parserOptions
   }],
 
   invalid: [{