Version 2.16.0-54.0.dev

Merge commit 'ad6ffaab952ae0ad6816b122d4340c5fd43fd587' into 'dev'
diff --git a/pkg/front_end/analysis_options_no_lints.yaml b/pkg/front_end/analysis_options_no_lints.yaml
index aca6b61..cc832b8 100644
--- a/pkg/front_end/analysis_options_no_lints.yaml
+++ b/pkg/front_end/analysis_options_no_lints.yaml
@@ -4,6 +4,7 @@
 
 analyzer:
   exclude:
+    - outline_extraction_testcases/**
     - parser_testcases/**
     - test/analyser_ignored/**
     - test/class_hierarchy/data/**
diff --git a/pkg/front_end/lib/src/fasta/util/abstracted_ast_nodes.dart b/pkg/front_end/lib/src/fasta/util/abstracted_ast_nodes.dart
new file mode 100644
index 0000000..57ae59a
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/util/abstracted_ast_nodes.dart
@@ -0,0 +1,864 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
+
+enum Coloring { Untouched, Marked }
+
+abstract class AstNode {
+  Map<String, List<AstNode>> scope = {};
+  Container? parent;
+  DirectParserASTContent get node;
+  Token get startInclusive;
+  Token get endInclusive;
+
+  Coloring marked = Coloring.Untouched;
+
+  StringBuffer toStringInternal(StringBuffer sb, int indent);
+
+  void buildScope();
+  Map<String, AstNode> selfScope();
+
+  List<AstNode>? findInScope(String name) {
+    return scope[name] ?? parent?.findInScope(name);
+  }
+}
+
+abstract class Container extends AstNode {
+  List<AstNode> _children = [];
+  Iterable<AstNode> get children => _children;
+
+  void addChild(AstNode child, Map<DirectParserASTContent, AstNode> map) {
+    child.parent = this;
+    _children.add(child);
+    map[child.node] = child;
+  }
+}
+
+class TopLevel extends Container {
+  final String sourceText;
+  final Uri uri;
+
+  @override
+  final DirectParserASTContent node;
+
+  final Map<DirectParserASTContent, AstNode> map;
+
+  TopLevel(this.sourceText, this.uri, this.node, this.map);
+
+  @override
+  String toString() => toStringInternal(new StringBuffer(), 0).toString();
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    if (_children.isEmpty) {
+      String stringIndent = " " * ((indent + 1) * 2);
+      sb.write(stringIndent);
+      sb.writeln("(empty)");
+    } else {
+      for (AstNode node in _children) {
+        sb.write(stringIndent);
+        node.toStringInternal(sb, indent + 1);
+      }
+    }
+    return sb;
+  }
+
+  @override
+  void buildScope() {
+    for (AstNode child in _children) {
+      child.buildScope();
+      for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
+        (scope[entry.key] ??= []).add(entry.value);
+      }
+    }
+  }
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+
+  @override
+  Token get endInclusive => throw new UnimplementedError();
+
+  @override
+  Token get startInclusive => throw new UnimplementedError();
+}
+
+class Class extends Container {
+  @override
+  final DirectParserASTContentTopLevelDeclarationEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Class(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Class $name");
+    if (_children.isEmpty) {
+      String stringIndent = " " * ((indent + 1) * 2);
+      sb.write(stringIndent);
+      sb.writeln("(empty)");
+    } else {
+      for (AstNode node in _children) {
+        node.toStringInternal(sb, indent + 1);
+      }
+    }
+    return sb;
+  }
+
+  @override
+  void buildScope() {
+    for (AstNode child in _children) {
+      child.buildScope();
+      for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
+        (scope[entry.key] ??= []).add(entry.value);
+      }
+    }
+  }
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class Mixin extends Container {
+  @override
+  final DirectParserASTContentTopLevelDeclarationEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Mixin(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Mixin $name");
+    if (_children.isEmpty) {
+      String stringIndent = " " * ((indent + 1) * 2);
+      sb.write(stringIndent);
+      sb.writeln("(empty)");
+    } else {
+      for (AstNode node in _children) {
+        node.toStringInternal(sb, indent + 1);
+      }
+    }
+    return sb;
+  }
+
+  @override
+  void buildScope() {
+    for (AstNode child in _children) {
+      child.buildScope();
+      for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
+        (scope[entry.key] ??= []).add(entry.value);
+      }
+    }
+  }
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class Extension extends Container {
+  @override
+  final DirectParserASTContentTopLevelDeclarationEnd node;
+  final String? name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Extension(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Extension $name");
+    if (_children.isEmpty) {
+      String stringIndent = " " * ((indent + 1) * 2);
+      sb.write(stringIndent);
+      sb.writeln("(empty)");
+    } else {
+      for (AstNode node in _children) {
+        node.toStringInternal(sb, indent + 1);
+      }
+    }
+    return sb;
+  }
+
+  @override
+  void buildScope() {
+    for (AstNode child in _children) {
+      child.buildScope();
+      for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
+        (scope[entry.key] ??= []).add(entry.value);
+      }
+    }
+  }
+
+  @override
+  Map<String, AstNode> selfScope() {
+    if (name != null) {
+      return {name!: this};
+    } else {
+      return const {};
+    }
+  }
+}
+
+class ClassConstructor extends AstNode {
+  @override
+  final DirectParserASTContentClassConstructorEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  ClassConstructor(
+      this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Class constructor $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    // TODO: Possibly this should be different...
+    return {name: this};
+  }
+}
+
+class ClassFactoryMethod extends AstNode {
+  @override
+  final DirectParserASTContentClassFactoryMethodEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  ClassFactoryMethod(
+      this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Class factory constructor $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    // TODO: Possibly this should be different...
+    return {name: this};
+  }
+}
+
+class ClassMethod extends AstNode {
+  @override
+  final DirectParserASTContentClassMethodEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  ClassMethod(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Class method $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class ExtensionMethod extends AstNode {
+  @override
+  final DirectParserASTContentExtensionMethodEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  ExtensionMethod(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Extension method $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class MixinMethod extends AstNode {
+  @override
+  final DirectParserASTContentMixinMethodEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  MixinMethod(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Mixin method $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class Enum extends AstNode {
+  @override
+  final DirectParserASTContentEnumEnd node;
+  final String name;
+  final List<String> members;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Enum(this.node, this.name, this.members, this.startInclusive,
+      this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Enum $name with members $members");
+    return sb;
+  }
+
+  @override
+  void buildScope() {
+    for (String child in members) {
+      scope[child] = [this];
+    }
+  }
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class Import extends AstNode {
+  @override
+  final DirectParserASTContentImportEnd node;
+  final Uri firstUri;
+  final List<Uri>? conditionalUris;
+  final String? asName;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Import(this.node, this.firstUri, this.conditionalUris, this.asName,
+      this.startInclusive, this.endInclusive);
+
+  List<Uri> get uris => [firstUri, ...?conditionalUris];
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    if (asName == null) {
+      sb.writeln("Import of $uris");
+    } else {
+      sb.writeln("Import of $uris as '$asName'");
+    }
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    if (asName != null) {
+      return {asName!: this};
+    }
+    return const {};
+  }
+}
+
+class Export extends AstNode {
+  @override
+  final DirectParserASTContentExportEnd node;
+  final Uri firstUri;
+  final List<Uri>? conditionalUris;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Export(this.node, this.firstUri, this.conditionalUris, this.startInclusive,
+      this.endInclusive);
+
+  List<Uri> get uris => [firstUri, ...?conditionalUris];
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Export of $uris");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+}
+
+class Part extends AstNode {
+  @override
+  final DirectParserASTContentPartEnd node;
+  final Uri uri;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Part(this.node, this.uri, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Part $uri");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+}
+
+class TopLevelFields extends AstNode {
+  @override
+  final DirectParserASTContentTopLevelFieldsEnd node;
+  final List<String> names;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  TopLevelFields(this.node, this.names, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Top level field(s) ${names.join(", ")}");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    Map<String, AstNode> scope = {};
+    for (String name in names) {
+      scope[name] = this;
+    }
+    return scope;
+  }
+}
+
+class TopLevelMethod extends AstNode {
+  @override
+  final DirectParserASTContentTopLevelMethodEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  TopLevelMethod(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Top level method $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class Typedef extends AstNode {
+  @override
+  final DirectParserASTContentTypedefEnd node;
+  final String name;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Typedef(this.node, this.name, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Top level method $name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return {name: this};
+  }
+}
+
+class ClassFields extends AstNode {
+  @override
+  final DirectParserASTContentClassFieldsEnd node;
+  final List<String> names;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  ClassFields(this.node, this.names, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Class field(s) ${names.join(", ")}");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    Map<String, AstNode> scope = {};
+    for (String name in names) {
+      scope[name] = this;
+    }
+    return scope;
+  }
+}
+
+class MixinFields extends AstNode {
+  @override
+  final DirectParserASTContentMixinFieldsEnd node;
+  final List<String> names;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  MixinFields(this.node, this.names, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Mixin field(s) ${names.join(", ")}");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    Map<String, AstNode> scope = {};
+    for (String name in names) {
+      scope[name] = this;
+    }
+    return scope;
+  }
+}
+
+class ExtensionFields extends AstNode {
+  @override
+  final DirectParserASTContentExtensionFieldsEnd node;
+  final List<String> names;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  ExtensionFields(
+      this.node, this.names, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("Extension field(s) ${names.join(", ")}");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    Map<String, AstNode> scope = {};
+    for (String name in names) {
+      scope[name] = this;
+    }
+    return scope;
+  }
+}
+
+class Metadata extends AstNode {
+  @override
+  final DirectParserASTContentMetadataEnd node;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  Metadata(this.node, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("metadata");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+}
+
+class LibraryName extends AstNode {
+  @override
+  final DirectParserASTContentLibraryNameEnd node;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  LibraryName(this.node, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("library name");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+}
+
+class PartOf extends AstNode {
+  @override
+  final DirectParserASTContentPartOfEnd node;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+  final Uri partOfUri;
+
+  PartOf(this.node, this.partOfUri, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("part of");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+}
+
+class LanguageVersion extends AstNode {
+  @override
+  final DirectParserASTContent node;
+  @override
+  final Token startInclusive;
+  @override
+  final Token endInclusive;
+
+  LanguageVersion(this.node, this.startInclusive, this.endInclusive);
+
+  @override
+  StringBuffer toStringInternal(StringBuffer sb, int indent) {
+    String stringIndent = " " * (indent * 2);
+    sb.write(stringIndent);
+    if (marked != Coloring.Untouched) {
+      sb.write("(marked) ");
+    }
+    sb.writeln("$startInclusive");
+    return sb;
+  }
+
+  @override
+  void buildScope() {}
+
+  @override
+  Map<String, AstNode> selfScope() {
+    return const {};
+  }
+}
diff --git a/pkg/front_end/lib/src/fasta/util/direct_parser_ast.dart b/pkg/front_end/lib/src/fasta/util/direct_parser_ast.dart
index 94a1865..a5bb20a 100644
--- a/pkg/front_end/lib/src/fasta/util/direct_parser_ast.dart
+++ b/pkg/front_end/lib/src/fasta/util/direct_parser_ast.dart
@@ -6,6 +6,7 @@
 
 import 'dart:io' show File;
 
+import 'package:_fe_analyzer_shared/src/messages/codes.dart';
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
     show ScannerConfiguration;
 
@@ -17,6 +18,13 @@
 
 import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
 
+import 'package:_fe_analyzer_shared/src/parser/listener.dart'
+    show UnescapeErrorListener;
+
+import 'package:_fe_analyzer_shared/src/parser/identifier_context.dart';
+
+import 'package:_fe_analyzer_shared/src/parser/quote.dart' show unescapeString;
+
 import '../source/diet_parser.dart';
 
 import 'direct_parser_ast_helper.dart';
@@ -26,7 +34,8 @@
     bool includeComments: false,
     bool enableExtensionMethods: false,
     bool enableNonNullable: false,
-    bool enableTripleShift: false}) {
+    bool enableTripleShift: false,
+    List<Token>? languageVersionsSeen}) {
   Uint8List bytes = new Uint8List(rawBytes.length + 1);
   bytes.setRange(0, rawBytes.length, rawBytes);
 
@@ -42,6 +51,7 @@
     languageVersionChanged: (scanner, languageVersion) {
       // For now don't do anything, but having it (making it non-null) means the
       // configuration won't be reset.
+      languageVersionsSeen?.add(languageVersion);
     },
   );
   Token firstToken = scanner.tokenize();
@@ -1018,15 +1028,60 @@
   }
 }
 
-extension ClassFieldsExtension on DirectParserASTContentClassFieldsEnd {
-  List<DirectParserASTContentIdentifierHandle?> getFieldIdentifiers() {
-    // For now blindly assume that the last count identifiers are the names
-    // of the fields.
+extension MixinFieldsExtension on DirectParserASTContentMixinFieldsEnd {
+  List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
     int countLeft = count;
     List<DirectParserASTContentIdentifierHandle>? identifiers;
     for (int i = children!.length - 1; i >= 0; i--) {
       DirectParserASTContent child = children![i];
-      if (child is DirectParserASTContentIdentifierHandle) {
+      if (child is DirectParserASTContentIdentifierHandle &&
+          child.context == IdentifierContext.fieldDeclaration) {
+        countLeft--;
+        if (identifiers == null) {
+          identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
+              count, child);
+        } else {
+          identifiers[countLeft] = child;
+        }
+        if (countLeft == 0) break;
+      }
+    }
+    if (countLeft != 0) throw "Didn't find the expected number of identifiers";
+    return identifiers ?? [];
+  }
+}
+
+extension ExtensionFieldsExtension on DirectParserASTContentExtensionFieldsEnd {
+  List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
+    int countLeft = count;
+    List<DirectParserASTContentIdentifierHandle>? identifiers;
+    for (int i = children!.length - 1; i >= 0; i--) {
+      DirectParserASTContent child = children![i];
+      if (child is DirectParserASTContentIdentifierHandle &&
+          child.context == IdentifierContext.fieldDeclaration) {
+        countLeft--;
+        if (identifiers == null) {
+          identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
+              count, child);
+        } else {
+          identifiers[countLeft] = child;
+        }
+        if (countLeft == 0) break;
+      }
+    }
+    if (countLeft != 0) throw "Didn't find the expected number of identifiers";
+    return identifiers ?? [];
+  }
+}
+
+extension ClassFieldsExtension on DirectParserASTContentClassFieldsEnd {
+  List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
+    int countLeft = count;
+    List<DirectParserASTContentIdentifierHandle>? identifiers;
+    for (int i = children!.length - 1; i >= 0; i--) {
+      DirectParserASTContent child = children![i];
+      if (child is DirectParserASTContentIdentifierHandle &&
+          child.context == IdentifierContext.fieldDeclaration) {
         countLeft--;
         if (identifiers == null) {
           identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
@@ -1056,6 +1111,190 @@
   }
 }
 
+extension EnumExtension on DirectParserASTContentEnumEnd {
+  List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
+    List<DirectParserASTContentIdentifierHandle> ids = [];
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) ids.add(child);
+    }
+    return ids;
+  }
+}
+
+extension ExtensionDeclarationExtension
+    on DirectParserASTContentExtensionDeclarationEnd {
+  List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
+    List<DirectParserASTContentIdentifierHandle> ids = [];
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) ids.add(child);
+    }
+    return ids;
+  }
+}
+
+extension TopLevelMethodExtension on DirectParserASTContentTopLevelMethodEnd {
+  DirectParserASTContentIdentifierHandle getNameIdentifier() {
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) {
+        if (child.context == IdentifierContext.topLevelFunctionDeclaration) {
+          return child;
+        }
+      }
+    }
+    throw "Didn't find the name identifier!";
+  }
+}
+
+extension TypedefExtension on DirectParserASTContentTypedefEnd {
+  DirectParserASTContentIdentifierHandle getNameIdentifier() {
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) {
+        if (child.context == IdentifierContext.typedefDeclaration) {
+          return child;
+        }
+      }
+    }
+    throw "Didn't find the name identifier!";
+  }
+}
+
+extension ImportExtension on DirectParserASTContentImportEnd {
+  DirectParserASTContentIdentifierHandle? getImportPrefix() {
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) {
+        if (child.context == IdentifierContext.importPrefixDeclaration) {
+          return child;
+        }
+      }
+    }
+  }
+
+  String getImportUriString() {
+    StringBuffer sb = new StringBuffer();
+    bool foundOne = false;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentLiteralStringEnd) {
+        DirectParserASTContentLiteralStringBegin uri =
+            child.children!.single as DirectParserASTContentLiteralStringBegin;
+        sb.write(unescapeString(
+            uri.token.lexeme, uri.token, const UnescapeErrorListenerDummy()));
+        foundOne = true;
+      }
+    }
+    if (!foundOne) throw "Didn't find any";
+    return sb.toString();
+  }
+
+  List<String>? getConditionalImportUriStrings() {
+    List<String>? result;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentConditionalUrisEnd) {
+        for (DirectParserASTContent child2 in child.children!) {
+          if (child2 is DirectParserASTContentConditionalUriEnd) {
+            DirectParserASTContentLiteralStringEnd end =
+                child2.children!.last as DirectParserASTContentLiteralStringEnd;
+            DirectParserASTContentLiteralStringBegin uri = end.children!.single
+                as DirectParserASTContentLiteralStringBegin;
+            (result ??= []).add(unescapeString(uri.token.lexeme, uri.token,
+                const UnescapeErrorListenerDummy()));
+          }
+        }
+        return result;
+      }
+    }
+    return result;
+  }
+}
+
+extension ExportExtension on DirectParserASTContentExportEnd {
+  String getExportUriString() {
+    StringBuffer sb = new StringBuffer();
+    bool foundOne = false;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentLiteralStringEnd) {
+        DirectParserASTContentLiteralStringBegin uri =
+            child.children!.single as DirectParserASTContentLiteralStringBegin;
+        sb.write(unescapeString(
+            uri.token.lexeme, uri.token, const UnescapeErrorListenerDummy()));
+        foundOne = true;
+      }
+    }
+    if (!foundOne) throw "Didn't find any";
+    return sb.toString();
+  }
+
+  List<String>? getConditionalExportUriStrings() {
+    List<String>? result;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentConditionalUrisEnd) {
+        for (DirectParserASTContent child2 in child.children!) {
+          if (child2 is DirectParserASTContentConditionalUriEnd) {
+            DirectParserASTContentLiteralStringEnd end =
+                child2.children!.last as DirectParserASTContentLiteralStringEnd;
+            DirectParserASTContentLiteralStringBegin uri = end.children!.single
+                as DirectParserASTContentLiteralStringBegin;
+            (result ??= []).add(unescapeString(uri.token.lexeme, uri.token,
+                const UnescapeErrorListenerDummy()));
+          }
+        }
+        return result;
+      }
+    }
+    return result;
+  }
+}
+
+extension PartExtension on DirectParserASTContentPartEnd {
+  String getPartUriString() {
+    StringBuffer sb = new StringBuffer();
+    bool foundOne = false;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentLiteralStringEnd) {
+        DirectParserASTContentLiteralStringBegin uri =
+            child.children!.single as DirectParserASTContentLiteralStringBegin;
+        sb.write(unescapeString(
+            uri.token.lexeme, uri.token, const UnescapeErrorListenerDummy()));
+        foundOne = true;
+      }
+    }
+    if (!foundOne) throw "Didn't find any";
+    return sb.toString();
+  }
+}
+
+class UnescapeErrorListenerDummy implements UnescapeErrorListener {
+  const UnescapeErrorListenerDummy();
+
+  @override
+  void handleUnescapeError(
+      Message message, covariant location, int offset, int length) {
+    // Purposely doesn't do anything.
+  }
+}
+
+extension TopLevelFieldsExtension on DirectParserASTContentTopLevelFieldsEnd {
+  List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
+    int countLeft = count;
+    List<DirectParserASTContentIdentifierHandle>? identifiers;
+    for (int i = children!.length - 1; i >= 0; i--) {
+      DirectParserASTContent child = children![i];
+      if (child is DirectParserASTContentIdentifierHandle &&
+          child.context == IdentifierContext.topLevelVariableDeclaration) {
+        countLeft--;
+        if (identifiers == null) {
+          identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
+              count, child);
+        } else {
+          identifiers[countLeft] = child;
+        }
+        if (countLeft == 0) break;
+      }
+    }
+    if (countLeft != 0) throw "Didn't find the expected number of identifiers";
+    return identifiers ?? [];
+  }
+}
+
 extension ClassMethodExtension on DirectParserASTContentClassMethodEnd {
   DirectParserASTContentBlockFunctionBodyEnd? getBlockFunctionBody() {
     for (DirectParserASTContent child in children!) {
@@ -1065,6 +1304,80 @@
     }
     return null;
   }
+
+  String getNameIdentifier() {
+    bool foundType = false;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentTypeHandle ||
+          child is DirectParserASTContentNoTypeHandle ||
+          child is DirectParserASTContentVoidKeywordHandle ||
+          child is DirectParserASTContentFunctionTypeEnd) {
+        foundType = true;
+      }
+      if (foundType && child is DirectParserASTContentIdentifierHandle) {
+        return child.token.lexeme;
+      } else if (foundType &&
+          child is DirectParserASTContentOperatorNameHandle) {
+        return child.token.lexeme;
+      }
+    }
+    throw "No identifier found: $children";
+  }
+}
+
+extension MixinMethodExtension on DirectParserASTContentMixinMethodEnd {
+  String getNameIdentifier() {
+    bool foundType = false;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentTypeHandle ||
+          child is DirectParserASTContentNoTypeHandle ||
+          child is DirectParserASTContentVoidKeywordHandle) {
+        foundType = true;
+      }
+      if (foundType && child is DirectParserASTContentIdentifierHandle) {
+        return child.token.lexeme;
+      } else if (foundType &&
+          child is DirectParserASTContentOperatorNameHandle) {
+        return child.token.lexeme;
+      }
+    }
+    throw "No identifier found: $children";
+  }
+}
+
+extension ExtensionMethodExtension on DirectParserASTContentExtensionMethodEnd {
+  String getNameIdentifier() {
+    bool foundType = false;
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentTypeHandle ||
+          child is DirectParserASTContentNoTypeHandle ||
+          child is DirectParserASTContentVoidKeywordHandle) {
+        foundType = true;
+      }
+      if (foundType && child is DirectParserASTContentIdentifierHandle) {
+        return child.token.lexeme;
+      } else if (foundType &&
+          child is DirectParserASTContentOperatorNameHandle) {
+        return child.token.lexeme;
+      }
+    }
+    throw "No identifier found: $children";
+  }
+}
+
+extension ClassFactoryMethodExtension
+    on DirectParserASTContentClassFactoryMethodEnd {
+  List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
+    List<DirectParserASTContentIdentifierHandle> result = [];
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) {
+        result.add(child);
+      } else if (child is DirectParserASTContentFormalParametersEnd) {
+        break;
+      }
+    }
+    return result;
+  }
 }
 
 extension ClassConstructorExtension
@@ -1095,6 +1408,16 @@
     }
     return null;
   }
+
+  List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
+    List<DirectParserASTContentIdentifierHandle> result = [];
+    for (DirectParserASTContent child in children!) {
+      if (child is DirectParserASTContentIdentifierHandle) {
+        result.add(child);
+      }
+    }
+    return result;
+  }
 }
 
 extension FormalParametersExtension
@@ -1271,6 +1594,9 @@
           throw "Unknown combination: begin$begin and end$end";
         }
         List<DirectParserASTContent> children = data.sublist(beginIndex);
+        for (DirectParserASTContent child in children) {
+          child.parent = entry;
+        }
         data.length = beginIndex;
         data.add(entry..children = children);
         break;
diff --git a/pkg/front_end/lib/src/fasta/util/direct_parser_ast_helper.dart b/pkg/front_end/lib/src/fasta/util/direct_parser_ast_helper.dart
index 2d73710..b0a2688 100644
--- a/pkg/front_end/lib/src/fasta/util/direct_parser_ast_helper.dart
+++ b/pkg/front_end/lib/src/fasta/util/direct_parser_ast_helper.dart
@@ -26,6 +26,7 @@
   final DirectParserASTType type;
   Map<String, Object?> get deprecatedArguments;
   List<DirectParserASTContent>? children;
+  DirectParserASTContent? parent;
 
   DirectParserASTContent(this.what, this.type);
 
diff --git a/pkg/front_end/lib/src/fasta/util/outline_extractor.dart b/pkg/front_end/lib/src/fasta/util/outline_extractor.dart
new file mode 100644
index 0000000..e8ba2cc
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/util/outline_extractor.dart
@@ -0,0 +1,964 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart'
+    show ScannerConfiguration;
+import 'package:front_end/src/api_prototype/compiler_options.dart';
+import 'package:front_end/src/api_prototype/file_system.dart';
+import 'package:front_end/src/base/processed_options.dart';
+import 'package:front_end/src/fasta/compiler_context.dart';
+import 'package:front_end/src/fasta/uri_translator.dart';
+import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
+import 'package:front_end/src/fasta/util/textual_outline.dart';
+import 'package:_fe_analyzer_shared/src/parser/identifier_context.dart';
+import 'package:kernel/target/targets.dart';
+
+import "direct_parser_ast.dart";
+import "abstracted_ast_nodes.dart";
+
+// Overall TODO(s):
+// * If entry is given as fileuri but exists as different import uri...
+//   Does that matter?
+// * Setters vs non-setters with naming conflicts.
+// * -> also these might be found on "different levels", e.g. the setter might
+//      be in the class and the getter might be in an import.
+// * show/hide on imports and exports.
+// * Handle importing/exporting non-existing files.
+// * Tests.
+// * Maybe bypass the direct-from-parser-ast stuff for speed?
+// * Probably some of the special classes can be combined if we want to
+//   (e.g. Class and Mixin).
+// * Extensions --- we currently basically mark all we see.
+//    => Could be perhaps only include them if the class they're talking about
+//       is included? (or we don't know).
+// * E.g. "factory Abc.b() => Abc3();" is the same as
+//   "factory Abc.b() { return Abc3(); }" and Abc3 shouldn't be marked by it.
+//    -> This is basically a rough edge on the textual outline though.
+//    -> Also, the same applies to other instances of "=>".
+// * It shouldn't lookup private stuff in other libraries.
+// * Could there be made a distinction between for instance
+//   `IdentifierContext.typeReference` and `IdentifierContext.expression`?
+//   => one might not have to include content of classes that only talk about
+//      typeReference I think.
+
+Future<void> main(List<String> args) async {
+  if (args.length != 2) {
+    throw "Needs 2 arguments: packages file/dir and file to process.";
+  }
+  Uri packages = Uri.base.resolve(args[0]);
+  Uri file = Uri.base.resolve(args[1]);
+  for (int i = 0; i < 1; i++) {
+    Stopwatch stopwatch = new Stopwatch()..start();
+    await extractOutline([file], packages: packages, verbosityLevel: 40);
+    print("Finished in ${stopwatch.elapsedMilliseconds} ms "
+        "(textual outline was "
+        "${latestProcessor!.textualOutlineStopwatch.elapsedMilliseconds} ms)"
+        "(get ast was "
+        "${latestProcessor!.getAstStopwatch.elapsedMilliseconds} ms)"
+        "(extract identifier was "
+        "${latestProcessor!.extractIdentifierStopwatch.elapsedMilliseconds} ms)"
+        "");
+  }
+}
+
+_Processor? latestProcessor;
+
+Future<Map<Uri, String>> extractOutline(List<Uri> entryPointUris,
+    {Uri? sdk,
+    required Uri? packages,
+    Uri? platform,
+    Target? target,
+    int verbosityLevel: 0}) {
+  CompilerOptions options = new CompilerOptions()
+    ..target = target
+    ..packagesFileUri = packages
+    ..sdkSummary = platform
+    ..sdkRoot = sdk;
+  ProcessedOptions pOptions =
+      new ProcessedOptions(options: options, inputs: entryPointUris);
+  return CompilerContext.runWithOptions(pOptions, (CompilerContext c) async {
+    FileSystem fileSystem = c.options.fileSystem;
+    UriTranslator uriTranslator = await c.options.getUriTranslator();
+    _Processor processor =
+        new _Processor(verbosityLevel, fileSystem, uriTranslator);
+    latestProcessor = processor;
+    List<TopLevel> entryPoints = [];
+    for (Uri entryPointUri in entryPointUris) {
+      TopLevel entryPoint = await processor.preprocessUri(entryPointUri);
+      entryPoints.add(entryPoint);
+    }
+    return await processor.calculate(entryPoints);
+  });
+}
+
+class _Processor {
+  final FileSystem fileSystem;
+  final UriTranslator uriTranslator;
+  final int verbosityLevel;
+
+  final Stopwatch textualOutlineStopwatch = new Stopwatch();
+  final Stopwatch getAstStopwatch = new Stopwatch();
+  final Stopwatch extractIdentifierStopwatch = new Stopwatch();
+
+  Map<Uri, TopLevel> parsed = {};
+
+  _Processor(this.verbosityLevel, this.fileSystem, this.uriTranslator);
+
+  void log(String s) {
+    if (verbosityLevel <= 0) return;
+    print(s);
+  }
+
+  Future<TopLevel> preprocessUri(Uri importUri, {Uri? partOf}) async {
+    if (verbosityLevel >= 20) log("$importUri =>");
+    Uri fileUri = importUri;
+    if (importUri.scheme == "package") {
+      fileUri = uriTranslator.translate(importUri)!;
+    }
+    if (verbosityLevel >= 20) log("$fileUri");
+    final List<int> bytes =
+        await fileSystem.entityForUri(fileUri).readAsBytes();
+    // TODO: Support updating the configuration; also default it to match
+    // the package version.
+    final ScannerConfiguration configuration = new ScannerConfiguration(
+        enableExtensionMethods: true,
+        enableNonNullable: true,
+        enableTripleShift: true);
+    textualOutlineStopwatch.start();
+    final String? outlined = textualOutline(bytes, configuration);
+    textualOutlineStopwatch.stop();
+    if (outlined == null) throw "Textual outline returned null";
+    final List<int> bytes2 = utf8.encode(outlined);
+    getAstStopwatch.start();
+    List<Token> languageVersionsSeen = [];
+    final DirectParserASTContent ast = getAST(bytes2,
+        enableExtensionMethods: configuration.enableExtensionMethods,
+        enableNonNullable: configuration.enableNonNullable,
+        enableTripleShift: configuration.enableTripleShift,
+        languageVersionsSeen: languageVersionsSeen);
+    getAstStopwatch.stop();
+
+    _ParserAstVisitor visitor = new _ParserAstVisitor(
+        verbosityLevel, outlined, importUri, partOf, ast, languageVersionsSeen);
+    TopLevel topLevel = visitor.currentContainer as TopLevel;
+    if (parsed[importUri] != null) throw "$importUri already set?!?";
+    parsed[importUri] = topLevel;
+    visitor.accept(ast);
+    topLevel.buildScope();
+
+    _IdentifierExtractor identifierExtractor = new _IdentifierExtractor();
+    extractIdentifierStopwatch.start();
+    identifierExtractor.extract(ast);
+    extractIdentifierStopwatch.stop();
+    for (DirectParserASTContentIdentifierHandle identifier
+        in identifierExtractor.identifiers) {
+      if (identifier.context == IdentifierContext.typeVariableDeclaration) {
+        // Hack: Put type variable declarations into scope so any overlap in
+        // name doesn't mark usages (e.g. a class E shouldn't be marked if we're
+        // talking about the type variable E).
+        DirectParserASTContent content = identifier;
+        AstNode? nearestAstNode = visitor.map[content];
+        while (nearestAstNode == null && content.parent != null) {
+          content = content.parent!;
+          nearestAstNode = visitor.map[content];
+        }
+        if (nearestAstNode == null) {
+          content = identifier;
+          nearestAstNode = visitor.map[content];
+          while (nearestAstNode == null && content.parent != null) {
+            content = content.parent!;
+            nearestAstNode = visitor.map[content];
+          }
+
+          StringBuffer sb = new StringBuffer();
+          Token t = identifier.token;
+          // for(int i = 0; i < 10; i++) {
+          //   t = t.previous!;
+          // }
+          for (int i = 0; i < 20; i++) {
+            sb.write("$t ");
+            t = t.next!;
+          }
+          throw "$fileUri --- couldn't even find nearest ast node for "
+              "${identifier.token} :( -- context $sb";
+        }
+        (nearestAstNode.scope[identifier.token.lexeme] ??= [])
+            .add(nearestAstNode);
+      }
+    }
+
+    return topLevel;
+  }
+
+  Future<void> _premarkTopLevel(List<_TopLevelAndAstNode> worklist,
+      Set<TopLevel> closed, TopLevel entrypointish) async {
+    if (!closed.add(entrypointish)) return;
+
+    for (AstNode child in entrypointish.children) {
+      child.marked = Coloring.Marked;
+      worklist.add(new _TopLevelAndAstNode(entrypointish, child));
+
+      if (child is Part) {
+        if (child.uri.scheme != "dart") {
+          TopLevel partTopLevel = parsed[child.uri] ??
+              await preprocessUri(child.uri, partOf: entrypointish.uri);
+          await _premarkTopLevel(worklist, closed, partTopLevel);
+        }
+      } else if (child is Export) {
+        for (Uri importedUri in child.uris) {
+          if (importedUri.scheme != "dart") {
+            TopLevel exportTopLevel =
+                parsed[importedUri] ?? await preprocessUri(importedUri);
+            await _premarkTopLevel(worklist, closed, exportTopLevel);
+          }
+        }
+      }
+    }
+  }
+
+  Future<List<TopLevel>> _preprocessImportsAsNeeded(
+      Map<TopLevel, List<TopLevel>> imports, TopLevel topLevel) async {
+    List<TopLevel>? imported = imports[topLevel];
+    if (imported == null) {
+      // Process all imports.
+      imported = [];
+      imports[topLevel] = imported;
+      for (AstNode child in topLevel.children) {
+        if (child is Import) {
+          child.marked = Coloring.Marked;
+          for (Uri importedUri in child.uris) {
+            if (importedUri.scheme != "dart") {
+              TopLevel importedTopLevel =
+                  parsed[importedUri] ?? await preprocessUri(importedUri);
+              imported.add(importedTopLevel);
+            }
+          }
+        } else if (child is PartOf) {
+          child.marked = Coloring.Marked;
+          if (child.partOfUri.scheme != "dart") {
+            TopLevel part = parsed[child.partOfUri]!;
+            List<TopLevel> importsFromPart =
+                await _preprocessImportsAsNeeded(imports, part);
+            imported.addAll(importsFromPart);
+          }
+        }
+      }
+    }
+    return imported;
+  }
+
+  Future<Map<Uri, String>> calculate(List<TopLevel> entryPoints) async {
+    List<_TopLevelAndAstNode> worklist = [];
+    Map<TopLevel, List<TopLevel>> imports = {};
+
+    // Mark all top-level in entry point. Also include parts and exports (and
+    // exports exports etc) of the entry point.
+    Set<TopLevel> closed = {};
+    for (TopLevel entryPoint in entryPoints) {
+      await _premarkTopLevel(worklist, closed, entryPoint);
+    }
+
+    Map<TopLevel, Set<String>> lookupsAll = {};
+    Map<TopLevel, List<String>> lookupsWorklist = {};
+    while (worklist.isNotEmpty || lookupsWorklist.isNotEmpty) {
+      while (worklist.isNotEmpty) {
+        _TopLevelAndAstNode entry = worklist.removeLast();
+        if (verbosityLevel >= 20) {
+          log("\n-----\nProcessing ${entry.entry.node.toString()}");
+        }
+        _IdentifierExtractor identifierExtractor = new _IdentifierExtractor();
+        identifierExtractor.extract(entry.entry.node);
+        if (verbosityLevel >= 20) {
+          log("Found ${identifierExtractor.identifiers}");
+        }
+        List<AstNode>? prevLookupResult;
+        nextIdentifier:
+        for (DirectParserASTContentIdentifierHandle identifier
+            in identifierExtractor.identifiers) {
+          DirectParserASTContent content = identifier;
+          AstNode? nearestAstNode = entry.topLevel.map[content];
+          while (nearestAstNode == null && content.parent != null) {
+            content = content.parent!;
+            nearestAstNode = entry.topLevel.map[content];
+          }
+          if (nearestAstNode == null) {
+            throw "couldn't even find nearest ast node for "
+                "${identifier.token} :(";
+          }
+
+          if (identifier.context == IdentifierContext.typeReference ||
+              identifier.context == IdentifierContext.prefixedTypeReference ||
+              identifier.context ==
+                  IdentifierContext.typeReferenceContinuation ||
+              identifier.context == IdentifierContext.constructorReference ||
+              identifier.context ==
+                  IdentifierContext.constructorReferenceContinuation ||
+              identifier.context == IdentifierContext.expression ||
+              identifier.context == IdentifierContext.expressionContinuation ||
+              identifier.context == IdentifierContext.metadataReference ||
+              identifier.context == IdentifierContext.metadataContinuation) {
+            bool lookupInThisScope = true;
+            if (!identifier.context.isContinuation) {
+              prevLookupResult = null;
+            } else if (prevLookupResult != null) {
+              // In continuation.
+              // either 0 or all should be imports.
+              for (AstNode prevResult in prevLookupResult) {
+                if (prevResult is Import) {
+                  lookupInThisScope = false;
+                } else {
+                  continue nextIdentifier;
+                }
+              }
+            } else {
+              // Still in continuation --- but prev lookup didn't yield
+              // anything. We shouldn't search for the continuation part in this
+              // scope (and thus skip looking in imports).
+              lookupInThisScope = false;
+            }
+            if (verbosityLevel >= 20) {
+              log("${identifier.token} (${identifier.context})");
+            }
+
+            // Now we need parts at this point. Either we're in the entry point
+            // in which case parts was read by [_premarkTopLevel], or we're here
+            // via lookups on an import, where parts were read too.
+            List<AstNode>? lookedUp;
+            if (lookupInThisScope) {
+              lookedUp = findInScope(
+                  identifier.token.lexeme, nearestAstNode, entry.topLevel);
+              prevLookupResult = lookedUp;
+            }
+            if (lookedUp != null) {
+              for (AstNode found in lookedUp) {
+                if (verbosityLevel >= 20) log(" => found $found");
+                if (found.marked == Coloring.Untouched) {
+                  found.marked = Coloring.Marked;
+                  TopLevel foundTopLevel = entry.topLevel;
+                  if (found.parent is TopLevel) {
+                    foundTopLevel = found.parent as TopLevel;
+                  }
+                  worklist.add(new _TopLevelAndAstNode(foundTopLevel, found));
+                }
+              }
+            } else {
+              if (verbosityLevel >= 20) {
+                log("=> Should find this via an import probably?");
+              }
+
+              List<TopLevel> imported =
+                  await _preprocessImportsAsNeeded(imports, entry.topLevel);
+
+              Set<Uri>? wantedImportUrls;
+              if (!lookupInThisScope && prevLookupResult != null) {
+                for (AstNode castMeAsImport in prevLookupResult) {
+                  Import import = castMeAsImport as Import;
+                  assert(import.asName != null);
+                  (wantedImportUrls ??= {}).addAll(import.uris);
+                }
+              }
+
+              for (TopLevel other in imported) {
+                if (!lookupInThisScope && prevLookupResult != null) {
+                  assert(wantedImportUrls != null);
+                  if (!wantedImportUrls!.contains(other.uri)) continue;
+                }
+
+                Set<String> lookupStrings = lookupsAll[other] ??= {};
+                if (lookupStrings.add(identifier.token.lexeme)) {
+                  List<String> lookupStringsWorklist =
+                      lookupsWorklist[other] ??= [];
+                  lookupStringsWorklist.add(identifier.token.lexeme);
+                }
+              }
+            }
+          } else {
+            if (verbosityLevel >= 30) {
+              log("Ignoring ${identifier.token} as it's a "
+                  "${identifier.context}");
+            }
+          }
+        }
+      }
+      Map<TopLevel, List<String>> lookupsWorklistTmp = {};
+      for (MapEntry<TopLevel, List<String>> lookups
+          in lookupsWorklist.entries) {
+        TopLevel topLevel = lookups.key;
+        // We have to make the same lookups in parts and exports too.
+        for (AstNode child in topLevel.children) {
+          TopLevel? other;
+          if (child is Part) {
+            child.marked = Coloring.Marked;
+            // do stuff to part.
+            if (child.uri.scheme != "dart") {
+              other = parsed[child.uri] ??
+                  await preprocessUri(child.uri, partOf: topLevel.uri);
+            }
+          } else if (child is Export) {
+            child.marked = Coloring.Marked;
+            // do stuff to export.
+            for (Uri importedUri in child.uris) {
+              if (importedUri.scheme != "dart") {
+                other = parsed[importedUri] ?? await preprocessUri(importedUri);
+              }
+            }
+          } else if (child is Extension) {
+            // TODO: Maybe put on a list to process later and only include if
+            // the on-class is included?
+            if (child.marked == Coloring.Untouched) {
+              child.marked = Coloring.Marked;
+              worklist.add(new _TopLevelAndAstNode(topLevel, child));
+            }
+          }
+          if (other != null) {
+            Set<String> lookupStrings = lookupsAll[other] ??= {};
+            for (String identifier in lookups.value) {
+              if (lookupStrings.add(identifier)) {
+                List<String> lookupStringsWorklist =
+                    lookupsWorklistTmp[other] ??= [];
+                lookupStringsWorklist.add(identifier);
+              }
+            }
+          }
+        }
+
+        for (String identifier in lookups.value) {
+          List<AstNode>? foundInScope = topLevel.findInScope(identifier);
+          if (foundInScope != null) {
+            for (AstNode found in foundInScope) {
+              if (found.marked == Coloring.Untouched) {
+                found.marked = Coloring.Marked;
+                worklist.add(new _TopLevelAndAstNode(topLevel, found));
+              }
+              if (verbosityLevel >= 20) {
+                log(" => found $found via import (${found.marked})");
+              }
+            }
+          }
+        }
+      }
+      lookupsWorklist = lookupsWorklistTmp;
+    }
+
+    if (verbosityLevel >= 40) {
+      log("\n\n---------\n\n");
+      log(parsed.toString());
+      log("\n\n---------\n\n");
+    }
+
+    // Extract.
+    int count = 0;
+    Map<Uri, String> result = {};
+    // We only read imports if we need to lookup in them, but if a import
+    // statement is included in the output the file has to exist if it actually
+    // exists to not get a compilation error.
+    Set<Uri> imported = {};
+    for (MapEntry<Uri, TopLevel> entry in parsed.entries) {
+      if (verbosityLevel >= 40) log("${entry.key}:");
+      StringBuffer sb = new StringBuffer();
+      for (AstNode child in entry.value.children) {
+        if (child.marked == Coloring.Marked) {
+          String substring = entry.value.sourceText.substring(
+              child.startInclusive.charOffset, child.endInclusive.charEnd);
+          sb.writeln(substring);
+          if (verbosityLevel >= 40) {
+            log(substring);
+          }
+          if (child is Import) {
+            for (Uri importedUri in child.uris) {
+              if (importedUri.scheme != "dart") {
+                imported.add(importedUri);
+              }
+            }
+          }
+        }
+      }
+      if (sb.isNotEmpty) count++;
+      Uri uri = entry.key;
+      Uri fileUri = uri;
+      if (uri.scheme == "package") {
+        fileUri = uriTranslator.translate(uri)!;
+      }
+      result[fileUri] = sb.toString();
+    }
+    for (Uri uri in imported) {
+      TopLevel? topLevel = parsed[uri];
+      if (topLevel != null) continue;
+      // uri imports a file we haven't read. Check if it exists and include it
+      // as an empty file if it does.
+      Uri fileUri = uri;
+      if (uri.scheme == "package") {
+        fileUri = uriTranslator.translate(uri)!;
+      }
+      if (await fileSystem.entityForUri(fileUri).exists()) {
+        result[fileUri] = "";
+      }
+    }
+
+    print("=> Long story short got it to $count non-empty files...");
+
+    return result;
+  }
+
+  List<AstNode>? findInScope(
+      String name, AstNode nearestAstNode, TopLevel topLevel,
+      {Set<TopLevel>? visited}) {
+    List<AstNode>? result;
+    result = nearestAstNode.findInScope(name);
+    if (result != null) return result;
+    for (AstNode child in topLevel.children) {
+      if (child is Part) {
+        visited ??= {topLevel};
+        TopLevel partTopLevel = parsed[child.uri]!;
+        if (visited.add(partTopLevel)) {
+          result =
+              findInScope(name, partTopLevel, partTopLevel, visited: visited);
+          if (result != null) return result;
+        }
+      } else if (child is PartOf) {
+        visited ??= {topLevel};
+        TopLevel partOwnerTopLevel = parsed[child.partOfUri]!;
+        if (visited.add(partOwnerTopLevel)) {
+          result = findInScope(name, partOwnerTopLevel, partOwnerTopLevel,
+              visited: visited);
+          if (result != null) return result;
+        }
+      }
+    }
+  }
+}
+
+class _TopLevelAndAstNode {
+  final TopLevel topLevel;
+  final AstNode entry;
+
+  _TopLevelAndAstNode(this.topLevel, this.entry);
+}
+
+class _IdentifierExtractor {
+  List<DirectParserASTContentIdentifierHandle> identifiers = [];
+
+  void extract(DirectParserASTContent ast) {
+    if (ast is DirectParserASTContentIdentifierHandle) {
+      identifiers.add(ast);
+    }
+    List<DirectParserASTContent>? children = ast.children;
+    if (children != null) {
+      for (DirectParserASTContent child in children) {
+        extract(child);
+      }
+    }
+  }
+}
+
+class _ParserAstVisitor extends DirectParserASTContentVisitor {
+  final Uri uri;
+  final Uri? partOfUri;
+  late Container currentContainer;
+  final Map<DirectParserASTContent, AstNode> map = {};
+  final int verbosityLevel;
+  final List<Token> languageVersionsSeen;
+
+  _ParserAstVisitor(
+      this.verbosityLevel,
+      String sourceText,
+      this.uri,
+      this.partOfUri,
+      DirectParserASTContent rootAst,
+      this.languageVersionsSeen) {
+    currentContainer = new TopLevel(sourceText, uri, rootAst, map);
+    if (languageVersionsSeen.isNotEmpty) {
+      // Use first one.
+      Token languageVersion = languageVersionsSeen.first;
+      DirectParserASTContent dummyNode =
+          new DirectParserASTContentNoInitializersHandle(
+              DirectParserASTType.HANDLE);
+      LanguageVersion version =
+          new LanguageVersion(dummyNode, languageVersion, languageVersion);
+      version.marked = Coloring.Marked;
+      currentContainer.addChild(version, map);
+    }
+  }
+
+  void log(String s) {
+    if (verbosityLevel <= 0) return;
+    Container? x = currentContainer.parent;
+    int level = 0;
+    while (x != null) {
+      level++;
+      x = x.parent;
+    }
+    print(" " * level + s);
+  }
+
+  @override
+  void visitClass(DirectParserASTContentClassDeclarationEnd node,
+      Token startInclusive, Token endInclusive) {
+    DirectParserASTContentTopLevelDeclarationEnd parent =
+        node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
+    DirectParserASTContentIdentifierHandle identifier = parent.getIdentifier();
+
+    log("Hello from class ${identifier.token}");
+
+    Class cls = new Class(
+        parent, identifier.token.lexeme, startInclusive, endInclusive);
+    currentContainer.addChild(cls, map);
+
+    Container previousContainer = currentContainer;
+    currentContainer = cls;
+    super.visitClass(node, startInclusive, endInclusive);
+    currentContainer = previousContainer;
+  }
+
+  @override
+  void visitClassConstructor(DirectParserASTContentClassConstructorEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Class);
+    List<DirectParserASTContentIdentifierHandle> ids = node.getIdentifiers();
+    if (ids.length == 1) {
+      ClassConstructor classConstructor = new ClassConstructor(
+          node, ids.single.token.lexeme, startInclusive, endInclusive);
+      currentContainer.addChild(classConstructor, map);
+      log("Hello from constructor ${ids.single.token}");
+    } else if (ids.length == 2) {
+      ClassConstructor classConstructor = new ClassConstructor(node,
+          "${ids.first.token}.${ids.last.token}", startInclusive, endInclusive);
+      map[node] = classConstructor;
+      currentContainer.addChild(classConstructor, map);
+      log("Hello from constructor ${ids.first.token}.${ids.last.token}");
+    } else {
+      throw "Unexpected identifiers in class constructor";
+    }
+
+    super.visitClassConstructor(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitClassFactoryMethod(DirectParserASTContentClassFactoryMethodEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Class);
+    List<DirectParserASTContentIdentifierHandle> ids = node.getIdentifiers();
+    if (ids.length == 1) {
+      ClassFactoryMethod classFactoryMethod = new ClassFactoryMethod(
+          node, ids.single.token.lexeme, startInclusive, endInclusive);
+      currentContainer.addChild(classFactoryMethod, map);
+      log("Hello from factory method ${ids.single.token}");
+    } else if (ids.length == 2) {
+      ClassFactoryMethod classFactoryMethod = new ClassFactoryMethod(node,
+          "${ids.first.token}.${ids.last.token}", startInclusive, endInclusive);
+      map[node] = classFactoryMethod;
+      currentContainer.addChild(classFactoryMethod, map);
+      log("Hello from factory method ${ids.first.token}.${ids.last.token}");
+    } else {
+      Container findTopLevel = currentContainer;
+      while (findTopLevel is! TopLevel) {
+        findTopLevel = findTopLevel.parent!;
+      }
+      String src = findTopLevel.sourceText
+          .substring(startInclusive.charOffset, endInclusive.charEnd);
+      throw "Unexpected identifiers in class factory method: $ids "
+          "(${ids.map((e) => e.token.lexeme).toList()}) --- "
+          "error on source ${src} --- "
+          "${node.children}";
+    }
+
+    super.visitClassFactoryMethod(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitClassFields(DirectParserASTContentClassFieldsEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Class);
+    List<String> fields =
+        node.getFieldIdentifiers().map((e) => e.token.lexeme).toList();
+    ClassFields classFields =
+        new ClassFields(node, fields, startInclusive, endInclusive);
+    currentContainer.addChild(classFields, map);
+    log("Hello from class fields ${fields.join(", ")}");
+    super.visitClassFields(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitClassMethod(DirectParserASTContentClassMethodEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Class);
+
+    String identifier;
+    try {
+      identifier = node.getNameIdentifier();
+    } catch (e) {
+      Container findTopLevel = currentContainer;
+      while (findTopLevel is! TopLevel) {
+        findTopLevel = findTopLevel.parent!;
+      }
+      String src = findTopLevel.sourceText
+          .substring(startInclusive.charOffset, endInclusive.charEnd);
+      throw "Unexpected identifiers in visitClassMethod --- "
+          "error on source ${src} --- "
+          "${node.children}";
+    }
+    ClassMethod classMethod =
+        new ClassMethod(node, identifier, startInclusive, endInclusive);
+    currentContainer.addChild(classMethod, map);
+    log("Hello from class method $identifier");
+    super.visitClassMethod(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitEnum(DirectParserASTContentEnumEnd node, Token startInclusive,
+      Token endInclusive) {
+    List<DirectParserASTContentIdentifierHandle> ids = node.getIdentifiers();
+
+    Enum e = new Enum(
+        node,
+        ids.first.token.lexeme,
+        ids.skip(1).map((e) => e.token.lexeme).toList(),
+        startInclusive,
+        endInclusive);
+    currentContainer.addChild(e, map);
+
+    log("Hello from enum ${ids.first.token} with content "
+        "${ids.skip(1).map((e) => e.token).join(", ")}");
+    super.visitEnum(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitExport(DirectParserASTContentExportEnd node, Token startInclusive,
+      Token endInclusive) {
+    String uriString = node.getExportUriString();
+    Uri exportUri = uri.resolve(uriString);
+    List<String>? conditionalUriStrings = node.getConditionalExportUriStrings();
+    List<Uri>? conditionalUris;
+    if (conditionalUriStrings != null) {
+      conditionalUris = [];
+      for (String conditionalUri in conditionalUriStrings) {
+        conditionalUris.add(uri.resolve(conditionalUri));
+      }
+    }
+    // TODO: Use 'show' and 'hide' stuff.
+    Export e = new Export(
+        node, exportUri, conditionalUris, startInclusive, endInclusive);
+    currentContainer.addChild(e, map);
+    log("Hello export");
+  }
+
+  @override
+  void visitExtension(DirectParserASTContentExtensionDeclarationEnd node,
+      Token startInclusive, Token endInclusive) {
+    DirectParserASTContentExtensionDeclarationBegin begin =
+        node.children!.first as DirectParserASTContentExtensionDeclarationBegin;
+    DirectParserASTContentTopLevelDeclarationEnd parent =
+        node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
+    log("Hello from extension ${begin.name}");
+    Extension extension =
+        new Extension(parent, begin.name?.lexeme, startInclusive, endInclusive);
+    currentContainer.addChild(extension, map);
+
+    Container previousContainer = currentContainer;
+    currentContainer = extension;
+    super.visitExtension(node, startInclusive, endInclusive);
+    currentContainer = previousContainer;
+  }
+
+  @override
+  void visitExtensionConstructor(
+      DirectParserASTContentExtensionConstructorEnd node,
+      Token startInclusive,
+      Token endInclusive) {
+    // TODO: implement visitExtensionConstructor
+    throw node;
+  }
+
+  @override
+  void visitExtensionFactoryMethod(
+      DirectParserASTContentExtensionFactoryMethodEnd node,
+      Token startInclusive,
+      Token endInclusive) {
+    // TODO: implement visitExtensionFactoryMethod
+    throw node;
+  }
+
+  @override
+  void visitExtensionFields(DirectParserASTContentExtensionFieldsEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Extension);
+    List<String> fields =
+        node.getFieldIdentifiers().map((e) => e.token.lexeme).toList();
+    ExtensionFields classFields =
+        new ExtensionFields(node, fields, startInclusive, endInclusive);
+    currentContainer.addChild(classFields, map);
+    log("Hello from extension fields ${fields.join(", ")}");
+    super.visitExtensionFields(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitExtensionMethod(DirectParserASTContentExtensionMethodEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Extension);
+    ExtensionMethod extensionMethod = new ExtensionMethod(
+        node, node.getNameIdentifier(), startInclusive, endInclusive);
+    currentContainer.addChild(extensionMethod, map);
+    log("Hello from extension method ${node.getNameIdentifier()}");
+    super.visitExtensionMethod(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitImport(DirectParserASTContentImportEnd node, Token startInclusive,
+      Token? endInclusive) {
+    DirectParserASTContentIdentifierHandle? prefix = node.getImportPrefix();
+    String uriString = node.getImportUriString();
+    Uri importUri = uri.resolve(uriString);
+    List<String>? conditionalUriStrings = node.getConditionalImportUriStrings();
+    List<Uri>? conditionalUris;
+    if (conditionalUriStrings != null) {
+      conditionalUris = [];
+      for (String conditionalUri in conditionalUriStrings) {
+        conditionalUris.add(uri.resolve(conditionalUri));
+      }
+    }
+    // TODO: Use 'show' and 'hide' stuff.
+
+    // endInclusive can be null on syntax errors and there's recovery of the
+    // import. For now we'll ignore this.
+    Import i = new Import(node, importUri, conditionalUris,
+        prefix?.token.lexeme, startInclusive, endInclusive!);
+    currentContainer.addChild(i, map);
+    if (prefix == null) {
+      log("Hello import");
+    } else {
+      log("Hello import as '${prefix.token}'");
+    }
+  }
+
+  @override
+  void visitLibraryName(DirectParserASTContentLibraryNameEnd node,
+      Token startInclusive, Token endInclusive) {
+    LibraryName name = new LibraryName(node, startInclusive, endInclusive);
+    name.marked = Coloring.Marked;
+    currentContainer.addChild(name, map);
+  }
+
+  @override
+  void visitMetadata(DirectParserASTContentMetadataEnd node,
+      Token startInclusive, Token endInclusive) {
+    Metadata m = new Metadata(node, startInclusive, endInclusive);
+    currentContainer.addChild(m, map);
+  }
+
+  @override
+  void visitMixin(DirectParserASTContentMixinDeclarationEnd node,
+      Token startInclusive, Token endInclusive) {
+    DirectParserASTContentTopLevelDeclarationEnd parent =
+        node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
+    DirectParserASTContentIdentifierHandle identifier = parent.getIdentifier();
+    log("Hello from mixin ${identifier.token}");
+
+    Mixin mixin = new Mixin(
+        parent, identifier.token.lexeme, startInclusive, endInclusive);
+    currentContainer.addChild(mixin, map);
+
+    Container previousContainer = currentContainer;
+    currentContainer = mixin;
+    super.visitMixin(node, startInclusive, endInclusive);
+    currentContainer = previousContainer;
+  }
+
+  @override
+  void visitMixinFields(DirectParserASTContentMixinFieldsEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Mixin);
+    List<String> fields =
+        node.getFieldIdentifiers().map((e) => e.token.lexeme).toList();
+    MixinFields mixinFields =
+        new MixinFields(node, fields, startInclusive, endInclusive);
+    currentContainer.addChild(mixinFields, map);
+    log("Hello from mixin fields ${fields.join(", ")}");
+    super.visitMixinFields(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitMixinMethod(DirectParserASTContentMixinMethodEnd node,
+      Token startInclusive, Token endInclusive) {
+    assert(currentContainer is Mixin);
+    MixinMethod classMethod = new MixinMethod(
+        node, node.getNameIdentifier(), startInclusive, endInclusive);
+    currentContainer.addChild(classMethod, map);
+    log("Hello from mixin method ${node.getNameIdentifier()}");
+    super.visitMixinMethod(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitNamedMixin(DirectParserASTContentNamedMixinApplicationEnd node,
+      Token startInclusive, Token endInclusive) {
+    DirectParserASTContentTopLevelDeclarationEnd parent =
+        node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
+    DirectParserASTContentIdentifierHandle identifier = parent.getIdentifier();
+    log("Hello from named mixin ${identifier.token}");
+
+    Mixin mixin = new Mixin(
+        parent, identifier.token.lexeme, startInclusive, endInclusive);
+    currentContainer.addChild(mixin, map);
+
+    Container previousContainer = currentContainer;
+    currentContainer = mixin;
+    super.visitNamedMixin(node, startInclusive, endInclusive);
+    currentContainer = previousContainer;
+  }
+
+  @override
+  void visitPart(DirectParserASTContentPartEnd node, Token startInclusive,
+      Token endInclusive) {
+    String uriString = node.getPartUriString();
+    Uri partUri = uri.resolve(uriString);
+
+    Part i = new Part(node, partUri, startInclusive, endInclusive);
+    currentContainer.addChild(i, map);
+    log("Hello part");
+  }
+
+  @override
+  void visitPartOf(DirectParserASTContentPartOfEnd node, Token startInclusive,
+      Token endInclusive) {
+    // We'll assume we've gotten here via a "part" so we'll ignore that for now.
+    // TODO: partOfUri could - in an error case - be null.
+    PartOf partof = new PartOf(node, partOfUri!, startInclusive, endInclusive);
+    partof.marked = Coloring.Marked;
+    currentContainer.addChild(partof, map);
+  }
+
+  @override
+  void visitTopLevelFields(DirectParserASTContentTopLevelFieldsEnd node,
+      Token startInclusive, Token endInclusive) {
+    List<String> fields =
+        node.getFieldIdentifiers().map((e) => e.token.lexeme).toList();
+    TopLevelFields f =
+        new TopLevelFields(node, fields, startInclusive, endInclusive);
+    currentContainer.addChild(f, map);
+    log("Hello from top level fields ${fields.join(", ")}");
+    super.visitTopLevelFields(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitTopLevelMethod(DirectParserASTContentTopLevelMethodEnd node,
+      Token startInclusive, Token endInclusive) {
+    TopLevelMethod m = new TopLevelMethod(node,
+        node.getNameIdentifier().token.lexeme, startInclusive, endInclusive);
+    currentContainer.addChild(m, map);
+    log("Hello from top level method ${node.getNameIdentifier().token}");
+    super.visitTopLevelMethod(node, startInclusive, endInclusive);
+  }
+
+  @override
+  void visitTypedef(DirectParserASTContentTypedefEnd node, Token startInclusive,
+      Token endInclusive) {
+    Typedef t = new Typedef(node, node.getNameIdentifier().token.lexeme,
+        startInclusive, endInclusive);
+    currentContainer.addChild(t, map);
+    log("Hello from typedef ${node.getNameIdentifier().token}");
+    super.visitTypedef(node, startInclusive, endInclusive);
+  }
+}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/a.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/a.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/a.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/a2.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/a2.dart
new file mode 100644
index 0000000..8c26988
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/a2.dart
@@ -0,0 +1 @@
+class Foo2 {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/b.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/b.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/b.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/b2.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/b2.dart
new file mode 100644
index 0000000..8c26988
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/b2.dart
@@ -0,0 +1 @@
+class Foo2 {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/c.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/c.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/c.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/c2.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/c2.dart
new file mode 100644
index 0000000..8c26988
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/c2.dart
@@ -0,0 +1 @@
+class Foo2 {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/main.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/main.dart
new file mode 100644
index 0000000..bc74a47
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/main.dart
@@ -0,0 +1,8 @@
+import 'a.dart' if (dart.library.html) 'b.dart' if (dart.library.io) 'c.dart';
+export 'a2.dart'
+    if (dart.library.html) 'b2.dart'
+    if (dart.library.io) 'c2.dart';
+
+Foo x() {
+  return new Foo();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/main.dart.outline_extracted
new file mode 100644
index 0000000..a516b91
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports/main.dart.outline_extracted
@@ -0,0 +1,40 @@
+org-dartlang-testcase:///main.dart:
+import 'a.dart' if (dart.library.html) 'b.dart' if (dart.library.io) 'c.dart';
+export 'a2.dart' if (dart.library.html) 'b2.dart' if (dart.library.io) 'c2.dart';
+Foo x() {}
+
+
+
+
+org-dartlang-testcase:///a2.dart:
+class Foo2 {}
+
+
+
+
+org-dartlang-testcase:///b2.dart:
+class Foo2 {}
+
+
+
+
+org-dartlang-testcase:///c2.dart:
+class Foo2 {}
+
+
+
+
+org-dartlang-testcase:///a.dart:
+class Foo {}
+
+
+
+
+org-dartlang-testcase:///b.dart:
+class Foo {}
+
+
+
+
+org-dartlang-testcase:///c.dart:
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_export_01/main.dart b/pkg/front_end/outline_extraction_testcases/exports_export_01/main.dart
new file mode 100644
index 0000000..997da89
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_export_01/main.dart
@@ -0,0 +1,3 @@
+import "test8.dart";
+
+ClassFromImportsExportsExport? zyx____xyz;
diff --git a/pkg/front_end/outline_extraction_testcases/exports_export_01/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/exports_export_01/main.dart.outline_extracted
new file mode 100644
index 0000000..1f5cc58
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_export_01/main.dart.outline_extracted
@@ -0,0 +1,22 @@
+org-dartlang-testcase:///main.dart:
+import "test8.dart";
+ClassFromImportsExportsExport? zyx____xyz;
+
+
+
+
+org-dartlang-testcase:///test8.dart:
+export "test9.dart";
+
+
+
+
+org-dartlang-testcase:///test9.dart:
+export "test10.dart";
+
+
+
+
+org-dartlang-testcase:///test10.dart:
+export "dart:async";
+class ClassFromImportsExportsExport {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_export_01/test10.dart b/pkg/front_end/outline_extraction_testcases/exports_export_01/test10.dart
new file mode 100644
index 0000000..8513138
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_export_01/test10.dart
@@ -0,0 +1,5 @@
+export "dart:async";
+
+void test10Method() {}
+
+class ClassFromImportsExportsExport {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_export_01/test8.dart b/pkg/front_end/outline_extraction_testcases/exports_export_01/test8.dart
new file mode 100644
index 0000000..c83a90a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_export_01/test8.dart
@@ -0,0 +1,3 @@
+export "test9.dart";
+
+void test8Method() {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_export_01/test9.dart b/pkg/front_end/outline_extraction_testcases/exports_export_01/test9.dart
new file mode 100644
index 0000000..d0fcb31
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_export_01/test9.dart
@@ -0,0 +1,3 @@
+export "test10.dart";
+
+void test9Method() {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_included/main.dart b/pkg/front_end/outline_extraction_testcases/exports_included/main.dart
new file mode 100644
index 0000000..52091d7
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_included/main.dart
@@ -0,0 +1 @@
+export "test6.dart";
diff --git a/pkg/front_end/outline_extraction_testcases/exports_included/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/exports_included/main.dart.outline_extracted
new file mode 100644
index 0000000..c2b824d
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_included/main.dart.outline_extracted
@@ -0,0 +1,15 @@
+org-dartlang-testcase:///main.dart:
+export "test6.dart";
+
+
+
+
+org-dartlang-testcase:///test6.dart:
+export "test7.dart";
+void test6() {}
+
+
+
+
+org-dartlang-testcase:///test7.dart:
+void test7() {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_included/test6.dart b/pkg/front_end/outline_extraction_testcases/exports_included/test6.dart
new file mode 100644
index 0000000..c130634
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_included/test6.dart
@@ -0,0 +1,3 @@
+export "test7.dart";
+
+void test6() {}
diff --git a/pkg/front_end/outline_extraction_testcases/exports_included/test7.dart b/pkg/front_end/outline_extraction_testcases/exports_included/test7.dart
new file mode 100644
index 0000000..5b3e78d
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/exports_included/test7.dart
@@ -0,0 +1 @@
+void test7() {}
diff --git a/pkg/front_end/outline_extraction_testcases/extends/foo.dart b/pkg/front_end/outline_extraction_testcases/extends/foo.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/extends/foo.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/extends/main.dart b/pkg/front_end/outline_extraction_testcases/extends/main.dart
new file mode 100644
index 0000000..a126b2a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/extends/main.dart
@@ -0,0 +1,3 @@
+import "foo.dart";
+
+class X extends Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/extends/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/extends/main.dart.outline_extracted
new file mode 100644
index 0000000..09b7413
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/extends/main.dart.outline_extracted
@@ -0,0 +1,9 @@
+org-dartlang-testcase:///main.dart:
+import "foo.dart";
+class X extends Foo {}
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/factories/main.dart b/pkg/front_end/outline_extraction_testcases/factories/main.dart
new file mode 100644
index 0000000..316b9c5
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/factories/main.dart
@@ -0,0 +1,12 @@
+import 'package:foo/test11.dart';
+
+class Abc {
+  Abc() {}
+  factory Abc.a() {
+    return Abc2();
+  }
+  // Abc3 currently gets in --- it doesn't have to.
+  factory Abc.b() => Abc3();
+  var v1 = Abc4();
+  var v2 = new Abc5();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/factories/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/factories/main.dart.outline_extracted
new file mode 100644
index 0000000..4127b46
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/factories/main.dart.outline_extracted
@@ -0,0 +1,24 @@
+org-dartlang-testcase:///main.dart:
+import 'package:foo/test11.dart';
+class Abc {
+  Abc() {}
+  factory Abc.a() {}
+  factory Abc.b() => Abc3();
+  var v1 = Abc4();
+  var v2 = new Abc5();
+}
+
+
+
+
+org-dartlang-testcase:///test11.dart:
+import 'package:foo/main.dart';
+class Abc3 extends Abc {
+  Abc3() {}
+}
+class Abc4 extends Abc {
+  Abc4() {}
+}
+class Abc5 extends Abc {
+  Abc5() {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/factories/test11.dart b/pkg/front_end/outline_extraction_testcases/factories/test11.dart
new file mode 100644
index 0000000..105387c
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/factories/test11.dart
@@ -0,0 +1,21 @@
+import 'package:foo/main.dart';
+
+class Abc2 extends Abc {
+  Abc2() {}
+}
+
+class Abc3 extends Abc {
+  Abc3() {}
+}
+
+class Abc4 extends Abc {
+  Abc4() {}
+}
+
+class Abc5 extends Abc {
+  Abc5() {}
+}
+
+class Abc6 extends Abc {
+  Abc6() {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/field_dotting_in/b.dart b/pkg/front_end/outline_extraction_testcases/field_dotting_in/b.dart
new file mode 100644
index 0000000..112fdbc
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/field_dotting_in/b.dart
@@ -0,0 +1,9 @@
+import "c.dart";
+
+class B {
+  C c() {
+    return new C();
+  }
+}
+
+class BPrime {}
diff --git a/pkg/front_end/outline_extraction_testcases/field_dotting_in/c.dart b/pkg/front_end/outline_extraction_testcases/field_dotting_in/c.dart
new file mode 100644
index 0000000..356bd56
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/field_dotting_in/c.dart
@@ -0,0 +1,9 @@
+import "d.dart";
+
+class C {
+  D d() {
+    return new D();
+  }
+}
+
+class CPrime {}
diff --git a/pkg/front_end/outline_extraction_testcases/field_dotting_in/d.dart b/pkg/front_end/outline_extraction_testcases/field_dotting_in/d.dart
new file mode 100644
index 0000000..8655cd4
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/field_dotting_in/d.dart
@@ -0,0 +1,7 @@
+class D {
+  String d() {
+    return "hello";
+  }
+}
+
+class DPrime {}
diff --git a/pkg/front_end/outline_extraction_testcases/field_dotting_in/main.dart b/pkg/front_end/outline_extraction_testcases/field_dotting_in/main.dart
new file mode 100644
index 0000000..3d53be6
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/field_dotting_in/main.dart
@@ -0,0 +1,9 @@
+import "b.dart";
+
+var x = A.b().c().d;
+
+class A {
+  static B b() {
+    return new B();
+  }
+}
diff --git a/pkg/front_end/outline_extraction_testcases/field_dotting_in/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/field_dotting_in/main.dart.outline_extracted
new file mode 100644
index 0000000..e9a8f51
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/field_dotting_in/main.dart.outline_extracted
@@ -0,0 +1,32 @@
+org-dartlang-testcase:///main.dart:
+import "b.dart";
+var x = A.b().c().d;
+class A {
+  static B b() {}
+}
+
+
+
+
+org-dartlang-testcase:///b.dart:
+import "c.dart";
+class B {
+  C c() {}
+}
+
+
+
+
+org-dartlang-testcase:///c.dart:
+import "d.dart";
+class C {
+  D d() {}
+}
+
+
+
+
+org-dartlang-testcase:///d.dart:
+class D {
+  String d() {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/fields/bar.dart b/pkg/front_end/outline_extraction_testcases/fields/bar.dart
new file mode 100644
index 0000000..79f980a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/fields/bar.dart
@@ -0,0 +1,11 @@
+class Bar {}
+
+class Bar2 {}
+
+class Bar3 {}
+
+class Bar4 {}
+
+class Foo3 {}
+
+class Foo4 {}
diff --git a/pkg/front_end/outline_extraction_testcases/fields/foo.dart b/pkg/front_end/outline_extraction_testcases/fields/foo.dart
new file mode 100644
index 0000000..43434c3
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/fields/foo.dart
@@ -0,0 +1,13 @@
+export "bar.dart";
+
+class Baz {}
+
+class Baz2 {}
+
+class Baz3 {}
+
+class Baz4 {}
+
+class Foo {}
+
+class Foo2 {}
diff --git a/pkg/front_end/outline_extraction_testcases/fields/main.dart b/pkg/front_end/outline_extraction_testcases/fields/main.dart
new file mode 100644
index 0000000..bf52fd9
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/fields/main.dart
@@ -0,0 +1,19 @@
+import "foo.dart";
+
+class A {
+  Bar field = new Bar();
+  Bar2 field2 = new Bar2();
+  var field3 = new Bar3(), field4 = new Bar4();
+}
+
+mixin A2 {
+  Baz field = new Baz();
+  Baz2 field2 = new Baz2();
+  var field3 = new Baz3(), field4 = new Baz4();
+}
+
+extension A3 on Object {
+  static Foo field = new Foo();
+  static Foo2 field2 = new Foo2();
+  static var field3 = new Foo3(), field4 = new Foo4();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/fields/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/fields/main.dart.outline_extracted
new file mode 100644
index 0000000..13e239a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/fields/main.dart.outline_extracted
@@ -0,0 +1,40 @@
+org-dartlang-testcase:///main.dart:
+import "foo.dart";
+class A {
+  Bar field = new Bar();
+  Bar2 field2 = new Bar2();
+  var field3 = new Bar3(), field4 = new Bar4();
+}
+mixin A2 {
+  Baz field = new Baz();
+  Baz2 field2 = new Baz2();
+  var field3 = new Baz3(), field4 = new Baz4();
+}
+extension A3 on Object {
+  static Foo field = new Foo();
+  static Foo2 field2 = new Foo2();
+  static var field3 = new Foo3(), field4 = new Foo4();
+}
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+export "bar.dart";
+class Baz {}
+class Baz2 {}
+class Baz3 {}
+class Baz4 {}
+class Foo {}
+class Foo2 {}
+
+
+
+
+org-dartlang-testcase:///bar.dart:
+class Bar {}
+class Bar2 {}
+class Bar3 {}
+class Bar4 {}
+class Foo3 {}
+class Foo4 {}
diff --git a/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/main.dart b/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/main.dart
new file mode 100644
index 0000000..3cfbb90
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/main.dart
@@ -0,0 +1,6 @@
+import "test16.dart" as test16;
+
+class Test16ClassHelper {
+  // the naming matching is what makes it annoying!
+  final test16toplevel = test16.test16toplevel("hello");
+}
diff --git a/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/main.dart.outline_extracted
new file mode 100644
index 0000000..e0a42eb
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/main.dart.outline_extracted
@@ -0,0 +1,11 @@
+org-dartlang-testcase:///main.dart:
+import "test16.dart" as test16;
+class Test16ClassHelper {
+  final test16toplevel = test16.test16toplevel("hello");
+}
+
+
+
+
+org-dartlang-testcase:///test16.dart:
+String test16toplevel(String s) {}
diff --git a/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/test16.dart b/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/test16.dart
new file mode 100644
index 0000000..41d882e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_prefix_overlap_with_field/test16.dart
@@ -0,0 +1,3 @@
+String test16toplevel(String s) {
+  return s * 2;
+}
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix/bar.dart b/pkg/front_end/outline_extraction_testcases/import_with_prefix/bar.dart
new file mode 100644
index 0000000..91b2144
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix/bar.dart
@@ -0,0 +1,5 @@
+int bar() {
+  return 42;
+}
+
+class Baz {}
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix/foo.dart b/pkg/front_end/outline_extraction_testcases/import_with_prefix/foo.dart
new file mode 100644
index 0000000..0ec48dd
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix/foo.dart
@@ -0,0 +1,5 @@
+String bar() {
+  return "hello";
+}
+
+class Baz {}
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix/main.dart b/pkg/front_end/outline_extraction_testcases/import_with_prefix/main.dart
new file mode 100644
index 0000000..aef942d
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix/main.dart
@@ -0,0 +1,7 @@
+import 'foo.dart' as foo;
+// This import isn't used --- foo.bar below explicitly wants bar from foo.
+import 'bar.dart';
+
+var x = foo.bar();
+
+foo.Baz? baz;
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/import_with_prefix/main.dart.outline_extracted
new file mode 100644
index 0000000..1c804cb
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix/main.dart.outline_extracted
@@ -0,0 +1,17 @@
+org-dartlang-testcase:///main.dart:
+import 'foo.dart' as foo;
+import 'bar.dart';
+var x = foo.bar();
+foo.Baz? baz;
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+String bar() {}
+class Baz {}
+
+
+
+
+org-dartlang-testcase:///bar.dart:
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/a.dart b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/a.dart
new file mode 100644
index 0000000..ff840ec
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/a.dart
@@ -0,0 +1,3 @@
+String foo() {
+  return "foo";
+}
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/b.dart b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/b.dart
new file mode 100644
index 0000000..07f55de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/b.dart
@@ -0,0 +1,3 @@
+String bar() {
+  return "bar";
+}
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/main.dart b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/main.dart
new file mode 100644
index 0000000..73d125a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/main.dart
@@ -0,0 +1,5 @@
+import 'a.dart' as x;
+import 'b.dart' as x;
+
+var foo = x.foo();
+var bar = x.bar();
diff --git a/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/main.dart.outline_extracted
new file mode 100644
index 0000000..166189c
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/import_with_prefix_02/main.dart.outline_extracted
@@ -0,0 +1,17 @@
+org-dartlang-testcase:///main.dart:
+import 'a.dart' as x;
+import 'b.dart' as x;
+var foo = x.foo();
+var bar = x.bar();
+
+
+
+
+org-dartlang-testcase:///a.dart:
+String foo() {}
+
+
+
+
+org-dartlang-testcase:///b.dart:
+String bar() {}
diff --git a/pkg/front_end/outline_extraction_testcases/initial_various/main.dart b/pkg/front_end/outline_extraction_testcases/initial_various/main.dart
new file mode 100644
index 0000000..2ff0638
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/initial_various/main.dart
@@ -0,0 +1,28 @@
+import "main.dart" as self;
+import "test3.dart";
+
+class Foo<E> extends Bar<int> with Qux1<int> implements Baz<Bar<int>> {
+  Foo<E>? parent;
+
+  Foo() {}
+  Foo.bar() {}
+  F? fooMethod1<F>() {
+    print(foo);
+    print(F);
+    print(x.A);
+  }
+
+  E? fooMethod2() {
+    print(E);
+    print(x.A);
+  }
+
+  self.Foo? fooMethod3() {
+    print(E);
+    print(x.A);
+  }
+
+  x fooMethod4() {
+    return x.A;
+  }
+}
diff --git a/pkg/front_end/outline_extraction_testcases/initial_various/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/initial_various/main.dart.outline_extracted
new file mode 100644
index 0000000..3426756
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/initial_various/main.dart.outline_extracted
@@ -0,0 +1,24 @@
+org-dartlang-testcase:///main.dart:
+import "main.dart" as self;
+import "test3.dart";
+class Foo<E> extends Bar<int> with Qux1<int> implements Baz<Bar<int>> {
+  Foo<E>? parent;
+  Foo() {}
+  Foo.bar() {}
+  F? fooMethod1<F>() {}
+  E? fooMethod2() {}
+  self.Foo? fooMethod3() {}
+  x fooMethod4() {}
+}
+
+
+
+
+org-dartlang-testcase:///test3.dart:
+class Bar<E> {}
+class Baz<E> {}
+class Qux1<E> {
+  Qux1AndAHalf? qux1AndAHalf() {}
+}
+class Qux1AndAHalf<E> {}
+enum x { A, B, C }
diff --git a/pkg/front_end/outline_extraction_testcases/initial_various/test3.dart b/pkg/front_end/outline_extraction_testcases/initial_various/test3.dart
new file mode 100644
index 0000000..530644b
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/initial_various/test3.dart
@@ -0,0 +1,25 @@
+import "test4.dart";
+
+class Bar<E> {}
+
+class Baz<E> {}
+
+class Qux1<E> {
+  Qux1AndAHalf? qux1AndAHalf() {
+    // nothing...
+  }
+}
+
+class Qux1AndAHalf<E> {}
+
+class Qux2<E> {
+  Qux3? foo() {}
+}
+
+enum x { A, B, C }
+
+int foo() {
+  return 42;
+}
+
+int foo2 = foo() * 2, foo3 = foo() * 3;
diff --git a/pkg/front_end/outline_extraction_testcases/initial_various/test4.dart b/pkg/front_end/outline_extraction_testcases/initial_various/test4.dart
new file mode 100644
index 0000000..3215ebf
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/initial_various/test4.dart
@@ -0,0 +1,8 @@
+import "test5.dart";
+export "test5.dart";
+
+class Qux3<E> {
+  Qux4? foo() {}
+}
+
+class Qux4<E> {}
diff --git a/pkg/front_end/outline_extraction_testcases/initial_various/test5.dart b/pkg/front_end/outline_extraction_testcases/initial_various/test5.dart
new file mode 100644
index 0000000..0315739
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/initial_various/test5.dart
@@ -0,0 +1,5 @@
+class Qux3x<E> {
+  Qux4x? foo() {}
+}
+
+class Qux4x<E> {}
diff --git a/pkg/front_end/outline_extraction_testcases/keeps_dart_version/bar.dart b/pkg/front_end/outline_extraction_testcases/keeps_dart_version/bar.dart
new file mode 100644
index 0000000..62e4db2
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/keeps_dart_version/bar.dart
@@ -0,0 +1,5 @@
+// @dart = 2.12
+
+class Bar {}
+
+class Baz {}
diff --git a/pkg/front_end/outline_extraction_testcases/keeps_dart_version/main.dart b/pkg/front_end/outline_extraction_testcases/keeps_dart_version/main.dart
new file mode 100644
index 0000000..f20e3af
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/keeps_dart_version/main.dart
@@ -0,0 +1,3 @@
+import "bar.dart";
+
+void foo(Bar bar) {}
diff --git a/pkg/front_end/outline_extraction_testcases/keeps_dart_version/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/keeps_dart_version/main.dart.outline_extracted
new file mode 100644
index 0000000..9a0da03
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/keeps_dart_version/main.dart.outline_extracted
@@ -0,0 +1,10 @@
+org-dartlang-testcase:///main.dart:
+import "bar.dart";
+void foo(Bar bar) {}
+
+
+
+
+org-dartlang-testcase:///bar.dart:
+// @dart = 2.12
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_01/a.dart b/pkg/front_end/outline_extraction_testcases/metadata_01/a.dart
new file mode 100644
index 0000000..e7267c5
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_01/a.dart
@@ -0,0 +1 @@
+class AUnused {}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_01/b.dart b/pkg/front_end/outline_extraction_testcases/metadata_01/b.dart
new file mode 100644
index 0000000..8b42bda
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_01/b.dart
@@ -0,0 +1,9 @@
+const AbcX = const _AbcX();
+
+class _AbcX {
+  const _AbcX();
+}
+
+class AbcX2 {
+  const AbcX2();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_01/main.dart b/pkg/front_end/outline_extraction_testcases/metadata_01/main.dart
new file mode 100644
index 0000000..a55d3df
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_01/main.dart
@@ -0,0 +1,5 @@
+import "a.dart";
+import "b.dart";
+
+@AbcX
+void foo() {}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_01/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/metadata_01/main.dart.outline_extracted
new file mode 100644
index 0000000..6d8ae63
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_01/main.dart.outline_extracted
@@ -0,0 +1,19 @@
+org-dartlang-testcase:///main.dart:
+import "a.dart";
+import "b.dart";
+@AbcX
+void foo() {}
+
+
+
+
+org-dartlang-testcase:///a.dart:
+
+
+
+
+org-dartlang-testcase:///b.dart:
+const AbcX = const _AbcX();
+class _AbcX {
+  const _AbcX();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_02/main.dart b/pkg/front_end/outline_extraction_testcases/metadata_02/main.dart
new file mode 100644
index 0000000..851e231
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_02/main.dart
@@ -0,0 +1,5 @@
+import "test15.dart" as $test15;
+import "nottest15.dart";
+
+@$test15.Test15()
+void test15thing() {}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_02/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/metadata_02/main.dart.outline_extracted
new file mode 100644
index 0000000..123667f
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_02/main.dart.outline_extracted
@@ -0,0 +1,18 @@
+org-dartlang-testcase:///main.dart:
+import "test15.dart" as $test15;
+import "nottest15.dart";
+@$test15.Test15()
+void test15thing() {}
+
+
+
+
+org-dartlang-testcase:///test15.dart:
+class Test15 {
+  const Test15();
+}
+
+
+
+
+org-dartlang-testcase:///nottest15.dart:
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_02/nottest15.dart b/pkg/front_end/outline_extraction_testcases/metadata_02/nottest15.dart
new file mode 100644
index 0000000..41594d9
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_02/nottest15.dart
@@ -0,0 +1,7 @@
+class Test15 {
+  const Test15();
+}
+
+class Test16 {
+  const Test16();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/metadata_02/test15.dart b/pkg/front_end/outline_extraction_testcases/metadata_02/test15.dart
new file mode 100644
index 0000000..41594d9
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/metadata_02/test15.dart
@@ -0,0 +1,7 @@
+class Test15 {
+  const Test15();
+}
+
+class Test16 {
+  const Test16();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/a.dart b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/a.dart
new file mode 100644
index 0000000..bde7250
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/a.dart
@@ -0,0 +1,5 @@
+library foo.a;
+
+export 'b.dart';
+export 'c.dart';
+export 'd.dart';
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/b.dart b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/b.dart
new file mode 100644
index 0000000..3a43d09
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/b.dart
@@ -0,0 +1 @@
+class B {}
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/c.dart b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/c.dart
new file mode 100644
index 0000000..ed37d13
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/c.dart
@@ -0,0 +1,7 @@
+class C<T> {
+  const C.b();
+}
+
+class C2<T> {
+  const C2.b();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/d.dart b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/d.dart
new file mode 100644
index 0000000..40416ad
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/d.dart
@@ -0,0 +1 @@
+class D {}
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/main.dart b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/main.dart
new file mode 100644
index 0000000..afbc02d
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/main.dart
@@ -0,0 +1,6 @@
+import 'package:foo/a.dart' as foo;
+
+class A {
+  var c1 = foo.C<A>.b();
+  var c2 = new foo.C2<A>.b();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/main.dart.outline_extracted
new file mode 100644
index 0000000..c1a663e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/main.dart.outline_extracted
@@ -0,0 +1,36 @@
+org-dartlang-testcase:///main.dart:
+import 'package:foo/a.dart' as foo;
+class A {
+  var c1 = foo.C<A>.b();
+  var c2 = new foo.C2<A>.b();
+}
+
+
+
+
+org-dartlang-testcase:///a.dart:
+library foo.a;
+export 'b.dart';
+export 'c.dart';
+export 'd.dart';
+
+
+
+
+org-dartlang-testcase:///b.dart:
+
+
+
+
+org-dartlang-testcase:///c.dart:
+class C<T> {
+  const C.b();
+}
+class C2<T> {
+  const C2.b();
+}
+
+
+
+
+org-dartlang-testcase:///d.dart:
diff --git a/pkg/front_end/outline_extraction_testcases/named_mixin/bar.dart b/pkg/front_end/outline_extraction_testcases/named_mixin/bar.dart
new file mode 100644
index 0000000..512eabb
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_mixin/bar.dart
@@ -0,0 +1,3 @@
+export "baz.dart";
+
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/named_mixin/baz.dart b/pkg/front_end/outline_extraction_testcases/named_mixin/baz.dart
new file mode 100644
index 0000000..f1e00d1
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_mixin/baz.dart
@@ -0,0 +1 @@
+class Baz {}
diff --git a/pkg/front_end/outline_extraction_testcases/named_mixin/main.dart b/pkg/front_end/outline_extraction_testcases/named_mixin/main.dart
new file mode 100644
index 0000000..4e0e374
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_mixin/main.dart
@@ -0,0 +1,3 @@
+import "bar.dart";
+
+class Foo = Object with Bar implements Baz;
diff --git a/pkg/front_end/outline_extraction_testcases/named_mixin/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/named_mixin/main.dart.outline_extracted
new file mode 100644
index 0000000..5ff64b3
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_mixin/main.dart.outline_extracted
@@ -0,0 +1,16 @@
+org-dartlang-testcase:///main.dart:
+import "bar.dart";
+class Foo = Object with Bar implements Baz;
+
+
+
+
+org-dartlang-testcase:///bar.dart:
+export "baz.dart";
+class Bar {}
+
+
+
+
+org-dartlang-testcase:///baz.dart:
+class Baz {}
diff --git a/pkg/front_end/outline_extraction_testcases/outline_extractor.status b/pkg/front_end/outline_extraction_testcases/outline_extractor.status
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/outline_extractor.status
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/main.dart b/pkg/front_end/outline_extraction_testcases/part_01/main.dart
new file mode 100644
index 0000000..1d7ff4e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/main.dart
@@ -0,0 +1,4 @@
+import "test12.dart";
+
+void test12part1usage(Test12Part1 x) {}
+void secondtest12part1usage(SecondTest12 x) {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/part_01/main.dart.outline_extracted
new file mode 100644
index 0000000..ee384f7
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/main.dart.outline_extracted
@@ -0,0 +1,47 @@
+org-dartlang-testcase:///main.dart:
+import "test12.dart";
+void test12part1usage(Test12Part1 x) {}
+void secondtest12part1usage(SecondTest12 x) {}
+
+
+
+
+org-dartlang-testcase:///test12.dart:
+import "test13.dart";
+import "test14.dart";
+part 'test12_part1.dart';
+part 'test12_part2.dart';
+class Test12 {}
+class SecondTest12 {
+  void foo(SecondTest12Part1 x) {}
+}
+
+
+
+
+org-dartlang-testcase:///test12_part1.dart:
+part of "test12.dart";
+class Test12Part1 {
+  void foo(Test12 x) {}
+  void bar(Test12Part2 x) {}
+  void baz(Test13 x) {}
+}
+class SecondTest12Part1 {}
+
+
+
+
+org-dartlang-testcase:///test12_part2.dart:
+part of "test12.dart";
+class Test12Part2 {}
+
+
+
+
+org-dartlang-testcase:///test13.dart:
+class Test13 {}
+
+
+
+
+org-dartlang-testcase:///test14.dart:
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/test12.dart b/pkg/front_end/outline_extraction_testcases/part_01/test12.dart
new file mode 100644
index 0000000..07c621e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/test12.dart
@@ -0,0 +1,11 @@
+import "test13.dart";
+import "test14.dart";
+
+part 'test12_part1.dart';
+part 'test12_part2.dart';
+
+class Test12 {}
+
+class SecondTest12 {
+  void foo(SecondTest12Part1 x) {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/test12_part1.dart b/pkg/front_end/outline_extraction_testcases/part_01/test12_part1.dart
new file mode 100644
index 0000000..44473ff
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/test12_part1.dart
@@ -0,0 +1,9 @@
+part of "test12.dart";
+
+class Test12Part1 {
+  void foo(Test12 x) {}
+  void bar(Test12Part2 x) {}
+  void baz(Test13 x) {}
+}
+
+class SecondTest12Part1 {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/test12_part2.dart b/pkg/front_end/outline_extraction_testcases/part_01/test12_part2.dart
new file mode 100644
index 0000000..413d50d
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/test12_part2.dart
@@ -0,0 +1,3 @@
+part of "test12.dart";
+
+class Test12Part2 {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/test13.dart b/pkg/front_end/outline_extraction_testcases/part_01/test13.dart
new file mode 100644
index 0000000..caa3ed4
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/test13.dart
@@ -0,0 +1,3 @@
+import "test13andahalf.dart";
+
+class Test13 {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/test13andahalf.dart b/pkg/front_end/outline_extraction_testcases/part_01/test13andahalf.dart
new file mode 100644
index 0000000..16bc96b
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/test13andahalf.dart
@@ -0,0 +1 @@
+class Test13AndAHalf {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_01/test14.dart b/pkg/front_end/outline_extraction_testcases/part_01/test14.dart
new file mode 100644
index 0000000..023fefd
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_01/test14.dart
@@ -0,0 +1 @@
+class Test14 {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_and_library_name/main.dart b/pkg/front_end/outline_extraction_testcases/part_and_library_name/main.dart
new file mode 100644
index 0000000..d9163f5
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_and_library_name/main.dart
@@ -0,0 +1,3 @@
+import 'test3.dart';
+
+var x = test3partfoo();
diff --git a/pkg/front_end/outline_extraction_testcases/part_and_library_name/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/part_and_library_name/main.dart.outline_extracted
new file mode 100644
index 0000000..2d108c7
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_and_library_name/main.dart.outline_extracted
@@ -0,0 +1,17 @@
+org-dartlang-testcase:///main.dart:
+import 'test3.dart';
+var x = test3partfoo();
+
+
+
+
+org-dartlang-testcase:///test3.dart:
+library test3;
+part "test3_part.dart";
+
+
+
+
+org-dartlang-testcase:///test3_part.dart:
+part of test3;
+void test3partfoo() {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_and_library_name/test3.dart b/pkg/front_end/outline_extraction_testcases/part_and_library_name/test3.dart
new file mode 100644
index 0000000..8fdefdd
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_and_library_name/test3.dart
@@ -0,0 +1,5 @@
+library test3;
+
+part "test3_part.dart";
+
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/part_and_library_name/test3_part.dart b/pkg/front_end/outline_extraction_testcases/part_and_library_name/test3_part.dart
new file mode 100644
index 0000000..e6e3fc4
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/part_and_library_name/test3_part.dart
@@ -0,0 +1,5 @@
+part of test3;
+
+void test3partfoo() {}
+
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/split_import_export_part/foo.dart b/pkg/front_end/outline_extraction_testcases/split_import_export_part/foo.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/split_import_export_part/foo.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/split_import_export_part/main.dart b/pkg/front_end/outline_extraction_testcases/split_import_export_part/main.dart
new file mode 100644
index 0000000..85d6722
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/split_import_export_part/main.dart
@@ -0,0 +1,25 @@
+import "package:f"
+    "oobar"
+    "/"
+    "foo"
+    ".dart";
+
+export "package:f"
+    "oobar"
+    "/"
+    "foo"
+    ".dart";
+
+part "package:f"
+    "oobar"
+    "/"
+    "part"
+    ".dart";
+
+Foo giveFoo() {
+  return new Foo();
+}
+
+Bar giveBar() {
+  return new Bar();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/split_import_export_part/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/split_import_export_part/main.dart.outline_extracted
new file mode 100644
index 0000000..ed23c5a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/split_import_export_part/main.dart.outline_extracted
@@ -0,0 +1,19 @@
+org-dartlang-testcase:///main.dart:
+import "package:f" "oobar" "/" "foo" ".dart";
+export "package:f" "oobar" "/" "foo" ".dart";
+part "package:f" "oobar" "/" "part" ".dart";
+Foo giveFoo() {}
+Bar giveBar() {}
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+class Foo {}
+
+
+
+
+org-dartlang-testcase:///part.dart:
+part of "package:f" "oobar" "/" "main" ".dart";
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/split_import_export_part/part.dart b/pkg/front_end/outline_extraction_testcases/split_import_export_part/part.dart
new file mode 100644
index 0000000..009eb8e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/split_import_export_part/part.dart
@@ -0,0 +1,7 @@
+part of "package:f"
+    "oobar"
+    "/"
+    "main"
+    ".dart";
+
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/type_parameter_extends/bar.dart b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/bar.dart
new file mode 100644
index 0000000..3507a7f
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/bar.dart
@@ -0,0 +1,5 @@
+class Bar {}
+
+class Bar2 {}
+
+class Bar3 {}
diff --git a/pkg/front_end/outline_extraction_testcases/type_parameter_extends/foo.dart b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/foo.dart
new file mode 100644
index 0000000..820b17e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/foo.dart
@@ -0,0 +1,7 @@
+export "bar.dart";
+
+class Baz {}
+
+class Baz2 {}
+
+class Baz3 {}
diff --git a/pkg/front_end/outline_extraction_testcases/type_parameter_extends/main.dart b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/main.dart
new file mode 100644
index 0000000..73b5565
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/main.dart
@@ -0,0 +1,16 @@
+import "foo.dart";
+
+class A<T extends Bar, U extends Baz> {
+  T? aMethod1() {}
+  U? aMethod2() {}
+}
+
+mixin A2<T extends Bar2, U extends Baz2> {
+  T? aMethod1() {}
+  U? aMethod2() {}
+}
+
+extension A3<T extends Bar3, U extends Baz3> on Object {
+  T? aMethod1() {}
+  U? aMethod2() {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/type_parameter_extends/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/main.dart.outline_extracted
new file mode 100644
index 0000000..8b4f0c5
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/type_parameter_extends/main.dart.outline_extracted
@@ -0,0 +1,31 @@
+org-dartlang-testcase:///main.dart:
+import "foo.dart";
+class A<T extends Bar, U extends Baz> {
+  T? aMethod1() {}
+  U? aMethod2() {}
+}
+mixin A2<T extends Bar2, U extends Baz2> {
+  T? aMethod1() {}
+  U? aMethod2() {}
+}
+extension A3<T extends Bar3, U extends Baz3> on Object {
+  T? aMethod1() {}
+  U? aMethod2() {}
+}
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+export "bar.dart";
+class Baz {}
+class Baz2 {}
+class Baz3 {}
+
+
+
+
+org-dartlang-testcase:///bar.dart:
+class Bar {}
+class Bar2 {}
+class Bar3 {}
diff --git a/pkg/front_end/outline_extraction_testcases/type_parameter_on_extension/main.dart b/pkg/front_end/outline_extraction_testcases/type_parameter_on_extension/main.dart
new file mode 100644
index 0000000..5093237
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/type_parameter_on_extension/main.dart
@@ -0,0 +1,5 @@
+class Foo<E> {}
+
+extension HiExtension<T extends Foo> on T {
+  void sayHi() {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/type_parameter_on_extension/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/type_parameter_on_extension/main.dart.outline_extracted
new file mode 100644
index 0000000..c561270
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/type_parameter_on_extension/main.dart.outline_extracted
@@ -0,0 +1,5 @@
+org-dartlang-testcase:///main.dart:
+class Foo<E> {}
+extension HiExtension<T extends Foo> on T {
+  void sayHi() {}
+}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import/foo.dart b/pkg/front_end/outline_extraction_testcases/unused_import/foo.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import/foo.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import/main.dart b/pkg/front_end/outline_extraction_testcases/unused_import/main.dart
new file mode 100644
index 0000000..8713cd2
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import/main.dart
@@ -0,0 +1,6 @@
+import "foo.dart";
+
+void main() {
+  Foo foo = new Foo();
+  print(foo);
+}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/unused_import/main.dart.outline_extracted
new file mode 100644
index 0000000..aea942e
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import/main.dart.outline_extracted
@@ -0,0 +1,8 @@
+org-dartlang-testcase:///main.dart:
+import "foo.dart";
+void main() {}
+
+
+
+
+org-dartlang-testcase:///foo.dart:
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import_02/bar.dart b/pkg/front_end/outline_extraction_testcases/unused_import_02/bar.dart
new file mode 100644
index 0000000..5db1c42
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import_02/bar.dart
@@ -0,0 +1,8 @@
+import "baz.dart";
+
+class Bar {
+  void bar() {
+    Baz baz = new Baz();
+    print(baz);
+  }
+}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import_02/baz.dart b/pkg/front_end/outline_extraction_testcases/unused_import_02/baz.dart
new file mode 100644
index 0000000..f1e00d1
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import_02/baz.dart
@@ -0,0 +1 @@
+class Baz {}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import_02/foo.dart b/pkg/front_end/outline_extraction_testcases/unused_import_02/foo.dart
new file mode 100644
index 0000000..4e6a6de
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import_02/foo.dart
@@ -0,0 +1 @@
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import_02/main.dart b/pkg/front_end/outline_extraction_testcases/unused_import_02/main.dart
new file mode 100644
index 0000000..732d2c4
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import_02/main.dart
@@ -0,0 +1,8 @@
+import "dart:core";
+import "foo.dart";
+export "bar.dart";
+
+void main() {
+  Foo foo = new Foo();
+  print(foo);
+}
diff --git a/pkg/front_end/outline_extraction_testcases/unused_import_02/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/unused_import_02/main.dart.outline_extracted
new file mode 100644
index 0000000..5e0cec5
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/unused_import_02/main.dart.outline_extracted
@@ -0,0 +1,24 @@
+org-dartlang-testcase:///main.dart:
+import "dart:core";
+import "foo.dart";
+export "bar.dart";
+void main() {}
+
+
+
+
+org-dartlang-testcase:///bar.dart:
+import "baz.dart";
+class Bar {
+  void bar() {}
+}
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+
+
+
+
+org-dartlang-testcase:///baz.dart:
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/foo.dart b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/foo.dart
new file mode 100644
index 0000000..9a99089
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/foo.dart
@@ -0,0 +1,11 @@
+extension Foo on String {
+  int get giveInt => 42;
+}
+
+// The below doesn't have to be included.
+
+extension BarExtension on Bar {
+  int get giveInt => 42;
+}
+
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/main.dart b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/main.dart
new file mode 100644
index 0000000..1b39ff1
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/main.dart
@@ -0,0 +1,5 @@
+import "foo.dart";
+
+int get giveInt => 43;
+
+var x = "hello".giveInt;
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/main.dart.outline_extracted
new file mode 100644
index 0000000..cb49619
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension/main.dart.outline_extracted
@@ -0,0 +1,16 @@
+org-dartlang-testcase:///main.dart:
+import "foo.dart";
+int get giveInt => 43;
+var x = "hello".giveInt;
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+extension Foo on String {
+  int get giveInt => 42;
+}
+extension BarExtension on Bar {
+  int get giveInt => 42;
+}
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/foo.dart b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/foo.dart
new file mode 100644
index 0000000..bd036e7
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/foo.dart
@@ -0,0 +1,17 @@
+enum Foo {
+  a,
+  b,
+  c,
+  d,
+  e,
+}
+
+extension FooExtension on Foo {
+  int get giveInt => 42;
+}
+
+extension BarExtension on Bar {
+  int get giveInt => 42;
+}
+
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/main.dart b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/main.dart
new file mode 100644
index 0000000..5deb533
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/main.dart
@@ -0,0 +1,5 @@
+import "foo.dart";
+
+final foo = [Foo.d, Foo.b];
+
+final foo2 = foo.map((f) => f.giveInt).toList();
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/main.dart.outline_extracted
new file mode 100644
index 0000000..1b7506a
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_2/main.dart.outline_extracted
@@ -0,0 +1,17 @@
+org-dartlang-testcase:///main.dart:
+import "foo.dart";
+final foo = [Foo.d, Foo.b];
+final foo2 = foo.map((f) => f.giveInt).toList();
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+enum Foo { a, b, c, d, e, }
+extension FooExtension on Foo {
+  int get giveInt => 42;
+}
+extension BarExtension on Bar {
+  int get giveInt => 42;
+}
+class Bar {}
diff --git a/pkg/front_end/test/outline_extractor_suite.dart b/pkg/front_end/test/outline_extractor_suite.dart
new file mode 100644
index 0000000..f022e0c
--- /dev/null
+++ b/pkg/front_end/test/outline_extractor_suite.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert' show jsonDecode;
+
+import 'dart:io' show File;
+
+import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
+import 'package:front_end/src/fasta/util/outline_extractor.dart';
+import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
+import 'package:testing/testing.dart'
+    show
+        Chain,
+        ChainContext,
+        ExpectationSet,
+        Result,
+        Step,
+        TestDescription,
+        runMe;
+import 'package:kernel/src/equivalence.dart';
+import 'package:front_end/src/api_prototype/compiler_options.dart';
+import 'package:front_end/src/api_prototype/memory_file_system.dart';
+import 'package:kernel/ast.dart';
+
+import 'fasta/testing/suite.dart' show UPDATE_EXPECTATIONS;
+import 'utils/kernel_chain.dart' show MatchContext;
+
+import 'testing_utils.dart' show checkEnvironment;
+
+import 'incremental_suite.dart' as helper;
+
+const String EXPECTATIONS = '''
+[
+  {
+    "name": "ExpectationFileMismatch",
+    "group": "Fail"
+  },
+  {
+    "name": "ExpectationFileMissing",
+    "group": "Fail"
+  }
+]
+''';
+
+void main([List<String> arguments = const []]) =>
+    runMe(arguments, createContext, configurationPath: "../testing.json");
+
+Future<Context> createContext(
+    Chain suite, Map<String, String> environment) async {
+  const Set<String> knownEnvironmentKeys = {
+    "updateExpectations",
+  };
+  checkEnvironment(environment, knownEnvironmentKeys);
+
+  bool updateExpectations = environment["updateExpectations"] == "true";
+
+  return new Context(suite.name, updateExpectations);
+}
+
+class Context extends ChainContext with MatchContext {
+  @override
+  final bool updateExpectations;
+
+  @override
+  String get updateExpectationsOption => '${UPDATE_EXPECTATIONS}=true';
+
+  @override
+  bool get canBeFixWithUpdateExpectations => true;
+
+  final String suiteName;
+
+  Context(this.suiteName, this.updateExpectations);
+
+  @override
+  final List<Step> steps = const <Step>[
+    const OutlineExtractorStep(),
+    const CompileAndCompareStep(),
+  ];
+
+  @override
+  final ExpectationSet expectationSet =
+      new ExpectationSet.fromJsonList(jsonDecode(EXPECTATIONS));
+
+  // Override special handling of negative tests.
+  @override
+  Result processTestResult(
+      TestDescription description, Result result, bool last) {
+    return result;
+  }
+}
+
+class OutlineExtractorStep
+    extends Step<TestDescription, TestDescription, Context> {
+  const OutlineExtractorStep();
+
+  @override
+  String get name => "OutlineExtractorStep";
+
+  @override
+  Future<Result<TestDescription>> run(
+      TestDescription description, Context context) async {
+    Uri? packages = description.uri.resolve(".packages");
+    if (!new File.fromUri(packages).existsSync()) {
+      packages = null;
+    }
+    Map<Uri, String> result =
+        await extractOutline([description.uri], packages: packages);
+
+    StringBuffer sb = new StringBuffer();
+    Uri uri = description.uri;
+    Uri base = uri.resolve(".");
+    Uri dartBase = Uri.base;
+
+    for (MapEntry<Uri, String> entry in result.entries) {
+      sb.writeln("${entry.key}:");
+      sb.writeln(entry.value);
+      sb.writeln("\n\n");
+    }
+
+    String actual = sb.toString();
+    actual = actual.replaceAll("$base", "org-dartlang-testcase:///");
+    actual = actual.replaceAll("$dartBase", "org-dartlang-testcase-sdk:///");
+    actual = actual.replaceAll("\\n", "\n");
+
+    return context.match<TestDescription>(
+      ".outline_extracted",
+      actual,
+      description.uri,
+      description,
+    );
+  }
+}
+
+class CompileAndCompareStep
+    extends Step<TestDescription, TestDescription, Context> {
+  const CompileAndCompareStep();
+
+  @override
+  String get name => "CompileAndCompare";
+
+  @override
+  Future<Result<TestDescription>> run(
+      TestDescription description, Context context) async {
+    Uri? packages = description.uri.resolve(".packages");
+    if (!new File.fromUri(packages).existsSync()) {
+      packages = null;
+    }
+    Map<Uri, String> processedFiles =
+        await extractOutline([description.uri], packages: packages);
+
+    void onDiagnostic(DiagnosticMessage message) {
+      if (message.codeName == "InferredPackageUri") return;
+      if (message.severity == Severity.error ||
+          message.severity == Severity.warning) {
+        throw ("Unexpected error: ${message.plainTextFormatted.join('\n')}");
+      }
+    }
+
+    Library lib1;
+    {
+      CompilerOptions options = helper.getOptions();
+      options.onDiagnostic = onDiagnostic;
+      options.packagesFileUri = packages;
+      helper.TestIncrementalCompiler compiler =
+          new helper.TestIncrementalCompiler(options, description.uri,
+              /* initializeFrom = */ null, /* outlineOnly = */ true);
+      IncrementalCompilerResult c = await compiler.computeDelta();
+      lib1 = c.component.libraries
+          .firstWhere((element) => element.fileUri == description.uri);
+    }
+    Library lib2;
+    {
+      CompilerOptions options = helper.getOptions();
+      options.onDiagnostic = onDiagnostic;
+      options.packagesFileUri = packages;
+      MemoryFileSystem mfs = new MemoryFileSystem(Uri.base);
+      if (packages != null) {
+        mfs.entityForUri(packages).writeAsBytesSync(
+            await options.fileSystem.entityForUri(packages).readAsBytes());
+      }
+      if (options.sdkSummary != null) {
+        mfs.entityForUri(options.sdkSummary!).writeAsBytesSync(await options
+            .fileSystem
+            .entityForUri(options.sdkSummary!)
+            .readAsBytes());
+      }
+      if (options.librariesSpecificationUri != null) {
+        mfs.entityForUri(options.librariesSpecificationUri!).writeAsBytesSync(
+            await options.fileSystem
+                .entityForUri(options.librariesSpecificationUri!)
+                .readAsBytes());
+      }
+      for (MapEntry<Uri, String> entry in processedFiles.entries) {
+        mfs.entityForUri(entry.key).writeAsStringSync(entry.value);
+      }
+      options.fileSystem = mfs;
+      helper.TestIncrementalCompiler compiler =
+          new helper.TestIncrementalCompiler(options, description.uri,
+              /* initializeFrom = */ null, /* outlineOnly = */ true);
+      IncrementalCompilerResult c = await compiler.computeDelta();
+      lib2 = c.component.libraries
+          .firstWhere((element) => element.fileUri == description.uri);
+    }
+    EquivalenceResult result =
+        checkEquivalence(lib1, lib2, strategy: const Strategy());
+
+    if (result.isEquivalent) {
+      return new Result<TestDescription>.pass(description);
+    } else {
+      print("Bad:");
+      print(result);
+      return new Result<TestDescription>.fail(
+          description, /* error = */ result);
+    }
+  }
+}
+
+class Strategy extends EquivalenceStrategy {
+  const Strategy();
+
+  @override
+  bool checkTreeNode_fileOffset(
+      EquivalenceVisitor visitor, TreeNode node, TreeNode other) {
+    return true;
+  }
+
+  @override
+  bool checkAssertStatement_conditionStartOffset(
+      EquivalenceVisitor visitor, AssertStatement node, AssertStatement other) {
+    return true;
+  }
+
+  @override
+  bool checkAssertStatement_conditionEndOffset(
+      EquivalenceVisitor visitor, AssertStatement node, AssertStatement other) {
+    return true;
+  }
+
+  @override
+  bool checkClass_startFileOffset(
+      EquivalenceVisitor visitor, Class node, Class other) {
+    return true;
+  }
+
+  @override
+  bool checkClass_fileEndOffset(
+      EquivalenceVisitor visitor, Class node, Class other) {
+    return true;
+  }
+
+  @override
+  bool checkProcedure_startFileOffset(
+      EquivalenceVisitor visitor, Procedure node, Procedure other) {
+    return true;
+  }
+
+  @override
+  bool checkConstructor_startFileOffset(
+      EquivalenceVisitor visitor, Constructor node, Constructor other) {
+    return true;
+  }
+
+  @override
+  bool checkMember_fileEndOffset(
+      EquivalenceVisitor visitor, Member node, Member other) {
+    return true;
+  }
+
+  @override
+  bool checkFunctionNode_fileEndOffset(
+      EquivalenceVisitor visitor, FunctionNode node, FunctionNode other) {
+    return true;
+  }
+
+  @override
+  bool checkBlock_fileEndOffset(
+      EquivalenceVisitor visitor, Block node, Block other) {
+    return true;
+  }
+
+  @override
+  bool checkLibrary_additionalExports(
+      EquivalenceVisitor visitor, Library node, Library other) {
+    return visitor.checkSets(
+        node.additionalExports.toSet(),
+        other.additionalExports.toSet(),
+        visitor.matchReferences,
+        visitor.checkReferences,
+        'additionalExports');
+  }
+
+  @override
+  bool checkClass_procedures(
+      EquivalenceVisitor visitor, Class node, Class other) {
+    // Check procedures as a set instead of a list to allow for reordering.
+    List<Procedure> a = node.procedures.toList();
+    int sorter(Procedure x, Procedure y) {
+      int result = x.name.text.compareTo(y.name.text);
+      if (result != 0) return result;
+      result = x.kind.index - y.kind.index;
+      if (result != 0) return result;
+      // other stuff?
+      return 0;
+    }
+
+    a.sort(sorter);
+    List<Procedure> b = other.procedures.toList();
+    b.sort(sorter);
+    // return visitor.checkSets(a.toSet(), b.toSet(),
+    //     visitor.matchNamedNodes, visitor.checkNodes, 'procedures');
+
+    return visitor.checkLists(a, b, visitor.checkNodes, 'procedures');
+  }
+}
diff --git a/pkg/front_end/test/outline_extractor_tester.dart b/pkg/front_end/test/outline_extractor_tester.dart
new file mode 100644
index 0000000..941b5c0
--- /dev/null
+++ b/pkg/front_end/test/outline_extractor_tester.dart
@@ -0,0 +1,301 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:front_end/src/api_prototype/compiler_options.dart';
+import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
+import 'package:front_end/src/api_prototype/memory_file_system.dart';
+import 'package:kernel/ast.dart';
+import 'package:kernel/src/equivalence.dart';
+import 'package:compiler/src/kernel/dart2js_target.dart' show Dart2jsTarget;
+import 'package:kernel/target/targets.dart';
+import 'incremental_suite.dart' as helper;
+import 'package:front_end/src/fasta/util/outline_extractor.dart';
+import 'package:package_config/package_config.dart';
+
+Future<void> main(List<String> args) async {
+  if (args.length != 1) throw "Wants 1 argument.";
+  Uri input = Uri.base.resolve(args.single);
+  Uri packageUri = input.resolve(".packages");
+  Stopwatch stopwatch = new Stopwatch()..start();
+  PackageConfig packageFile = await loadPackageConfigUri(packageUri);
+  print("Read packages file in ${stopwatch.elapsedMilliseconds} ms");
+  List<Package> packages = packageFile.packages.toList();
+  int packageNum = 0;
+  for (Package package in packages) {
+    packageNum++;
+    print("\n\nProcessing package #$packageNum (${package.name}) "
+        "of ${packages.length}");
+    Directory dir = new Directory.fromUri(package.packageUriRoot);
+    List<Uri> uris = [];
+    for (FileSystemEntity entry in dir.listSync(recursive: true)) {
+      if (entry is File && entry.path.endsWith(".dart")) {
+        // Hack.
+        String content = entry.readAsStringSync();
+        if (content.contains("part of")) continue;
+        String asString = "${entry.uri}";
+        String packageName = package.name;
+        Uri packageUri = package.packageUriRoot;
+        String prefix = "${packageUri}";
+        if (asString.startsWith(prefix)) {
+          Uri reversed = Uri.parse(
+              "package:$packageName/${asString.substring(prefix.length)}");
+          uris.add(reversed);
+        } else {
+          throw "Unexpected!";
+        }
+      }
+    }
+    print("(found ${uris.length} files)");
+    if (uris.isEmpty) continue;
+    await processUri(uris, null, packageUri);
+  }
+  print(" => That's ${packages.length} packages!");
+
+  if (1 + 1 == 2) return;
+
+  Component fullComponent = await processUri([input], null, packageUri);
+  List<Uri> uris = fullComponent.libraries.map((l) => l.importUri).toList();
+  int i = 0;
+  for (Uri uri in uris) {
+    i++;
+    print("\n\nProcessing $uri (${i} of ${uris.length})");
+    try {
+      await processUri([uri], fullComponent, packageUri);
+    } catch (e, st) {
+      print("\n\n-------------\n\n");
+      print("Crashed on uri $uri");
+      print("Exception: '$e'");
+      print(st);
+      print("\n\n-------------\n\n");
+    }
+  }
+}
+
+Future<Component> processUri(final List<Uri> inputs, Component? fullComponent,
+    final Uri packageUri) async {
+  TargetFlags targetFlags =
+      new TargetFlags(enableNullSafety: true, trackWidgetCreation: false);
+  Target? target = new Dart2jsTarget("dart2js", targetFlags);
+  Uri sdkSummary = Uri.base.resolve("out/ReleaseX64/dart2js_outline.dill");
+  Stopwatch stopwatch = new Stopwatch()..start();
+  Stopwatch extractCompile = new Stopwatch()..start();
+  Map<Uri, String> processedFiles = await extractOutline(inputs,
+      packages: packageUri, target: target, platform: sdkSummary);
+  extractCompile.stop();
+  print("Got ${processedFiles.keys.length} files "
+      "in ${stopwatch.elapsedMilliseconds} ms");
+
+  Set<Uri> inputsSet = inputs.toSet();
+
+  Stopwatch plainCompile = new Stopwatch()..start();
+  List<Library> libs1;
+  {
+    stopwatch.reset();
+    CompilerOptions options = helper.getOptions();
+    options.target = target;
+    options.sdkSummary = sdkSummary;
+    options.packagesFileUri = packageUri;
+    helper.TestIncrementalCompiler compiler =
+        new helper.TestIncrementalCompiler(options, inputs.first,
+            /* initializeFrom = */ null, /* outlineOnly = */ true);
+    fullComponent = fullComponent ??
+        (await compiler.computeDelta(entryPoints: inputs)).component;
+    print("Compiled full in ${stopwatch.elapsedMilliseconds} ms "
+        "to ${fullComponent.libraries.length} libraries");
+    plainCompile.stop();
+
+    libs1 = fullComponent.libraries
+        .where((element) => inputsSet.contains(element.importUri))
+        .toList();
+  }
+  List<Library> libs2;
+  {
+    stopwatch.reset();
+    extractCompile.start();
+    CompilerOptions options = helper.getOptions();
+    options.target = target;
+    options.sdkSummary = sdkSummary;
+    options.packagesFileUri = packageUri;
+    MemoryFileSystem mfs = new MemoryFileSystem(Uri.base);
+    mfs.entityForUri(packageUri).writeAsBytesSync(
+        await options.fileSystem.entityForUri(packageUri).readAsBytes());
+    if (options.sdkSummary != null) {
+      mfs.entityForUri(options.sdkSummary!).writeAsBytesSync(await options
+          .fileSystem
+          .entityForUri(options.sdkSummary!)
+          .readAsBytes());
+    }
+    if (options.librariesSpecificationUri != null) {
+      mfs.entityForUri(options.librariesSpecificationUri!).writeAsBytesSync(
+          await options.fileSystem
+              .entityForUri(options.librariesSpecificationUri!)
+              .readAsBytes());
+    }
+    for (MapEntry<Uri, String> entry in processedFiles.entries) {
+      mfs.entityForUri(entry.key).writeAsStringSync(entry.value);
+    }
+    options.fileSystem = mfs;
+    helper.TestIncrementalCompiler compiler =
+        new helper.TestIncrementalCompiler(options, inputs.first,
+            /* initializeFrom = */ null, /* outlineOnly = */ true);
+    IncrementalCompilerResult c =
+        await compiler.computeDelta(entryPoints: inputs);
+    print("Compiled outlined in ${stopwatch.elapsedMilliseconds} ms "
+        "to ${c.component.libraries.length} libraries");
+    extractCompile.stop();
+
+    libs2 = c.component.libraries
+        .where((element) => inputsSet.contains(element.importUri))
+        .toList();
+  }
+
+  int libSorter(Library a, Library b) {
+    return a.importUri.toString().compareTo(b.importUri.toString());
+  }
+
+  libs1.sort(libSorter);
+  libs2.sort(libSorter);
+  if (libs1.length != libs2.length) {
+    print("Bad:");
+    print(
+        "Not the same amount of libraries: ${libs1.length} vs ${libs2.length}");
+    throw "bad result for $inputs";
+  }
+  List<EquivalenceResult> badResults = [];
+  for (int i = 0; i < libs1.length; i++) {
+    EquivalenceResult result =
+        checkEquivalence(libs1[i], libs2[i], strategy: const Strategy());
+    if (!result.isEquivalent) {
+      badResults.add(result);
+    }
+  }
+
+  if (badResults.isEmpty) {
+    print("OK");
+  } else {
+    print("Bad:");
+    for (EquivalenceResult badResult in badResults) {
+      print(badResult);
+      print("---");
+    }
+    // globalDebuggingNames = new NameSystem();
+    // print(lib1.leakingDebugToString());
+    // print("\n---\nvs\n----\n");
+    // globalDebuggingNames = new NameSystem();
+    // print(lib2.leakingDebugToString());
+    throw "bad result for $inputs";
+  }
+
+  if (plainCompile.elapsedMilliseconds > extractCompile.elapsedMilliseconds) {
+    print("=> Plain compile slower! "
+        "(${plainCompile.elapsedMilliseconds} vs "
+        "${extractCompile.elapsedMilliseconds})");
+  } else {
+    print("=> Plain compile faster! "
+        "(${plainCompile.elapsedMilliseconds} vs "
+        "${extractCompile.elapsedMilliseconds})");
+  }
+
+  return fullComponent;
+}
+
+class Strategy extends EquivalenceStrategy {
+  const Strategy();
+
+  @override
+  bool checkTreeNode_fileOffset(
+      EquivalenceVisitor visitor, TreeNode node, TreeNode other) {
+    return true;
+  }
+
+  @override
+  bool checkAssertStatement_conditionStartOffset(
+      EquivalenceVisitor visitor, AssertStatement node, AssertStatement other) {
+    return true;
+  }
+
+  @override
+  bool checkAssertStatement_conditionEndOffset(
+      EquivalenceVisitor visitor, AssertStatement node, AssertStatement other) {
+    return true;
+  }
+
+  @override
+  bool checkClass_startFileOffset(
+      EquivalenceVisitor visitor, Class node, Class other) {
+    return true;
+  }
+
+  @override
+  bool checkClass_fileEndOffset(
+      EquivalenceVisitor visitor, Class node, Class other) {
+    return true;
+  }
+
+  @override
+  bool checkProcedure_startFileOffset(
+      EquivalenceVisitor visitor, Procedure node, Procedure other) {
+    return true;
+  }
+
+  @override
+  bool checkConstructor_startFileOffset(
+      EquivalenceVisitor visitor, Constructor node, Constructor other) {
+    return true;
+  }
+
+  @override
+  bool checkMember_fileEndOffset(
+      EquivalenceVisitor visitor, Member node, Member other) {
+    return true;
+  }
+
+  @override
+  bool checkFunctionNode_fileEndOffset(
+      EquivalenceVisitor visitor, FunctionNode node, FunctionNode other) {
+    return true;
+  }
+
+  @override
+  bool checkBlock_fileEndOffset(
+      EquivalenceVisitor visitor, Block node, Block other) {
+    return true;
+  }
+
+  @override
+  bool checkLibrary_additionalExports(
+      EquivalenceVisitor visitor, Library node, Library other) {
+    return visitor.checkSets(
+        node.additionalExports.toSet(),
+        other.additionalExports.toSet(),
+        visitor.matchReferences,
+        visitor.checkReferences,
+        'additionalExports');
+  }
+
+  @override
+  bool checkClass_procedures(
+      EquivalenceVisitor visitor, Class node, Class other) {
+    // Check procedures as a set instead of a list to allow for reordering.
+    List<Procedure> a = node.procedures.toList();
+    int sorter(Procedure x, Procedure y) {
+      int result = x.name.text.compareTo(y.name.text);
+      if (result != 0) return result;
+      result = x.kind.index - y.kind.index;
+      if (result != 0) return result;
+      // other stuff?
+      return 0;
+    }
+
+    a.sort(sorter);
+    List<Procedure> b = other.procedures.toList();
+    b.sort(sorter);
+    // return visitor.checkSets(a.toSet(), b.toSet(),
+    //     visitor.matchNamedNodes, visitor.checkNodes, 'procedures');
+
+    return visitor.checkLists(a, b, visitor.checkNodes, 'procedures');
+  }
+}
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 0f0d0b8..9790926 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -11,6 +11,7 @@
 
 a+b
 abbreviate
+abc
 abcdef
 abs
 accounting
@@ -216,6 +217,7 @@
 codec
 codes
 collision
+coloring
 colorized
 com
 combinations
@@ -399,6 +401,7 @@
 engineered
 enhanced
 enters
+entrypointish
 enumerates
 env
 eof
@@ -458,6 +461,7 @@
 fieldformal
 file's
 filenames
+fileuri
 finally's
 finv
 firsts
@@ -689,6 +693,7 @@
 len
 lets
 letting
+levels
 lex
 lexemes
 lf
@@ -863,6 +868,7 @@
 orphans
 ors
 os
+outlined
 outputs
 outputting
 overlap
@@ -894,6 +900,7 @@
 parens
 parenteses
 particularly
+partof
 patchup
 path
 patterns
@@ -934,7 +941,9 @@
 prebuilt
 preexisted
 preexisting
+premark
 preorder
+preprocess
 presented
 presubmit
 presumably
@@ -945,6 +954,7 @@
 println
 prioritization
 proc
+processor
 producers
 product
 progresses
@@ -1087,6 +1097,7 @@
 role
 room
 rooted
+rough
 roughly
 rounding
 roundtrip
@@ -1201,6 +1212,7 @@
 stopgap
 stopped
 storage
+story
 str
 strategies
 streak
@@ -1388,6 +1400,7 @@
 unsortable
 unsound
 unsoundness
+untouched
 unwrapper
 unwraps
 unwritten
@@ -1398,6 +1411,7 @@
 uri's
 url
 urls
+usages
 usr
 usual
 usually
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index bb52fe8..5927e03 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -659,6 +659,7 @@
 metric
 metrics
 mf
+mfs
 micro
 minimize
 minimizer
@@ -699,6 +700,7 @@
 nonexisting
 noo
 noted
+nottest
 numerator
 ob
 obool
@@ -709,6 +711,7 @@
 ol
 onull
 oo
+oobar
 oocf
 ooo
 oovf
@@ -783,6 +786,9 @@
 quux
 quuz
 qux
+qux1
+qux3x
+qux4x
 r"
 r"\s
 r"k
@@ -843,6 +849,7 @@
 sdkroot
 sdks
 secondary
+secondtest12part1usage
 segment
 selection
 semifuzz
@@ -925,8 +932,19 @@
 tails
 talk
 templates
+test10
+test12
+test12part1usage
+test13
+test13andahalf
+test15thing
+test16
+test16toplevel
 test3a
 test3b
+test3partfoo
+test8
+test9
 theoretically
 thereof
 thread
@@ -996,6 +1014,7 @@
 waiting
 waits
 walt
+wants
 warmup
 week
 weekly
@@ -1014,8 +1033,10 @@
 xxx
 xxxxxxxx
 xxxxxxxxxxxx
+xyz
 y's
 year
 yxxx
 yy
+zyx
 zz
diff --git a/pkg/front_end/testing.json b/pkg/front_end/testing.json
index 3e12d26..11fca520 100644
--- a/pkg/front_end/testing.json
+++ b/pkg/front_end/testing.json
@@ -229,6 +229,17 @@
       "exclude": []
     },
     {
+      "name": "outline_extractor",
+      "kind": "Chain",
+      "source": "test/outline_extractor_suite.dart",
+      "path": "outline_extraction_testcases/",
+      "status": "outline_extraction_testcases/outline_extractor.status",
+      "pattern": [
+        "main\\.dart$"
+      ],
+      "exclude": []
+    },
+    {
       "name": "parser_equivalence",
       "kind": "Chain",
       "source": "test/parser_equivalence_suite.dart",
diff --git a/pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart b/pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart
index f368c90..2cfa64c 100644
--- a/pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart
+++ b/pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart
@@ -64,6 +64,7 @@
   final DirectParserASTType type;
   Map<String, Object?> get deprecatedArguments;
   List<DirectParserASTContent>? children;
+  DirectParserASTContent? parent;
 
   DirectParserASTContent(this.what, this.type);
 
diff --git a/pkg/front_end/tool/kernel_ast_file_rewriter.dart b/pkg/front_end/tool/kernel_ast_file_rewriter.dart
index 16f15e7..d80b5c2 100644
--- a/pkg/front_end/tool/kernel_ast_file_rewriter.dart
+++ b/pkg/front_end/tool/kernel_ast_file_rewriter.dart
@@ -80,7 +80,7 @@
       } else if (member.isClassFields()) {
         DirectParserASTContentClassFieldsEnd classFields =
             member.getClassFields();
-        Token identifierToken = classFields.getFieldIdentifiers().single!.token;
+        Token identifierToken = classFields.getFieldIdentifiers().single.token;
         String identifier = identifierToken.toString();
         namedFields.add(identifier);
       }
@@ -152,7 +152,7 @@
     throw "Notice ${classFields.count}";
   }
 
-  Token identifierToken = classFields.getFieldIdentifiers().single!.token;
+  Token identifierToken = classFields.getFieldIdentifiers().single.token;
   String identifier = identifierToken.toString();
 
   if (identifier == "frozen" && entry.key == "TreeNode") return;
diff --git a/pkg/front_end/tool/parser_direct_ast/viewer.dart b/pkg/front_end/tool/parser_direct_ast/viewer.dart
index aeee1b5..edf1159 100644
--- a/pkg/front_end/tool/parser_direct_ast/viewer.dart
+++ b/pkg/front_end/tool/parser_direct_ast/viewer.dart
@@ -17,7 +17,7 @@
     uri = Uri.base.resolve(args.first);
   }
   Uint8List bytes = new File.fromUri(uri).readAsBytesSync();
-  DirectParserASTContent ast = getAST(bytes);
+  DirectParserASTContent ast = getAST(bytes, enableExtensionMethods: true);
 
   Widget widget = new QuitOnQWidget(
     new WithSingleLineBottomWidget(
diff --git a/tools/VERSION b/tools/VERSION
index bab89d6..24fa5a6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 53
+PRERELEASE 54
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/package_deps/bin/package_deps.dart b/tools/package_deps/bin/package_deps.dart
index d0a8abc..38cca34 100644
--- a/tools/package_deps/bin/package_deps.dart
+++ b/tools/package_deps/bin/package_deps.dart
@@ -321,6 +321,7 @@
         // Skip 'pkg/analyzer_cli/test/data'.
         // Skip 'pkg/front_end/test/id_testing/data/'.
         // Skip 'pkg/front_end/test/language_versioning/data/'.
+        // Skip 'pkg/front_end/outline_extraction_testcases/'.
         if (name == 'data' && path.split(entity.parent.path).contains('test')) {
           continue;
         }
@@ -335,6 +336,11 @@
           continue;
         }
 
+        // Skip 'pkg/front_end/outline_extraction_testcases'.
+        if (name == 'outline_extraction_testcases') {
+          continue;
+        }
+
         if (!name.startsWith('.')) {
           _collectDartFiles(entity, files);
         }