Support for override completions in mixins.

Also moves tests for override implementations into a separate class.
New tests:
  test_mixin_method_of_interface
  test_mixin_method_of_superclassConstraint

Changes for deciding whether to generate `super` invocation.

R=brianwilkerson@google.com

Change-Id: I7f875695ce81f0212863fbd2f9fe1f1b9d7aaa3f
Reviewed-on: https://dart-review.googlesource.com/c/90784
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart
index 99eb8b5..22692c1 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart
@@ -9,9 +9,7 @@
 import 'package:analysis_server/src/protocol_server.dart' as protocol
     hide CompletionSuggestion, CompletionSuggestionKind;
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
-import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
 import 'package:analyzer/src/generated/source.dart';
@@ -33,30 +31,29 @@
     if (targetId == null) {
       return const <CompletionSuggestion>[];
     }
-    ClassDeclaration classDecl =
-        targetId.thisOrAncestorOfType<ClassDeclaration>();
+    var classDecl = targetId.thisOrAncestorOfType<ClassOrMixinDeclaration>();
     if (classDecl == null) {
       return const <CompletionSuggestion>[];
     }
 
-    // TODO(brianwilkerson) Consider making the type system visible from the
-    // request.result.
-    var inheritance = new InheritanceManager2(
-        await request.result.libraryElement.session.typeSystem);
+    var inheritance = new InheritanceManager2(request.result.typeSystem);
 
     // Generate a collection of inherited members
-    ClassElement classElem = classDecl.declaredElement;
-    var interface = inheritance.getInterface(classElem.type).map;
-    var namesToOverride = _namesToOverride(classElem, interface.keys);
+    var classElem = classDecl.declaredElement;
+    var interface = inheritance.getInterface(classElem.type);
+    var interfaceMap = interface.map;
+    var namesToOverride =
+        _namesToOverride(classElem.librarySource.uri, interface);
 
     // Build suggestions
     List<CompletionSuggestion> suggestions = <CompletionSuggestion>[];
     for (Name name in namesToOverride) {
-      FunctionType signature = interface[name];
+      FunctionType signature = interfaceMap[name];
       // Gracefully degrade if the overridden element has not been resolved.
       if (signature.returnType != null) {
-        CompletionSuggestion suggestion =
-            await _buildSuggestion(request, targetId, signature);
+        var invokeSuper = interface.isSuperImplemented(name);
+        var suggestion =
+            await _buildSuggestion(request, targetId, signature, invokeSuper);
         if (suggestion != null) {
           suggestions.add(suggestion);
         }
@@ -66,41 +63,26 @@
   }
 
   /**
-   * Return a template for an override of the given [signature]. If selected,
-   * the template will replace [targetId].
+   * Build a suggestion to replace [targetId] in the given [request] with an
+   * override of the given [signature].
    */
-  Future<DartChangeBuilder> _buildReplacementText(
-      ResolvedUnitResult result,
+  Future<CompletionSuggestion> _buildSuggestion(
+      DartCompletionRequest request,
       SimpleIdentifier targetId,
       FunctionType signature,
-      StringBuffer displayTextBuffer) async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
-    DartChangeBuilder builder = new DartChangeBuilder(result.session);
-    await builder.addFileEdit(result.path, (DartFileEditBuilder builder) {
-      builder.addReplacement(range.node(targetId), (DartEditBuilder builder) {
-        ExecutableElement element = signature.element;
+      bool invokeSuper) async {
+    var displayTextBuffer = new StringBuffer();
+    var builder = new DartChangeBuilder(request.result.session);
+    await builder.addFileEdit(request.result.path, (builder) {
+      builder.addReplacement(range.node(targetId), (builder) {
         builder.writeOverride(
           signature,
           displayTextBuffer: displayTextBuffer,
-          invokeSuper: !element.isAbstract,
+          invokeSuper: invokeSuper,
         );
       });
     });
-    return builder;
-  }
 
-  /**
-   * Build a suggestion to replace [targetId] in the given [unit]
-   * with an override of the given [signature].
-   */
-  Future<CompletionSuggestion> _buildSuggestion(DartCompletionRequest request,
-      SimpleIdentifier targetId, FunctionType signature) async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
-    StringBuffer displayTextBuffer = new StringBuffer();
-    DartChangeBuilder builder = await _buildReplacementText(
-        request.result, targetId, signature, displayTextBuffer);
     String replacement = builder.sourceChange.edits[0].edits[0].replacement;
     String completion = replacement.trim();
     String overrideAnnotation = '@override';
@@ -139,7 +121,7 @@
    */
   SimpleIdentifier _getTargetId(CompletionTarget target) {
     AstNode node = target.containingNode;
-    if (node is ClassDeclaration) {
+    if (node is ClassOrMixinDeclaration) {
       Object entity = target.entity;
       if (entity is FieldDeclaration) {
         return _getTargetIdFromVarList(entity.fields);
@@ -175,17 +157,6 @@
   }
 
   /**
-   * Return `true` if the given [classElement] directly declares a member with
-   * the given [memberName].
-   */
-  bool _hasMember(ClassElement classElement, String memberName) {
-    return classElement.getField(memberName) != null ||
-        classElement.getGetter(memberName) != null ||
-        classElement.getMethod(memberName) != null ||
-        classElement.getSetter(memberName) != null;
-  }
-
-  /**
    * Return `true` if the given [node] has an `override` annotation.
    */
   bool _hasOverride(AstNode node) {
@@ -202,16 +173,14 @@
   }
 
   /**
-   * Return a list containing the subset of [interfaceNames] that are not
-   * defined yet in the given [classElement].
+   * Return the list of names that belong to the [interface] of a class, but
+   * are not yet declared in the class.
    */
-  List<Name> _namesToOverride(
-      ClassElement classElement, Iterable<Name> interfaceNames) {
-    var libraryUri = classElement.library.source.uri;
+  List<Name> _namesToOverride(Uri libraryUri, Interface interface) {
     var namesToOverride = <Name>[];
-    for (var name in interfaceNames) {
+    for (var name in interface.map.keys) {
       if (name.isAccessibleFor(libraryUri)) {
-        if (!_hasMember(classElement, name.name)) {
+        if (!interface.declared.containsKey(name)) {
           namesToOverride.add(name);
         }
       }
diff --git a/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart
index 49a5e34..076ae44 100644
--- a/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart
@@ -21,6 +21,22 @@
     return new OverrideContributor();
   }
 
+  test_alreadyOverridden() async {
+    addTestSource('''
+class A {
+  void foo() {}
+  void bar() {}
+}
+
+class B implements A {
+  void bar() {}
+  f^
+}
+''');
+    await computeSuggestions();
+    _assertNoOverrideContaining('bar');
+  }
+
   test_fromMultipleSuperclasses() async {
     addTestSource(r'''
 class A {
@@ -152,6 +168,61 @@
         selectionLength: 27);
   }
 
+  test_inClass_of_interface() async {
+    addTestSource('''
+class A {
+  void foo() {}
+}
+
+class B implements A {
+  f^
+}
+''');
+    await computeSuggestions();
+    _assertOverride('''
+@override
+  void foo() {
+    // TODO: implement foo
+  }''', displayText: 'foo() { … }', selectionOffset: 51, selectionLength: 0);
+  }
+
+  test_inMixin_of_interface() async {
+    addTestSource('''
+class A {
+  void foo() {}
+}
+
+mixin M implements A {
+  f^
+}
+''');
+    await computeSuggestions();
+    _assertOverride('''
+@override
+  void foo() {
+    // TODO: implement foo
+  }''', displayText: 'foo() { … }', selectionOffset: 51, selectionLength: 0);
+  }
+
+  test_inMixin_of_superclassConstraint() async {
+    addTestSource('''
+class A {
+  void foo() {}
+}
+
+mixin M on A {
+  f^
+}
+''');
+    await computeSuggestions();
+    _assertOverride('''
+@override
+  void foo() {
+    // TODO: implement foo
+    super.foo();
+  }''', displayText: 'foo() { … }', selectionOffset: 56, selectionLength: 12);
+  }
+
   @failingTest
   test_insideBareClass() async {
     addTestSource('''
@@ -278,6 +349,14 @@
         selectionLength: 22);
   }
 
+  void _assertNoOverrideContaining(String search) {
+    expect(
+        suggestions.where((c) =>
+            c.kind == CompletionSuggestionKind.OVERRIDE &&
+            c.completion.contains(search)),
+        isEmpty);
+  }
+
   CompletionSuggestion _assertOverride(String completion,
       {String displayText, int selectionOffset, int selectionLength}) {
     CompletionSuggestion cs = getSuggest(
diff --git a/pkg/analyzer/lib/src/dart/element/inheritance_manager2.dart b/pkg/analyzer/lib/src/dart/element/inheritance_manager2.dart
index 40f988c..8acdc92 100644
--- a/pkg/analyzer/lib/src/dart/element/inheritance_manager2.dart
+++ b/pkg/analyzer/lib/src/dart/element/inheritance_manager2.dart
@@ -450,6 +450,11 @@
     this._superImplemented,
     this.conflicts,
   );
+
+  /// Return `true` if the [name] is implemented in the supertype.
+  bool isSuperImplemented(Name name) {
+    return _superImplemented.last.containsKey(name);
+  }
 }
 
 /// A public name, or a private name qualified by a library URI.
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
index b9d641f..3cd8e09 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
@@ -29,6 +29,7 @@
     defineReflectiveTests(DartFileEditBuilderImplTest);
     defineReflectiveTests(DartLinkedEditBuilderImplTest);
     defineReflectiveTests(ImportLibraryTest);
+    defineReflectiveTests(WriteOverrideTest);
   });
 }
 
@@ -871,464 +872,6 @@
     expect(edit.replacement, equalsIgnoringWhitespace('mixin M on A { }'));
   }
 
-  test_writeOverride_getter_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  int get zero;
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'zero',
-      expected: '''
-  @override
-  // TODO: implement zero
-  int get zero => null;
-''',
-      displayText: 'zero => …',
-      selection: new SourceRange(111, 4),
-    );
-  }
-
-  test_writeOverride_getter_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  int get zero => 0;
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'zero',
-      expected: '''
-  @override
-  // TODO: implement zero
-  int get zero => super.zero;
-''',
-      displayText: 'zero => …',
-      selection: new SourceRange(107, 10),
-    );
-  }
-
-  test_writeOverride_method_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  A add(A a);
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'add',
-      expected: '''
-  @override
-  A add(A a) {
-    // TODO: implement add
-    return null;
-  }
-''',
-      displayText: 'add(A a) { … }',
-      selection: new SourceRange(111, 12),
-    );
-  }
-
-  test_writeOverride_method_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  A add(A a) => null;
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'add',
-      expected: '''
-  @override
-  A add(A a) {
-    // TODO: implement add
-    return super.add(a);
-  }
-''',
-      displayText: 'add(A a) { … }',
-      selection: new SourceRange(110, 20),
-    );
-  }
-
-  test_writeOverride_method_functionTypeAlias_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-typedef int F(int left, int right);
-abstract class A {
-  void perform(F f);
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'perform',
-      expected: '''
-  @override
-  void perform(F f) {
-    // TODO: implement perform
-  }
-''',
-      displayText: 'perform(F f) { … }',
-    );
-  }
-
-  test_writeOverride_method_functionTypeAlias_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-typedef int F(int left, int right);
-class A {
-  void perform(F f) {}
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'perform',
-      expected: '''
-  @override
-  void perform(F f) {
-    // TODO: implement perform
-    super.perform(f);
-  }
-''',
-      displayText: 'perform(F f) { … }',
-      selection: new SourceRange(158, 17),
-    );
-  }
-
-  test_writeOverride_method_functionTypedParameter_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  forEach(int f(double p1, String p2));
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'forEach',
-      expected: '''
-  @override
-  forEach(int Function(double p1, String p2) f) {
-    // TODO: implement forEach
-    return null;
-  }
-''',
-      displayText: 'forEach(int Function(double p1, String p2) f) { … }',
-      selection: new SourceRange(176, 12),
-    );
-  }
-
-  test_writeOverride_method_functionTypedParameter_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  forEach(int f(double p1, String p2)) {}
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'forEach',
-      expected: '''
-  @override
-  forEach(int Function(double p1, String p2) f) {
-    // TODO: implement forEach
-    return super.forEach(f);
-  }
-''',
-      displayText: 'forEach(int Function(double p1, String p2) f) { … }',
-      selection: new SourceRange(169, 24),
-    );
-  }
-
-  test_writeOverride_method_generic_noBounds_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  List<T> get<T>(T key);
-}
-class B implements A {
-}
-''',
-      nameToOverride: 'get',
-      expected: '''
-  @override
-  List<T> get<T>(T key) {
-    // TODO: implement get
-    return null;
-  }
-''',
-      displayText: 'get<T>(T key) { … }',
-      selection: new SourceRange(136, 12),
-    );
-  }
-
-  test_writeOverride_method_generic_noBounds_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  List<T> get<T>(T key) {}
-}
-class B implements A {
-}
-''',
-      nameToOverride: 'get',
-      expected: '''
-  @override
-  List<T> get<T>(T key) {
-    // TODO: implement get
-    return super.get(key);
-  }
-''',
-      displayText: 'get<T>(T key) { … }',
-      selection: new SourceRange(129, 22),
-    );
-  }
-
-  test_writeOverride_method_generic_withBounds_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A<K1, V1> {
-  List<T> get<T extends V1>(K1 key);
-}
-class B<K2, V2> implements A<K2, V2> {
-}
-''',
-      nameToOverride: 'get',
-      expected: '''
-  @override
-  List<T> get<T extends V2>(K2 key) {
-    // TODO: implement get
-    return null;
-  }
-''',
-      displayText: 'get<T extends V2>(K2 key) { … }',
-      selection: new SourceRange(184, 12),
-    );
-  }
-
-  test_writeOverride_method_generic_withBounds_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A<K1, V1> {
-  List<T> get<T extends V1>(K1 key) {
-    return null;
-  }
-}
-class B<K2, V2> implements A<K2, V2> {
-}
-''',
-      nameToOverride: 'get',
-      expected: '''
-  @override
-  List<T> get<T extends V2>(K2 key) {
-    // TODO: implement get
-    return super.get(key);
-  }
-''',
-      displayText: 'get<T extends V2>(K2 key) { … }',
-      selection: new SourceRange(197, 22),
-    );
-  }
-
-  test_writeOverride_method_genericFunctionTypedParameter_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  int foo(T Function<T>() fn);
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'foo',
-      expected: '''
-  @override
-  int foo(T Function<T>() fn) {
-    // TODO: implement foo
-    return null;
- }
-''',
-      displayText: 'foo(T Function<T>() fn) { … }',
-      selection: new SourceRange(145, 12),
-    );
-  }
-
-  test_writeOverride_method_genericFunctionTypedParameter_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  int foo(T Function<T>() fn) => 0;
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'foo',
-      expected: '''
-  @override
-  int foo(T Function<T>() fn) {
-    // TODO: implement foo
-    return super.foo(fn);
- }
-''',
-      displayText: 'foo(T Function<T>() fn) { … }',
-      selection: new SourceRange(141, 21),
-    );
-  }
-
-  test_writeOverride_method_nullAsTypeArgument_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  List<Null> foo();
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'foo',
-      expected: '''
-  @override
-  List<Null> foo() {
-    // TODO: implement foo
-    return null;
- }
-''',
-      displayText: 'foo() { … }',
-      selection: new SourceRange(123, 12),
-    );
-  }
-
-  test_writeOverride_method_nullAsTypeArgument_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  List<Null> foo() => null
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'foo',
-      expected: '''
-  @override
-  List<Null> foo() {
-    // TODO: implement foo
-    return super.foo();
- }
-''',
-      displayText: 'foo() { … }',
-      selection: new SourceRange(121, 19),
-    );
-  }
-
-  test_writeOverride_method_returnVoid_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  void test();
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'test',
-      expected: '''
-  @override
-  void test() {
-    // TODO: implement test
-  }
-''',
-      displayText: 'test() { … }',
-      selection: new SourceRange(109, 0),
-    );
-  }
-
-  test_writeOverride_method_voidAsTypeArgument_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  List<void> foo();
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'foo',
-      expected: '''
-  @override
-  List<void> foo() {
-    // TODO: implement foo
-    return null;
-  }
-''',
-      displayText: 'foo() { … }',
-      selection: new SourceRange(123, 12),
-    );
-  }
-
-  test_writeOverride_method_voidAsTypeArgument_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  List<void> foo() => null;
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'foo',
-      expected: '''
-  @override
-  List<void> foo() {
-    // TODO: implement foo
-    return super.foo();
-  }
-''',
-      displayText: 'foo() { … }',
-      selection: new SourceRange(122, 19),
-    );
-  }
-
-  test_writeOverride_setter_abstract() async {
-    await _assertWriteOverride(
-      content: '''
-abstract class A {
-  set value(int value);
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'value=',
-      expected: '''
-  @override
-  void set value(int value) {
-    // TODO: implement value
-  }
-''',
-      displayText: 'value(int value) { … }',
-      selection: new SourceRange(133, 0),
-    );
-  }
-
-  test_writeOverride_setter_concrete() async {
-    await _assertWriteOverride(
-      content: '''
-class A {
-  set value(int value) {}
-}
-class B extends A {
-}
-''',
-      nameToOverride: 'value=',
-      expected: '''
-  @override
-  void set value(int value) {
-    // TODO: implement value
-    super.value = value;
-  }
-''',
-      displayText: 'value(int value) { … }',
-      selection: new SourceRange(131, 20),
-    );
-  }
-
   test_writeParameter() async {
     String path = convertPath('/home/test/lib/test.dart');
     String content = 'class A {}';
@@ -1948,54 +1491,6 @@
     expect(edit.replacement, equalsIgnoringWhitespace('implements A, B'));
   }
 
-  /**
-   * Assuming that the [content] being edited defines a class named `A` whose
-   * member with the given [nameToOverride] to be overridden and has
-   * `class B extends A {...}` to which an inherited method is to be added,
-   * assert that the text of the overridden member matches the [expected] text
-   * (modulo white space). Assert that the generated display text matches the
-   * given [displayText]. If a [selection] is provided, assert that the
-   * generated selection range matches it.
-   */
-  _assertWriteOverride({
-    String content,
-    String nameToOverride,
-    String expected,
-    String displayText,
-    SourceRange selection,
-  }) async {
-    String path = convertPath('/home/test/lib/test.dart');
-    addSource(path, content);
-
-    TypeSystem typeSystem = await session.typeSystem;
-    var b = await _getClassElement(path, 'B');
-    var inherited = new InheritanceManager2(typeSystem).getInherited(
-      b.type,
-      new Name(null, nameToOverride),
-    );
-
-    StringBuffer displayBuffer =
-        displayText != null ? new StringBuffer() : null;
-
-    DartChangeBuilderImpl builder = newBuilder();
-    await builder.addFileEdit(path, (FileEditBuilder builder) {
-      builder.addInsertion(content.length - 2, (EditBuilder builder) {
-        ExecutableElement element = inherited.element as ExecutableElement;
-        (builder as DartEditBuilder).writeOverride(
-          inherited,
-          displayTextBuffer: displayBuffer,
-          invokeSuper: !element.isAbstract,
-        );
-      });
-    });
-    SourceEdit edit = getEdit(builder);
-    expect(edit.replacement, equalsIgnoringWhitespace(expected));
-    expect(displayBuffer?.toString(), displayText);
-    if (selection != null) {
-      expect(builder.selectionRange, selection);
-    }
-  }
-
   Future<void> _assertWriteType(String typeCode, {String declarations}) async {
     String path = convertPath('/home/test/lib/test.dart');
     String content = (declarations ?? '') + '$typeCode v;';
@@ -2562,3 +2057,583 @@
     expect(resultCode, expectedCode);
   }
 }
+
+@reflectiveTest
+class WriteOverrideTest extends AbstractContextTest with BuilderTestMixin {
+  test_getter_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  int get zero;
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'zero',
+      expected: '''
+  @override
+  // TODO: implement zero
+  int get zero => null;
+''',
+      displayText: 'zero => …',
+      selection: new SourceRange(111, 4),
+    );
+  }
+
+  test_getter_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  int get zero => 0;
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'zero',
+      invokeSuper: true,
+      expected: '''
+  @override
+  // TODO: implement zero
+  int get zero => super.zero;
+''',
+      displayText: 'zero => …',
+      selection: new SourceRange(107, 10),
+    );
+  }
+
+  test_method_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  A add(A a);
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'add',
+      expected: '''
+  @override
+  A add(A a) {
+    // TODO: implement add
+    return null;
+  }
+''',
+      displayText: 'add(A a) { … }',
+      selection: new SourceRange(111, 12),
+    );
+  }
+
+  test_method_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  A add(A a) => null;
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'add',
+      invokeSuper: true,
+      expected: '''
+  @override
+  A add(A a) {
+    // TODO: implement add
+    return super.add(a);
+  }
+''',
+      displayText: 'add(A a) { … }',
+      selection: new SourceRange(110, 20),
+    );
+  }
+
+  test_method_functionTypeAlias_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+typedef int F(int left, int right);
+abstract class A {
+  void perform(F f);
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'perform',
+      expected: '''
+  @override
+  void perform(F f) {
+    // TODO: implement perform
+  }
+''',
+      displayText: 'perform(F f) { … }',
+    );
+  }
+
+  test_method_functionTypeAlias_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+typedef int F(int left, int right);
+class A {
+  void perform(F f) {}
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'perform',
+      invokeSuper: true,
+      expected: '''
+  @override
+  void perform(F f) {
+    // TODO: implement perform
+    super.perform(f);
+  }
+''',
+      displayText: 'perform(F f) { … }',
+      selection: new SourceRange(158, 17),
+    );
+  }
+
+  test_method_functionTypedParameter_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  forEach(int f(double p1, String p2));
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'forEach',
+      expected: '''
+  @override
+  forEach(int Function(double p1, String p2) f) {
+    // TODO: implement forEach
+    return null;
+  }
+''',
+      displayText: 'forEach(int Function(double p1, String p2) f) { … }',
+      selection: new SourceRange(176, 12),
+    );
+  }
+
+  test_method_functionTypedParameter_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  forEach(int f(double p1, String p2)) {}
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'forEach',
+      invokeSuper: true,
+      expected: '''
+  @override
+  forEach(int Function(double p1, String p2) f) {
+    // TODO: implement forEach
+    return super.forEach(f);
+  }
+''',
+      displayText: 'forEach(int Function(double p1, String p2) f) { … }',
+      selection: new SourceRange(169, 24),
+    );
+  }
+
+  test_method_generic_noBounds_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  List<T> get<T>(T key);
+}
+class B implements A {
+}
+''',
+      nameToOverride: 'get',
+      expected: '''
+  @override
+  List<T> get<T>(T key) {
+    // TODO: implement get
+    return null;
+  }
+''',
+      displayText: 'get<T>(T key) { … }',
+      selection: new SourceRange(136, 12),
+    );
+  }
+
+  test_method_generic_noBounds_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  List<T> get<T>(T key) {}
+}
+class B implements A {
+}
+''',
+      nameToOverride: 'get',
+      invokeSuper: true,
+      expected: '''
+  @override
+  List<T> get<T>(T key) {
+    // TODO: implement get
+    return super.get(key);
+  }
+''',
+      displayText: 'get<T>(T key) { … }',
+      selection: new SourceRange(129, 22),
+    );
+  }
+
+  test_method_generic_withBounds_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A<K1, V1> {
+  List<T> get<T extends V1>(K1 key);
+}
+class B<K2, V2> implements A<K2, V2> {
+}
+''',
+      nameToOverride: 'get',
+      expected: '''
+  @override
+  List<T> get<T extends V2>(K2 key) {
+    // TODO: implement get
+    return null;
+  }
+''',
+      displayText: 'get<T extends V2>(K2 key) { … }',
+      selection: new SourceRange(184, 12),
+    );
+  }
+
+  test_method_generic_withBounds_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A<K1, V1> {
+  List<T> get<T extends V1>(K1 key) {
+    return null;
+  }
+}
+class B<K2, V2> implements A<K2, V2> {
+}
+''',
+      nameToOverride: 'get',
+      invokeSuper: true,
+      expected: '''
+  @override
+  List<T> get<T extends V2>(K2 key) {
+    // TODO: implement get
+    return super.get(key);
+  }
+''',
+      displayText: 'get<T extends V2>(K2 key) { … }',
+      selection: new SourceRange(197, 22),
+    );
+  }
+
+  test_method_genericFunctionTypedParameter_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  int foo(T Function<T>() fn);
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'foo',
+      expected: '''
+  @override
+  int foo(T Function<T>() fn) {
+    // TODO: implement foo
+    return null;
+ }
+''',
+      displayText: 'foo(T Function<T>() fn) { … }',
+      selection: new SourceRange(145, 12),
+    );
+  }
+
+  test_method_genericFunctionTypedParameter_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  int foo(T Function<T>() fn) => 0;
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'foo',
+      invokeSuper: true,
+      expected: '''
+  @override
+  int foo(T Function<T>() fn) {
+    // TODO: implement foo
+    return super.foo(fn);
+ }
+''',
+      displayText: 'foo(T Function<T>() fn) { … }',
+      selection: new SourceRange(141, 21),
+    );
+  }
+
+  test_method_nullAsTypeArgument_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  List<Null> foo();
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'foo',
+      expected: '''
+  @override
+  List<Null> foo() {
+    // TODO: implement foo
+    return null;
+ }
+''',
+      displayText: 'foo() { … }',
+      selection: new SourceRange(123, 12),
+    );
+  }
+
+  test_method_nullAsTypeArgument_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  List<Null> foo() => null
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'foo',
+      invokeSuper: true,
+      expected: '''
+  @override
+  List<Null> foo() {
+    // TODO: implement foo
+    return super.foo();
+ }
+''',
+      displayText: 'foo() { … }',
+      selection: new SourceRange(121, 19),
+    );
+  }
+
+  test_method_returnVoid_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  void test();
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'test',
+      expected: '''
+  @override
+  void test() {
+    // TODO: implement test
+  }
+''',
+      displayText: 'test() { … }',
+      selection: new SourceRange(109, 0),
+    );
+  }
+
+  test_method_voidAsTypeArgument_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  List<void> foo();
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'foo',
+      expected: '''
+  @override
+  List<void> foo() {
+    // TODO: implement foo
+    return null;
+  }
+''',
+      displayText: 'foo() { … }',
+      selection: new SourceRange(123, 12),
+    );
+  }
+
+  test_method_voidAsTypeArgument_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  List<void> foo() => null;
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'foo',
+      invokeSuper: true,
+      expected: '''
+  @override
+  List<void> foo() {
+    // TODO: implement foo
+    return super.foo();
+  }
+''',
+      displayText: 'foo() { … }',
+      selection: new SourceRange(122, 19),
+    );
+  }
+
+  test_mixin_method_of_interface() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  void foo(int a) {}
+}
+
+mixin M implements A {
+}
+''',
+      nameToOverride: 'foo',
+      targetMixinName: 'M',
+      expected: '''
+  @override
+  void foo(int a) {
+    // TODO: implement foo
+  }
+''',
+      displayText: 'foo(int a) { … }',
+      selection: new SourceRange(113, 0),
+    );
+  }
+
+  test_mixin_method_of_superclassConstraint() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  void foo(int a) {}
+}
+
+mixin M on A {
+}
+''',
+      nameToOverride: 'foo',
+      targetMixinName: 'M',
+      invokeSuper: true,
+      expected: '''
+  @override
+  void foo(int a) {
+    // TODO: implement foo
+    super.foo(a);
+  }
+''',
+      displayText: 'foo(int a) { … }',
+      selection: new SourceRange(110, 13),
+    );
+  }
+
+  test_setter_abstract() async {
+    await _assertWriteOverride(
+      content: '''
+abstract class A {
+  set value(int value);
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'value=',
+      expected: '''
+  @override
+  void set value(int value) {
+    // TODO: implement value
+  }
+''',
+      displayText: 'value(int value) { … }',
+      selection: new SourceRange(133, 0),
+    );
+  }
+
+  test_setter_concrete() async {
+    await _assertWriteOverride(
+      content: '''
+class A {
+  set value(int value) {}
+}
+class B extends A {
+}
+''',
+      nameToOverride: 'value=',
+      invokeSuper: true,
+      expected: '''
+  @override
+  void set value(int value) {
+    // TODO: implement value
+    super.value = value;
+  }
+''',
+      displayText: 'value(int value) { … }',
+      selection: new SourceRange(131, 20),
+    );
+  }
+
+  /**
+   * Assuming that the [content] being edited defines a class named `A` whose
+   * member with the given [nameToOverride] to be overridden and has
+   * `class B extends A {...}` to which an inherited method is to be added,
+   * assert that the text of the overridden member matches the [expected] text
+   * (modulo white space). Assert that the generated display text matches the
+   * given [displayText]. If a [selection] is provided, assert that the
+   * generated selection range matches it.
+   */
+  _assertWriteOverride({
+    String content,
+    String nameToOverride,
+    String expected,
+    String displayText,
+    SourceRange selection,
+    String targetClassName = 'B',
+    String targetMixinName,
+    bool invokeSuper = false,
+  }) async {
+    String path = convertPath('/home/test/lib/test.dart');
+    addSource(path, content);
+
+    ClassElement targetElement;
+    {
+      var unitResult = await driver.getUnitElement(path);
+      if (targetMixinName != null) {
+        targetElement = unitResult.element.mixins
+            .firstWhere((e) => e.name == targetMixinName);
+      } else {
+        targetElement = unitResult.element.types
+            .firstWhere((e) => e.name == targetClassName);
+      }
+    }
+
+    TypeSystem typeSystem = await session.typeSystem;
+    var inherited = new InheritanceManager2(typeSystem).getInherited(
+      targetElement.type,
+      new Name(null, nameToOverride),
+    );
+
+    StringBuffer displayBuffer =
+        displayText != null ? new StringBuffer() : null;
+
+    DartChangeBuilderImpl builder = newBuilder();
+    await builder.addFileEdit(path, (FileEditBuilder builder) {
+      builder.addInsertion(content.length - 2, (EditBuilder builder) {
+        (builder as DartEditBuilder).writeOverride(
+          inherited,
+          displayTextBuffer: displayBuffer,
+          invokeSuper: invokeSuper,
+        );
+      });
+    });
+    SourceEdit edit = getEdit(builder);
+    expect(edit.replacement, equalsIgnoringWhitespace(expected));
+    expect(displayBuffer?.toString(), displayText);
+    if (selection != null) {
+      expect(builder.selectionRange, selection);
+    }
+  }
+}