hint for duplicated lint rules

Change-Id: Ieee5add24bc3517e61fdf4c1f90fb217f8321295
Reviewed-on: https://dart-review.googlesource.com/c/87140
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/lint/options_rule_validator.dart b/pkg/analyzer/lib/src/lint/options_rule_validator.dart
index 0858037..9b28b1a 100644
--- a/pkg/analyzer/lib/src/lint/options_rule_validator.dart
+++ b/pkg/analyzer/lib/src/lint/options_rule_validator.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:collection';
+
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/analysis_options/error/option_codes.dart';
@@ -22,6 +24,17 @@
         "'{0}' is a deprecated lint rule and should not be used");
 
 /**
+ * Duplicate rules.
+ *
+ * Parameters:
+ * 0: the rule name
+ */
+const AnalysisOptionsHintCode DUPLICATE_RULE_HINT = const AnalysisOptionsHintCode(
+    'DUPLICATE_RULE',
+    "The rule {0} is already specified and doesn't need to be specified again.",
+    correction: "Try removing all but one specification of the rule.");
+
+/**
  * An error code indicating an undefined lint rule.
  *
  * Parameters:
@@ -48,6 +61,9 @@
   LinterRuleOptionsValidator({LintRuleProvider provider})
       : ruleProvider = provider ?? (() => Registry.ruleRegistry.rules);
 
+  LintRule getRegisteredLint(Object value) => ruleProvider()
+      .firstWhere((rule) => rule.name == value, orElse: () => null);
+
   @override
   List<AnalysisError> validate(ErrorReporter reporter, YamlMap options) {
     List<AnalysisError> errors = <AnalysisError>[];
@@ -61,6 +77,7 @@
 
   validateRules(YamlNode rules, ErrorReporter reporter) {
     if (rules is YamlList) {
+      Set<String> seenRules = new HashSet<String>();
       rules.nodes.forEach((YamlNode ruleNode) {
         Object value = ruleNode.value;
         if (value != null) {
@@ -68,6 +85,9 @@
           if (rule == null) {
             reporter.reportErrorForSpan(
                 UNDEFINED_LINT_WARNING, ruleNode.span, [value]);
+          } else if (!seenRules.add(rule.name)) {
+            reporter.reportErrorForSpan(
+                DUPLICATE_RULE_HINT, ruleNode.span, [value]);
           } else if (rule.maturity == Maturity.deprecated) {
             reporter.reportErrorForSpan(
                 DEPRECATED_LINT_HINT, ruleNode.span, [value]);
@@ -76,7 +96,4 @@
       });
     }
   }
-
-  LintRule getRegisteredLint(Object value) => ruleProvider()
-      .firstWhere((rule) => rule.name == value, orElse: () => null);
 }
diff --git a/pkg/analyzer/test/src/options/options_rule_validator_test.dart b/pkg/analyzer/test/src/options/options_rule_validator_test.dart
index fad56f3..5150828 100644
--- a/pkg/analyzer/test/src/options/options_rule_validator_test.dart
+++ b/pkg/analyzer/test/src/options/options_rule_validator_test.dart
@@ -27,12 +27,6 @@
             maturity: Maturity.deprecated);
 }
 
-class StableLint extends LintRule {
-  StableLint()
-      : super(
-            name: 'stable_lint', group: Group.style, maturity: Maturity.stable);
-}
-
 @reflectiveTest
 class OptionsRuleValidatorTest extends Object with ResourceProviderMixin {
   LinterRuleOptionsValidator validator = new LinterRuleOptionsValidator(
@@ -58,6 +52,15 @@
       ''', [DEPRECATED_LINT_HINT]);
   }
 
+  test_duplicated_rule() {
+    assertErrors('''
+linter:
+  rules:
+    - stable_lint
+    - stable_lint
+      ''', [DUPLICATE_RULE_HINT]);
+  }
+
   test_stable_rule() {
     assertErrors('''
 linter:
@@ -74,3 +77,9 @@
       ''', [UNDEFINED_LINT_WARNING]);
   }
 }
+
+class StableLint extends LintRule {
+  StableLint()
+      : super(
+            name: 'stable_lint', group: Group.style, maturity: Maturity.stable);
+}