MockBuilder: Add GenerateMocks customClasses field and constructor parameter.

This field allows more customizable mock generation:

* A mock class name can be specified, in order to avoid name collisions.
* A mock class can extend a class with type arguments. E.g.

  @GenerateMocks([], customMocks: MockSpec<Foo<int>>())

  generates

  class MockFoo implements Mock extends Foo<int> {}

PiperOrigin-RevId: 321560783
diff --git a/lib/annotations.dart b/lib/annotations.dart
index bf1f5da..551778e 100644
--- a/lib/annotations.dart
+++ b/lib/annotations.dart
@@ -12,8 +12,62 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+/// An annotation to direct Mockito to generate mock classes.
+///
+/// During [code generation][NULL_SAFETY_README], Mockito will generate a
+/// `Mock{Type} extends Mock` class for each class to be mocked, in
+/// `{name}.mocks.dart`, where `{name}` is the basename of the file in which
+/// `@GenerateMocks` is used.
+///
+/// For example, if `@GenerateMocks([Foo])` is found at the top-level of a Dart
+/// library, `foo_test.dart`, then Mockito will generate
+/// `class MockFoo extends Mock implements Foo` in a new library,
+/// `foo_test.mocks.dart`.
+///
+/// If the class-to-mock is generic, then the mock will be identically generic.
+/// For example, given the class `class Foo<T, U>`, Mockito will generate
+/// `class MockFoo<T, U> extends Mock implements Foo<T, U>`.
+///
+/// Custom mocks can be generated with the `customMocks:` named argument. Each
+/// mock is specified with a [MockSpec] object.
+///
+/// [NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md
 class GenerateMocks {
   final List<Type> classes;
+  final List<MockSpec> customMocks;
 
-  const GenerateMocks(this.classes);
+  const GenerateMocks(this.classes, {this.customMocks = const []});
+}
+
+/// A specification of how to mock a specific class.
+///
+/// The type argument `T` is the class-to-mock. If this class is generic, and no
+/// explicit type arguments are given, then the mock class is generic.
+/// If the class is generic, and `T` has been specified with type argument(s),
+/// the mock class is not generic, and it extends the mocked class using the
+/// given type arguments.
+///
+/// The name of the mock class is either specified with the `as` named argument,
+/// or is the name of the class being mocked, prefixed with 'Mock'.
+///
+/// For example, given the generic class, `class Foo<T>`, then this
+/// annotation:
+///
+/// ```dart
+/// @GenerateMocks([], customMocks: [
+///     MockSpec<Foo>(),
+///     MockSpec<Foo<int>>(as: #MockFooOfInt),
+/// ])
+/// ```
+///
+/// directs Mockito to generate two mocks:
+/// `class MockFoo<T> extends Mocks implements Foo<T>` and
+/// `class MockFooOfInt extends Mock implements Foo<int>`.
+// TODO(srawlins): Document this in NULL_SAFETY_README.md.
+// TODO(srawlins): Add 'returnNullOnMissingStub'.
+// TODO(srawlins): Add 'mixingIn'.
+class MockSpec<T> {
+  final Symbol mockName;
+
+  const MockSpec({Symbol as}) : mockName = as;
 }
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index d8aaacb..fe21e9f 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import 'package:analyzer/dart/constant/value.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart' as analyzer;
 import 'package:analyzer/dart/element/type_provider.dart';
@@ -46,7 +45,7 @@
     final mockTargetGatherer = _MockTargetGatherer(entryLib);
 
     final mockLibrary = Library((b) {
-      var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._classesToMock,
+      var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets,
           sourceLibIsNonNullable: sourceLibIsNonNullable,
           typeProvider: entryLib.typeProvider,
           typeSystem: entryLib.typeSystem);
@@ -73,16 +72,26 @@
   };
 }
 
+class _MockTarget {
+  /// The class to be mocked.
+  final analyzer.InterfaceType classType;
+
+  /// The desired name of the mock class.
+  final String mockName;
+
+  _MockTarget(this.classType, this.mockName);
+
+  ClassElement get classElement => classType.element;
+}
+
 /// This class gathers and verifies mock targets referenced in `GenerateMocks`
 /// annotations.
-// TODO(srawlins): This also needs to gather mock targets (with overridden
-// names, type arguments, etc.) found in `GenerateMock` annotations.
 class _MockTargetGatherer {
   final LibraryElement _entryLib;
 
-  final List<analyzer.DartType> _classesToMock;
+  final List<_MockTarget> _mockTargets;
 
-  _MockTargetGatherer._(this._entryLib, this._classesToMock) {
+  _MockTargetGatherer._(this._entryLib, this._mockTargets) {
     _checkClassesToMockAreValid();
   }
 
@@ -90,34 +99,68 @@
   /// annotations and creates a [_MockTargetGatherer] with all of the classes
   /// identified as mocking targets.
   factory _MockTargetGatherer(LibraryElement entryLib) {
-    final objectsToMock = <DartObject>{};
+    final mockTargets = <_MockTarget>{};
 
     for (final element in entryLib.topLevelElements) {
       // TODO(srawlins): Re-think the idea of multiple @GenerateMocks
       // annotations, on one element or even on different elements in a library.
       for (final annotation in element.metadata) {
         if (annotation == null) continue;
-        if (annotation.element is! ConstructorElement ||
-            annotation.element.enclosingElement.name != 'GenerateMocks') {
-          continue;
+        if (annotation.element is! ConstructorElement) continue;
+        final annotationClass = annotation.element.enclosingElement.name;
+        // TODO(srawlins): check library as well.
+        if (annotationClass == 'GenerateMocks') {
+          mockTargets
+              .addAll(_mockTargetsFromGenerateMocks(annotation, entryLib));
         }
-        final generateMocksValue = annotation.computeConstantValue();
-        // TODO(srawlins): handle `generateMocksValue == null`?
-        // I am unable to think of a case which results in this situation.
-        final classesField = generateMocksValue.getField('classes');
-        if (classesField.isNull) {
-          throw InvalidMockitoAnnotationException(
-              'The GenerateMocks "classes" argument is missing, includes an '
-              'unknown type, or includes an extension');
-        }
-        objectsToMock.addAll(classesField.toListValue());
       }
     }
 
-    var classesToMock =
-        _mapAnnotationValuesToClasses(objectsToMock, entryLib.typeProvider);
+    return _MockTargetGatherer._(entryLib, mockTargets.toList());
+  }
 
-    return _MockTargetGatherer._(entryLib, classesToMock);
+  static Iterable<_MockTarget> _mockTargetsFromGenerateMocks(
+      ElementAnnotation annotation, LibraryElement entryLib) {
+    final generateMocksValue = annotation.computeConstantValue();
+    final classesField = generateMocksValue.getField('classes');
+    if (classesField.isNull) {
+      throw InvalidMockitoAnnotationException(
+          'The GenerateMocks "classes" argument is missing, includes an '
+          'unknown type, or includes an extension');
+    }
+    final mockTargets = <_MockTarget>[];
+    for (var objectToMock in classesField.toListValue()) {
+      final typeToMock = objectToMock.toTypeValue();
+      if (typeToMock == null) {
+        throw InvalidMockitoAnnotationException(
+            'The "classes" argument includes a non-type: $objectToMock');
+      }
+      if (typeToMock.isDynamic) {
+        throw InvalidMockitoAnnotationException(
+            'Mockito cannot mock `dynamic`');
+      }
+      final type = _determineDartType(typeToMock, entryLib.typeProvider);
+      final mockName = 'Mock${type.element.name}';
+      mockTargets.add(_MockTarget(type, mockName));
+    }
+    final customMocksField = generateMocksValue.getField('customMocks');
+    if (customMocksField != null && !customMocksField.isNull) {
+      for (var mockSpec in customMocksField.toListValue()) {
+        final mockSpecType = mockSpec.type;
+        assert(mockSpecType.typeArguments.length == 1);
+        final typeToMock = mockSpecType.typeArguments.single;
+        if (typeToMock.isDynamic) {
+          throw InvalidMockitoAnnotationException(
+              'Mockito cannot mock `dynamic`; be sure to declare type '
+              'arguments on MockSpec(), in @GenerateMocks.');
+        }
+        var type = _determineDartType(typeToMock, entryLib.typeProvider);
+        final mockName = mockSpec.getField('mockName').toSymbolValue() ??
+            'Mock${type.element.name}';
+        mockTargets.add(_MockTarget(type, mockName));
+      }
+    }
+    return mockTargets;
   }
 
   /// Map the values passed to the GenerateMocks annotation to the classes which
@@ -126,89 +169,75 @@
   /// This function is responsible for ensuring that each value is an
   /// appropriate target for mocking. It will throw an
   /// [InvalidMockitoAnnotationException] under various conditions.
-  static List<analyzer.DartType> _mapAnnotationValuesToClasses(
-      Iterable<DartObject> objectsToMock, TypeProvider typeProvider) {
-    var classesToMock = <analyzer.DartType>[];
-
-    for (final objectToMock in objectsToMock) {
-      final typeToMock = objectToMock.toTypeValue();
-      if (typeToMock == null) {
+  static analyzer.InterfaceType _determineDartType(
+      analyzer.DartType typeToMock, TypeProvider typeProvider) {
+    final elementToMock = typeToMock.element;
+    if (elementToMock is ClassElement) {
+      if (elementToMock.isEnum) {
         throw InvalidMockitoAnnotationException(
-            'The "classes" argument includes a non-type: $objectToMock');
+            'Mockito cannot mock an enum: ${elementToMock.displayName}');
       }
-
-      final elementToMock = typeToMock.element;
-      if (elementToMock is ClassElement) {
-        if (elementToMock.isEnum) {
-          throw InvalidMockitoAnnotationException(
-              'The "classes" argument includes an enum: '
-              '${elementToMock.displayName}');
-        }
-        if (typeProvider.nonSubtypableClasses.contains(elementToMock)) {
-          throw InvalidMockitoAnnotationException(
-              'The "classes" argument includes a non-subtypable type: '
-              '${elementToMock.displayName}. It is illegal to subtype this '
-              'type.');
-        }
-        if (elementToMock.isPrivate) {
-          throw InvalidMockitoAnnotationException(
-              'The "classes" argument includes a private type: '
-              '${elementToMock.displayName}.');
-        }
-        var typeParameterErrors =
-            _checkTypeParameters(elementToMock.typeParameters, elementToMock);
-        if (typeParameterErrors.isNotEmpty) {
-          var joinedMessages =
-              typeParameterErrors.map((m) => '    $m').join('\n');
-          throw InvalidMockitoAnnotationException(
-              'Mockito cannot generate a valid mock class which implements '
-              "'${elementToMock.displayName}' for the following reasons:\n"
-              '$joinedMessages');
-        }
-        classesToMock.add(typeToMock);
-      } else if (elementToMock is GenericFunctionTypeElement &&
-          elementToMock.enclosingElement is FunctionTypeAliasElement) {
+      if (typeProvider.nonSubtypableClasses.contains(elementToMock)) {
         throw InvalidMockitoAnnotationException(
-            'The "classes" argument includes a typedef: '
-            '${elementToMock.enclosingElement.displayName}');
-      } else {
-        throw InvalidMockitoAnnotationException(
-            'The "classes" argument includes a non-class: '
-            '${elementToMock.displayName}');
+            'Mockito cannot mock a non-subtypable type: '
+            '${elementToMock.displayName}. It is illegal to subtype this '
+            'type.');
       }
+      if (elementToMock.isPrivate) {
+        throw InvalidMockitoAnnotationException(
+            'Mockito cannot mock a private type: '
+            '${elementToMock.displayName}.');
+      }
+      var typeParameterErrors =
+          _checkTypeParameters(elementToMock.typeParameters, elementToMock);
+      if (typeParameterErrors.isNotEmpty) {
+        var joinedMessages =
+            typeParameterErrors.map((m) => '    $m').join('\n');
+        throw InvalidMockitoAnnotationException(
+            'Mockito cannot generate a valid mock class which implements '
+            "'${elementToMock.displayName}' for the following reasons:\n"
+            '$joinedMessages');
+      }
+      return typeToMock as analyzer.InterfaceType;
+    } else if (elementToMock is GenericFunctionTypeElement &&
+        elementToMock.enclosingElement is FunctionTypeAliasElement) {
+      throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: '
+          '${elementToMock.enclosingElement.displayName}');
+    } else {
+      throw InvalidMockitoAnnotationException(
+          'Mockito cannot mock a non-class: ${elementToMock.displayName}');
     }
-    return classesToMock;
   }
 
   void _checkClassesToMockAreValid() {
     var classesInEntryLib =
         _entryLib.topLevelElements.whereType<ClassElement>();
     var classNamesToMock = <String, ClassElement>{};
-    for (var class_ in _classesToMock) {
-      var name = class_.element.name;
+    var uniqueNameSuggestion =
+        "use the 'customMocks' argument in @GenerateMocks to specify a unique "
+        'name';
+    for (final mockTarget in _mockTargets) {
+      var name = mockTarget.mockName;
       if (classNamesToMock.containsKey(name)) {
         var firstSource = classNamesToMock[name].source.fullName;
-        var secondSource = class_.element.source.fullName;
-        // TODO(srawlins): Support an optional @GenerateMocks API that allows
-        // users to choose names. One class might be named MockFoo and the other
-        // named MockPbFoo, for example.
+        var secondSource = mockTarget.classElement.source.fullName;
         throw InvalidMockitoAnnotationException(
-            'The GenerateMocks "classes" argument contains two classes with '
-            'the same name: $name. One declared in $firstSource, the other in '
-            '$secondSource.');
+            'Mockito cannot generate two mocks with the same name: $name (for '
+            '${classNamesToMock[name].name} declared in $firstSource, and for '
+            '${mockTarget.classElement.name} declared in $secondSource); '
+            '$uniqueNameSuggestion.');
       }
-      classNamesToMock[name] = class_.element as ClassElement;
+      classNamesToMock[name] = mockTarget.classElement;
     }
 
     classNamesToMock.forEach((name, element) {
-      var conflictingClass = classesInEntryLib.firstWhere(
-          (c) => c.name == 'Mock${element.name}',
+      var conflictingClass = classesInEntryLib.firstWhere((c) => c.name == name,
           orElse: () => null);
       if (conflictingClass != null) {
         throw InvalidMockitoAnnotationException(
-            'The GenerateMocks "classes" argument contains a class which '
-            'conflicts with another class declared in this library: '
-            '${conflictingClass.name}');
+            'Mockito cannot generate a mock with a name which conflicts with '
+            'another class declared in this library: ${conflictingClass.name}; '
+            '$uniqueNameSuggestion.');
       }
 
       var preexistingMock = classesInEntryLib.firstWhere(
@@ -218,8 +247,9 @@
           orElse: () => null);
       if (preexistingMock != null) {
         throw InvalidMockitoAnnotationException(
-            'The GenerateMocks "classes" argument contains a class which '
-            'appears to already be mocked inline: ${preexistingMock.name}');
+            'The GenerateMocks annotation contains a class which appears to '
+            'already be mocked inline: ${preexistingMock.name}; '
+            '$uniqueNameSuggestion.');
       }
 
       _checkMethodsToStubAreValid(element);
@@ -376,23 +406,48 @@
   /// fake classes are added to the generated library.
   final fakedClassElements = <ClassElement>[];
 
-  /// Build mock classes for [classesToMock], a list of classes obtained from a
-  /// `@GenerateMocks` annotation.
-  _MockLibraryInfo(List<analyzer.DartType> classesToMock,
+  /// Build mock classes for [mockTargets].
+  _MockLibraryInfo(Iterable<_MockTarget> mockTargets,
       {this.sourceLibIsNonNullable, this.typeProvider, this.typeSystem}) {
-    for (final classToMock in classesToMock) {
-      mockClasses.add(_buildMockClass(classToMock));
+    for (final mockTarget in mockTargets) {
+      mockClasses.add(_buildMockClass(mockTarget));
     }
   }
 
-  Class _buildMockClass(analyzer.DartType dartType) {
-    final classToMock = dartType.element as ClassElement;
-    final className = dartType.name;
-    final mockClassName = 'Mock$className';
+  bool _hasExplicitTypeArguments(analyzer.InterfaceType type) {
+    if (type.typeArguments == null) return false;
+
+    // If it appears that one type argument was given, then they all were. This
+    // returns the wrong result when the type arguments given are all `dynamic`,
+    // or are each equal to the bound of the corresponding type parameter. There
+    // may not be a way to get around this.
+    for (var i = 0; i < type.typeArguments.length; i++) {
+      var typeArgument = type.typeArguments[i];
+      var bound =
+          type.element.typeParameters[i].bound ?? typeProvider.dynamicType;
+      // If [type] was given to @GenerateMocks as a Type, and no explicit type
+      // argument is given, [typeArgument] is `dynamic` (_not_ the bound, as one
+      // might think). We determine that an explicit type argument was given if
+      // it is not `dynamic`.
+      //
+      // If, on the other hand, [type] was given to @GenerateMock as a type
+      // argument to `Of()`, and no type argument is given, [typeArgument] is
+      // the bound of the corresponding type paramter (dynamic or otherwise). We
+      // determine that an explicit type argument was given if [typeArgument] is
+      // is not [bound].
+      if (!typeArgument.isDynamic && typeArgument != bound) return true;
+    }
+    return false;
+  }
+
+  Class _buildMockClass(_MockTarget mockTarget) {
+    final typeToMock = mockTarget.classType;
+    final classToMock = mockTarget.classElement;
+    final className = classToMock.name;
 
     return Class((cBuilder) {
       cBuilder
-        ..name = mockClassName
+        ..name = mockTarget.mockName
         ..extend = refer('Mock', 'package:mockito/mockito.dart')
         ..docs.add('/// A class which mocks [$className].')
         ..docs.add('///')
@@ -402,7 +457,19 @@
       // parameter with same type variables, and a mirrored type argument for
       // the "implements" clause.
       var typeArguments = <Reference>[];
-      if (classToMock.typeParameters != null) {
+      if (_hasExplicitTypeArguments(typeToMock)) {
+        // [typeToMock] is a reference to a type with type arguments (for
+        // example: `Foo<int>`). Generate a non-generic mock class which
+        // implements the mock target with said type arguments. For example:
+        // `class MockFoo extends Mock implements Foo<int> {}`
+        for (var typeArgument in typeToMock.typeArguments) {
+          typeArguments.add(refer(typeArgument.element.name));
+        }
+      } else if (classToMock.typeParameters != null) {
+        // [typeToMock] is a simple reference to a generic type (for example:
+        // `Foo`, a reference to `class Foo<T> {}`). Generate a generic mock
+        // class which perfectly mirrors the type parameters on [typeToMock],
+        // forwarding them to the "implements" clause.
         for (var typeParameter in classToMock.typeParameters) {
           cBuilder.types.add(_typeParameterReference(typeParameter));
           typeArguments.add(refer(typeParameter.name));
@@ -410,8 +477,8 @@
       }
       cBuilder.implements.add(TypeReference((b) {
         b
-          ..symbol = dartType.name
-          ..url = _typeImport(dartType)
+          ..symbol = classToMock.name
+          ..url = _typeImport(mockTarget.classType)
           ..types.addAll(typeArguments);
       }));
 
diff --git a/test/builder_test.dart b/test/builder/auto_mocks_test.dart
similarity index 97%
rename from test/builder_test.dart
rename to test/builder/auto_mocks_test.dart
index 8ca269b..c456abe 100644
--- a/test/builder_test.dart
+++ b/test/builder/auto_mocks_test.dart
@@ -27,8 +27,15 @@
   'mockito|lib/annotations.dart': '''
 class GenerateMocks {
   final List<Type> classes;
+  final List<MockSpec> customMocks;
 
-  const GenerateMocks(this.classes);
+  const GenerateMocks(this.classes, {this.customMocks = []});
+}
+
+class MockSpec<T> {
+  final Symbol mockName;
+
+  const MockSpec({Symbol as}) : mockName = as;
 }
 '''
 };
@@ -185,34 +192,6 @@
     );
   });
 
-  test('deduplicates classes listed multiply in GenerateMocks', () async {
-    await _testWithNonNullable(
-      {
-        ...annotationsAsset,
-        'foo|lib/foo.dart': dedent(r'''
-        class Foo {}
-        '''),
-        'foo|test/foo_test.dart': '''
-        import 'package:foo/foo.dart';
-        import 'package:mockito/annotations.dart';
-        @GenerateMocks([Foo, Foo])
-        void main() {}
-        '''
-      },
-      outputs: {
-        'foo|test/foo_test.mocks.dart': dedent(r'''
-        import 'package:mockito/mockito.dart' as _i1;
-        import 'package:foo/foo.dart' as _i2;
-
-        /// A class which mocks [Foo].
-        ///
-        /// See the documentation for Mockito's code generation for more information.
-        class MockFoo extends _i1.Mock implements _i2.Foo {}
-        '''),
-      },
-    );
-  });
-
   test('generates mock classes from multiple annotations', () async {
     await _testWithNonNullable(
       {
@@ -1194,6 +1173,27 @@
     );
   });
 
+  test('throws when GenerateMocks is given a class multiple times', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([Foo, Foo])
+        void main() {}
+        '''
+      },
+      message: contains(
+          'Mockito cannot generate two mocks with the same name: MockFoo (for '
+          'Foo declared in /foo/lib/foo.dart, and for Foo declared in '
+          '/foo/lib/foo.dart)'),
+    );
+  });
+
   test(
       'throws when GenerateMocks is given a class with a method with a '
       'private return type', () async {
@@ -1452,14 +1452,12 @@
         ...annotationsAsset,
         'foo|test/foo_test.dart': dedent('''
         import 'package:mockito/annotations.dart';
-        // Missing required argument to GenerateMocks.
         @GenerateMocks([_Foo])
         void main() {}
         class _Foo {}
         '''),
       },
-      message:
-          contains('The "classes" argument includes a private type: _Foo.'),
+      message: contains('Mockito cannot mock a private type: _Foo.'),
     );
   });
 
@@ -1501,8 +1499,9 @@
         '''),
       },
       message: contains(
-          'contains two classes with the same name: Foo. One declared in '
-          '/foo/lib/a.dart, the other in /foo/lib/b.dart'),
+          'Mockito cannot generate two mocks with the same name: MockFoo (for '
+          'Foo declared in /foo/lib/a.dart, and for Foo declared in '
+          '/foo/lib/b.dart)'),
     );
   });
 
@@ -1522,8 +1521,8 @@
         '''),
       },
       message: contains(
-          'contains a class which conflicts with another class declared in '
-          'this library: MockFoo'),
+          'Mockito cannot generate a mock with a name which conflicts with '
+          'another class declared in this library: MockFoo'),
     );
   });
 
@@ -1572,7 +1571,7 @@
         typedef Foo = void Function();
         '''),
       },
-      message: 'The "classes" argument includes a typedef: Foo',
+      message: 'Mockito cannot mock a typedef: Foo',
     );
   });
 
@@ -1585,7 +1584,7 @@
         enum Foo {}
         '''),
       },
-      message: 'The "classes" argument includes an enum: Foo',
+      message: 'Mockito cannot mock an enum: Foo',
     );
   });
 
@@ -1612,8 +1611,7 @@
         void main() {}
         '''),
       },
-      message: contains(
-          'The "classes" argument includes a non-subtypable type: int'),
+      message: contains('Mockito cannot mock a non-subtypable type: int'),
     );
   });
 
diff --git a/test/builder/custom_mocks_test.dart b/test/builder/custom_mocks_test.dart
new file mode 100644
index 0000000..fbcfa1f
--- /dev/null
+++ b/test/builder/custom_mocks_test.dart
@@ -0,0 +1,474 @@
+// Copyright 2020 Dart Mockito authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@TestOn('vm')
+import 'package:build/build.dart';
+import 'package:build/experiments.dart';
+import 'package:build_test/build_test.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/src/builder.dart';
+import 'package:package_config/package_config.dart';
+import 'package:test/test.dart';
+
+Builder buildMocks(BuilderOptions options) => MockBuilder();
+
+const annotationsAsset = {
+  'mockito|lib/annotations.dart': '''
+class GenerateMocks {
+  final List<Type> classes;
+  final List<MockSpec> customMocks;
+
+  const GenerateMocks(this.classes, {this.customMocks = []});
+}
+
+class MockSpec<T> {
+  final Symbol mockName;
+
+  const MockSpec({Symbol as}) : mockName = as;
+}
+'''
+};
+
+const mockitoAssets = {
+  'mockito|lib/mockito.dart': '''
+export 'src/mock.dart';
+''',
+  'mockito|lib/src/mock.dart': '''
+class Mock {}
+'''
+};
+
+const simpleTestAsset = {
+  'foo|test/foo_test.dart': '''
+import 'package:foo/foo.dart';
+import 'package:mockito/annotations.dart';
+@GenerateMocks([], customMocks: [MockSpec<Foo>()])
+void main() {}
+'''
+};
+
+void main() {
+  test('generates a generic mock class without type arguments', () async {
+    await _testWithNonNullable(
+      {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo<T> {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo>(as: #MockFoo)])
+        void main() {}
+        '''
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+          'class MockFoo<T> extends _i1.Mock implements _i2.Foo<T> {}',
+        ),
+      },
+    );
+  });
+
+  test('generates a generic mock class with type arguments', () async {
+    await _testWithNonNullable(
+      {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo<T, U> {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks(
+            [], customMocks: [MockSpec<Foo<int, bool>>(as: #MockFooOfIntBool)])
+        void main() {}
+        '''
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+          'class MockFooOfIntBool extends _i1.Mock implements _i2.Foo<int, bool> {}',
+        ),
+      },
+    );
+  });
+
+  test('generates a generic mock class with type arguments but no name',
+      () async {
+    await _testWithNonNullable(
+      {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo<T> {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo<int>>()])
+        void main() {}
+        '''
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+          'class MockFoo extends _i1.Mock implements _i2.Foo<int> {}',
+        ),
+      },
+    );
+  });
+
+  test('generates a generic, bounded mock class without type arguments',
+      () async {
+    await _testWithNonNullable(
+      {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo<T extends Object> {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo>(as: #MockFoo)])
+        void main() {}
+        '''
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+          'class MockFoo<T extends Object> extends _i1.Mock implements _i2.Foo<T> {}',
+        ),
+      },
+    );
+  });
+
+  test('generates mock classes from multiple annotations', () async {
+    await _testWithNonNullable(
+      {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo {}
+        class Bar {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo>()])
+        void fooTests() {}
+        @GenerateMocks([], customMocks: [MockSpec<Bar>()])
+        void barTests() {}
+        '''
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+          'class MockFoo extends _i1.Mock implements _i2.Foo {}',
+          'class MockBar extends _i1.Mock implements _i2.Bar {}',
+        ),
+      },
+    );
+  });
+
+  test('generates mock classes from multiple annotations on a single element',
+      () async {
+    await _testWithNonNullable(
+      {
+        ...annotationsAsset,
+        'foo|lib/a.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|lib/b.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|test/foo_test.dart': '''
+        import 'package:foo/a.dart' as a;
+        import 'package:foo/b.dart' as b;
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<a.Foo>(as: #MockAFoo)])
+        @GenerateMocks([], customMocks: [MockSpec<b.Foo>(as: #MockBFoo)])
+        void main() {}
+        '''
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+          'class MockAFoo extends _i1.Mock implements _i2.Foo {}',
+          'class MockBFoo extends _i1.Mock implements _i3.Foo {}',
+        ),
+      },
+    );
+  });
+
+  test(
+      'throws when GenerateMock is given a class with a type parameter with a '
+      'private bound', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo<T extends _Bar> {
+          void m(int a) {}
+        }
+        class _Bar {}
+        '''),
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo>()])
+        void main() {}
+        '''),
+      },
+      message: contains(
+          "The class 'Foo' features a private type parameter bound, and cannot "
+          'be stubbed.'),
+    );
+  });
+
+  test("throws when GenerateMock's Of argument is missing a type argument",
+      () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:mockito/annotations.dart';
+        // Missing required type argument to GenerateMock.
+        @GenerateMocks([], customMocks: [MockSpec()])
+        void main() {}
+        '''),
+      },
+      message: contains('Mockito cannot mock `dynamic`'),
+    );
+  });
+
+  test('throws when GenerateMock is given a private class', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<_Foo>()])
+        void main() {}
+        class _Foo {}
+        '''),
+      },
+      message: contains('Mockito cannot mock a private type: _Foo.'),
+    );
+  });
+
+  test('throws when two distinct classes with the same name are mocked',
+      () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|lib/a.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|lib/b.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:foo/a.dart' as a;
+        import 'package:foo/b.dart' as b;
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<a.Foo>()])
+        @GenerateMocks([], customMocks: [MockSpec<b.Foo>()])
+        void main() {}
+        '''),
+      },
+      message: contains(
+          'Mockito cannot generate two mocks with the same name: MockFoo (for '
+          'Foo declared in /foo/lib/a.dart, and for Foo declared in '
+          '/foo/lib/b.dart)'),
+    );
+  });
+
+  test('throws when a mock class of the same name already exists', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo>()])
+        void main() {}
+        class MockFoo {}
+        '''),
+      },
+      message: contains(
+          'Mockito cannot generate a mock with a name which conflicts with '
+          'another class declared in this library: MockFoo'),
+    );
+  });
+
+  test('throws when a mock class of class-to-mock already exists', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        ...mockitoAssets,
+        'foo|lib/foo.dart': dedent(r'''
+        class Foo {}
+        '''),
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:foo/foo.dart';
+        import 'package:mockito/annotations.dart';
+        import 'package:mockito/mockito.dart';
+        @GenerateMocks([], customMocks: [MockSpec<Foo>()])
+        void main() {}
+        class FakeFoo extends Mock implements Foo {}
+        '''),
+      },
+      message: contains(
+          'contains a class which appears to already be mocked inline: FakeFoo'),
+    );
+  });
+
+  test('throws when GenerateMock references a typedef', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        ...simpleTestAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        typedef Foo = void Function();
+        '''),
+      },
+      message: 'Mockito cannot mock a typedef: Foo',
+    );
+  });
+
+  test('throws when GenerateMock references an enum', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        ...simpleTestAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        enum Foo {}
+        '''),
+      },
+      message: 'Mockito cannot mock an enum: Foo',
+    );
+  });
+
+  test('throws when GenerateMock references a non-subtypeable type', () async {
+    _expectBuilderThrows(
+      assets: {
+        ...annotationsAsset,
+        'foo|test/foo_test.dart': dedent('''
+        import 'package:mockito/annotations.dart';
+        @GenerateMocks([], customMocks: [MockSpec<int>()])
+        void main() {}
+        '''),
+      },
+      message: contains('Mockito cannot mock a non-subtypable type: int'),
+    );
+  });
+
+  test('given a pre-non-nullable library, does not override any members',
+      () async {
+    await _testPreNonNullable(
+      {
+        ...annotationsAsset,
+        ...simpleTestAsset,
+        'foo|lib/foo.dart': dedent(r'''
+        abstract class Foo {
+          int f(int a);
+        }
+        '''),
+      },
+      outputs: {
+        'foo|test/foo_test.mocks.dart': _containsAllOf(
+            'class MockFoo extends _i1.Mock implements _i2.Foo {}'),
+      },
+    );
+  });
+}
+
+/// Test [MockBuilder] in a package which has not opted into the non-nullable
+/// type system.
+///
+/// Whether the non-nullable experiment is enabled depends on the SDK executing
+/// this test, but that does not affect the opt-in state of the package under
+/// test.
+Future<void> _testPreNonNullable(Map<String, String> sourceAssets,
+    {Map<String, /*String|Matcher<String>*/ dynamic> outputs}) async {
+  var packageConfig = PackageConfig([
+    Package('foo', Uri.file('/foo/'),
+        packageUriRoot: Uri.file('/foo/lib/'),
+        languageVersion: LanguageVersion(2, 7))
+  ]);
+  await testBuilder(buildMocks(BuilderOptions({})), sourceAssets,
+      outputs: outputs, packageConfig: packageConfig);
+}
+
+/// Test [MockBuilder] in a package which has opted into the non-nullable type
+/// system, and with the non-nullable experiment enabled.
+Future<void> _testWithNonNullable(Map<String, String> sourceAssets,
+    {Map<String, /*String|Matcher<List<int>>*/ dynamic> outputs}) async {
+  var packageConfig = PackageConfig([
+    Package('foo', Uri.file('/foo/'),
+        packageUriRoot: Uri.file('/foo/lib/'),
+        languageVersion: LanguageVersion(2, 9))
+  ]);
+  await withEnabledExperiments(
+    () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets,
+        outputs: outputs, packageConfig: packageConfig),
+    ['non-nullable'],
+  );
+}
+
+/// Test [MockBuilder] on a single source file, in a package which has opted
+/// into the non-nullable type system, and with the non-nullable experiment
+/// enabled.
+Future<void> _expectSingleNonNullableOutput(
+    String sourceAssetText,
+    /*String|Matcher<List<int>>*/ dynamic output) async {
+  var packageConfig = PackageConfig([
+    Package('foo', Uri.file('/foo/'),
+        packageUriRoot: Uri.file('/foo/lib/'),
+        languageVersion: LanguageVersion(2, 9))
+  ]);
+
+  await withEnabledExperiments(
+    () async => await testBuilder(
+        buildMocks(BuilderOptions({})),
+        {
+          ...annotationsAsset,
+          ...simpleTestAsset,
+          'foo|lib/foo.dart': sourceAssetText,
+        },
+        outputs: {'foo|test/foo_test.mocks.dart': output},
+        packageConfig: packageConfig),
+    ['non-nullable'],
+  );
+}
+
+TypeMatcher<List<int>> _containsAllOf(a, [b]) => decodedMatches(
+    b == null ? allOf(contains(a)) : allOf(contains(a), contains(b)));
+
+/// Expect that [testBuilder], given [assets], throws an
+/// [InvalidMockitoAnnotationException] with a message containing [message].
+void _expectBuilderThrows(
+    {@required Map<String, String> assets,
+    @required dynamic /*String|Matcher<List<int>>*/ message}) {
+  expect(
+      () async => await testBuilder(buildMocks(BuilderOptions({})), assets),
+      throwsA(TypeMatcher<InvalidMockitoAnnotationException>()
+          .having((e) => e.message, 'message', message)));
+}
+
+/// Dedent [input], so that each line is shifted to the left, so that the first
+/// line is at the 0 column.
+String dedent(String input) {
+  final indentMatch = RegExp(r'^(\s*)').firstMatch(input);
+  final indent = ''.padRight(indentMatch.group(1).length);
+  return input.splitMapJoin('\n',
+      onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), ''));
+}