Add fixes for a couple of hints

Change-Id: I2e1b6c74c30857788b3bfbfbca46fc932dd7817a
Reviewed-on: https://dart-review.googlesource.com/c/90440
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index e9bf18d..2f872621 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -201,6 +201,8 @@
       'MOVE_TYPE_ARGUMENTS_TO_CLASS',
       50,
       "Move type arguments to after class name");
+  static const REMOVE_ANNOTATION =
+      const FixKind('REMOVE_ANNOTATION', 50, "Remove the '{0}' annotation");
   static const REMOVE_AWAIT = const FixKind('REMOVE_AWAIT', 50, "Remove await");
   static const REMOVE_DEAD_CODE =
       const FixKind('REMOVE_DEAD_CODE', 50, "Remove dead code");
@@ -220,6 +222,8 @@
       "Remove unnecessary interpolation braces");
   static const REMOVE_METHOD_DECLARATION = const FixKind(
       'REMOVE_METHOD_DECLARATION', 50, "Remove method declaration");
+  static const REMOVE_NAME_FROM_COMBINATOR = const FixKind(
+      'REMOVE_NAME_FROM_COMBINATOR', 50, "Remove name from '{0}'");
   static const REMOVE_PARAMETERS_IN_GETTER_DECLARATION = const FixKind(
       'REMOVE_PARAMETERS_IN_GETTER_DECLARATION',
       50,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 0db0fb2..2d8e769 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -282,27 +282,107 @@
     if (errorCode == HintCode.DEAD_CODE) {
       await _addFix_removeDeadCode();
     }
+    if (errorCode == HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH ||
+        errorCode == HintCode.DEAD_CODE_ON_CATCH_SUBTYPE) {
+      await _addFix_removeDeadCode();
+      // TODO(brianwilkerson) Add a fix to move the unreachable catch clause to
+      //  a place where it can be reached (when possible).
+    }
+    // TODO(brianwilkerson) Define a syntax for deprecated members to indicate
+    //  how to update the code and implement a fix to apply the update.
+//    if (errorCode == HintCode.DEPRECATED_MEMBER_USE ||
+//        errorCode == HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE) {
+//      await _addFix_replaceDeprecatedMemberUse();
+//    }
     if (errorCode == HintCode.DIVISION_OPTIMIZATION) {
       await _addFix_useEffectiveIntegerDivision();
     }
+    if (errorCode == HintCode.DUPLICATE_IMPORT) {
+      await _addFix_removeUnusedImport();
+    }
+    if (errorCode == HintCode.DUPLICATE_HIDDEN_NAME ||
+        errorCode == HintCode.DUPLICATE_SHOWN_NAME) {
+      await _addFix_removeNameFromCombinator();
+    }
+    // TODO(brianwilkerson) Add a fix to convert the path to a package: import.
+//    if (errorCode == HintCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE) {
+//      await _addFix_convertPathToPackageUri();
+//    }
+    if (errorCode == HintCode.INVALID_FACTORY_ANNOTATION ||
+        errorCode == HintCode.INVALID_IMMUTABLE_ANNOTATION ||
+        errorCode == HintCode.INVALID_LITERAL_ANNOTATION ||
+        errorCode == HintCode.INVALID_REQUIRED_PARAM ||
+        errorCode == HintCode.INVALID_SEALED_ANNOTATION) {
+      await _addFix_removeAnnotation();
+    }
+    if (errorCode == HintCode.MISSING_REQUIRED_PARAM ||
+        errorCode == HintCode.MISSING_REQUIRED_PARAM_WITH_DETAILS) {
+      await _addFix_addMissingRequiredArgument();
+    }
+    if (errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER ||
+        errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD ||
+        errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD ||
+        errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER) {
+      await _addFix_removeAnnotation();
+    }
+    // TODO(brianwilkerson) Add a fix to normalize the path.
+//    if (errorCode == HintCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT) {
+//      await _addFix_normalizeUri();
+//    }
+    if (errorCode == HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE) {
+      await _addFix_importAsync();
+      await _addFix_updateSdkConstraints();
+    }
     if (errorCode == HintCode.TYPE_CHECK_IS_NOT_NULL) {
       await _addFix_isNotNull();
     }
     if (errorCode == HintCode.TYPE_CHECK_IS_NULL) {
       await _addFix_isNull();
     }
+    if (errorCode == HintCode.UNDEFINED_HIDDEN_NAME ||
+        errorCode == HintCode.UNDEFINED_SHOWN_NAME) {
+      await _addFix_removeNameFromCombinator();
+    }
     if (errorCode == HintCode.UNNECESSARY_CAST) {
       await _addFix_removeUnnecessaryCast();
     }
+    // TODO(brianwilkerson) Add a fix to remove the method.
+//    if (errorCode == HintCode.UNNECESSARY_NO_SUCH_METHOD) {
+//      await _addFix_removeMethodDeclaration();
+//    }
+    // TODO(brianwilkerson) Add a fix to remove the type check.
+//    if (errorCode == HintCode.UNNECESSARY_TYPE_CHECK_FALSE ||
+//        errorCode == HintCode.UNNECESSARY_TYPE_CHECK_TRUE) {
+//      await _addFix_removeUnnecessaryTypeCheck();
+//    }
     if (errorCode == HintCode.UNUSED_CATCH_CLAUSE) {
       await _addFix_removeUnusedCatchClause();
     }
     if (errorCode == HintCode.UNUSED_CATCH_STACK) {
       await _addFix_removeUnusedCatchStack();
     }
+    // TODO(brianwilkerson) Add a fix to remove the declaration. Decide whether
+    //  this should be a single general fix, or multiple more specific fixes
+    //  such as [_addFix_removeMethodDeclaration].
+//    if (errorCode == HintCode.UNUSED_ELEMENT ||
+//        errorCode == HintCode.UNUSED_FIELD) {
+//      await _addFix_removeUnusedDeclaration();
+//    }
     if (errorCode == HintCode.UNUSED_IMPORT) {
       await _addFix_removeUnusedImport();
     }
+    // TODO(brianwilkerson) Add a fix to remove the label.
+//    if (errorCode == HintCode.UNUSED_LABEL) {
+//      await _addFix_removeUnusedLabel();
+//    }
+    // TODO(brianwilkerson) Add a fix to remove the local variable, either with
+    //  or without the initialization code.
+//    if (errorCode == HintCode.UNUSED_LOCAL_VARIABLE) {
+//      await _addFix_removeUnusedLocalVariable();
+//    }
+    if (errorCode == HintCode.UNUSED_SHOWN_NAME) {
+      await _addFix_removeNameFromCombinator();
+    }
     if (errorCode == ParserErrorCode.EXPECTED_TOKEN) {
       await _addFix_insertSemicolon();
     }
@@ -327,10 +407,6 @@
       await _addFix_createConstructor_insteadOfSyntheticDefault();
       await _addFix_addMissingParameter();
     }
-    if (errorCode == HintCode.MISSING_REQUIRED_PARAM ||
-        errorCode == HintCode.MISSING_REQUIRED_PARAM_WITH_DETAILS) {
-      await _addFix_addMissingRequiredArgument();
-    }
     if (errorCode == StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR) {
       await _addFix_createConstructor_named();
     }
@@ -469,10 +545,6 @@
         CompileTimeErrorCode.MIXIN_APPLICATION_NOT_IMPLEMENTED_INTERFACE) {
       await _addFix_extendClassForMixin();
     }
-    if (errorCode == HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE) {
-      await _addFix_importAsync();
-      await _addFix_updateSdkConstraints();
-    }
     // lints
     if (errorCode is LintCode) {
       String name = errorCode.name;
@@ -2509,6 +2581,49 @@
     _addFixFromBuilder(changeBuilder, DartFixKind.ADD_NE_NULL);
   }
 
+  Future<void> _addFix_removeAnnotation() async {
+    void addFix(Annotation node) async {
+      if (node == null) {
+        return;
+      }
+      Token followingToken = node.endToken.next;
+      followingToken = followingToken.precedingComments ?? followingToken;
+      DartChangeBuilder changeBuilder = _newDartChangeBuilder();
+      await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
+        builder.addDeletion(range.startStart(node, followingToken));
+      });
+      _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_ANNOTATION,
+          args: [node.name.name]);
+    }
+
+    Annotation findAnnotation(
+        NodeList<Annotation> metadata, String targetName) {
+      return metadata.firstWhere(
+          (annotation) => annotation.name.name == targetName,
+          orElse: () => null);
+    }
+
+    AstNode node = this.coveredNode;
+    if (node is Annotation) {
+      await addFix(node);
+    } else if (node is DefaultFormalParameter) {
+      await addFix(findAnnotation(node.parameter.metadata, 'required'));
+    } else if (node is NormalFormalParameter) {
+      await addFix(findAnnotation(node.metadata, 'required'));
+    } else if (node is DeclaredSimpleIdentifier) {
+      AstNode parent = node.parent;
+      if (parent is MethodDeclaration) {
+        await addFix(findAnnotation(parent.metadata, 'override'));
+      } else if (parent is VariableDeclaration) {
+        FieldDeclaration fieldDeclaration =
+            parent.thisOrAncestorOfType<FieldDeclaration>();
+        if (fieldDeclaration != null) {
+          await addFix(findAnnotation(fieldDeclaration.metadata, 'override'));
+        }
+      }
+    }
+  }
+
   Future<void> _addFix_removeAwait() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
@@ -2524,8 +2639,6 @@
   }
 
   Future<void> _addFix_removeDeadCode() async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
     AstNode coveringNode = this.coveredNode;
     if (coveringNode is Expression) {
       AstNode parent = coveredNode.parent;
@@ -2564,6 +2677,17 @@
         builder.addDeletion(rangeToRemove);
       });
       _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_DEAD_CODE);
+    } else if (coveringNode is CatchClause) {
+      TryStatement tryStatement = coveringNode.parent;
+      NodeList<CatchClause> catchClauses = tryStatement.catchClauses;
+      int index = catchClauses.indexOf(coveringNode);
+      AstNode previous =
+          index == 0 ? tryStatement.body : catchClauses[index - 1];
+      DartChangeBuilder changeBuilder = _newDartChangeBuilder();
+      await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
+        builder.addDeletion(range.endEnd(previous, coveringNode));
+      });
+      _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_DEAD_CODE);
     }
   }
 
@@ -2673,6 +2797,68 @@
     }
   }
 
+  Future<void> _addFix_removeNameFromCombinator() async {
+    SourceRange rangeForCombinator(Combinator combinator) {
+      AstNode parent = combinator.parent;
+      if (parent is NamespaceDirective) {
+        NodeList<Combinator> combinators = parent.combinators;
+        if (combinators.length == 1) {
+          Token previousToken =
+              combinator.parent.findPrevious(combinator.beginToken);
+          return range.endEnd(previousToken, combinator);
+        }
+        int index = combinators.indexOf(combinator);
+        if (index < 0) {
+          return null;
+        } else if (index == combinators.length - 1) {
+          return range.endEnd(combinators[index - 1], combinator);
+        }
+        return range.startStart(combinator, combinators[index + 1]);
+      }
+      return null;
+    }
+
+    SourceRange rangeForNameInCombinator(
+        Combinator combinator, SimpleIdentifier name) {
+      NodeList<SimpleIdentifier> names;
+      if (combinator is HideCombinator) {
+        names = combinator.hiddenNames;
+      } else if (combinator is ShowCombinator) {
+        names = combinator.shownNames;
+      } else {
+        return null;
+      }
+      if (names.length == 1) {
+        return rangeForCombinator(combinator);
+      }
+      int index = names.indexOf(name);
+      if (index < 0) {
+        return null;
+      } else if (index == names.length - 1) {
+        return range.endEnd(names[index - 1], name);
+      }
+      return range.startStart(name, names[index + 1]);
+    }
+
+    AstNode node = this.coveredNode;
+    if (node is SimpleIdentifier) {
+      AstNode parent = coveredNode.parent;
+      if (parent is Combinator) {
+        SourceRange rangeToRemove = rangeForNameInCombinator(parent, node);
+        if (rangeToRemove == null) {
+          return;
+        }
+        DartChangeBuilder changeBuilder = _newDartChangeBuilder();
+        await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
+          builder.addDeletion(rangeToRemove);
+        });
+        _addFixFromBuilder(
+            changeBuilder, DartFixKind.REMOVE_NAME_FROM_COMBINATOR,
+            args: [parent is HideCombinator ? 'hide' : 'show']);
+      }
+    }
+  }
+
   Future<void> _addFix_removeParameters_inGetterDeclaration() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
diff --git a/pkg/analysis_server/test/abstract_context.dart b/pkg/analysis_server/test/abstract_context.dart
index 2d94d63..d9ffdb2 100644
--- a/pkg/analysis_server/test/abstract_context.dart
+++ b/pkg/analysis_server/test/abstract_context.dart
@@ -67,17 +67,67 @@
     addPackageFile('meta', 'meta.dart', r'''
 library meta;
 
+const _AlwaysThrows alwaysThrows = const _AlwaysThrows();
+
+@deprecated
+const _Checked checked = const _Checked();
+
+const _Experimental experimental = const _Experimental();
+
+const _Factory factory = const _Factory();
+
+const Immutable immutable = const Immutable();
+
 const _IsTest isTest = const _IsTest();
 
 const _IsTestGroup isTestGroup = const _IsTestGroup();
 
+const _Literal literal = const _Literal();
+
+const _MustCallSuper mustCallSuper = const _MustCallSuper();
+
+const _OptionalTypeArgs optionalTypeArgs = const _OptionalTypeArgs();
+
+const _Protected protected = const _Protected();
+
 const Required required = const Required();
 
+const _Sealed sealed = const _Sealed();
+
+@deprecated
+const _Virtual virtual = const _Virtual();
+
+const _VisibleForOverriding visibleForOverriding =
+    const _VisibleForOverriding();
+
+const _VisibleForTesting visibleForTesting = const _VisibleForTesting();
+
+class Immutable {
+  final String reason;
+  const Immutable([this.reason]);
+}
+
 class Required {
   final String reason;
   const Required([this.reason]);
 }
 
+class _AlwaysThrows {
+  const _AlwaysThrows();
+}
+
+class _Checked {
+  const _Checked();
+}
+
+class _Experimental {
+  const _Experimental();
+}
+
+class _Factory {
+  const _Factory();
+}
+
 class _IsTest {
   const _IsTest();
 }
@@ -85,6 +135,39 @@
 class _IsTestGroup {
   const _IsTestGroup();
 }
+
+class _Literal {
+  const _Literal();
+}
+
+class _MustCallSuper {
+  const _MustCallSuper();
+}
+
+class _OptionalTypeArgs {
+  const _OptionalTypeArgs();
+}
+
+class _Protected {
+  const _Protected();
+}
+
+class _Sealed {
+  const _Sealed();
+}
+
+@deprecated
+class _Virtual {
+  const _Virtual();
+}
+
+class _VisibleForOverriding {
+  const _VisibleForOverriding();
+}
+
+class _VisibleForTesting {
+  const _VisibleForTesting();
+}
 ''');
   }
 
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_annotation_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_annotation_test.dart
new file mode 100644
index 0000000..1874b6f
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_annotation_test.dart
@@ -0,0 +1,178 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// 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 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(RemoveAnnotationTest);
+  });
+}
+
+@reflectiveTest
+class RemoveAnnotationTest extends FixProcessorTest {
+  @override
+  FixKind get kind => DartFixKind.REMOVE_ANNOTATION;
+
+  @override
+  void setUp() {
+    super.setUp();
+    addMetaPackage();
+  }
+
+  test_factory() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+@factory
+f() {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f() {}
+''');
+  }
+
+  test_immutable() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+@immutable
+f() {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f() {}
+''');
+  }
+
+  test_literal() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+@literal
+f() {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f() {}
+''');
+  }
+
+  test_override_field() async {
+    await resolveTestUnit('''
+class A {
+  @override
+  String name;
+}
+''');
+    await assertHasFix('''
+class A {
+  String name;
+}
+''');
+  }
+
+  test_override_getter() async {
+    await resolveTestUnit('''
+class A {
+  @override
+  int get zero => 0;
+}
+''');
+    await assertHasFix('''
+class A {
+  int get zero => 0;
+}
+''');
+  }
+
+  test_override_method() async {
+    await resolveTestUnit('''
+class A {
+  @override
+  void m() {}
+}
+''');
+    await assertHasFix('''
+class A {
+  void m() {}
+}
+''');
+  }
+
+  test_override_setter() async {
+    await resolveTestUnit('''
+class A {
+  @override
+  set value(v) {}
+}
+''');
+    await assertHasFix('''
+class A {
+  set value(v) {}
+}
+''');
+  }
+
+  test_required_namedWithDefault() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+f({@required int x = 0}) {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f({int x = 0}) {}
+''');
+  }
+
+  test_required_positional() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+f([@required int x]) {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f([int x]) {}
+''');
+  }
+
+  test_required_required() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+f(@required int x) {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f(int x) {}
+''');
+  }
+
+  test_sealed() async {
+    await resolveTestUnit('''
+import 'package:meta/meta.dart';
+
+@sealed
+f() {}
+''');
+    await assertHasFix('''
+import 'package:meta/meta.dart';
+
+f() {}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_dead_code_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_dead_code_test.dart
index eec8f7c..7f2fc41 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_dead_code_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_dead_code_test.dart
@@ -19,6 +19,73 @@
   @override
   FixKind get kind => DartFixKind.REMOVE_DEAD_CODE;
 
+  test_catch_afterCatchAll_catch() async {
+    await resolveTestUnit('''
+main() {
+  try {
+  } catch (e) {
+    print('a');
+  } catch (e) {
+    print('b');
+  }
+}
+''');
+    await assertHasFix('''
+main() {
+  try {
+  } catch (e) {
+    print('a');
+  }
+}
+''');
+  }
+
+  test_catch_afterCatchAll_on() async {
+    await resolveTestUnit('''
+main() {
+  try {
+  } on Object {
+    print('a');
+  } catch (e) {
+    print('b');
+  }
+}
+''');
+    await assertHasFix('''
+main() {
+  try {
+  } on Object {
+    print('a');
+  }
+}
+''');
+  }
+
+  test_catch_subtype() async {
+    await resolveTestUnit('''
+class A {}
+class B extends A {}
+main() {
+  try {
+  } on A {
+    print('a');
+  } on B {
+    print('b');
+  }
+}
+''');
+    await assertHasFix('''
+class A {}
+class B extends A {}
+main() {
+  try {
+  } on A {
+    print('a');
+  }
+}
+''');
+  }
+
   test_condition() async {
     await resolveTestUnit('''
 main(int p) {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_name_from_combinator_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_name_from_combinator_test.dart
new file mode 100644
index 0000000..3ed155e
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_name_from_combinator_test.dart
@@ -0,0 +1,357 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// 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 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(RemoveNameFromCombinatorTest);
+  });
+}
+
+@reflectiveTest
+class RemoveNameFromCombinatorTest extends FixProcessorTest {
+  @override
+  FixKind get kind => DartFixKind.REMOVE_NAME_FROM_COMBINATOR;
+
+  test_duplicateHiddenName_last() async {
+    await resolveTestUnit('''
+import 'dart:math' hide cos, sin, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  test_duplicateHiddenName_middle() async {
+    await resolveTestUnit('''
+import 'dart:math' hide cos, cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  @failingTest
+  test_duplicateHiddenName_only_last() async {
+    // It appears that the hint does not detect names that are duplicated across
+    // multiple combinators.
+    await resolveTestUnit('''
+import 'dart:math' hide cos, sin hide sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  @failingTest
+  test_duplicateHiddenName_only_middle() async {
+    // It appears that the hint does not detect names that are duplicated across
+    // multiple combinators.
+    await resolveTestUnit('''
+import 'dart:math' hide cos hide cos hide sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos hide sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  test_duplicateShownName_last() async {
+    await resolveTestUnit(
+      '''
+import 'dart:math' show cos, sin, sin;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''',
+    );
+    await assertHasFix('''
+import 'dart:math' show cos, sin;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+  }
+
+  test_duplicateShownName_middle() async {
+    await resolveTestUnit('''
+import 'dart:math' show cos, cos, sin;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show cos, sin;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+  }
+
+  test_undefinedHiddenName_first() async {
+    await resolveTestUnit('''
+import 'dart:math' hide aaa, sin, tan;
+
+f(x) {
+  print(cos(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide sin, tan;
+
+f(x) {
+  print(cos(x));
+}
+''');
+  }
+
+  test_undefinedHiddenName_last() async {
+    await resolveTestUnit('''
+import 'dart:math' hide cos, sin, xxx;
+
+f(x) {
+  print(tan(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, sin;
+
+f(x) {
+  print(tan(x));
+}
+''');
+  }
+
+  test_undefinedHiddenName_middle() async {
+    await resolveTestUnit('''
+import 'dart:math' hide cos, mmm, tan;
+
+f(x) {
+  print(sin(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, tan;
+
+f(x) {
+  print(sin(x));
+}
+''');
+  }
+
+  test_undefinedHiddenName_only_first() async {
+    await resolveTestUnit('''
+import 'dart:math' hide aaa hide cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  test_undefinedHiddenName_only_last() async {
+    await resolveTestUnit('''
+import 'dart:math' hide cos, sin hide aaa;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos, sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  test_undefinedHiddenName_only_middle() async {
+    await resolveTestUnit('''
+import 'dart:math' hide cos hide aaa hide sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' hide cos hide sin;
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
+  test_undefinedHiddenName_only_only() async {
+    await resolveTestUnit('''
+import 'dart:math' hide aaa;
+var c = sin(0.3);
+''');
+    await assertHasFix('''
+import 'dart:math';
+var c = sin(0.3);
+''');
+  }
+
+  test_undefinedHiddenName_only_only_withAs() async {
+    await resolveTestUnit('''
+import 'dart:math' as math hide aaa;
+var c = math.sin(0.3);
+''');
+    await assertHasFix('''
+import 'dart:math' as math;
+var c = math.sin(0.3);
+''');
+  }
+
+  test_undefinedShownName_first() async {
+    await resolveTestUnit('''
+import 'dart:math' show aaa, sin, tan;
+
+f(x) {
+  print(sin(x) + tan(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show sin, tan;
+
+f(x) {
+  print(sin(x) + tan(x));
+}
+''');
+  }
+
+  test_undefinedShownName_last() async {
+    await resolveTestUnit('''
+import 'dart:math' show cos, sin, xxx;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show cos, sin;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+  }
+
+  test_undefinedShownName_middle() async {
+    await resolveTestUnit('''
+import 'dart:math' show cos, mmm, tan;
+
+f(x) {
+  print(cos(x) + tan(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show cos, tan;
+
+f(x) {
+  print(cos(x) + tan(x));
+}
+''');
+  }
+
+  test_unusedShownName_first() async {
+    await resolveTestUnit('''
+import 'dart:math' show cos, sin, tan;
+
+f(x) {
+  print(sin(x) + tan(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show sin, tan;
+
+f(x) {
+  print(sin(x) + tan(x));
+}
+''');
+  }
+
+  test_unusedShownName_last() async {
+    await resolveTestUnit('''
+import 'dart:math' show cos, sin, tan;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show cos, sin;
+
+f(x) {
+  print(cos(x) + sin(x));
+}
+''');
+  }
+
+  test_unusedShownName_middle() async {
+    await resolveTestUnit('''
+import 'dart:math' show cos, sin, tan;
+
+f(x) {
+  print(cos(x) + tan(x));
+}
+''');
+    await assertHasFix('''
+import 'dart:math' show cos, tan;
+
+f(x) {
+  print(cos(x) + tan(x));
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
index 96ee02f..e7a4db9 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
@@ -86,6 +86,24 @@
 ''');
   }
 
+  test_duplicateImport() async {
+    await resolveTestUnit('''
+import 'dart:math';
+import 'dart:math';
+
+main() {
+  print(min(0, 1));
+}
+''');
+    await assertHasFix('''
+import 'dart:math';
+
+main() {
+  print(min(0, 1));
+}
+''');
+  }
+
   test_multipleOfSame_all() async {
     await resolveTestUnit('''
 import 'dart:math';
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index de6f043..30cad40 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -54,6 +54,7 @@
 import 'make_field_not_final_test.dart' as make_field_not_final;
 import 'make_final_test.dart' as make_final;
 import 'move_type_arguments_to_class_test.dart' as move_type_arguments_to_class;
+import 'remove_annotation_test.dart' as remove_annotation;
 import 'remove_await_test.dart' as remove_await;
 import 'remove_dead_code_test.dart' as remove_dead_code;
 import 'remove_empty_catch_test.dart' as remove_empty_catch;
@@ -64,6 +65,7 @@
 import 'remove_initializer_test.dart' as remove_initializer;
 import 'remove_interpolation_braces_test.dart' as remove_interpolation_braces;
 import 'remove_method_declaration_test.dart' as remove_method_declaration;
+import 'remove_name_from_combinator_test.dart' as remove_name_from_combinator;
 import 'remove_parameters_in_getter_declaration_test.dart'
     as remove_parameters_in_getter_declaration;
 import 'remove_parentheses_in_getter_invocation_test.dart'
@@ -141,6 +143,7 @@
     make_field_not_final.main();
     make_final.main();
     move_type_arguments_to_class.main();
+    remove_annotation.main();
     remove_await.main();
     remove_dead_code.main();
     remove_empty_catch.main();
@@ -150,6 +153,7 @@
     remove_initializer.main();
     remove_interpolation_braces.main();
     remove_method_declaration.main();
+    remove_name_from_combinator.main();
     remove_parameters_in_getter_declaration.main();
     remove_parentheses_in_getter_invocation.main();
     remove_this_expression.main();
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 2147e57..a0c3768 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -338,8 +338,8 @@
       }
     } else if (element?.isSealed == true) {
       if (!(parent is ClassDeclaration || parent is ClassTypeAlias)) {
-        _errorReporter.reportErrorForNode(HintCode.INVALID_SEALED_ANNOTATION,
-            node.parent, [node.element.name]);
+        _errorReporter.reportErrorForNode(
+            HintCode.INVALID_SEALED_ANNOTATION, node, [node.element.name]);
       }
     }
     super.visitAnnotation(node);
@@ -3382,26 +3382,31 @@
     }
   }
 
-  /// Report an [HintCode.UNUSED_SHOWN_NAME] hint for each unused shown name.
+  /// Use the error [reporter] to report an [HintCode.UNUSED_SHOWN_NAME] hint
+  /// for each unused shown name.
   ///
-  /// Only call this method after all of the compilation units have been visited
-  /// by this visitor.
-  ///
-  /// @param errorReporter the error reporter used to report the set of
-  ///        [HintCode.UNUSED_SHOWN_NAME] hints
+  /// This method should only be invoked after all of the compilation units have
+  /// been visited by this visitor.
   void generateUnusedShownNameHints(ErrorReporter reporter) {
     _unusedShownNamesMap.forEach(
         (ImportDirective importDirective, List<SimpleIdentifier> identifiers) {
       if (_unusedImports.contains(importDirective)) {
-        // This import is actually wholly unused, not just one or more shown names from it.
-        // This is then an "unused import", rather than unused shown names.
+        // The whole import is unused, not just one or more shown names from it,
+        // so an "unused_import" hint will be generated, making it unnecessary
+        // to generate hints for the individual names.
         return;
       }
       int length = identifiers.length;
       for (int i = 0; i < length; i++) {
         Identifier identifier = identifiers[i];
-        reporter.reportErrorForNode(
-            HintCode.UNUSED_SHOWN_NAME, identifier, [identifier.name]);
+        List<SimpleIdentifier> duplicateNames =
+            _duplicateShownNamesMap[importDirective];
+        if (duplicateNames == null || !duplicateNames.contains(identifier)) {
+          // Only generate a hint if we won't also generate a
+          // "duplicate_shown_name" hint for the same identifier.
+          reporter.reportErrorForNode(
+              HintCode.UNUSED_SHOWN_NAME, identifier, [identifier.name]);
+        }
       }
     });
   }
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index b68d13e..7420ee9 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -435,6 +435,7 @@
 external double cos(num radians);
 external double sin(num radians);
 external double sqrt(num radians);
+external double tan(num radians);
 class Random {
   bool nextBool() => true;
   double nextDouble() => 2.0;