Version 2.12.0-265.0.dev

Merge commit '934980be801c8007189500ee6980fd81b9eca079' into 'dev'
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index d552d08..aacaaeed 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -1324,9 +1324,6 @@
     if (info.excludes(path)) {
       return;
     }
-    if (info.ignored(path)) {
-      return;
-    }
     // handle the change
     switch (type) {
       case ChangeType.ADD:
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/library_member_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/library_member_contributor.dart
index 4801bf8..151f658 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/library_member_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/library_member_contributor.dart
@@ -62,7 +62,7 @@
             } else {
               if (element is ClassElement ||
                   element is ExtensionElement ||
-                  element is FunctionTypeAliasElement) {
+                  element is TypeAliasElement) {
                 builder.suggestElement(element,
                     kind: CompletionSuggestionKind.INVOCATION);
               } else if (!typesOnly &&
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart
index 4b1b121..32e454f 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart
@@ -96,7 +96,7 @@
   @override
   void visitFunctionTypeAliasElement(FunctionTypeAliasElement element) {
     if (opType.includeTypeNameSuggestions) {
-      builder.suggestFunctionTypeAlias(element, prefix: prefix);
+      builder.suggestTypeAlias(element, prefix: prefix);
     }
   }
 
@@ -126,6 +126,13 @@
     }
   }
 
+  @override
+  void visitTypeAliasElement(TypeAliasElement element) {
+    if (opType.includeTypeNameSuggestions) {
+      builder.suggestTypeAlias(element, prefix: prefix);
+    }
+  }
+
   /// Add constructor suggestions for the given class.
   void _addConstructorSuggestions(ClassElement classElem) {
     for (var constructor in classElem.constructors) {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
index 4c87bce..53fed10 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
@@ -310,16 +310,14 @@
   @override
   void declaredFunctionTypeAlias(FunctionTypeAlias declaration) {
     if (opType.includeTypeNameSuggestions) {
-      builder.suggestFunctionTypeAlias(declaration.declaredElement,
-          kind: _defaultKind);
+      builder.suggestTypeAlias(declaration.declaredElement, kind: _defaultKind);
     }
   }
 
   @override
   void declaredGenericTypeAlias(GenericTypeAlias declaration) {
     if (opType.includeTypeNameSuggestions) {
-      builder.suggestFunctionTypeAlias(declaration.declaredElement,
-          kind: _defaultKind);
+      builder.suggestTypeAlias(declaration.declaredElement, kind: _defaultKind);
     }
   }
 
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index 0cb3b8b..276c615 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -382,11 +382,11 @@
     } else if (element is FunctionElement &&
         element.enclosingElement is CompilationUnitElement) {
       suggestTopLevelFunction(element, kind: kind);
-    } else if (element is FunctionTypeAliasElement) {
-      suggestFunctionTypeAlias(element, kind: kind);
     } else if (element is PropertyAccessorElement &&
         element.enclosingElement is CompilationUnitElement) {
       suggestTopLevelPropertyAccessor(element, kind: kind);
+    } else if (element is TypeAliasElement) {
+      suggestTypeAlias(element, kind: kind);
     } else {
       throw ArgumentError('Cannot suggest a ${element.runtimeType}');
     }
@@ -487,19 +487,6 @@
     ));
   }
 
-  /// Add a suggestion for a [functionTypeAlias]. If a [kind] is provided it
-  /// will be used as the kind for the suggestion. If the alias can only be
-  /// referenced using a prefix, then the [prefix] should be provided.
-  void suggestFunctionTypeAlias(FunctionTypeAliasElement functionTypeAlias,
-      {CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
-      String prefix}) {
-    var relevance = _computeTopLevelRelevance(functionTypeAlias,
-        defaultRelevance: 750,
-        elementType: _instantiateFunctionTypeAlias(functionTypeAlias));
-    _add(_createSuggestion(functionTypeAlias,
-        kind: kind, prefix: prefix, relevance: relevance));
-  }
-
   /// Add a suggestion for a [keyword]. The [offset] is the offset from the
   /// beginning of the keyword where the cursor will be left.
   void suggestKeyword(String keyword, {int offset}) {
@@ -863,6 +850,18 @@
         kind: kind, prefix: prefix, relevance: relevance));
   }
 
+  /// Add a suggestion for a [typeAlias]. If a [kind] is provided it
+  /// will be used as the kind for the suggestion. If the alias can only be
+  /// referenced using a prefix, then the [prefix] should be provided.
+  void suggestTypeAlias(TypeAliasElement typeAlias,
+      {CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
+      String prefix}) {
+    var relevance = _computeTopLevelRelevance(typeAlias,
+        defaultRelevance: 750, elementType: _instantiateTypeAlias(typeAlias));
+    _add(_createSuggestion(typeAlias,
+        kind: kind, prefix: prefix, relevance: relevance));
+  }
+
   /// Add a suggestion for a type [parameter].
   void suggestTypeParameter(TypeParameterElement parameter) {
     var elementKind = _computeElementKind(parameter);
@@ -1083,7 +1082,7 @@
     );
   }
 
-  FunctionType _instantiateFunctionTypeAlias(FunctionTypeAliasElement element) {
+  DartType _instantiateTypeAlias(TypeAliasElement element) {
     var typeParameters = element.typeParameters;
     var typeArguments = const <DartType>[];
     if (typeParameters.isNotEmpty) {
diff --git a/pkg/analysis_server/lib/src/services/refactoring/naming_conventions.dart b/pkg/analysis_server/lib/src/services/refactoring/naming_conventions.dart
index d139ce5..3cd4fe3 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/naming_conventions.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/naming_conventions.dart
@@ -46,14 +46,6 @@
 /// - OK if the name is valid;
 /// - WARNING if the name is discouraged;
 /// - FATAL if the name is illegal.
-RefactoringStatus validateFunctionTypeAliasName(String name) {
-  return _validateUpperCamelCase(name, 'Function type alias');
-}
-
-/// Returns the [RefactoringStatus] with severity:
-/// - OK if the name is valid;
-/// - WARNING if the name is discouraged;
-/// - FATAL if the name is illegal.
 RefactoringStatus validateImportPrefixName(String name) {
   if (name != null && name.isEmpty) {
     return RefactoringStatus();
@@ -124,6 +116,14 @@
 /// - OK if the name is valid;
 /// - WARNING if the name is discouraged;
 /// - FATAL if the name is illegal.
+RefactoringStatus validateTypeAliasName(String name) {
+  return _validateUpperCamelCase(name, 'Type alias');
+}
+
+/// Returns the [RefactoringStatus] with severity:
+/// - OK if the name is valid;
+/// - WARNING if the name is discouraged;
+/// - FATAL if the name is illegal.
 RefactoringStatus validateVariableName(String name) {
   return _validateLowerCamelCase(name, 'Variable', allowBuiltIn: true);
 }
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart
index 0838f89..05cf609 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart
@@ -54,12 +54,12 @@
     if (element is FunctionElement) {
       return 'Rename Top-Level Function';
     }
-    if (element is FunctionTypeAliasElement) {
-      return 'Rename Function Type Alias';
-    }
     if (element is TopLevelVariableElement) {
       return 'Rename Top-Level Variable';
     }
+    if (element is TypeAliasElement) {
+      return 'Rename Type Alias';
+    }
     return 'Rename Class';
   }
 
@@ -94,8 +94,8 @@
     if (element is FunctionElement) {
       result.addStatus(validateFunctionName(newName));
     }
-    if (element is FunctionTypeAliasElement) {
-      result.addStatus(validateFunctionTypeAliasName(newName));
+    if (element is TypeAliasElement) {
+      result.addStatus(validateTypeAliasName(newName));
     }
     if (element is ClassElement) {
       result.addStatus(validateClassName(newName));
diff --git a/pkg/analysis_server/test/abstract_context.dart b/pkg/analysis_server/test/abstract_context.dart
index 25a3347..f6cf3a5 100644
--- a/pkg/analysis_server/test/abstract_context.dart
+++ b/pkg/analysis_server/test/abstract_context.dart
@@ -282,7 +282,6 @@
   @override
   String get testPackageLanguageVersion => null;
 
-  @nonVirtual
   @override
   void setUp() {
     super.setUp();
diff --git a/pkg/analysis_server/test/context_manager_test.dart b/pkg/analysis_server/test/context_manager_test.dart
index 61e3ffa..fea3b43 100644
--- a/pkg/analysis_server/test/context_manager_test.dart
+++ b/pkg/analysis_server/test/context_manager_test.dart
@@ -227,32 +227,6 @@
     });
   }
 
-  Future<void> test_path_filter() async {
-    // Setup context.
-    var root = newFolder(projPath);
-    manager.setRoots(<String>[projPath], <String>[]);
-    expect(callbacks.currentFilePaths, isEmpty);
-    // Set ignore patterns for context.
-    var rootInfo = manager.getContextInfoFor(root);
-    manager.setIgnorePatternsForContext(
-        rootInfo, ['sdk_ext/**', 'lib/ignoreme.dart']);
-    // Start creating files.
-    newFile('$projPath/${ContextManagerImpl.PUBSPEC_NAME}');
-    var libPath = '$projPath/${ContextManagerTest.LIB_NAME}';
-    newFile('$libPath/main.dart');
-    newFile('$libPath/ignoreme.dart');
-    var sdkExtPath = '$projPath/sdk_ext';
-    newFile('$sdkExtPath/entry.dart');
-    var sdkExtSrcPath = '$projPath/sdk_ext/src';
-    newFile('$sdkExtSrcPath/part.dart');
-    // Pump event loop so new files are discovered and added to context.
-    await pumpEventQueue();
-    // Verify that ignored files were ignored.
-    var filePaths = callbacks.currentFilePaths;
-    expect(filePaths, hasLength(1));
-    expect(filePaths, contains(convertPath('/my/proj/lib/main.dart')));
-  }
-
   Future<void> test_refresh_folder_with_packagespec() {
     // create a context with a .packages file
     var packagespecFile = join(projPath, '.packages');
diff --git a/pkg/analysis_server/test/domain_analysis_test.dart b/pkg/analysis_server/test/domain_analysis_test.dart
index 9b966d2..06c2e37 100644
--- a/pkg/analysis_server/test/domain_analysis_test.dart
+++ b/pkg/analysis_server/test/domain_analysis_test.dart
@@ -305,6 +305,18 @@
 class AnalysisDomainTest extends AbstractAnalysisTest {
   Map<String, List<AnalysisError>> filesErrors = {};
 
+  void assertHasErrors(String path) {
+    expect(filesErrors[path], isNotEmpty);
+  }
+
+  void assertNoErrors(String path) {
+    expect(filesErrors[path], isEmpty);
+  }
+
+  void assertNoErrorsNotification(String path) {
+    expect(filesErrors[path], isNull);
+  }
+
   @override
   void processNotification(Notification notification) {
     if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) {
@@ -313,6 +325,117 @@
     }
   }
 
+  Future<void> test_fileSystem_addFile_excluded() async {
+    var a_path = convertPath('$projectPath/lib/a.dart');
+    var b_path = convertPath('$projectPath/lib/b.dart');
+
+    newFile('$projectPath/analysis_options.yaml', content: r'''
+analyzer:
+  exclude:
+    - "**/a.dart"
+''');
+
+    newFile(b_path, content: r'''
+import 'a.dart';
+void f(A a) {}
+''');
+
+    createProject();
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We don't have a.dart, so the import cannot be resolved.
+    assertHasErrors(b_path);
+
+    newFile(a_path, content: r'''
+class A {}
+''');
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We excluded 'a.dart' from analysis, no errors notification for it.
+    assertNoErrorsNotification(a_path);
+
+    // We added a.dart with `A`, so no errors.
+    assertNoErrors(b_path);
+  }
+
+  Future<void> test_fileSystem_changeFile_excluded() async {
+    var a_path = convertPath('$projectPath/lib/a.dart');
+    var b_path = convertPath('$projectPath/lib/b.dart');
+
+    newFile('$projectPath/analysis_options.yaml', content: r'''
+analyzer:
+  exclude:
+    - "**/a.dart"
+''');
+
+    newFile(a_path, content: r'''
+class B {}
+''');
+
+    newFile(b_path, content: r'''
+import 'a.dart';
+void f(A a) {}
+''');
+
+    createProject();
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We excluded 'a.dart' from analysis, no errors notification for it.
+    assertNoErrorsNotification(a_path);
+
+    // We have `B`, not `A`, in a.dart, so has errors.
+    assertHasErrors(b_path);
+
+    newFile(a_path, content: r'''
+class A {}
+''');
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We changed a.dart, to have `A`, so no errors.
+    assertNoErrors(b_path);
+  }
+
+  Future<void> test_fileSystem_deleteFile_excluded() async {
+    var a_path = convertPath('$projectPath/lib/a.dart');
+    var b_path = convertPath('$projectPath/lib/b.dart');
+
+    newFile('$projectPath/analysis_options.yaml', content: r'''
+analyzer:
+  exclude:
+    - "**/a.dart"
+''');
+
+    newFile(a_path, content: r'''
+class A {}
+''');
+
+    newFile(b_path, content: r'''
+import 'a.dart';
+void f(A a) {}
+''');
+
+    createProject();
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We excluded 'a.dart' from analysis, no errors notification for it.
+    assertNoErrorsNotification(a_path);
+
+    // We have `A` in a.dart, so no errors.
+    assertNoErrors(b_path);
+
+    deleteFile(a_path);
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We deleted a.dart, so `A` cannot be resolved.
+    assertHasErrors(b_path);
+  }
+
   Future<void> test_setRoots_packages() {
     // prepare package
     var pkgFile = newFile('/packages/pkgA/libA.dart', content: '''
diff --git a/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
index f91b4f7..0f6e004 100644
--- a/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
@@ -715,8 +715,8 @@
     addTestSource('main() { int.parse("16", ^);}');
     await computeSuggestions();
     assertSuggestArgumentsAndTypes(namedArgumentsWithTypes: {
-      'radix': 'int',
-      'onError': 'int Function(String)'
+      'radix': 'int?',
+      'onError': 'int Function(String)?'
     });
   }
 
@@ -725,8 +725,8 @@
     addTestSource('main() { int.parse("16", r^);}');
     await computeSuggestions();
     assertSuggestArgumentsAndTypes(namedArgumentsWithTypes: {
-      'radix': 'int',
-      'onError': 'int Function(String)'
+      'radix': 'int?',
+      'onError': 'int Function(String)?'
     });
   }
 
@@ -735,7 +735,7 @@
     addTestSource('main() { int.parse("16", radix: 7, ^);}');
     await computeSuggestions();
     assertSuggestArgumentsAndTypes(
-        namedArgumentsWithTypes: {'onError': 'int Function(String)'});
+        namedArgumentsWithTypes: {'onError': 'int Function(String)?'});
   }
 
   Future<void> test_ArgumentList_imported_function_named_param2a() async {
@@ -750,8 +750,8 @@
     addTestSource('main() { int.parse("16", r^: 16);}');
     await computeSuggestions();
     assertSuggestArgumentsAndTypes(namedArgumentsWithTypes: {
-      'radix': 'int',
-      'onError': 'int Function(String)'
+      'radix': 'int?',
+      'onError': 'int Function(String)?'
     }, includeColon: false);
   }
 
@@ -767,8 +767,8 @@
     addTestSource('main() { int.parse("16", ^: 16);}');
     await computeSuggestions();
     assertSuggestArgumentsAndTypes(namedArgumentsWithTypes: {
-      'radix': 'int',
-      'onError': 'int Function(String)'
+      'radix': 'int?',
+      'onError': 'int Function(String)?'
     });
   }
 
diff --git a/pkg/analysis_server/test/services/completion/dart/combinator_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/combinator_contributor_test.dart
index f5faa6a..ae98d85 100644
--- a/pkg/analysis_server/test/services/completion/dart/combinator_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/combinator_contributor_test.dart
@@ -107,7 +107,7 @@
     assertSuggestFunction('F1', 'PB',
         kind: CompletionSuggestionKind.IDENTIFIER);
     assertSuggestClass('Clz', kind: CompletionSuggestionKind.IDENTIFIER);
-    assertSuggestFunctionTypeAlias('F2', null,
+    assertSuggestTypeAlias('F2', null,
         kind: CompletionSuggestionKind.IDENTIFIER);
     assertNotSuggested('C');
     assertNotSuggested('D');
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart b/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
index 89353f4..781fb5e 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
@@ -68,7 +68,8 @@
   }
 }
 
-abstract class _BaseDartCompletionContributorTest extends AbstractContextTest {
+abstract class _BaseDartCompletionContributorTest extends AbstractContextTest
+    with WithNonFunctionTypeAliasesMixin {
   static const String _UNCHECKED = '__UNCHECKED__';
   String testFile;
   int completionOffset;
@@ -354,36 +355,6 @@
     return cs;
   }
 
-  CompletionSuggestion assertSuggestFunctionTypeAlias(
-    String name,
-    String returnType, {
-    bool isDeprecated = false,
-    CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
-  }) {
-    var cs = assertSuggest(name, csKind: kind, isDeprecated: isDeprecated);
-    if (returnType != null) {
-      expect(cs.returnType, returnType);
-    } else if (isNullExpectedReturnTypeConsideredDynamic) {
-      expect(cs.returnType, 'dynamic');
-    } else {
-      expect(cs.returnType, isNull);
-    }
-    var element = cs.element;
-    expect(element, isNotNull);
-    expect(element.kind, equals(ElementKind.FUNCTION_TYPE_ALIAS));
-    expect(element.name, equals(name));
-    expect(element.isDeprecated, equals(isDeprecated));
-    // TODO (danrubel) Determine why params are null
-    //    String param = element.parameters;
-    //    expect(param, isNotNull);
-    //    expect(param[0], equals('('));
-    //    expect(param[param.length - 1], equals(')'));
-    expect(element.returnType, equals(returnType ?? 'dynamic'));
-    // TODO (danrubel) Determine why param info is missing
-    //    assertHasParameterInfo(cs);
-    return cs;
-  }
-
   CompletionSuggestion assertSuggestGetter(String name, String returnType,
       {CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
       bool isDeprecated = false}) {
@@ -529,6 +500,36 @@
     return cs;
   }
 
+  CompletionSuggestion assertSuggestTypeAlias(
+    String name,
+    String returnType, {
+    bool isDeprecated = false,
+    CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
+  }) {
+    var cs = assertSuggest(name, csKind: kind, isDeprecated: isDeprecated);
+    if (returnType != null) {
+      expect(cs.returnType, returnType);
+    } else if (isNullExpectedReturnTypeConsideredDynamic) {
+      expect(cs.returnType, 'dynamic');
+    } else {
+      expect(cs.returnType, isNull);
+    }
+    var element = cs.element;
+    expect(element, isNotNull);
+    expect(element.kind, equals(ElementKind.TYPE_ALIAS));
+    expect(element.name, equals(name));
+    expect(element.isDeprecated, equals(isDeprecated));
+    // TODO (danrubel) Determine why params are null
+    //    String param = element.parameters;
+    //    expect(param, isNotNull);
+    //    expect(param[0], equals('('));
+    //    expect(param[param.length - 1], equals(')'));
+    expect(element.returnType, equals(returnType ?? 'dynamic'));
+    // TODO (danrubel) Determine why param info is missing
+    //    assertHasParameterInfo(cs);
+    return cs;
+  }
+
   CompletionSuggestion assertSuggestTypeParameter(String name) {
     var cs = assertSuggest(name, csKind: CompletionSuggestionKind.IDENTIFIER);
     expect(cs.returnType, isNull);
diff --git a/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart
index a83e323..e6b02e7 100644
--- a/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart
@@ -2400,7 +2400,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -2430,7 +2430,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -2461,7 +2461,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -2498,7 +2498,7 @@
 }
 ''');
     await computeSuggestions();
-    assertSuggestFunctionTypeAlias('F', 'void');
+    assertSuggestTypeAlias('F', 'void');
   }
 
   Future<void> test_functionTypeAlias_old() async {
@@ -2513,7 +2513,7 @@
 }
 ''');
     await computeSuggestions();
-    assertSuggestFunctionTypeAlias('F', 'void');
+    assertSuggestTypeAlias('F', 'void');
   }
 
   Future<void> test_IfStatement() async {
@@ -2914,7 +2914,7 @@
     // in which case suggestions will have null (unresolved) returnType
     assertSuggestTopLevelVar('T1', null);
     assertSuggestFunction('F1', null);
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -3178,7 +3178,7 @@
     // to be resolved.
     assertSuggestTopLevelVar('T1', /* null */ 'int');
     assertSuggestFunction('F1', /* null */ 'dynamic');
-    assertSuggestFunctionTypeAlias('D1', /* null */ 'dynamic');
+    assertSuggestTypeAlias('D1', /* null */ 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -3477,7 +3477,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -3506,7 +3506,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -3535,7 +3535,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -3566,7 +3566,7 @@
     assertSuggestClass('Object');
     assertNotSuggested('T1');
     assertNotSuggested('F1');
-    assertSuggestFunctionTypeAlias('D1', 'dynamic');
+    assertSuggestTypeAlias('D1', 'dynamic');
     assertSuggestClass('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
@@ -4564,7 +4564,7 @@
     expect(replacementLength, 0);
     assertSuggestClass('Object');
     assertSuggestClass('C1');
-    assertSuggestFunctionTypeAlias('T1', 'String');
+    assertSuggestTypeAlias('T1', 'String');
     assertNotSuggested('C2');
     assertNotSuggested('T2');
     assertNotSuggested('F1');
diff --git a/pkg/analysis_server/test/services/completion/dart/library_member_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/library_member_contributor_test.dart
index 95a7d99..f73cdd7 100644
--- a/pkg/analysis_server/test/services/completion/dart/library_member_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/library_member_contributor_test.dart
@@ -224,7 +224,10 @@
         lib B;
         var T1;
         class X { }
-        class Y { }''');
+        class Y { }
+        typedef void TypeAliasLegacy();
+        typedef TypeAliasFunctionType = void Function();
+        typedef TypeAliasInterfaceType = List<int>;''');
     addTestSource('''
         import "b.dart" as b;
         var T2;
@@ -235,6 +238,9 @@
     expect(replacementLength, 0);
     assertSuggestClass('X');
     assertSuggestClass('Y');
+    assertSuggestTypeAlias('TypeAliasLegacy', 'void');
+    assertSuggestTypeAlias('TypeAliasFunctionType', 'void');
+    assertSuggestTypeAlias('TypeAliasInterfaceType', 'List<int>');
     assertNotSuggested('T1');
     assertNotSuggested('T2');
     assertNotSuggested('Object');
diff --git a/pkg/analysis_server/test/services/completion/dart/local_library_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/local_library_contributor_test.dart
index 08f9a72..ebf0fdf 100644
--- a/pkg/analysis_server/test/services/completion/dart/local_library_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/local_library_contributor_test.dart
@@ -212,6 +212,8 @@
         class A { var a1; a2(){}}
         var m;
         typedef t1(int blue);
+        typedef t2 = void Function(int blue);
+        typedef t3 = List<int>;
         int af() {return 0;}''');
     addTestSource('''
         part of libA;
@@ -227,7 +229,9 @@
     }
     assertSuggestFunction('af', 'int');
     assertSuggestTopLevelVar('m', null);
-    assertSuggestFunctionTypeAlias('t1', null);
+    assertSuggestTypeAlias('t1', null);
+    assertSuggestTypeAlias('t2', 'void');
+    assertSuggestTypeAlias('t3', 'List<int>');
     assertNotSuggested('a1');
     assertNotSuggested('a2');
     // Suggested by LocalConstructorContributor
@@ -270,7 +274,7 @@
     }
     assertSuggestFunction('bf', 'int');
     assertSuggestTopLevelVar('n', null);
-    assertSuggestFunctionTypeAlias('t1', null);
+    assertSuggestTypeAlias('t1', null);
     assertNotSuggested('b1');
     assertNotSuggested('b2');
     // Suggested by ConstructorContributor
diff --git a/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart
index 571d7b2..5475a78 100644
--- a/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart
@@ -1667,7 +1667,7 @@
 
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
-    assertSuggestParameter('e', null);
+    assertSuggestParameter('e', 'Object');
     assertSuggestParameter('s', 'StackTrace');
     assertSuggestMethod('a', 'A', null);
     assertNotSuggested('Object');
@@ -2588,7 +2588,7 @@
     assertSuggestClass('C');
     assertSuggestMethod('foo', 'C', null);
     assertSuggestMethod('bar', 'C', 'void');
-    assertSuggestFunctionTypeAlias('F2', 'int');
+    assertSuggestTypeAlias('F2', 'int');
     assertSuggestClass('Clz');
     assertSuggestClass('C');
     assertNotSuggested('x');
@@ -3242,7 +3242,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -3272,7 +3272,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -3303,7 +3303,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -3387,39 +3387,6 @@
     assertSuggestTypeParameter('T');
   }
 
-  Future<void> test_functionTypeAlias_genericTypeAlias() async {
-    addTestSource(r'''
-typedef F = void Function();
-main() {
-  ^
-}
-''');
-    await computeSuggestions();
-    assertSuggestFunctionTypeAlias('F', 'void');
-  }
-
-  Future<void> test_functionTypeAlias_genericTypeAlias_incomplete() async {
-    addTestSource(r'''
-typedef F = int;
-main() {
-  ^
-}
-''');
-    await computeSuggestions();
-    assertSuggestFunctionTypeAlias('F', 'dynamic');
-  }
-
-  Future<void> test_functionTypeAlias_old() async {
-    addTestSource(r'''
-typedef void F();
-main() {
-  ^
-}
-''');
-    await computeSuggestions();
-    assertSuggestFunctionTypeAlias('F', 'void');
-  }
-
   Future<void> test_genericFunctionType_parameterList() async {
     addTestSource('''
 void f(int Function<T>(^) g) {}
@@ -3903,7 +3870,7 @@
     assertNotSuggested('C1');
     assertSuggestTopLevelVar('T2', 'int');
     assertSuggestFunction('F2', null);
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertSuggestLocalVariable('name', 'String');
   }
@@ -4251,7 +4218,7 @@
     assertNotSuggested('C1');
     assertSuggestTopLevelVar('T2', 'int');
     assertSuggestFunction('F2', null);
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
   }
 
@@ -4800,7 +4767,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -4829,7 +4796,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -4858,7 +4825,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -4889,7 +4856,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T2');
     assertNotSuggested('F2');
-    assertSuggestFunctionTypeAlias('D2', 'dynamic');
+    assertSuggestTypeAlias('D2', 'dynamic');
     assertSuggestClass('C2');
     assertNotSuggested('name');
   }
@@ -6060,6 +6027,39 @@
     assertNoSuggestions();
   }
 
+  Future<void> test_typeAlias_functionType() async {
+    addTestSource(r'''
+typedef F = void Function();
+main() {
+  ^
+}
+''');
+    await computeSuggestions();
+    assertSuggestTypeAlias('F', 'void');
+  }
+
+  Future<void> test_typeAlias_interfaceType() async {
+    addTestSource(r'''
+typedef F = List<int>;
+main() {
+  ^
+}
+''');
+    await computeSuggestions();
+    assertSuggestTypeAlias('F', 'List<int>');
+  }
+
+  Future<void> test_typeAlias_legacy() async {
+    addTestSource(r'''
+typedef void F();
+main() {
+  ^
+}
+''');
+    await computeSuggestions();
+    assertSuggestTypeAlias('F', 'void');
+  }
+
   Future<void> test_TypeArgumentList() async {
     // SimpleIdentifier  BinaryExpression  ExpressionStatement
     addSource('/home/test/lib/a.dart', '''
@@ -6081,7 +6081,7 @@
     assertNotSuggested('C1');
     assertNotSuggested('T1');
     assertSuggestClass('C2');
-    assertSuggestFunctionTypeAlias('T2', 'int');
+    assertSuggestTypeAlias('T2', 'int');
     assertNotSuggested('F1');
     assertNotSuggested('F2');
   }
diff --git a/pkg/analysis_server/test/services/refactoring/naming_conventions_test.dart b/pkg/analysis_server/test/services/refactoring/naming_conventions_test.dart
index 8c9ec86..ea21e44 100644
--- a/pkg/analysis_server/test/services/refactoring/naming_conventions_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/naming_conventions_test.dart
@@ -268,74 +268,6 @@
         expectedMessage: 'Function name must not start or end with a blank.');
   }
 
-  void test_validateFunctionTypeAliasName_doesNotStartWithLowerCase() {
-    assertRefactoringStatus(validateFunctionTypeAliasName('newName'),
-        RefactoringProblemSeverity.WARNING,
-        expectedMessage:
-            'Function type alias name should start with an uppercase letter.');
-  }
-
-  void test_validateFunctionTypeAliasName_empty() {
-    assertRefactoringStatus(
-        validateFunctionTypeAliasName(''), RefactoringProblemSeverity.FATAL,
-        expectedMessage: 'Function type alias name must not be empty.');
-  }
-
-  void test_validateFunctionTypeAliasName_invalidCharacters() {
-    assertRefactoringStatus(validateFunctionTypeAliasName('New-Name'),
-        RefactoringProblemSeverity.FATAL,
-        expectedMessage: "Function type alias name must not contain \'-\'.");
-  }
-
-  void test_validateFunctionTypeAliasName_leadingBlanks() {
-    assertRefactoringStatus(validateFunctionTypeAliasName(' NewName'),
-        RefactoringProblemSeverity.FATAL,
-        expectedMessage:
-            'Function type alias name must not start or end with a blank.');
-  }
-
-  void test_validateFunctionTypeAliasName_notIdentifierMiddle() {
-    assertRefactoringStatus(validateFunctionTypeAliasName('New-Name'),
-        RefactoringProblemSeverity.FATAL,
-        expectedMessage: "Function type alias name must not contain '-'.");
-  }
-
-  void test_validateFunctionTypeAliasName_notIdentifierStart() {
-    assertRefactoringStatus(validateFunctionTypeAliasName('newName'),
-        RefactoringProblemSeverity.WARNING,
-        expectedMessage:
-            'Function type alias name should start with an uppercase letter.');
-  }
-
-  void test_validateFunctionTypeAliasName_null() {
-    assertRefactoringStatus(
-        validateFunctionTypeAliasName(null), RefactoringProblemSeverity.FATAL,
-        expectedMessage: 'Function type alias name must not be null.');
-  }
-
-  void test_validateFunctionTypeAliasName_OK() {
-    assertRefactoringStatusOK(validateFunctionTypeAliasName('NewName'));
-  }
-
-  void test_validateFunctionTypeAliasName_OK_leadingDollar() {
-    assertRefactoringStatusOK(validateFunctionTypeAliasName('\$NewName'));
-  }
-
-  void test_validateFunctionTypeAliasName_OK_leadingUnderscore() {
-    assertRefactoringStatusOK(validateFunctionTypeAliasName('_NewName'));
-  }
-
-  void test_validateFunctionTypeAliasName_OK_middleDollar() {
-    assertRefactoringStatusOK(validateFunctionTypeAliasName('New\$Name'));
-  }
-
-  void test_validateFunctionTypeAliasName_trailingBlanks() {
-    assertRefactoringStatus(validateFunctionTypeAliasName('NewName '),
-        RefactoringProblemSeverity.FATAL,
-        expectedMessage:
-            'Function type alias name must not start or end with a blank.');
-  }
-
   void test_validateImportPrefixName_doesNotStartWithLowerCase() {
     assertRefactoringStatus(
         validateImportPrefixName('NewName'), RefactoringProblemSeverity.WARNING,
@@ -681,6 +613,72 @@
         expectedMessage: 'Parameter name must not start or end with a blank.');
   }
 
+  void test_validateTypeAliasName_doesNotStartWithLowerCase() {
+    assertRefactoringStatus(
+        validateTypeAliasName('newName'), RefactoringProblemSeverity.WARNING,
+        expectedMessage:
+            'Type alias name should start with an uppercase letter.');
+  }
+
+  void test_validateTypeAliasName_empty() {
+    assertRefactoringStatus(
+        validateTypeAliasName(''), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not be empty.');
+  }
+
+  void test_validateTypeAliasName_invalidCharacters() {
+    assertRefactoringStatus(
+        validateTypeAliasName('New-Name'), RefactoringProblemSeverity.FATAL,
+        expectedMessage: "Type alias name must not contain \'-\'.");
+  }
+
+  void test_validateTypeAliasName_leadingBlanks() {
+    assertRefactoringStatus(
+        validateTypeAliasName(' NewName'), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not start or end with a blank.');
+  }
+
+  void test_validateTypeAliasName_notIdentifierMiddle() {
+    assertRefactoringStatus(
+        validateTypeAliasName('New-Name'), RefactoringProblemSeverity.FATAL,
+        expectedMessage: "Type alias name must not contain '-'.");
+  }
+
+  void test_validateTypeAliasName_notIdentifierStart() {
+    assertRefactoringStatus(
+        validateTypeAliasName('newName'), RefactoringProblemSeverity.WARNING,
+        expectedMessage:
+            'Type alias name should start with an uppercase letter.');
+  }
+
+  void test_validateTypeAliasName_null() {
+    assertRefactoringStatus(
+        validateTypeAliasName(null), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not be null.');
+  }
+
+  void test_validateTypeAliasName_OK() {
+    assertRefactoringStatusOK(validateTypeAliasName('NewName'));
+  }
+
+  void test_validateTypeAliasName_OK_leadingDollar() {
+    assertRefactoringStatusOK(validateTypeAliasName('\$NewName'));
+  }
+
+  void test_validateTypeAliasName_OK_leadingUnderscore() {
+    assertRefactoringStatusOK(validateTypeAliasName('_NewName'));
+  }
+
+  void test_validateTypeAliasName_OK_middleDollar() {
+    assertRefactoringStatusOK(validateTypeAliasName('New\$Name'));
+  }
+
+  void test_validateTypeAliasName_trailingBlanks() {
+    assertRefactoringStatus(
+        validateTypeAliasName('NewName '), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not start or end with a blank.');
+  }
+
   void test_validateVariableName_builtIn() {
     _assertWarningBuiltIn(validateVariableName('abstract'));
   }
diff --git a/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart b/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart
index ab7d05d..5aa122f 100644
--- a/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart
@@ -6,6 +6,7 @@
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import '../../abstract_context.dart';
 import 'abstract_rename.dart';
 
 void main() {
@@ -15,7 +16,8 @@
 }
 
 @reflectiveTest
-class RenameUnitMemberTest extends RenameRefactoringTest {
+class RenameUnitMemberTest extends RenameRefactoringTest
+    with WithNonFunctionTypeAliasesMixin {
   Future<void> test_checkFinalConditions_hasTopLevel_ClassElement() async {
     await indexTestUnit('''
 class Test {}
@@ -42,7 +44,7 @@
     var status = await refactoring.checkFinalConditions();
     assertRefactoringStatus(status, RefactoringProblemSeverity.ERROR,
         expectedMessage:
-            "Library already declares function type alias with name 'NewName'.",
+            "Library already declares type alias with name 'NewName'.",
         expectedContextSearch: 'NewName(); // existing');
   }
 
@@ -306,21 +308,6 @@
     assertRefactoringStatusOK(refactoring.checkNewName());
   }
 
-  Future<void> test_checkNewName_FunctionTypeAliasElement() async {
-    await indexTestUnit('''
-typedef Test();
-''');
-    createRenameRefactoringAtString('Test();');
-    // null
-    refactoring.newName = null;
-    assertRefactoringStatus(
-        refactoring.checkNewName(), RefactoringProblemSeverity.FATAL,
-        expectedMessage: 'Function type alias name must not be null.');
-    // OK
-    refactoring.newName = 'NewName';
-    assertRefactoringStatusOK(refactoring.checkNewName());
-  }
-
   Future<void> test_checkNewName_TopLevelVariableElement() async {
     await indexTestUnit('''
 var test;
@@ -341,6 +328,51 @@
     assertRefactoringStatusOK(refactoring.checkNewName());
   }
 
+  Future<void> test_checkNewName_TypeAliasElement_functionType() async {
+    await indexTestUnit('''
+typedef Test = void Function();
+''');
+    createRenameRefactoringAtString('Test =');
+    // null
+    refactoring.newName = null;
+    assertRefactoringStatus(
+        refactoring.checkNewName(), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not be null.');
+    // OK
+    refactoring.newName = 'NewName';
+    assertRefactoringStatusOK(refactoring.checkNewName());
+  }
+
+  Future<void> test_checkNewName_TypeAliasElement_interfaceType() async {
+    await indexTestUnit('''
+typedef Test = List<int>;
+''');
+    createRenameRefactoringAtString('Test =');
+    // null
+    refactoring.newName = null;
+    assertRefactoringStatus(
+        refactoring.checkNewName(), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not be null.');
+    // OK
+    refactoring.newName = 'NewName';
+    assertRefactoringStatusOK(refactoring.checkNewName());
+  }
+
+  Future<void> test_checkNewName_TypeAliasElement_legacy() async {
+    await indexTestUnit('''
+typedef Test();
+''');
+    createRenameRefactoringAtString('Test();');
+    // null
+    refactoring.newName = null;
+    assertRefactoringStatus(
+        refactoring.checkNewName(), RefactoringProblemSeverity.FATAL,
+        expectedMessage: 'Type alias name must not be null.');
+    // OK
+    refactoring.newName = 'NewName';
+    assertRefactoringStatusOK(refactoring.checkNewName());
+  }
+
   Future<void> test_createChange_ClassElement() async {
     await indexTestUnit('''
 class Test implements Other {
@@ -393,7 +425,7 @@
 
 class TestPageState extends State<TestPage> {
   @override
-  Widget build(BuildContext context) => null;
+  Widget build(BuildContext context) => throw 0;
 }
 ''');
     createRenameRefactoringAtString('TestPage extends');
@@ -415,7 +447,7 @@
 
 class NewPageState extends State<NewPage> {
   @override
-  Widget build(BuildContext context) => null;
+  Widget build(BuildContext context) => throw 0;
 }
 ''');
   }
@@ -435,7 +467,7 @@
 
 class _TestPageState extends State<_TestPage> {
   @override
-  Widget build(BuildContext context) => null;
+  Widget build(BuildContext context) => throw 0;
 }
 ''');
     createRenameRefactoringAtString('_TestPage extends');
@@ -457,7 +489,7 @@
 
 class _NewPageState extends State<_NewPage> {
   @override
-  Widget build(BuildContext context) => null;
+  Widget build(BuildContext context) => throw 0;
 }
 ''');
   }
@@ -477,7 +509,7 @@
 
 class _TestPageState extends State<TestPage> {
   @override
-  Widget build(BuildContext context) => null;
+  Widget build(BuildContext context) => throw 0;
 }
 ''');
     createRenameRefactoringAtString('TestPage extends');
@@ -499,7 +531,7 @@
 
 class _NewPageState extends State<NewPage> {
   @override
-  Widget build(BuildContext context) => null;
+  Widget build(BuildContext context) => throw 0;
 }
 ''');
   }
@@ -531,10 +563,8 @@
 
   Future<void> test_createChange_ClassElement_parameterTypeNested() async {
     await indexTestUnit('''
-class Test {
-}
-main(f(Test p)) {
-}
+class Test {}
+void f(g(Test p)) {}
 ''');
     // configure refactoring
     createRenameRefactoringAtString('Test {');
@@ -543,10 +573,8 @@
     refactoring.newName = 'NewName';
     // validate change
     return assertSuccessfulRefactoring('''
-class NewName {
-}
-main(f(NewName p)) {
-}
+class NewName {}
+void f(g(NewName p)) {}
 ''');
   }
 
@@ -554,8 +582,7 @@
     await indexTestUnit('''
 class A {}
 class Test = Object with A;
-main(Test t) {
-}
+void f(Test t) {}
 ''');
     // configure refactoring
     createRenameRefactoringAtString('Test =');
@@ -567,8 +594,7 @@
     return assertSuccessfulRefactoring('''
 class A {}
 class NewName = Object with A;
-main(NewName t) {
-}
+void f(NewName t) {}
 ''');
   }
 
@@ -634,30 +660,6 @@
 ''');
   }
 
-  Future<void> test_createChange_FunctionTypeAliasElement() async {
-    await indexTestUnit('''
-typedef void F();
-void foo<T>() {}
-void main() {
-  foo<F>();
-}
-''');
-    // configure refactoring
-    createRenameRefactoringAtString('F()');
-    expect(refactoring.refactoringName, 'Rename Function Type Alias');
-    expect(refactoring.elementKindName, 'function type alias');
-    expect(refactoring.oldName, 'F');
-    refactoring.newName = 'G';
-    // validate change
-    return assertSuccessfulRefactoring('''
-typedef void G();
-void foo<T>() {}
-void main() {
-  foo<G>();
-}
-''');
-  }
-
   Future<void> test_createChange_outsideOfProject_referenceInPart() async {
     newFile('/home/part.dart', content: r'''
 part of test;
@@ -675,7 +677,7 @@
 
 class Test {}
 
-Test test;
+void f(Test a) {}
 ''');
     createRenameRefactoringAtString('Test {}');
     refactoring.newName = 'NewName';
@@ -687,7 +689,7 @@
 
 class NewName {}
 
-NewName test;
+void f(NewName a) {}
 ''');
 
     expect(refactoringChange.edits, hasLength(1));
@@ -732,6 +734,60 @@
     await _test_createChange_TopLevelVariableElement('test = 1');
   }
 
+  Future<void> test_createChange_typeAlias_functionType() async {
+    await indexTestUnit('''
+typedef F = void Function();
+void f(F a) {}
+''');
+    // configure refactoring
+    createRenameRefactoringAtString('F =');
+    expect(refactoring.refactoringName, 'Rename Type Alias');
+    expect(refactoring.elementKindName, 'type alias');
+    expect(refactoring.oldName, 'F');
+    refactoring.newName = 'NewName';
+    // validate change
+    return assertSuccessfulRefactoring('''
+typedef NewName = void Function();
+void f(NewName a) {}
+''');
+  }
+
+  Future<void> test_createChange_typeAlias_interfaceType() async {
+    await indexTestUnit('''
+typedef A<T> = Map<int, T>;
+void f(A<String> a) {}
+''');
+    // configure refactoring
+    createRenameRefactoringAtString('A<T>');
+    expect(refactoring.refactoringName, 'Rename Type Alias');
+    expect(refactoring.elementKindName, 'type alias');
+    expect(refactoring.oldName, 'A');
+    refactoring.newName = 'NewName';
+    // validate change
+    return assertSuccessfulRefactoring('''
+typedef NewName<T> = Map<int, T>;
+void f(NewName<String> a) {}
+''');
+  }
+
+  Future<void> test_createChange_typeAlias_legacy() async {
+    await indexTestUnit('''
+typedef void F();
+void f(F a) {}
+''');
+    // configure refactoring
+    createRenameRefactoringAtString('F()');
+    expect(refactoring.refactoringName, 'Rename Type Alias');
+    expect(refactoring.elementKindName, 'type alias');
+    expect(refactoring.oldName, 'F');
+    refactoring.newName = 'G';
+    // validate change
+    return assertSuccessfulRefactoring('''
+typedef void G();
+void f(G a) {}
+''');
+  }
+
   Future<void> _test_createChange_PropertyAccessorElement(String search) async {
     await indexTestUnit('''
 get test {}
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index 1d1665c..3cffbee 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:convert';
 import 'dart:io' as io;
 import 'dart:math' as math;
 
@@ -14,18 +13,26 @@
 import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
 import 'package:analysis_server/src/services/completion/dart/utilities.dart';
+import 'package:analysis_server/src/status/pages.dart';
 import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/dart/analysis/context_root.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/element/element.dart'
     show
         ClassElement,
-        Element,
-        ExtensionElement,
         ClassMemberElement,
+        CompilationUnitElement,
+        Element,
         ExecutableElement,
+        ExtensionElement,
         FieldElement,
+        FunctionElement,
+        LocalVariableElement,
+        ParameterElement,
+        PrefixElement,
+        TypeParameterElement,
         VariableElement;
 import 'package:analyzer/diagnostic/diagnostic.dart';
 import 'package:analyzer/error/error.dart' as err;
@@ -37,7 +44,6 @@
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/services/available_declarations.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
-import 'package:analyzer_plugin/protocol/protocol_common.dart' show ElementKind;
 import 'package:analyzer_plugin/src/utilities/completion/optype.dart';
 import 'package:args/args.dart';
 import 'package:meta/meta.dart';
@@ -54,72 +60,21 @@
     return io.exit(1);
   }
 
-  var options = CompletionMetricsOptions(
-      availableSuggestions: result[AVAILABLE_SUGGESTIONS],
-      overlay: result[OVERLAY],
-      printMissedCompletionDetails: result[PRINT_MISSED_COMPLETION_DETAILS],
-      printMissedCompletionSummary: result[PRINT_MISSED_COMPLETION_SUMMARY],
-      printMissingInformation: result[PRINT_MISSING_INFORMATION],
-      printMrrByLocation: result[PRINT_MRR_BY_LOCATION],
-      printSlowestResults: result[PRINT_SLOWEST_RESULTS],
-      printWorstResults: result[PRINT_WORST_RESULTS]);
+  var options = CompletionMetricsOptions(result);
   var root = result.rest[0];
   print('Analyzing root: "$root"');
   var stopwatch = Stopwatch()..start();
-  var code = await CompletionMetricsComputer(root, options).compute();
+  var computer = CompletionMetricsComputer(root, options);
+  var code = await computer.computeMetrics();
   stopwatch.stop();
 
   var duration = Duration(milliseconds: stopwatch.elapsedMilliseconds);
   print('');
   print('Metrics computed in $duration');
+  computer.printResults();
   return io.exit(code);
 }
 
-const String AVAILABLE_SUGGESTIONS = 'available-suggestions';
-
-/// An option to control whether and how overlays should be produced.
-const String OVERLAY = 'overlay';
-
-/// A mode indicating that no overlays should be produced.
-const String OVERLAY_NONE = 'none';
-
-/// A mode indicating that everything from the completion offset to the end of
-/// the file should be removed.
-const String OVERLAY_REMOVE_REST_OF_FILE = 'remove-rest-of-file';
-
-/// A mode indicating that the token whose offset is the same as the
-/// completion offset should be removed.
-const String OVERLAY_REMOVE_TOKEN = 'remove-token';
-
-/// A flag that causes detailed information to be printed every time a
-/// completion request fails to produce a suggestions matching the expected
-/// suggestion.
-const String PRINT_MISSED_COMPLETION_DETAILS =
-    'print-missed-completion-details';
-
-/// A flag that causes summary information to be printed about the times that a
-/// completion request failed to produce a suggestions matching the expected
-/// suggestion.
-const String PRINT_MISSED_COMPLETION_SUMMARY =
-    'print-missed-completion-summary';
-
-/// A flag that causes information to be printed about places where no
-/// completion location was computed and about information that's missing in the
-/// completion tables.
-const String PRINT_MISSING_INFORMATION = 'print-missing-information';
-
-/// A flag that causes information to be printed about the mrr score achieved at
-/// each completion location.
-const String PRINT_MRR_BY_LOCATION = 'print-mrr-by-location';
-
-/// A flag that causes information to be printed about the completion requests
-/// that were the slowest to return suggestions.
-const String PRINT_SLOWEST_RESULTS = 'print-slowest-results';
-
-/// A flag that causes information to be printed about the completion requests
-/// that had the worst mrr scores.
-const String PRINT_WORST_RESULTS = 'print-worst-results';
-
 /// A [Counter] to track the performance of each of the completion strategies
 /// that are being compared.
 Counter rankComparison = Counter('relevance rank comparison');
@@ -127,7 +82,7 @@
 /// Create a parser that can be used to parse the command-line arguments.
 ArgParser createArgParser() {
   return ArgParser()
-    ..addFlag(AVAILABLE_SUGGESTIONS,
+    ..addFlag(CompletionMetricsOptions.AVAILABLE_SUGGESTIONS,
         abbr: 'a',
         help:
             'Use the available suggestions feature in the Analysis Server when '
@@ -137,42 +92,46 @@
             'support in LSP.',
         defaultsTo: false,
         negatable: false)
+    ..addFlag(CompletionMetricsOptions.MD,
+        help: 'Use markdown as the output format.',
+        defaultsTo: false,
+        negatable: false)
     ..addOption(
       'help',
       abbr: 'h',
       help: 'Print this help message.',
     )
-    ..addOption(OVERLAY,
+    ..addOption(CompletionMetricsOptions.OVERLAY,
         allowed: [
-          OVERLAY_NONE,
-          OVERLAY_REMOVE_TOKEN,
-          OVERLAY_REMOVE_REST_OF_FILE
+          CompletionMetricsOptions.OVERLAY_NONE,
+          CompletionMetricsOptions.OVERLAY_REMOVE_TOKEN,
+          CompletionMetricsOptions.OVERLAY_REMOVE_REST_OF_FILE
         ],
-        defaultsTo: OVERLAY_NONE,
+        defaultsTo: CompletionMetricsOptions.OVERLAY_NONE,
         help:
             'Before attempting a completion at the location of each token, the '
             'token can be removed, or the rest of the file can be removed to '
             'test code completion with diverse methods. The default mode is to '
             'complete at the start of the token without modifying the file.')
-    ..addFlag(PRINT_MISSED_COMPLETION_DETAILS,
+    ..addFlag(CompletionMetricsOptions.PRINT_MISSED_COMPLETION_DETAILS,
         defaultsTo: false,
         help:
             'Print detailed information every time a completion request fails '
             'to produce a suggestions matching the expected suggestion.',
         negatable: false)
-    ..addFlag(PRINT_MISSED_COMPLETION_SUMMARY,
+    ..addFlag(CompletionMetricsOptions.PRINT_MISSED_COMPLETION_SUMMARY,
         defaultsTo: false,
         help: 'Print summary information about the times that a completion '
             'request failed to produce a suggestions matching the expected '
             'suggestion.',
         negatable: false)
-    ..addFlag(PRINT_MISSING_INFORMATION,
+    ..addFlag(CompletionMetricsOptions.PRINT_MISSING_INFORMATION,
         defaultsTo: false,
         help: 'Print information about places where no completion location was '
             'computed and about information that is missing in the completion '
             'tables.',
         negatable: false)
-    ..addFlag(PRINT_MRR_BY_LOCATION,
+    ..addFlag(CompletionMetricsOptions.PRINT_MRR_BY_LOCATION,
         defaultsTo: false,
         help:
             'Print information about the mrr score achieved at each completion '
@@ -180,16 +139,15 @@
             'score by pointing out the locations that are causing the biggest '
             'impact.',
         negatable: false)
-    ..addFlag(PRINT_SLOWEST_RESULTS,
+    ..addFlag(CompletionMetricsOptions.PRINT_SLOWEST_RESULTS,
         defaultsTo: false,
         help: 'Print information about the completion requests that were the '
             'slowest to return suggestions.',
         negatable: false)
-    ..addFlag(PRINT_WORST_RESULTS,
+    ..addFlag(CompletionMetricsOptions.PRINT_WORST_RESULTS,
         defaultsTo: false,
-        help:
-            'Print information about the completion requests that had the worst '
-            'mrr scores.',
+        help: 'Print information about the completion requests that had the '
+            'worst mrr scores.',
         negatable: false);
 }
 
@@ -227,12 +185,34 @@
 /// An indication of the group in which the completion falls for the purposes of
 /// subdividing the results.
 enum CompletionGroup {
+  classElement,
+  constructorElement,
+  enumElement,
+  extensionElement,
+
+  /// An instance member of a class, enum, mixin or extension.
   instanceMember,
+
+  labelElement,
+  localFunctionElement,
+  localVariableElement,
+  mixinElement,
+  parameterElement,
+  prefixElement,
+
+  /// A static member of a class, enum, mixin or extension.
   staticMember,
-  typeReference,
-  localReference,
-  paramReference,
-  topLevel
+
+  topLevelMember,
+  typeParameterElement,
+
+  // Groups for keywords.
+
+  keywordDynamic,
+  keywordVoid,
+
+  /// Anything that doesn't fit in one of the other groups.
+  unknown,
 }
 
 /// A wrapper for the collection of [Counter] and [MeanReciprocalRankComputer]
@@ -253,7 +233,7 @@
   /// The function to be executed when this metrics collector is disabled.
   final void Function() disableFunction;
 
-  Counter completionCounter = Counter('successful/ unsuccessful completions');
+  Counter completionCounter = Counter('all completions');
 
   Counter completionMissedTokenCounter =
       Counter('unsuccessful completion token counter');
@@ -268,29 +248,17 @@
       ArithmeticMeanComputer('ms per completion');
 
   MeanReciprocalRankComputer mrrComputer =
-      MeanReciprocalRankComputer('successful/ unsuccessful completions');
+      MeanReciprocalRankComputer('all completions');
 
   MeanReciprocalRankComputer successfulMrrComputer =
       MeanReciprocalRankComputer('successful completions');
 
-  MeanReciprocalRankComputer instanceMemberMrrComputer =
-      MeanReciprocalRankComputer('instance member completions');
+  /// A table mapping completion groups to the mrr computer used to track the
+  /// quality of suggestions for those groups.
+  Map<CompletionGroup, MeanReciprocalRankComputer> groupMrrComputers = {};
 
-  MeanReciprocalRankComputer staticMemberMrrComputer =
-      MeanReciprocalRankComputer('static member completions');
-
-  MeanReciprocalRankComputer typeRefMrrComputer =
-      MeanReciprocalRankComputer('type reference completions');
-
-  MeanReciprocalRankComputer localRefMrrComputer =
-      MeanReciprocalRankComputer('local reference completions');
-
-  MeanReciprocalRankComputer paramRefMrrComputer =
-      MeanReciprocalRankComputer('param reference completions');
-
-  MeanReciprocalRankComputer topLevelMrrComputer =
-      MeanReciprocalRankComputer('non-type member completions');
-
+  /// A table mapping locations to the mrr computer used to track the quality of
+  /// suggestions for those locations.
   Map<String, MeanReciprocalRankComputer> locationMrrComputers = {};
 
   ArithmeticMeanComputer charsBeforeTop =
@@ -309,55 +277,11 @@
   /// The completion locations for which no relevance table was available.
   Set<String> missingCompletionLocationTables = {};
 
-  /// A list of the top [maxWorstResults] completion results with the highest
-  /// (worst) ranks for completing to instance members.
-  List<CompletionResult> instanceMemberWorstResults = [];
+  Map<CompletionGroup, List<CompletionResult>> slowestResults = {};
 
-  /// A list of the top [maxWorstResults] completion results with the highest
-  /// (worst) ranks for completing to static members.
-  List<CompletionResult> staticMemberWorstResults = [];
+  Map<CompletionGroup, List<CompletionResult>> worstResults = {};
 
-  /// A list of the top [maxWorstResults] completion results with the highest
-  /// (worst) ranks for completing to type references.
-  List<CompletionResult> typeRefWorstResults = [];
-
-  /// A list of the top [maxWorstResults] completion results with the highest
-  /// (worst) ranks for completing to local references.
-  List<CompletionResult> localRefWorstResults = [];
-
-  /// A list of the top [maxWorstResults] completion results with the highest
-  /// (worst) ranks for completing to parameter references.
-  List<CompletionResult> paramRefWorstResults = [];
-
-  /// A list of the top [maxWorstResults] completion results with the highest
-  /// (worst) ranks for completing to top-level declarations.
-  List<CompletionResult> topLevelWorstResults = [];
-
-  /// A list of the top [maxSlowestResults] completion results that took the
-  /// longest top compute for instance members.
-  List<CompletionResult> instanceMemberSlowestResults = [];
-
-  /// A list of the top [maxSlowestResults] completion results that took the
-  /// longest top compute for static members.
-  List<CompletionResult> staticMemberSlowestResults = [];
-
-  /// A list of the top [maxSlowestResults] completion results that took the
-  /// longest top compute for type references.
-  List<CompletionResult> typeRefSlowestResults = [];
-
-  /// A list of the top [maxSlowestResults] completion results that took the
-  /// longest top compute for local references.
-  List<CompletionResult> localRefSlowestResults = [];
-
-  /// A list of the top [maxSlowestResults] completion results that took the
-  /// longest top compute for parameter references.
-  List<CompletionResult> paramRefSlowestResults = [];
-
-  /// A list of the top [maxSlowestResults] completion results that took the
-  /// longest top compute for top-level declarations.
-  List<CompletionResult> topLevelSlowestResults = [];
-
-  CompletionMetrics(this.name, this.enableFunction, this.disableFunction);
+  CompletionMetrics(this.name, {this.enableFunction, this.disableFunction});
 
   /// Perform any operations required in order to revert computing the kind of
   /// completions represented by this metrics collector.
@@ -375,24 +299,25 @@
     }
   }
 
-  /// Record this completion result, this method handles the worst ranked items
+  /// Record the completion [result]. This method handles the worst ranked items
   /// as well as the longest sets of results to compute.
-  void recordCompletionResult(CompletionResult result) {
+  void recordCompletionResult(
+      CompletionResult result, MetricsSuggestionListener listener) {
     _recordTime(result);
     _recordMrr(result);
     _recordWorstResult(result);
     _recordSlowestResult(result);
-    _recordMissingInformation(result);
+    _recordMissingInformation(listener);
   }
 
   /// If the completion location was requested but missing when computing the
   /// [result], then record where that happened.
-  void _recordMissingInformation(CompletionResult result) {
-    var location = result.listener?.missingCompletionLocation;
+  void _recordMissingInformation(MetricsSuggestionListener listener) {
+    var location = listener?.missingCompletionLocation;
     if (location != null) {
       missingCompletionLocations.add(location);
     } else {
-      location = result.listener?.missingCompletionLocationTable;
+      location = listener?.missingCompletionLocationTable;
       if (location != null) {
         missingCompletionLocationTables.add(location);
       }
@@ -405,26 +330,10 @@
     // Record globally.
     successfulMrrComputer.addRank(rank);
     // Record by group.
-    switch (result.group) {
-      case CompletionGroup.instanceMember:
-        instanceMemberMrrComputer.addRank(rank);
-        break;
-      case CompletionGroup.staticMember:
-        staticMemberMrrComputer.addRank(rank);
-        break;
-      case CompletionGroup.typeReference:
-        typeRefMrrComputer.addRank(rank);
-        break;
-      case CompletionGroup.localReference:
-        localRefMrrComputer.addRank(rank);
-        break;
-      case CompletionGroup.paramReference:
-        paramRefMrrComputer.addRank(rank);
-        break;
-      case CompletionGroup.topLevel:
-        topLevelMrrComputer.addRank(rank);
-        break;
-    }
+    var group = result.group;
+    groupMrrComputers
+        .putIfAbsent(group, () => MeanReciprocalRankComputer(group.name))
+        .addRank(rank);
     // Record by completion location.
     var location = result.completionLocation;
     if (location != null) {
@@ -437,33 +346,15 @@
   /// If the [result] is took longer than any previously recorded results,
   /// record it.
   void _recordSlowestResult(CompletionResult result) {
-    List<CompletionResult> getSlowestResults() {
-      switch (result.group) {
-        case CompletionGroup.instanceMember:
-          return instanceMemberSlowestResults;
-        case CompletionGroup.staticMember:
-          return staticMemberSlowestResults;
-        case CompletionGroup.typeReference:
-          return typeRefSlowestResults;
-        case CompletionGroup.localReference:
-          return localRefSlowestResults;
-        case CompletionGroup.paramReference:
-          return paramRefSlowestResults;
-        case CompletionGroup.topLevel:
-          return topLevelSlowestResults;
-      }
-      return const <CompletionResult>[];
-    }
-
-    var slowestResults = getSlowestResults();
-    if (slowestResults.length >= maxSlowestResults) {
-      if (result.elapsedMS <= slowestResults.last.elapsedMS) {
+    var results = slowestResults.putIfAbsent(result.group, () => []);
+    if (results.length >= maxSlowestResults) {
+      if (result.elapsedMS <= results.last.elapsedMS) {
         return;
       }
-      slowestResults.removeLast();
+      results.removeLast();
     }
-    slowestResults.add(result);
-    slowestResults.sort((first, second) => second.elapsedMS - first.elapsedMS);
+    results.add(result);
+    results.sort((first, second) => second.elapsedMS - first.elapsedMS);
   }
 
   /// Record this elapsed ms count for the average ms count.
@@ -473,33 +364,15 @@
 
   /// If the [result] is worse than any previously recorded results, record it.
   void _recordWorstResult(CompletionResult result) {
-    List<CompletionResult> getWorstResults() {
-      switch (result.group) {
-        case CompletionGroup.instanceMember:
-          return instanceMemberWorstResults;
-        case CompletionGroup.staticMember:
-          return staticMemberWorstResults;
-        case CompletionGroup.typeReference:
-          return typeRefWorstResults;
-        case CompletionGroup.localReference:
-          return localRefWorstResults;
-        case CompletionGroup.paramReference:
-          return paramRefWorstResults;
-        case CompletionGroup.topLevel:
-          return topLevelWorstResults;
-      }
-      return const <CompletionResult>[];
-    }
-
-    var worstResults = getWorstResults();
-    if (worstResults.length >= maxWorstResults) {
-      if (result.place.rank <= worstResults.last.place.rank) {
+    var results = worstResults.putIfAbsent(result.group, () => []);
+    if (results.length >= maxWorstResults) {
+      if (result.place.rank <= results.last.place.rank) {
         return;
       }
-      worstResults.removeLast();
+      results.removeLast();
     }
-    worstResults.add(result);
-    worstResults.sort((first, second) => second.place.rank - first.place.rank);
+    results.add(result);
+    results.sort((first, second) => second.place.rank - first.place.rank);
   }
 }
 
@@ -513,7 +386,7 @@
 
   ResolvedUnitResult _resolvedUnitResult;
 
-  /// The int to be returned from the [compute] call.
+  /// The int to be returned from the [computeMetrics] call.
   int resultCode;
 
   /// A list of the metrics to be computed.
@@ -526,11 +399,13 @@
 
   CompletionMetricsComputer(this.rootPath, this.options);
 
-  Future<int> compute() async {
+  Future<int> computeMetrics() async {
     resultCode = 0;
     // To compare two or more changes to completions, add a `CompletionMetrics`
     // object with enable and disable functions to the list of `targetMetrics`.
-    targetMetrics.add(CompletionMetrics('shipping', null, null));
+    targetMetrics.add(CompletionMetrics('shipping',
+        enableFunction: null, disableFunction: null));
+
     final collection = AnalysisContextCollection(
       includedPaths: [rootPath],
       resourceProvider: PhysicalResourceProvider.INSTANCE,
@@ -538,25 +413,6 @@
     for (var context in collection.contexts) {
       await _computeInContext(context.contextRoot);
     }
-    printComparison();
-    for (var metrics in targetMetrics) {
-      printMetrics(metrics);
-
-      print('');
-      print('====================');
-      rankComparison.printCounterValues();
-      print('====================');
-
-      if (options.printMissingInformation) {
-        printMissingInformation(metrics);
-      }
-      if (options.printSlowestResults) {
-        printSlowestResults(metrics);
-      }
-      if (options.printWorstResults) {
-        printWorstResults(metrics);
-      }
-    }
     return resultCode;
   }
 
@@ -567,8 +423,7 @@
       String completionLocation,
       List<protocol.CompletionSuggestion> suggestions,
       CompletionMetrics metrics,
-      int elapsedMS,
-      bool doPrintMissedCompletions) {
+      int elapsedMS) {
     assert(suggestions != null);
 
     var place = placementInSuggestionList(suggestions, expectedCompletion);
@@ -578,8 +433,36 @@
     if (place.denominator != 0) {
       metrics.completionCounter.count('successful');
 
-      metrics.recordCompletionResult(CompletionResult(place, request, listener,
-          suggestions, expectedCompletion, completionLocation, elapsedMS));
+      var rank = place.rank;
+      var suggestion = suggestions[rank - 1];
+      var actualSuggestion =
+          SuggestionData(suggestion, listener.featureMap[suggestion]);
+      List<SuggestionData> topSuggestions;
+      Map<int, int> precedingRelevanceCounts;
+      if (options.printWorstResults) {
+        topSuggestions = suggestions
+            .sublist(0, math.min(10, suggestions.length))
+            .map((suggestion) =>
+                SuggestionData(suggestion, listener.featureMap[suggestion]))
+            .toList();
+        precedingRelevanceCounts = <int, int>{};
+        for (var i = 0; i < rank - 1; i++) {
+          var relevance = suggestions[i].relevance;
+          precedingRelevanceCounts[relevance] =
+              (precedingRelevanceCounts[relevance] ?? 0) + 1;
+        }
+      }
+      metrics.recordCompletionResult(
+          CompletionResult(
+              place,
+              request,
+              actualSuggestion,
+              topSuggestions,
+              precedingRelevanceCounts,
+              expectedCompletion,
+              completionLocation,
+              elapsedMS),
+          listener);
 
       var charsBeforeTop =
           _computeCharsBeforeTop(expectedCompletion, suggestions);
@@ -598,7 +481,7 @@
       metrics.completionElementKindCounter
           .count(expectedCompletion.elementKind.toString());
 
-      if (doPrintMissedCompletions) {
+      if (options.printMissedCompletionDetails) {
         protocol.CompletionSuggestion closeMatchSuggestion;
         for (var suggestion in suggestions) {
           if (suggestion.completion == expectedCompletion.completion) {
@@ -619,50 +502,64 @@
     }
   }
 
-  void printComparison() {
-    List<String> toRow(Iterable<MeanReciprocalRankComputer> computers) {
-      return [
-        computers.first.name,
-        for (var computer in computers) (1 / computer.mrr).toStringAsFixed(3),
-      ];
+  void printComparisons() {
+    printHeading(1, 'Comparison of experiments');
+    printMrrComparison();
+    printCounter(rankComparison);
+    printOtherMetrics();
+    printCompletionCounts();
+  }
+
+  void printCompletionCounts() {
+    String toString(int count, int totalCount) {
+      return '$count (${printPercentage(count / totalCount, 2)})';
     }
 
-    var buffer = StringBuffer();
+    var counters = targetMetrics.map((metrics) => metrics.completionCounter);
     var table = [
       ['', for (var metrics in targetMetrics) metrics.name],
-      toRow(targetMetrics.map((metrics) => metrics.mrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.successfulMrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.instanceMemberMrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.staticMemberMrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.typeRefMrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.localRefMrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.paramRefMrrComputer)),
-      toRow(targetMetrics.map((metrics) => metrics.topLevelMrrComputer)),
+      ['total', for (var counter in counters) counter.totalCount.toString()],
+      [
+        'successful',
+        for (var counter in counters)
+          toString(counter.getCountOf('successful'), counter.totalCount)
+      ],
+      [
+        'unsuccessful',
+        for (var counter in counters)
+          toString(counter.getCountOf('unsuccessful'), counter.totalCount)
+      ],
     ];
-    for (var i = 1; i < table[0].length; i++) {
-      rightJustifyColumn(i, table);
-    }
-    buffer.writeTable(table);
+    rightJustifyColumns(table, range(1, table[0].length));
 
+    printHeading(2, 'Comparison of completion counts');
+    printTable(table);
+  }
+
+  void printCounter(Counter counter) {
+    var name = counter.name;
+    var total = counter.totalCount;
+    printHeading(2, "Counts for '$name' (total = $total)");
+    counter.printCounterValues();
+  }
+
+  void printHeading(int level, String heading) {
+    if (options.markdown) {
+      var prefix = '#' * level;
+      print('$prefix $heading');
+    } else {
+      print(heading);
+      print(((level == 1) ? '=' : '-') * heading.length);
+    }
     print('');
-    print('Comparison of inverse mean reciprocal ranks (lower is better)');
-    print('');
-    print(buffer.toString());
   }
 
   void printMetrics(CompletionMetrics metrics) {
-    print('====================');
-    print('Completion metrics for ${metrics.name}:');
-    print('');
+    printHeading(1, 'Completion metrics for ${metrics.name}');
     if (options.printMissedCompletionSummary) {
-      metrics.completionMissedTokenCounter.printCounterValues();
-      print('');
-
-      metrics.completionKindCounter.printCounterValues();
-      print('');
-
-      metrics.completionElementKindCounter.printCounterValues();
-      print('');
+      printCounter(metrics.completionMissedTokenCounter);
+      printCounter(metrics.completionKindCounter);
+      printCounter(metrics.completionElementKindCounter);
     }
 
     List<String> toRow(MeanReciprocalRankComputer computer) {
@@ -676,26 +573,18 @@
       ];
     }
 
-    var buffer = StringBuffer();
+    var groups = metrics.groupMrrComputers.keys.toList();
+    groups.sort((first, second) => first.name.compareTo(second.name));
     var table = [
       ['', 'mrr', 'inverse mrr', 'mrr_5', 'inverse mrr_5', 'count'],
       toRow(metrics.mrrComputer),
       toRow(metrics.successfulMrrComputer),
-      toRow(metrics.instanceMemberMrrComputer),
-      toRow(metrics.staticMemberMrrComputer),
-      toRow(metrics.typeRefMrrComputer),
-      toRow(metrics.localRefMrrComputer),
-      toRow(metrics.paramRefMrrComputer),
-      toRow(metrics.topLevelMrrComputer),
+      for (var group in groups) toRow(metrics.groupMrrComputers[group]),
     ];
-    rightJustifyColumn(2, table);
-    rightJustifyColumn(4, table);
-    rightJustifyColumn(5, table);
-    buffer.writeTable(table);
+    rightJustifyColumns(table, [2, 4, 5]);
 
-    print('Mean Reciprocal Rank');
-    print('');
-    print(buffer.toString());
+    printHeading(2, 'Mean Reciprocal Rank');
+    printTable(table);
 
     if (options.printMrrByLocation) {
       var lines = <LocationTableLine>[];
@@ -722,69 +611,158 @@
         var mrr_5 = line.mrr_5.toStringAsFixed(3);
         table.add([location, product, count, mrr, mrr_5]);
       }
-      var buffer = StringBuffer();
-      buffer.writeTable(table);
-      print(buffer.toString());
-      print('');
+      printTable(table);
     }
-
-    metrics.charsBeforeTop.printMean();
-    metrics.charsBeforeTopFive.printMean();
-    metrics.insertionLengthTheoretical.printMean();
-    print('');
-
-    print('Summary for $rootPath:');
-    metrics.meanCompletionMS.printMean();
-    metrics.completionCounter.printCounterValues();
-    print('====================');
   }
 
   void printMissingInformation(CompletionMetrics metrics) {
     var locations = metrics.missingCompletionLocations;
     if (locations.isNotEmpty) {
       print('');
-      print('====================');
-      print('Missing completion location in the following places:');
+      printHeading(2, 'Missing completion location in the following places');
       for (var location in locations.toList()..sort()) {
-        print('  $location');
+        print('- $location');
       }
     }
 
     var tables = metrics.missingCompletionLocationTables;
     if (tables.isNotEmpty) {
       print('');
-      print('====================');
-      print('Missing tables for the following completion locations:');
+      printHeading(2, 'Missing tables for the following completion locations');
       for (var table in tables.toList()..sort()) {
-        print('  $table');
+        print('- $table');
+      }
+    }
+  }
+
+  void printMrrComparison() {
+    List<String> toRow(Iterable<MeanReciprocalRankComputer> sources) {
+      var computers = sources.toList();
+      var baseComputer = computers.first;
+      var row = [baseComputer.name];
+      var baseInverseMrr = 1 / baseComputer.mrr;
+      row.add(baseInverseMrr.toStringAsFixed(3));
+      for (var i = 1; i < computers.length; i++) {
+        var inverseMrr = 1 / computers[i].mrr;
+        var delta = inverseMrr - baseInverseMrr;
+        row.add('|');
+        row.add(inverseMrr.toStringAsFixed(3));
+        row.add(delta.toStringAsFixed(3));
+      }
+      return row;
+    }
+
+    var columnHeaders = [' ', targetMetrics[0].name];
+    for (var i = 1; i < targetMetrics.length; i++) {
+      columnHeaders.add('|');
+      columnHeaders.add('${targetMetrics[i].name}');
+      columnHeaders.add('delta');
+    }
+    var blankRow = [for (int i = 0; i < columnHeaders.length; i++) ''];
+    var table = [
+      columnHeaders,
+      toRow(targetMetrics.map((metrics) => metrics.mrrComputer)),
+      toRow(targetMetrics.map((metrics) => metrics.successfulMrrComputer)),
+      blankRow,
+    ];
+    var elementKinds = targetMetrics
+        .expand((metrics) => metrics.groupMrrComputers.keys)
+        .toSet()
+        .toList();
+    elementKinds.sort((first, second) => first.name.compareTo(second.name));
+    for (var kind in elementKinds) {
+      table.add(toRow(
+          targetMetrics.map((metrics) => metrics.groupMrrComputers[kind])));
+    }
+    if (options.printMrrByLocation) {
+      table.add(blankRow);
+      var locations = targetMetrics
+          .expand((metrics) => metrics.locationMrrComputers.keys)
+          .toSet()
+          .toList();
+      locations.sort();
+      for (var location in locations) {
+        table.add(toRow(targetMetrics
+            .map((metrics) => metrics.locationMrrComputers[location])));
+      }
+    }
+    rightJustifyColumns(table, range(1, table[0].length));
+
+    printHeading(2, 'Comparison of inverse mean reciprocal ranks');
+    print('A lower value is better, so a negative delta is good.');
+    print('');
+    printTable(table);
+  }
+
+  void printOtherMetrics() {
+    List<String> toRow(Iterable<ArithmeticMeanComputer> sources) {
+      var computers = sources.toList();
+      var row = [computers.first.name];
+      for (var computer in computers) {
+        row.add(computer.mean.toStringAsFixed(6));
+      }
+      return row;
+    }
+
+    var table = [
+      ['', for (var metrics in targetMetrics) metrics.name],
+      toRow(targetMetrics.map((metrics) => metrics.meanCompletionMS)),
+      toRow(targetMetrics.map((metrics) => metrics.charsBeforeTop)),
+      toRow(targetMetrics.map((metrics) => metrics.charsBeforeTopFive)),
+      toRow(targetMetrics.map((metrics) => metrics.insertionLengthTheoretical)),
+    ];
+    rightJustifyColumns(table, range(1, table[0].length));
+
+    printHeading(2, 'Comparison of other metrics');
+    printTable(table);
+  }
+
+  void printResults() {
+    if (targetMetrics.length > 1) {
+      print('');
+      printComparisons();
+    }
+    var needsBlankLine = false;
+    for (var metrics in targetMetrics) {
+      if (needsBlankLine) {
+        print('');
+      } else {
+        needsBlankLine = true;
+      }
+      printMetrics(metrics);
+
+      if (options.printMissingInformation) {
+        printMissingInformation(metrics);
+      }
+      if (options.printSlowestResults) {
+        printSlowestResults(metrics);
+      }
+      if (options.printWorstResults) {
+        printWorstResults(metrics);
       }
     }
   }
 
   void printSlowestResults(CompletionMetrics metrics) {
+    var slowestResults = metrics.slowestResults;
+    var groups = slowestResults.keys.toList();
+    groups.sort((first, second) => first.name.compareTo(second.name));
     print('');
-    print('====================');
-    print('The slowest completion results to compute');
-    _printSlowestResults(
-        'Instance members', metrics.instanceMemberSlowestResults);
-    _printSlowestResults('Static members', metrics.staticMemberSlowestResults);
-    _printSlowestResults('Type references', metrics.typeRefSlowestResults);
-    _printSlowestResults('Local references', metrics.localRefSlowestResults);
-    _printSlowestResults(
-        'Parameter references', metrics.paramRefSlowestResults);
-    _printSlowestResults('Top level', metrics.topLevelSlowestResults);
+    printHeading(2, 'The slowest completion results to compute');
+    for (var group in groups) {
+      _printSlowestResults('In ${group.name}', slowestResults[group]);
+    }
   }
 
   void printWorstResults(CompletionMetrics metrics) {
+    var worstResults = metrics.worstResults;
+    var groups = worstResults.keys.toList();
+    groups.sort((first, second) => first.name.compareTo(second.name));
     print('');
-    print('====================');
-    print('The worst completion results');
-    _printWorstResults('Instance members', metrics.instanceMemberWorstResults);
-    _printWorstResults('Static members', metrics.staticMemberWorstResults);
-    _printWorstResults('Type references', metrics.topLevelWorstResults);
-    _printWorstResults('Local references', metrics.localRefWorstResults);
-    _printWorstResults('Parameter references', metrics.paramRefWorstResults);
-    _printWorstResults('Top level', metrics.topLevelWorstResults);
+    printHeading(2, 'The worst completion results');
+    for (var group in groups) {
+      _printWorstResults('In ${group.name}', worstResults[group]);
+    }
   }
 
   int _computeCharsBeforeTop(ExpectedCompletion target,
@@ -948,11 +926,9 @@
 
             // If an overlay option is being used, compute the overlay file, and
             // have the context reanalyze the file
-            if (options.overlay != OVERLAY_NONE) {
+            if (options.overlay != CompletionMetricsOptions.OVERLAY_NONE) {
               var overlayContents = _getOverlayContents(
-                  _resolvedUnitResult.content,
-                  expectedCompletion,
-                  options.overlay);
+                  _resolvedUnitResult.content, expectedCompletion);
 
               _provider.setOverlay(filePath,
                   content: overlayContents,
@@ -970,8 +946,7 @@
 
             Future<int> handleExpectedCompletion(
                 {MetricsSuggestionListener listener,
-                @required CompletionMetrics metrics,
-                @required bool printMissedCompletions}) async {
+                @required CompletionMetrics metrics}) async {
               var stopwatch = Stopwatch()..start();
               var request = CompletionRequestImpl(
                 resolvedUnitResult,
@@ -1006,8 +981,7 @@
                   opType.completionLocation,
                   suggestions,
                   metrics,
-                  stopwatch.elapsedMilliseconds,
-                  printMissedCompletions);
+                  stopwatch.elapsedMilliseconds);
             }
 
             var bestRank = -1;
@@ -1015,11 +989,14 @@
             for (var metrics in targetMetrics) {
               // Compute the completions.
               metrics.enable();
+              // if (FeatureComputer.noDisabledFeatures) {
+              //   var line = expectedCompletion.lineNumber;
+              //   var column = expectedCompletion.columnNumber;
+              //   print('$filePath:$line:$column');
+              // }
               var listener = MetricsSuggestionListener();
               var rank = await handleExpectedCompletion(
-                  listener: listener,
-                  metrics: metrics,
-                  printMissedCompletions: options.printMissedCompletionDetails);
+                  listener: listener, metrics: metrics);
               if (bestRank < 0 || rank < bestRank) {
                 bestRank = rank;
                 bestName = metrics.name;
@@ -1030,7 +1007,7 @@
 
             // If an overlay option is being used, remove the overlay applied
             // earlier
-            if (options.overlay != OVERLAY_NONE) {
+            if (options.overlay != CompletionMetricsOptions.OVERLAY_NONE) {
               _provider.removeOverlay(filePath);
             }
           }
@@ -1046,38 +1023,46 @@
 
   List<protocol.CompletionSuggestion> _filterSuggestions(
       String prefix, List<protocol.CompletionSuggestion> suggestions) {
-    // TODO(brianwilkerson) Replace this with a more realistic filtering algorithm.
+    // TODO(brianwilkerson) Replace this with a more realistic filtering
+    //  algorithm.
     return suggestions
         .where((suggestion) => suggestion.completion.startsWith(prefix))
         .toList();
   }
 
-  String _getOverlayContents(String contents,
-      ExpectedCompletion expectedCompletion, String overlayMode) {
+  String _getOverlayContents(
+      String contents, ExpectedCompletion expectedCompletion) {
     assert(contents.isNotEmpty);
     var offset = expectedCompletion.offset;
     var length = expectedCompletion.syntacticEntity.length;
     assert(offset >= 0);
     assert(length > 0);
-    if (overlayMode == OVERLAY_REMOVE_TOKEN) {
+    if (options.overlay == CompletionMetricsOptions.OVERLAY_REMOVE_TOKEN) {
       return contents.substring(0, offset) +
           contents.substring(offset + length);
-    } else if (overlayMode == OVERLAY_REMOVE_REST_OF_FILE) {
+    } else if (options.overlay ==
+        CompletionMetricsOptions.OVERLAY_REMOVE_REST_OF_FILE) {
       return contents.substring(0, offset);
     } else {
+      var removeToken = CompletionMetricsOptions.OVERLAY_REMOVE_TOKEN;
+      var removeRest = CompletionMetricsOptions.OVERLAY_REMOVE_REST_OF_FILE;
       throw Exception('\'_getOverlayContents\' called with option other than'
-          '$OVERLAY_REMOVE_TOKEN and $OVERLAY_REMOVE_REST_OF_FILE: $overlayMode');
+          '$removeToken and $removeRest: ${options.overlay}');
     }
   }
 
   void _printSlowestResults(
       String title, List<CompletionResult> slowestResults) {
-    print('');
-    print(title);
+    printHeading(3, title);
+    var needsBlankLine = false;
     for (var result in slowestResults) {
       var elapsedMS = result.elapsedMS;
       var expected = result.expectedCompletion;
-      print('');
+      if (needsBlankLine) {
+        print('');
+      } else {
+        needsBlankLine = true;
+      }
       print('  Elapsed ms: $elapsedMS');
       print('  Completion: ${expected.completion}');
       print('  Completion kind: ${expected.kind}');
@@ -1087,45 +1072,79 @@
   }
 
   void _printWorstResults(String title, List<CompletionResult> worstResults) {
-    print('');
-    print(title);
+    List<String> suggestionRow(int rank, SuggestionData data) {
+      var suggestion = data.suggestion;
+      return [
+        rank.toString(),
+        suggestion.relevance.toString(),
+        suggestion.completion,
+        suggestion.kind.toString()
+      ];
+    }
+
+    List<String> featuresRow(int rank, SuggestionData data) {
+      var features = data.features;
+      return [
+        rank.toString(),
+        for (var feature in features) feature.toStringAsFixed(4)
+      ];
+    }
+
+    printHeading(3, title);
+    var needsBlankLine = false;
     for (var result in worstResults) {
       var rank = result.place.rank;
+      var actualSuggestion = result.actualSuggestion;
       var expected = result.expectedCompletion;
-      var suggestions = result.suggestions;
-      var suggestion = suggestions[rank - 1];
 
-      var features = result.listener?.featureMap[suggestion];
-      var topSuggestions =
-          suggestions.sublist(0, math.min(10, suggestions.length));
+      var topSuggestions = result.topSuggestions;
       var topSuggestionCount = topSuggestions.length;
 
-      var preceding = <int, int>{};
-      for (var i = 0; i < rank - 1; i++) {
-        var relevance = suggestions[i].relevance;
-        preceding[relevance] = (preceding[relevance] ?? 0) + 1;
-      }
+      var preceding = result.precedingRelevanceCounts;
       var precedingRelevances = preceding.keys.toList();
       precedingRelevances.sort();
 
-      print('');
+      var suggestionsTable = [
+        ['Rank', 'Relevance', 'Completion', 'Kind']
+      ];
+      for (var i = 0; i < topSuggestionCount; i++) {
+        suggestionsTable.add(suggestionRow(i, topSuggestions[i]));
+      }
+      suggestionsTable.add(suggestionRow(rank, actualSuggestion));
+      rightJustifyColumns(suggestionsTable, [0, 1]);
+
+      var featuresTable = [
+        [
+          'Rank',
+          'contextType',
+          'elementKind',
+          'hasDeprecated',
+          'inheritanceDistance',
+          'isConstant',
+          'keyword',
+          'localVariableDistance',
+          'startsWithDollar',
+          'superMatches'
+        ]
+      ];
+      for (var i = 0; i < topSuggestionCount; i++) {
+        featuresTable.add(featuresRow(i, topSuggestions[i]));
+      }
+      featuresTable.add(featuresRow(rank, actualSuggestion));
+      rightJustifyColumns(featuresTable, range(0, featuresTable[0].length));
+
+      if (needsBlankLine) {
+        print('');
+      } else {
+        needsBlankLine = true;
+      }
       print('  Rank: $rank');
       print('  Location: ${expected.location}');
-      print('  Suggestion: ${suggestion.description}');
-      print('  Features: $features');
-      print('  Top $topSuggestionCount suggestions:');
-      for (var i = 0; i < topSuggestionCount; i++) {
-        var topSuggestion = topSuggestions[i];
-        print('  $i Suggestion: ${topSuggestion.description}');
-        if (result.listener != null) {
-          var feature = result.listener.featureMap[topSuggestion];
-          if (feature == null || feature.isEmpty) {
-            print('    Features: <none>');
-          } else {
-            print('    Features: $feature');
-          }
-        }
-      }
+      print('  Comparison with the top $topSuggestionCount suggestions:');
+      printTable(suggestionsTable);
+      print('  Comparison of features with the top $topSuggestionCount '
+          'suggestions:');
+      printTable(featuresTable);
       print('  Preceding relevance scores and counts:');
       for (var relevance in precedingRelevances.reversed) {
         print('    $relevance: ${preceding[relevance]}');
@@ -1161,10 +1180,63 @@
 
 /// The options specified on the command-line.
 class CompletionMetricsOptions {
+  /// A flag that causes the available suggestion sets to be used while
+  /// computing suggestions.
+  static const String AVAILABLE_SUGGESTIONS = 'available-suggestions';
+
+  /// A flag that causes the output to be in markdown format.
+  static const String MD = 'md';
+
+  /// An option to control whether and how overlays should be produced.
+  static const String OVERLAY = 'overlay';
+
+  /// A mode indicating that no overlays should be produced.
+  static const String OVERLAY_NONE = 'none';
+
+  /// A mode indicating that everything from the completion offset to the end of
+  /// the file should be removed.
+  static const String OVERLAY_REMOVE_REST_OF_FILE = 'remove-rest-of-file';
+
+  /// A mode indicating that the token whose offset is the same as the
+  /// completion offset should be removed.
+  static const String OVERLAY_REMOVE_TOKEN = 'remove-token';
+
+  /// A flag that causes detailed information to be printed every time a
+  /// completion request fails to produce a suggestions matching the expected
+  /// suggestion.
+  static const String PRINT_MISSED_COMPLETION_DETAILS =
+      'print-missed-completion-details';
+
+  /// A flag that causes summary information to be printed about the times that
+  /// a completion request failed to produce a suggestions matching the expected
+  /// suggestion.
+  static const String PRINT_MISSED_COMPLETION_SUMMARY =
+      'print-missed-completion-summary';
+
+  /// A flag that causes information to be printed about places where no
+  /// completion location was computed and about information that's missing in
+  /// the completion tables.
+  static const String PRINT_MISSING_INFORMATION = 'print-missing-information';
+
+  /// A flag that causes information to be printed about the mrr score achieved
+  /// at each completion location.
+  static const String PRINT_MRR_BY_LOCATION = 'print-mrr-by-location';
+
+  /// A flag that causes information to be printed about the completion requests
+  /// that were the slowest to return suggestions.
+  static const String PRINT_SLOWEST_RESULTS = 'print-slowest-results';
+
+  /// A flag that causes information to be printed about the completion requests
+  /// that had the worst mrr scores.
+  static const String PRINT_WORST_RESULTS = 'print-worst-results';
+
   /// A flag indicating whether available suggestions should be enabled for this
   /// run.
   final bool availableSuggestions;
 
+  /// A flag indicating whether the output should use markdown.
+  final bool markdown;
+
   /// The overlay mode that should be used.
   final String overlay;
 
@@ -1195,8 +1267,22 @@
   /// completion requests that had the worst mrr scores.
   final bool printWorstResults;
 
-  CompletionMetricsOptions(
+  factory CompletionMetricsOptions(results) {
+    return CompletionMetricsOptions._(
+        availableSuggestions: results[AVAILABLE_SUGGESTIONS],
+        markdown: results[MD],
+        overlay: results[OVERLAY],
+        printMissedCompletionDetails: results[PRINT_MISSED_COMPLETION_DETAILS],
+        printMissedCompletionSummary: results[PRINT_MISSED_COMPLETION_SUMMARY],
+        printMissingInformation: results[PRINT_MISSING_INFORMATION],
+        printMrrByLocation: results[PRINT_MRR_BY_LOCATION],
+        printSlowestResults: results[PRINT_SLOWEST_RESULTS],
+        printWorstResults: results[PRINT_WORST_RESULTS]);
+  }
+
+  CompletionMetricsOptions._(
       {@required this.availableSuggestions,
+      @required this.markdown,
       @required this.overlay,
       @required this.printMissedCompletionDetails,
       @required this.printMissedCompletionSummary,
@@ -1215,9 +1301,9 @@
 
   final CompletionRequestImpl request;
 
-  final MetricsSuggestionListener listener;
+  final SuggestionData actualSuggestion;
 
-  final List<protocol.CompletionSuggestion> suggestions;
+  final List<SuggestionData> topSuggestions;
 
   final ExpectedCompletion expectedCompletion;
 
@@ -1225,13 +1311,23 @@
 
   final int elapsedMS;
 
-  CompletionResult(this.place, this.request, this.listener, this.suggestions,
-      this.expectedCompletion, this.completionLocation, this.elapsedMS);
+  final Map<int, int> precedingRelevanceCounts;
+
+  CompletionResult(
+      this.place,
+      this.request,
+      this.actualSuggestion,
+      this.topSuggestions,
+      this.precedingRelevanceCounts,
+      this.expectedCompletion,
+      this.completionLocation,
+      this.elapsedMS);
 
   /// Return the completion group for the location at which completion was
   /// requested.
   CompletionGroup get group {
-    var element = _getElement(expectedCompletion.syntacticEntity);
+    var entity = expectedCompletion.syntacticEntity;
+    var element = _getElement(entity);
     if (element != null) {
       var parent = element.enclosingElement;
       if (parent is ClassElement || parent is ExtensionElement) {
@@ -1240,25 +1336,75 @@
         } else {
           return CompletionGroup.instanceMember;
         }
-      } else if (expectedCompletion.elementKind == ElementKind.CLASS ||
-          expectedCompletion.elementKind == ElementKind.MIXIN ||
-          expectedCompletion.elementKind == ElementKind.ENUM ||
-          expectedCompletion.elementKind == ElementKind.TYPE_PARAMETER) {
-        return CompletionGroup.typeReference;
-      } else if (expectedCompletion.elementKind == ElementKind.LOCAL_VARIABLE) {
-        return CompletionGroup.localReference;
-      } else if (expectedCompletion.elementKind == ElementKind.PARAMETER) {
-        return CompletionGroup.paramReference;
+      } else if (parent is CompilationUnitElement &&
+          element is! ClassElement &&
+          element is! ExtensionElement) {
+        return CompletionGroup.topLevelMember;
+      }
+      if (element is ClassElement) {
+        if (element.isEnum) {
+          return CompletionGroup.enumElement;
+        } else if (element.isMixin) {
+          return CompletionGroup.mixinElement;
+        }
+        if (entity is SimpleIdentifier &&
+            entity.parent is TypeName &&
+            entity.parent.parent is ConstructorName &&
+            entity.parent.parent.parent is InstanceCreationExpression) {
+          return CompletionGroup.constructorElement;
+        }
+        return CompletionGroup.classElement;
+      } else if (element is ExtensionElement) {
+        return CompletionGroup.extensionElement;
+      } else if (element is FunctionElement) {
+        return CompletionGroup.localFunctionElement;
+      } else if (element is LocalVariableElement) {
+        return CompletionGroup.localVariableElement;
+      } else if (element is ParameterElement) {
+        return CompletionGroup.parameterElement;
+      } else if (element is PrefixElement) {
+        return CompletionGroup.prefixElement;
+      } else if (element is TypeParameterElement) {
+        return CompletionGroup.typeParameterElement;
       }
     }
-    return CompletionGroup.topLevel;
+    if (entity is SimpleIdentifier) {
+      var name = entity.name;
+      if (name == 'void') {
+        return CompletionGroup.keywordVoid;
+      } else if (name == 'dynamic') {
+        return CompletionGroup.keywordDynamic;
+      }
+    }
+    return CompletionGroup.unknown;
   }
 
   /// Return the element associated with the syntactic [entity], or `null` if
   /// there is no such element.
   Element _getElement(SyntacticEntity entity) {
     if (entity is SimpleIdentifier) {
-      return entity.staticElement;
+      var element = entity.staticElement;
+      if (element != null) {
+        return element;
+      }
+      AstNode node = entity;
+      while (node != null) {
+        var parent = node.parent;
+        if (parent is AssignmentExpression) {
+          if (node == parent.leftHandSide) {
+            return parent.readElement ?? parent.writeElement;
+          }
+          return null;
+        } else if (parent is PrefixExpression) {
+          if (parent.operator.type == TokenType.PLUS_PLUS ||
+              parent.operator.type == TokenType.MINUS_MINUS) {
+            return parent.readElement ?? parent.writeElement;
+          }
+        } else if (parent is PostfixExpression) {
+          return parent.readElement ?? parent.writeElement;
+        }
+        node = parent;
+      }
     }
     return null;
   }
@@ -1297,48 +1443,52 @@
 }
 
 class MetricsSuggestionListener implements SuggestionListener {
-  Map<protocol.CompletionSuggestion, String> featureMap = {};
+  Map<protocol.CompletionSuggestion, List<double>> featureMap = {};
 
-  String cachedFeatures = '';
+  List<double> cachedFeatures = const [
+    0.0,
+    0.0,
+    0.0,
+    0.0,
+    0.0,
+    0.0,
+    0.0,
+    0.0,
+    0.0
+  ];
 
   String missingCompletionLocation;
+
   String missingCompletionLocationTable;
 
   @override
   void builtSuggestion(protocol.CompletionSuggestion suggestion) {
     featureMap[suggestion] = cachedFeatures;
-    cachedFeatures = '';
+    cachedFeatures = const [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
   }
 
   @override
   void computedFeatures(
-      {double contextType,
-      double elementKind,
-      double hasDeprecated,
-      double inheritanceDistance,
-      double startsWithDollar,
-      double superMatches}) {
-    var buffer = StringBuffer();
-
-    bool write(String label, double value, bool needsComma) {
-      if (value != null) {
-        if (needsComma) {
-          buffer.write(', ');
-        }
-        buffer.write('$label: $value');
-        return true;
-      }
-      return needsComma;
-    }
-
-    var needsComma = false;
-    needsComma = write('contextType', contextType, needsComma);
-    needsComma = write('elementKind', elementKind, needsComma);
-    needsComma = write('hasDeprecated', hasDeprecated, needsComma);
-    needsComma = write('inheritanceDistance', inheritanceDistance, needsComma);
-    needsComma = write('startsWithDollar', startsWithDollar, needsComma);
-    needsComma = write('superMatches', superMatches, needsComma);
-    cachedFeatures = buffer.toString();
+      {double contextType = 0.0,
+      double elementKind = 0.0,
+      double hasDeprecated = 0.0,
+      double inheritanceDistance = 0.0,
+      double isConstant = 0.0,
+      double keyword = 0.0,
+      double localVariableDistance = 0.0,
+      double startsWithDollar = 0.0,
+      double superMatches = 0.0}) {
+    cachedFeatures = [
+      contextType,
+      elementKind,
+      hasDeprecated,
+      inheritanceDistance,
+      isConstant,
+      keyword,
+      localVariableDistance,
+      startsWithDollar,
+      superMatches
+    ];
   }
 
   @override
@@ -1364,10 +1514,59 @@
   }
 }
 
-extension on protocol.CompletionSuggestion {
-  /// A shorter description of the suggestion than [toString] provides.
-  String get description =>
-      json.encode(toJson()..remove('docSummary')..remove('docComplete'));
+/// The information being remembered about an individual suggestion.
+class SuggestionData {
+  /// The suggestion that was produced.
+  protocol.CompletionSuggestion suggestion;
+
+  /// The values of the features used to compute the suggestion.
+  List<double> features;
+
+  SuggestionData(this.suggestion, this.features);
+}
+
+extension on CompletionGroup {
+  String get name {
+    switch (this) {
+      case CompletionGroup.classElement:
+        return 'class';
+      case CompletionGroup.constructorElement:
+        return 'constructor';
+      case CompletionGroup.enumElement:
+        return 'enum';
+      case CompletionGroup.extensionElement:
+        return 'extension';
+      case CompletionGroup.instanceMember:
+        return 'instance member';
+      case CompletionGroup.labelElement:
+        return 'label';
+      case CompletionGroup.localFunctionElement:
+        return 'local function';
+      case CompletionGroup.localVariableElement:
+        return 'local variable';
+      case CompletionGroup.mixinElement:
+        return 'mixin';
+      case CompletionGroup.parameterElement:
+        return 'parameter';
+      case CompletionGroup.prefixElement:
+        return 'prefix';
+      case CompletionGroup.staticMember:
+        return 'static member';
+      case CompletionGroup.topLevelMember:
+        return 'top level member';
+      case CompletionGroup.typeParameterElement:
+        return 'type parameter';
+
+      case CompletionGroup.keywordDynamic:
+        return 'keyword dynamic';
+      case CompletionGroup.keywordVoid:
+        return 'keyword void';
+
+      case CompletionGroup.unknown:
+        return 'unknown';
+    }
+    return '<unknown>';
+  }
 }
 
 extension AvailableSuggestionsExtension on protocol.AvailableSuggestion {
diff --git a/pkg/analysis_server/tool/code_completion/metrics_util.dart b/pkg/analysis_server/tool/code_completion/metrics_util.dart
index 6078af8d..98acfce 100644
--- a/pkg/analysis_server/tool/code_completion/metrics_util.dart
+++ b/pkg/analysis_server/tool/code_completion/metrics_util.dart
@@ -5,6 +5,8 @@
 import 'package:analysis_server/src/status/pages.dart';
 import 'package:analyzer/src/generated/utilities_general.dart';
 
+import 'output_utilities.dart';
+
 /// https://en.wikipedia.org/wiki/Average#Arithmetic_mean
 class ArithmeticMeanComputer {
   final String name;
@@ -74,15 +76,19 @@
   int getCountOf(String id) => _buckets[id] ?? 0;
 
   void printCounterValues() {
-    print('Counts for \'$name\' (total = $_totalCount):');
     if (_totalCount > 0) {
+      var table = [
+        ['', 'count', 'percent']
+      ];
       var entries = _buckets.entries.toList();
       entries.sort((first, second) => second.value - first.value);
       for (var entry in entries) {
         var id = entry.key;
         var count = entry.value;
-        print('[$id] $count (${printPercentage(count / _totalCount, 2)})');
+        table.add(
+            [id, count.toString(), printPercentage(count / _totalCount, 2)]);
       }
+      printTable(table);
     } else {
       print('<no counts>');
     }
diff --git a/pkg/analysis_server/tool/code_completion/output_utilities.dart b/pkg/analysis_server/tool/code_completion/output_utilities.dart
index 71660df..8f7e096 100644
--- a/pkg/analysis_server/tool/code_completion/output_utilities.dart
+++ b/pkg/analysis_server/tool/code_completion/output_utilities.dart
@@ -4,16 +4,30 @@
 
 import 'dart:math' as math;
 
+/// Print the given [table].
+void printTable(List<List<String>> table) {
+  var buffer = StringBuffer();
+  buffer.writeTable(table);
+  print(buffer.toString());
+}
+
+/// Return an interable that will produce all of the integer values between
+/// [first] and [last] inclusive.
+Iterable<int> range(int first, int last) sync* {
+  for (var i = first; i < last; i++) {
+    yield i;
+  }
+}
+
 /// Given a [table] represented as a list of rows, right justify all of the
-/// cells in the given [column], except for the first row under the assumption
-/// that the first row contains headers.
-void rightJustifyColumn(int column, List<List<String>> table) {
+/// cells in the given [column].
+void rightJustifyColumn(List<List<String>> table, int column) {
   var width = 0;
   for (var i = 0; i < table.length; i++) {
     var row = table[i];
     width = math.max(width, row[column].length);
   }
-  for (var i = 1; i < table.length; i++) {
+  for (var i = 0; i < table.length; i++) {
     var row = table[i];
     var cellValue = row[column];
     var length = cellValue.length;
@@ -24,6 +38,14 @@
   }
 }
 
+/// Given a [table] represented as a list of rows, right justify all of the
+/// cells in the given [columns].
+void rightJustifyColumns(List<List<String>> table, Iterable<int> columns) {
+  for (var column in columns) {
+    rightJustifyColumn(table, column);
+  }
+}
+
 extension OutputUtilities on StringSink {
   /// Write the given [table].
   ///
diff --git a/pkg/analysis_server/tool/code_completion/visitors.dart b/pkg/analysis_server/tool/code_completion/visitors.dart
index 0b130f7..db428f9 100644
--- a/pkg/analysis_server/tool/code_completion/visitors.dart
+++ b/pkg/analysis_server/tool/code_completion/visitors.dart
@@ -210,68 +210,78 @@
   @override
   void visitAsExpression(AsExpression node) {
     safelyRecordKeywordCompletion(node.asOperator);
-    return super.visitAsExpression(node);
+    super.visitAsExpression(node);
+  }
+
+  @override
+  void visitAssertInitializer(AssertInitializer node) {
+    safelyRecordKeywordCompletion(node.assertKeyword);
+    super.visitAssertInitializer(node);
+  }
+
+  @override
+  void visitAssertStatement(AssertStatement node) {
+    safelyRecordKeywordCompletion(node.assertKeyword);
+    super.visitAssertStatement(node);
   }
 
   @override
   void visitAwaitExpression(AwaitExpression node) {
     safelyRecordKeywordCompletion(node.awaitKeyword);
-    return super.visitAwaitExpression(node);
+    super.visitAwaitExpression(node);
   }
 
   @override
   void visitBlockFunctionBody(BlockFunctionBody node) {
     // 'async' | 'async' '*' | 'sync' '*':
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitBlockFunctionBody(node);
+    super.visitBlockFunctionBody(node);
   }
 
   @override
   void visitBooleanLiteral(BooleanLiteral node) {
     // 'false' | 'true'
     safelyRecordKeywordCompletion(node.literal);
-    return super.visitBooleanLiteral(node);
+    super.visitBooleanLiteral(node);
   }
 
   @override
   void visitBreakStatement(BreakStatement node) {
     safelyRecordKeywordCompletion(node.breakKeyword);
-    return super.visitBreakStatement(node);
+    super.visitBreakStatement(node);
   }
 
   @override
   void visitCatchClause(CatchClause node) {
-    // Should we 'catch', it won't be suggested when it already exists as a
-    // keyword in the file?
     safelyRecordKeywordCompletion(node.catchKeyword);
     safelyRecordKeywordCompletion(node.onKeyword);
-    return super.visitCatchClause(node);
+    super.visitCatchClause(node);
   }
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
     safelyRecordKeywordCompletion(node.abstractKeyword);
     safelyRecordKeywordCompletion(node.classKeyword);
-    return super.visitClassDeclaration(node);
+    super.visitClassDeclaration(node);
   }
 
   @override
   void visitClassTypeAlias(ClassTypeAlias node) {
     safelyRecordKeywordCompletion(node.abstractKeyword);
     safelyRecordKeywordCompletion(node.typedefKeyword);
-    return super.visitClassTypeAlias(node);
+    super.visitClassTypeAlias(node);
   }
 
   @override
   void visitCompilationUnit(CompilationUnit node) {
     _enclosingCompilationUnit = node;
-    return super.visitCompilationUnit(node);
+    super.visitCompilationUnit(node);
   }
 
   @override
   void visitConfiguration(Configuration node) {
     safelyRecordKeywordCompletion(node.ifKeyword);
-    return super.visitConfiguration(node);
+    super.visitConfiguration(node);
   }
 
   @override
@@ -279,70 +289,64 @@
     safelyRecordKeywordCompletion(node.externalKeyword);
     safelyRecordKeywordCompletion(node.constKeyword);
     safelyRecordKeywordCompletion(node.factoryKeyword);
-    return super.visitConstructorDeclaration(node);
+    super.visitConstructorDeclaration(node);
   }
 
   @override
   void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
     safelyRecordKeywordCompletion(node.thisKeyword);
-    return super.visitConstructorFieldInitializer(node);
+    super.visitConstructorFieldInitializer(node);
   }
 
   @override
   void visitContinueStatement(ContinueStatement node) {
     safelyRecordKeywordCompletion(node.continueKeyword);
-    return super.visitContinueStatement(node);
+    super.visitContinueStatement(node);
   }
 
   @override
   void visitDeclaredIdentifier(DeclaredIdentifier node) {
     // 'final', 'const' or 'var'
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitDeclaredIdentifier(node);
+    super.visitDeclaredIdentifier(node);
   }
 
   @override
   void visitDoStatement(DoStatement node) {
     safelyRecordKeywordCompletion(node.doKeyword);
     safelyRecordKeywordCompletion(node.whileKeyword);
-    return super.visitDoStatement(node);
+    super.visitDoStatement(node);
   }
 
   @override
   void visitEnumDeclaration(EnumDeclaration node) {
     safelyRecordKeywordCompletion(node.enumKeyword);
-    return super.visitEnumDeclaration(node);
+    super.visitEnumDeclaration(node);
   }
 
   @override
   void visitExportDirective(ExportDirective node) {
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitExportDirective(node);
+    super.visitExportDirective(node);
   }
 
   @override
   void visitExpressionFunctionBody(ExpressionFunctionBody node) {
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitExpressionFunctionBody(node);
+    super.visitExpressionFunctionBody(node);
   }
 
   @override
   void visitExtendsClause(ExtendsClause node) {
     safelyRecordKeywordCompletion(node.extendsKeyword);
-    return super.visitExtendsClause(node);
+    super.visitExtendsClause(node);
   }
 
   @override
   void visitExtensionDeclaration(ExtensionDeclaration node) {
     safelyRecordKeywordCompletion(node.extensionKeyword);
     safelyRecordKeywordCompletion(node.onKeyword);
-    return super.visitExtensionDeclaration(node);
-  }
-
-  @override
-  void visitExtensionOverride(ExtensionOverride node) {
-    node.visitChildren(this);
-    return null;
+    super.visitExtensionDeclaration(node);
   }
 
   @override
@@ -351,7 +355,7 @@
     safelyRecordKeywordCompletion(node.covariantKeyword);
     safelyRecordKeywordCompletion(node.externalKeyword);
     safelyRecordKeywordCompletion(node.staticKeyword);
-    return super.visitFieldDeclaration(node);
+    super.visitFieldDeclaration(node);
   }
 
   @override
@@ -359,33 +363,33 @@
     // 'final', 'const' or 'var'
     safelyRecordKeywordCompletion(node.keyword);
     safelyRecordKeywordCompletion(node.thisKeyword);
-    return super.visitFieldFormalParameter(node);
+    super.visitFieldFormalParameter(node);
   }
 
   @override
   void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
     safelyRecordKeywordCompletion(node.inKeyword);
-    return super.visitForEachPartsWithDeclaration(node);
+    super.visitForEachPartsWithDeclaration(node);
   }
 
   @override
   void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
     safelyRecordKeywordCompletion(node.inKeyword);
-    return super.visitForEachPartsWithIdentifier(node);
+    super.visitForEachPartsWithIdentifier(node);
   }
 
   @override
   void visitForElement(ForElement node) {
     safelyRecordKeywordCompletion(node.awaitKeyword);
     safelyRecordKeywordCompletion(node.forKeyword);
-    return super.visitForElement(node);
+    super.visitForElement(node);
   }
 
   @override
   void visitForStatement(ForStatement node) {
     safelyRecordKeywordCompletion(node.awaitKeyword);
     safelyRecordKeywordCompletion(node.forKeyword);
-    return super.visitForStatement(node);
+    super.visitForStatement(node);
   }
 
   @override
@@ -393,58 +397,59 @@
     safelyRecordKeywordCompletion(node.externalKeyword);
     // 'get' or 'set':
     safelyRecordKeywordCompletion(node.propertyKeyword);
-    return super.visitFunctionDeclaration(node);
+    super.visitFunctionDeclaration(node);
   }
 
   @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
     safelyRecordKeywordCompletion(node.typedefKeyword);
-    return super.visitFunctionTypeAlias(node);
+    super.visitFunctionTypeAlias(node);
   }
 
   @override
   void visitGenericFunctionType(GenericFunctionType node) {
     safelyRecordKeywordCompletion(node.functionKeyword);
-    return super.visitGenericFunctionType(node);
+    super.visitGenericFunctionType(node);
   }
 
   @override
   void visitGenericTypeAlias(GenericTypeAlias node) {
     safelyRecordKeywordCompletion(node.typedefKeyword);
-    return super.visitGenericTypeAlias(node);
+    super.visitGenericTypeAlias(node);
   }
 
   @override
   void visitHideCombinator(HideCombinator node) {
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitHideCombinator(node);
+    super.visitHideCombinator(node);
   }
 
   @override
   void visitIfElement(IfElement node) {
     safelyRecordKeywordCompletion(node.ifKeyword);
     safelyRecordKeywordCompletion(node.elseKeyword);
-    return super.visitIfElement(node);
+    super.visitIfElement(node);
   }
 
   @override
   void visitIfStatement(IfStatement node) {
     safelyRecordKeywordCompletion(node.ifKeyword);
     safelyRecordKeywordCompletion(node.elseKeyword);
-    return super.visitIfStatement(node);
+    super.visitIfStatement(node);
   }
 
   @override
   void visitImplementsClause(ImplementsClause node) {
     safelyRecordKeywordCompletion(node.implementsKeyword);
-    return super.visitImplementsClause(node);
+    super.visitImplementsClause(node);
   }
 
   @override
   void visitImportDirective(ImportDirective node) {
     safelyRecordKeywordCompletion(node.keyword);
     safelyRecordKeywordCompletion(node.asKeyword);
-    return super.visitImportDirective(node);
+    safelyRecordKeywordCompletion(node.deferredKeyword);
+    super.visitImportDirective(node);
   }
 
   @override
@@ -452,29 +457,28 @@
     // Here we explicitly do not record 'new' as we don't suggest it in the
     // completion service.
     // https://dart-review.googlesource.com/c/sdk/+/131020
-    var keywordStr = node.keyword?.lexeme;
-    if (Keyword.CONST.lexeme == keywordStr) {
+    if (node.keyword?.type == Keyword.CONST) {
       safelyRecordKeywordCompletion(node.keyword);
     }
-    return super.visitInstanceCreationExpression(node);
+    super.visitInstanceCreationExpression(node);
   }
 
   @override
   void visitIsExpression(IsExpression node) {
     safelyRecordKeywordCompletion(node.isOperator);
-    return super.visitIsExpression(node);
+    super.visitIsExpression(node);
   }
 
   @override
   void visitLibraryDirective(LibraryDirective node) {
     safelyRecordKeywordCompletion(node.libraryKeyword);
-    return super.visitLibraryDirective(node);
+    super.visitLibraryDirective(node);
   }
 
   @override
   void visitListLiteral(ListLiteral node) {
     safelyRecordKeywordCompletion(node.constKeyword);
-    return super.visitListLiteral(node);
+    super.visitListLiteral(node);
   }
 
   @override
@@ -483,57 +487,57 @@
     safelyRecordKeywordCompletion(node.modifierKeyword);
     safelyRecordKeywordCompletion(node.operatorKeyword);
     safelyRecordKeywordCompletion(node.propertyKeyword);
-    return super.visitMethodDeclaration(node);
+    super.visitMethodDeclaration(node);
   }
 
   @override
   void visitMixinDeclaration(MixinDeclaration node) {
     safelyRecordKeywordCompletion(node.mixinKeyword);
-    return super.visitMixinDeclaration(node);
+    super.visitMixinDeclaration(node);
   }
 
   @override
   void visitNativeClause(NativeClause node) {
     safelyRecordKeywordCompletion(node.nativeKeyword);
-    return super.visitNativeClause(node);
+    super.visitNativeClause(node);
   }
 
   @override
   void visitNativeFunctionBody(NativeFunctionBody node) {
     safelyRecordKeywordCompletion(node.nativeKeyword);
-    return super.visitNativeFunctionBody(node);
+    super.visitNativeFunctionBody(node);
   }
 
   @override
   void visitNullLiteral(NullLiteral node) {
     safelyRecordKeywordCompletion(node.literal);
-    return super.visitNullLiteral(node);
+    super.visitNullLiteral(node);
   }
 
   @override
   void visitOnClause(OnClause node) {
     safelyRecordKeywordCompletion(node.onKeyword);
-    return super.visitOnClause(node);
+    super.visitOnClause(node);
   }
 
   @override
   void visitPartDirective(PartDirective node) {
     safelyRecordKeywordCompletion(node.partKeyword);
-    return super.visitPartDirective(node);
+    super.visitPartDirective(node);
   }
 
   @override
   void visitPartOfDirective(PartOfDirective node) {
     safelyRecordKeywordCompletion(node.partKeyword);
     safelyRecordKeywordCompletion(node.ofKeyword);
-    return super.visitPartOfDirective(node);
+    super.visitPartOfDirective(node);
   }
 
   @override
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     safelyRecordKeywordCompletion(node.thisKeyword);
-    return super.visitRedirectingConstructorInvocation(node);
+    super.visitRedirectingConstructorInvocation(node);
   }
 
   @override
@@ -545,19 +549,19 @@
   @override
   void visitReturnStatement(ReturnStatement node) {
     safelyRecordKeywordCompletion(node.returnKeyword);
-    return super.visitReturnStatement(node);
+    super.visitReturnStatement(node);
   }
 
   @override
   void visitSetOrMapLiteral(SetOrMapLiteral node) {
     safelyRecordKeywordCompletion(node.constKeyword);
-    return super.visitSetOrMapLiteral(node);
+    super.visitSetOrMapLiteral(node);
   }
 
   @override
   void visitShowCombinator(ShowCombinator node) {
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitShowCombinator(node);
+    super.visitShowCombinator(node);
   }
 
   @override
@@ -565,7 +569,7 @@
     // 'final', 'const' or 'var'
     safelyRecordKeywordCompletion(node.keyword);
     safelyRecordKeywordCompletion(node.covariantKeyword);
-    return super.visitSimpleFormalParameter(node);
+    super.visitSimpleFormalParameter(node);
   }
 
   @override
@@ -620,68 +624,68 @@
       }
       safelyRecordEntity(node, elementKind: elementKind);
     }
-    return super.visitSimpleIdentifier(node);
+    super.visitSimpleIdentifier(node);
   }
 
   @override
   void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
     safelyRecordKeywordCompletion(node.superKeyword);
-    return super.visitSuperConstructorInvocation(node);
+    super.visitSuperConstructorInvocation(node);
   }
 
   @override
   void visitSuperExpression(SuperExpression node) {
     safelyRecordKeywordCompletion(node.superKeyword);
-    return super.visitSuperExpression(node);
+    super.visitSuperExpression(node);
   }
 
   @override
   void visitSwitchCase(SwitchCase node) {
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitSwitchCase(node);
+    super.visitSwitchCase(node);
   }
 
   @override
   void visitSwitchDefault(SwitchDefault node) {
     safelyRecordKeywordCompletion(node.keyword);
-    return super.visitSwitchDefault(node);
+    super.visitSwitchDefault(node);
   }
 
   @override
   void visitSwitchStatement(SwitchStatement node) {
     safelyRecordKeywordCompletion(node.switchKeyword);
-    return super.visitSwitchStatement(node);
+    super.visitSwitchStatement(node);
   }
 
   @override
   void visitThisExpression(ThisExpression node) {
     safelyRecordKeywordCompletion(node.thisKeyword);
-    return super.visitThisExpression(node);
+    super.visitThisExpression(node);
   }
 
   @override
   void visitThrowExpression(ThrowExpression node) {
     safelyRecordKeywordCompletion(node.throwKeyword);
-    return super.visitThrowExpression(node);
+    super.visitThrowExpression(node);
   }
 
   @override
   void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
     safelyRecordKeywordCompletion(node.externalKeyword);
-    return super.visitTopLevelVariableDeclaration(node);
+    super.visitTopLevelVariableDeclaration(node);
   }
 
   @override
   void visitTryStatement(TryStatement node) {
     safelyRecordKeywordCompletion(node.tryKeyword);
     safelyRecordKeywordCompletion(node.finallyKeyword);
-    return super.visitTryStatement(node);
+    super.visitTryStatement(node);
   }
 
   @override
   void visitTypeParameter(TypeParameter node) {
     safelyRecordKeywordCompletion(node.extendsKeyword);
-    return super.visitTypeParameter(node);
+    super.visitTypeParameter(node);
   }
 
   @override
@@ -689,25 +693,25 @@
     // 'final', 'const' or 'var'
     safelyRecordKeywordCompletion(node.keyword);
     safelyRecordKeywordCompletion(node.lateKeyword);
-    return super.visitVariableDeclarationList(node);
+    super.visitVariableDeclarationList(node);
   }
 
   @override
   void visitWhileStatement(WhileStatement node) {
     safelyRecordKeywordCompletion(node.whileKeyword);
-    return super.visitWhileStatement(node);
+    super.visitWhileStatement(node);
   }
 
   @override
   void visitWithClause(WithClause node) {
     safelyRecordKeywordCompletion(node.withKeyword);
-    return super.visitWithClause(node);
+    super.visitWithClause(node);
   }
 
   @override
   void visitYieldStatement(YieldStatement node) {
     safelyRecordKeywordCompletion(node.yieldKeyword);
-    return super.visitYieldStatement(node);
+    super.visitYieldStatement(node);
   }
 
   bool _doIncludeSimpleIdentifier(SimpleIdentifier node) {
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index b275202..c9a953d 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -114,26 +114,28 @@
 
   DartType get returnType => catchErrorOnErrorReturnType ?? element.returnType;
 
-  static bool _inFactoryConstructor(ExecutableElement element) {
+  static bool _inFactoryConstructor(Element element) {
+    var enclosing = element?.enclosingElement;
+    if (enclosing == null) {
+      return false;
+    }
     if (element is ConstructorElement) {
       return element.isFactory;
     }
-    var enclosing = element?.enclosingElement;
-    if (enclosing is ExecutableElement) {
-      return _inFactoryConstructor(enclosing);
-    }
-    return false;
+    return _inFactoryConstructor(enclosing);
   }
 
-  static bool _inStaticMethod(ExecutableElement element) {
+  static bool _inStaticMethod(Element element) {
     var enclosing = element?.enclosingElement;
+    if (enclosing == null) {
+      return false;
+    }
     if (enclosing is ClassElement || enclosing is ExtensionElement) {
-      return element.isStatic;
+      if (element is ExecutableElement) {
+        return element.isStatic;
+      }
     }
-    if (enclosing is ExecutableElement) {
-      return _inStaticMethod(enclosing);
-    }
-    return false;
+    return _inStaticMethod(enclosing);
   }
 }
 
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 8a32c10..7693984 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -775,7 +775,7 @@
   }
 
   void _validateRefIndexed(IndexExpression node) {
-    DartType targetType = node.target?.staticType;
+    DartType targetType = node.realTarget?.staticType;
     if (!_isValidFfiNativeType(targetType, false, true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
@@ -795,7 +795,7 @@
   }
 
   void _validateRefPropertyAccess(PropertyAccess node) {
-    DartType targetType = node.target?.staticType;
+    DartType targetType = node.realTarget?.staticType;
     if (!_isValidFfiNativeType(targetType, false, true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index 36be56b..16f0bcd 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -661,6 +661,8 @@
 }
 
 class Pointer<T extends NativeType> extends NativeType {
+  external factory Pointer.fromAddress(int ptr);
+
   static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
       @DartRepresentationOf("T") Function f,
       [Object exceptionalReturn]) {}
@@ -685,6 +687,12 @@
 class DartRepresentationOf {
   const DartRepresentationOf(String nativeType);
 }
+
+extension StructPointer<T extends Struct> on Pointer<T> {
+  external T get ref;
+
+  external T operator [](int index);
+}
 ''',
   )
 ]);
diff --git a/pkg/analyzer/test/src/diagnostics/instance_member_access_from_factory_test.dart b/pkg/analyzer/test/src/diagnostics/instance_member_access_from_factory_test.dart
index 8791394..d4de128 100644
--- a/pkg/analyzer/test/src/diagnostics/instance_member_access_from_factory_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/instance_member_access_from_factory_test.dart
@@ -15,66 +15,97 @@
 
 @reflectiveTest
 class InstanceMemberAccessFromFactoryTest extends PubPackageResolutionTest {
-  test_named() async {
+  test_named_getter() async {
     await assertErrorsInCode(r'''
 class A {
-  m() {}
-  A();
+  int get foo => 0;
+
   factory A.make() {
-    m();
-    return new A();
+    foo;
+    throw 0;
   }
 }
 ''', [
-      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 51, 1),
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 56, 3),
     ]);
   }
 
-  test_property() async {
+  test_named_getter_localFunction() async {
     await assertErrorsInCode(r'''
 class A {
-  int m;
-  A();
-  factory A.make() {
-    m;
-    return new A();
-  }
-}
-''', [
-      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 51, 1),
-    ]);
-  }
+  int get foo => 0;
 
-  test_property_fromClosure() async {
-    await assertErrorsInCode(r'''
-class A {
-  int m;
-  A();
   factory A.make() {
     void f() {
-      m;
+      foo;
     }
     f();
-    return new A();
+    throw 0;
   }
 }
 ''', [
-      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 68, 1),
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 73, 3),
     ]);
   }
 
-  test_unnamed() async {
+  test_named_method() async {
     await assertErrorsInCode(r'''
 class A {
-  m() {}
-  A._();
-  factory A() {
-    m();
-    return new A._();
+  void foo() {}
+
+  factory A.make() {
+    foo();
+    throw 0;
   }
 }
 ''', [
-      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 48, 1),
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 52, 3),
+    ]);
+  }
+
+  test_named_method_functionExpression() async {
+    await assertErrorsInCode(r'''
+class A {
+  void foo() {}
+
+  factory A.make() {
+    () => foo();
+    throw 0;
+  }
+}
+''', [
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 58, 3),
+    ]);
+  }
+
+  test_named_method_functionExpression_localVariable() async {
+    await assertErrorsInCode(r'''
+class A {
+  void foo() {}
+
+  factory A.make() {
+    // ignore:unused_local_variable
+    var x = () => foo();
+    throw 0;
+  }
+}
+''', [
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 102, 3),
+    ]);
+  }
+
+  test_unnamed_method() async {
+    await assertErrorsInCode(r'''
+class A {
+  void foo() {}
+
+  factory A() {
+    foo();
+    throw 0;
+  }
+}
+''', [
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, 47, 3),
     ]);
   }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/instance_member_access_from_static_test.dart b/pkg/analyzer/test/src/diagnostics/instance_member_access_from_static_test.dart
index c1a122b..f56d41a 100644
--- a/pkg/analyzer/test/src/diagnostics/instance_member_access_from_static_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/instance_member_access_from_static_test.dart
@@ -61,6 +61,35 @@
     ]);
   }
 
+  test_class_thisGetter_fromMethod_functionExpression() async {
+    await assertErrorsInCode(r'''
+class A {
+  int get foo => 0;
+
+  static void bar() {
+    () => foo;
+  }
+}
+''', [
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_STATIC, 63, 3),
+    ]);
+  }
+
+  test_class_thisGetter_fromMethod_functionExpression_localVariable() async {
+    await assertErrorsInCode(r'''
+class A {
+  int get foo => 0;
+
+  static void bar() {
+    // ignore:unused_local_variable
+    var x = () => foo;
+  }
+}
+''', [
+      error(CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_STATIC, 107, 3),
+    ]);
+  }
+
   test_class_thisMethod_fromMethod() async {
     await assertErrorsInCode(r'''
 class A {
diff --git a/pkg/analyzer/test/src/diagnostics/non_constant_type_argument_test.dart b/pkg/analyzer/test/src/diagnostics/non_constant_type_argument_test.dart
index eee33d4..f783c71 100644
--- a/pkg/analyzer/test/src/diagnostics/non_constant_type_argument_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/non_constant_type_argument_test.dart
@@ -9,11 +9,53 @@
 
 main() {
   defineReflectiveSuite(() {
+    defineReflectiveTests(NonConstantTypeArgumentNoWarningTest);
+    defineReflectiveTests(NonConstantTypeArgumentNoWarningTest2);
     defineReflectiveTests(NonConstantTypeArgumentTest);
+    defineReflectiveTests(NonConstantTypeArgumentWarningTest);
   });
 }
 
 @reflectiveTest
+class NonConstantTypeArgumentNoWarningTest extends PubPackageResolutionTest {
+  test_asFunction_R() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class MyStruct extends Struct {
+  @Uint8()
+  int myField;
+}
+
+void main(){
+  final pointer = Pointer<MyStruct>.fromAddress(0);
+  pointer.ref.myField = 1;
+}
+''');
+  }
+}
+
+@reflectiveTest
+class NonConstantTypeArgumentNoWarningTest2 extends PubPackageResolutionTest {
+  test_asFunction_R() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class MyStruct extends Struct {
+  @Uint8()
+  int myField;
+}
+
+void main(){
+  final pointer = Pointer<MyStruct>.fromAddress(0)
+    ..ref.myField = 1;
+  print(pointer);
+}
+''');
+  }
+}
+
+@reflectiveTest
 class NonConstantTypeArgumentTest extends PubPackageResolutionTest {
   test_asFunction_R() async {
     await assertErrorsInCode(r'''
@@ -29,3 +71,15 @@
     ]);
   }
 }
+
+@reflectiveTest
+class NonConstantTypeArgumentWarningTest extends PubPackageResolutionTest {
+  test_asFunction_R() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+T genericRef<T extends Struct>(Pointer<T> p) =>
+    p.ref;
+''', [error(FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING, 72, 5)]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart b/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
index 7073a39..85129db 100644
--- a/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
@@ -75,7 +75,9 @@
   test_Pointer() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
-class C extends Pointer {}
+class C extends Pointer {
+  external factory C();
+}
 ''', [
       error(FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS, 35, 7),
     ]);
diff --git a/runtime/vm/flags.cc b/runtime/vm/flags.cc
index f3fc0ef..8b5ba1b 100644
--- a/runtime/vm/flags.cc
+++ b/runtime/vm/flags.cc
@@ -478,11 +478,6 @@
     // isolate that has not yet initialized the global field.
     FLAG_fields_may_be_reset = true;
 
-    // We will start by only allowing compilation to unoptimized code.
-    FLAG_optimization_counter_threshold = -1;
-    FLAG_background_compilation = false;
-    FLAG_force_clone_compiler_objects = true;
-
     // To eliminate potential flakiness, we will start by disabling field guards
     // and CHA-based compilations.
     FLAG_use_field_guards = false;
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 5a0c2ea..c888a56 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -1048,17 +1048,26 @@
   const Code& target_code = Code::Handle(zone, target_function.EnsureHasCode());
   // Before patching verify that we are not repeatedly patching to the same
   // target.
-  ASSERT(target_code.ptr() !=
-         CodePatcher::GetStaticCallTargetAt(caller_frame->pc(), caller_code));
-  CodePatcher::PatchStaticCallAt(caller_frame->pc(), caller_code, target_code);
-  caller_code.SetStaticCallTargetCodeAt(caller_frame->pc(), target_code);
-  if (FLAG_trace_patching) {
-    THR_Print("PatchStaticCall: patching caller pc %#" Px
-              ""
-              " to '%s' new entry point %#" Px " (%s)\n",
-              caller_frame->pc(), target_function.ToFullyQualifiedCString(),
-              target_code.EntryPoint(),
-              target_code.is_optimized() ? "optimized" : "unoptimized");
+  ASSERT(FLAG_enable_isolate_groups ||
+         target_code.ptr() != CodePatcher::GetStaticCallTargetAt(
+                                  caller_frame->pc(), caller_code));
+  if (target_code.ptr() !=
+      CodePatcher::GetStaticCallTargetAt(caller_frame->pc(), caller_code)) {
+    SafepointOperationScope safepoint(thread);
+    if (target_code.ptr() !=
+        CodePatcher::GetStaticCallTargetAt(caller_frame->pc(), caller_code)) {
+      CodePatcher::PatchStaticCallAt(caller_frame->pc(), caller_code,
+                                     target_code);
+      caller_code.SetStaticCallTargetCodeAt(caller_frame->pc(), target_code);
+      if (FLAG_trace_patching) {
+        THR_Print("PatchStaticCall: patching caller pc %#" Px
+                  ""
+                  " to '%s' new entry point %#" Px " (%s)\n",
+                  caller_frame->pc(), target_function.ToFullyQualifiedCString(),
+                  target_code.EntryPoint(),
+                  target_code.is_optimized() ? "optimized" : "unoptimized");
+      }
+    }
   }
   arguments.SetReturn(target_code);
 #else
diff --git a/tests/ffi/structs_test.dart b/tests/ffi/structs_test.dart
index 6bc1ed1..a38c241 100644
--- a/tests/ffi/structs_test.dart
+++ b/tests/ffi/structs_test.dart
@@ -24,6 +24,7 @@
     testBareStruct();
     testTypeTest();
     testUtf8();
+    testDotDotRef();
   }
 }
 
@@ -135,3 +136,10 @@
   Expect.equals(test, Utf8.fromUtf8(medium));
   calloc.free(medium);
 }
+
+void testDotDotRef() {
+  final pointer = calloc<Coordinate>()
+    ..ref.x = 1
+    ..ref.y = 1;
+  calloc.free(pointer);
+}
diff --git a/tests/ffi_2/structs_test.dart b/tests/ffi_2/structs_test.dart
index 2759056..57bdbc6 100644
--- a/tests/ffi_2/structs_test.dart
+++ b/tests/ffi_2/structs_test.dart
@@ -24,6 +24,7 @@
     testBareStruct();
     testTypeTest();
     testUtf8();
+    testDotDotRef();
   }
 }
 
@@ -135,3 +136,10 @@
   Expect.equals(test, Utf8.fromUtf8(medium));
   calloc.free(medium);
 }
+
+void testDotDotRef() {
+  final pointer = calloc<Coordinate>()
+    ..ref.x = 1
+    ..ref.y = 1;
+  calloc.free(pointer);
+}
diff --git a/tools/VERSION b/tools/VERSION
index b8d079b..c1f184a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 264
+PRERELEASE 265
 PRERELEASE_PATCH 0
\ No newline at end of file