Repository: yannickcr/eslint-plugin-react (js)

Issue:
Rule proposal: Warn on duplicate props
When you're in strict-mode you get warnings when you have duplicate properties in an object, a similiar warning for props on React components would be nice.

```javascript
return (
   <Mycomponent className={classes}
                            onClick={this.handleClick}
                            someOtherProp={stuff}
                            onClick={this.onClick} /> // Duplicate prop, should produce an ESLint error
);
```

Pull request description:
Added rule for no duplicated props
Added rule that checks for duplicate props sent to a React component written in JSX.
Has the options of specifying whether casing should be ignored when comparing, via `{ignoreCase: true}`

Closes #143

Patch:
diff --git a/index.js b/index.js
index 57446f64..7735937e 100644
--- a/index.js
+++ b/index.js
@@ -21,7 +21,8 @@ module.exports = {
     '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'),
-    'require-extension': require('./lib/rules/require-extension')
+    'require-extension': require('./lib/rules/require-extension'),
+    'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props')
   },
   rulesConfig: {
     'jsx-uses-react': 0,
@@ -43,6 +44,7 @@ module.exports = {
     'jsx-sort-prop-types': 0,
     'jsx-boolean-value': 0,
     'sort-comp': 0,
-    'require-extension': 0
+    'require-extension': 0,
+    'jsx-no-duplicate-props': 0
   }
 };
diff --git a/lib/rules/jsx-no-duplicate-props.js b/lib/rules/jsx-no-duplicate-props.js
new file mode 100644
index 00000000..0bdb84b0
--- /dev/null
+++ b/lib/rules/jsx-no-duplicate-props.js
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Enforce no duplicate props
+ * @author Markus Ånöstam
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function (context) {
+
+  var configuration = context.options[0] || {};
+  var ignoreCase = configuration.ignoreCase || false;
+
+  return {
+    JSXOpeningElement: function (node) {
+      var props = {};
+
+      node.attributes.forEach(function(decl) {
+        if (decl.type === 'JSXSpreadAttribute') {
+          return;
+        }
+
+        var name = decl.name.name;
+
+        if (ignoreCase) {
+          name = name.toLowerCase();
+        }
+
+        if (props.hasOwnProperty(name)) {
+          context.report(decl, 'No duplicate props allowed');
+        } else {
+          props[name] = 1;
+        }
+      });
+    }
+  };
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    ignoreCase: {
+      type: 'boolean'
+    }
+  },
+  additionalProperties: false
+}];


Test patch:
diff --git a/tests/lib/rules/jsx-no-duplicate-props.js b/tests/lib/rules/jsx-no-duplicate-props.js
new file mode 100644
index 00000000..603fb53a
--- /dev/null
+++ b/tests/lib/rules/jsx-no-duplicate-props.js
@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Enforce no duplicate props
+ * @author Markus Ånöstam
+ */
+
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint-tester');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+
+var expectedError = {
+  message: 'No duplicate props allowed',
+  type: 'JSXAttribute'
+};
+
+var ignoreCaseArgs = [1, {
+  ignoreCase: true
+}];
+
+var features = {
+  jsx: true
+};
+
+eslintTester.addRuleTest('lib/rules/jsx-no-duplicate-props', {
+  valid: [
+    {code: '<App />;', ecmaFeatures: features},
+    {code: '<App {...this.props} />;', ecmaFeatures: features},
+    {code: '<App a b c />;', ecmaFeatures: features},
+    {code: '<App a b c A />;', ecmaFeatures: features},
+    {code: '<App {...this.props} a b c />;', ecmaFeatures: features},
+    {code: '<App c {...this.props} a b />;', ecmaFeatures: features},
+    {code: '<App a="c" b="b" c="a" />;', ecmaFeatures: features},
+    {code: '<App {...this.props} a="c" b="b" c="a" />;', ecmaFeatures: features},
+    {code: '<App c="a" {...this.props} a="c" b="b" />;', ecmaFeatures: features},
+    {code: '<App A a />;', ecmaFeatures: features},
+    {code: '<App A b a />;', ecmaFeatures: features},
+    {code: '<App A="a" b="b" B="B" />;', ecmaFeatures: features}
+  ],
+  invalid: [
+    {code: '<App a a />;', errors: [expectedError], ecmaFeatures: features},
+    {code: '<App A b c A />;', errors: [expectedError], ecmaFeatures: features},
+    {code: '<App a="a" b="b" a="a" />;', errors: [expectedError], ecmaFeatures: features},
+    {code: '<App A a />;', args: ignoreCaseArgs, errors: [expectedError], ecmaFeatures: features},
+    {code: '<App a b c A />;', args: ignoreCaseArgs, errors: [expectedError], ecmaFeatures: features},
+    {code: '<App A="a" b="b" B="B" />;', args: ignoreCaseArgs, errors: [expectedError], ecmaFeatures: features}
+  ]
+});