[CFE] Fixup outline extraction

The outline extraction suite - as it turns out - wasn't run.
This has caused the tests to start fail for various reasons without
anyone noticing.
This CL enabled the suite; fixes up the failures; adds a few more tests
and fixes issues shown by those as well.

Change-Id: Iba2145abc319609e03c07d9a5141402b811b4959
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245981
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/front_end/lib/src/fasta/util/outline_extractor.dart b/pkg/front_end/lib/src/fasta/util/outline_extractor.dart
index d1f5d0a..011171f 100644
--- a/pkg/front_end/lib/src/fasta/util/outline_extractor.dart
+++ b/pkg/front_end/lib/src/fasta/util/outline_extractor.dart
@@ -78,9 +78,18 @@
     ..packagesFileUri = packages
     ..sdkSummary = platform
     ..sdkRoot = sdk;
-  ProcessedOptions pOptions =
+  ProcessedOptions processedOptions =
       new ProcessedOptions(options: options, inputs: entryPointUris);
-  return CompilerContext.runWithOptions(pOptions, (CompilerContext c) async {
+  return extractOutlineWithProcessedOptions(entryPointUris,
+      verbosityLevel: verbosityLevel, processedOptions: processedOptions);
+}
+
+Future<Map<Uri, String>> extractOutlineWithProcessedOptions(
+    List<Uri> entryPointUris,
+    {int verbosityLevel: 0,
+    required ProcessedOptions processedOptions}) {
+  return CompilerContext.runWithOptions(processedOptions,
+      (CompilerContext c) async {
     FileSystem fileSystem = c.options.fileSystem;
     UriTranslator uriTranslator = await c.options.getUriTranslator();
     _Processor processor =
@@ -208,17 +217,26 @@
           await _premarkTopLevel(worklist, closed, partTopLevel);
         }
       } else if (child is Export) {
-        for (Uri importedUri in child.uris) {
-          if (!importedUri.isScheme("dart")) {
-            TopLevel exportTopLevel =
-                parsed[importedUri] ?? await preprocessUri(importedUri);
-            await _premarkTopLevel(worklist, closed, exportTopLevel);
-          }
+        for (Uri exportedUri in child.uris) {
+          if (exportedUri.isScheme("dart")) continue;
+          // E.g. conditional exports could point to non-existing files.
+          if (!await _exists(exportedUri)) continue;
+          TopLevel exportTopLevel =
+              parsed[exportedUri] ?? await preprocessUri(exportedUri);
+          await _premarkTopLevel(worklist, closed, exportTopLevel);
         }
       }
     }
   }
 
+  Future<bool> _exists(Uri uri) {
+    Uri fileUri = uri;
+    if (fileUri.isScheme("package")) {
+      fileUri = uriTranslator.translate(fileUri)!;
+    }
+    return fileSystem.entityForUri(fileUri).exists();
+  }
+
   Future<List<TopLevel>> _preprocessImportsAsNeeded(
       Map<TopLevel, List<TopLevel>> imports, TopLevel topLevel) async {
     List<TopLevel>? imported = imports[topLevel];
@@ -230,11 +248,13 @@
         if (child is Import) {
           child.marked = Coloring.Marked;
           for (Uri importedUri in child.uris) {
-            if (!importedUri.isScheme("dart")) {
-              TopLevel importedTopLevel =
-                  parsed[importedUri] ?? await preprocessUri(importedUri);
-              imported.add(importedTopLevel);
-            }
+            if (importedUri.isScheme("dart")) continue;
+            // E.g. conditional imports could point to non-existing files.
+            if (!await _exists(importedUri)) continue;
+
+            TopLevel importedTopLevel =
+                parsed[importedUri] ?? await preprocessUri(importedUri);
+            imported.add(importedTopLevel);
           }
         } else if (child is PartOf) {
           child.marked = Coloring.Marked;
@@ -399,10 +419,11 @@
           } else if (child is Export) {
             child.marked = Coloring.Marked;
             // do stuff to export.
-            for (Uri importedUri in child.uris) {
-              if (!importedUri.isScheme("dart")) {
-                other = parsed[importedUri] ?? await preprocessUri(importedUri);
-              }
+            for (Uri exportedUri in child.uris) {
+              if (exportedUri.isScheme("dart")) continue;
+              // E.g. conditional exports could point to non-existing files.
+              if (!await _exists(exportedUri)) continue;
+              other = parsed[exportedUri] ?? await preprocessUri(exportedUri);
             }
           } else if (child is Extension) {
             // TODO: Maybe put on a list to process later and only include if
@@ -645,16 +666,12 @@
       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}";
+      debugDumpSource(
+          startInclusive,
+          endInclusive,
+          node,
+          "Unexpected identifiers in class factory method: $ids "
+          "(${ids.map((e) => e.token.lexeme).toList()}).");
     }
 
     super.visitClassFactoryMethod(node, startInclusive, endInclusive);
@@ -678,20 +695,7 @@
       ClassMethodEnd 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}";
-    }
+    String identifier = node.getNameIdentifier();
     ClassMethod classMethod =
         new ClassMethod(node, identifier, startInclusive, endInclusive);
     currentContainer.addChild(classMethod, map);
@@ -701,18 +705,16 @@
 
   @override
   void visitEnum(EnumEnd node, Token startInclusive, Token endInclusive) {
+    TopLevelDeclarationEnd parent = node.parent! as TopLevelDeclarationEnd;
+    IdentifierHandle identifier = parent.getIdentifier();
     List<IdentifierHandle> ids = node.getIdentifiers();
 
-    Enum e = new Enum(
-        node,
-        ids.first.token.lexeme,
-        ids.skip(1).map((e) => e.token.lexeme).toList(),
-        startInclusive,
-        endInclusive);
+    Enum e = new Enum(node, identifier.token.lexeme,
+        ids.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(", ")}");
+    log("Hello from enum ${identifier.token} with content "
+        "${ids.map((e) => e.token).join(", ")}");
     super.visitEnum(node, startInclusive, endInclusive);
   }
 
@@ -790,6 +792,19 @@
     super.visitExtensionMethod(node, startInclusive, endInclusive);
   }
 
+  void debugDumpSource(Token startInclusive, Token endInclusive,
+      ParserAstNode node, String message) {
+    Container findTopLevel = currentContainer;
+    while (findTopLevel is! TopLevel) {
+      findTopLevel = findTopLevel.parent!;
+    }
+    String src = findTopLevel.sourceText
+        .substring(startInclusive.charOffset, endInclusive.charEnd);
+    throw "Error on source ${src} --- \n\n"
+        "$message ---\n\n"
+        "${node.children}";
+  }
+
   @override
   void visitImport(ImportEnd node, Token startInclusive, Token? endInclusive) {
     IdentifierHandle? prefix = node.getImportPrefix();
@@ -904,6 +919,7 @@
   void visitPartOf(PartOfEnd 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.
+    if (partOfUri == null) throw "partOfUri is null -- uri $uri";
     PartOf partof = new PartOf(node, partOfUri!, startInclusive, endInclusive);
     partof.marked = Coloring.Marked;
     currentContainer.addChild(partof, map);
diff --git a/pkg/front_end/lib/src/fasta/util/parser_ast.dart b/pkg/front_end/lib/src/fasta/util/parser_ast.dart
index 3b131cf..f30a4a8 100644
--- a/pkg/front_end/lib/src/fasta/util/parser_ast.dart
+++ b/pkg/front_end/lib/src/fasta/util/parser_ast.dart
@@ -1293,7 +1293,8 @@
     for (ParserAstNode child in children!) {
       if (child is TypeHandle ||
           child is NoTypeHandle ||
-          child is VoidKeywordHandle) {
+          child is VoidKeywordHandle ||
+          child is FunctionTypeEnd) {
         foundType = true;
       }
       if (foundType && child is IdentifierHandle) {
@@ -1312,7 +1313,8 @@
     for (ParserAstNode child in children!) {
       if (child is TypeHandle ||
           child is NoTypeHandle ||
-          child is VoidKeywordHandle) {
+          child is VoidKeywordHandle ||
+          child is FunctionTypeEnd) {
         foundType = true;
       }
       if (foundType && child is IdentifierHandle) {
diff --git a/pkg/front_end/outline_extraction_testcases/.packages b/pkg/front_end/outline_extraction_testcases/.packages
new file mode 100644
index 0000000..68325fa
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/.packages
@@ -0,0 +1 @@
+# dummy
\ No newline at end of file
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/exists.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/exists.dart
new file mode 100644
index 0000000..45a8112
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/exists.dart
@@ -0,0 +1,3 @@
+export 'nonexisting.dart' if (dart.library.io) 'exists2.dart';
+
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/exists2.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/exists2.dart
new file mode 100644
index 0000000..8c26988
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/exists2.dart
@@ -0,0 +1 @@
+class Foo2 {}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/main.dart b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/main.dart
new file mode 100644
index 0000000..90e030f
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/main.dart
@@ -0,0 +1,8 @@
+import 'nonexisting.dart' if (dart.library.io) 'exists.dart';
+export 'nonexisting.dart'
+    if (dart.library.html) 'exists2.dart'
+    if (dart.library.io) 'exists.dart';
+
+Foo x() {
+  return new Foo();
+}
diff --git a/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/main.dart.outline_extracted
new file mode 100644
index 0000000..b26416d
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/conditional_imports_exports_2/main.dart.outline_extracted
@@ -0,0 +1,17 @@
+org-dartlang-testcase:///main.dart:
+import 'nonexisting.dart' if (dart.library.io) 'exists.dart';
+export 'nonexisting.dart' if (dart.library.html) 'exists2.dart' if (dart.library.io) 'exists.dart';
+Foo x() {}
+
+
+
+
+org-dartlang-testcase:///exists2.dart:
+class Foo2 {}
+
+
+
+
+org-dartlang-testcase:///exists.dart:
+export 'nonexisting.dart' if (dart.library.io) 'exists2.dart';
+class Foo {}
diff --git a/pkg/front_end/outline_extraction_testcases/factories/.packages b/pkg/front_end/outline_extraction_testcases/factories/.packages
new file mode 100644
index 0000000..6607aec
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/factories/.packages
@@ -0,0 +1 @@
+foo:.
\ No newline at end of file
diff --git a/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/.packages b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/.packages
new file mode 100644
index 0000000..6607aec
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/named_import_with_export_and_named_constructor_and_class_type_parameter/.packages
@@ -0,0 +1 @@
+foo:.
\ No newline at end of file
diff --git a/pkg/front_end/outline_extraction_testcases/split_import_export_part/.packages b/pkg/front_end/outline_extraction_testcases/split_import_export_part/.packages
new file mode 100644
index 0000000..d19a7e0
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/split_import_export_part/.packages
@@ -0,0 +1 @@
+foobar:.
\ No newline at end of file
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
index bd036e7..e48bd35 100644
--- 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
@@ -10,6 +10,8 @@
   int get giveInt => 42;
 }
 
+// The below doesn't have to be included.
+
 extension BarExtension on Bar {
   int get giveInt => 42;
 }
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/foo.dart b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/foo.dart
new file mode 100644
index 0000000..b6168cc
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/foo.dart
@@ -0,0 +1,19 @@
+enum Foo {
+  a,
+  b,
+  c,
+  d,
+  e,
+}
+
+extension FooExtension on Foo {
+  Function(int) get foobar => (int value) => 42;
+}
+
+// The below doesn't have to be included.
+
+extension BarExtension on Bar {
+  Function(int) get foobar => (int value) => 42;
+}
+
+class Bar {}
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/main.dart b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/main.dart
new file mode 100644
index 0000000..cf29838
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/main.dart
@@ -0,0 +1,5 @@
+import "foo.dart";
+
+final foo = [Foo.d, Foo.b];
+
+final foo2 = foo.map((f) => f.foobar).toList();
diff --git a/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/main.dart.outline_extracted b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/main.dart.outline_extracted
new file mode 100644
index 0000000..29e4991
--- /dev/null
+++ b/pkg/front_end/outline_extraction_testcases/use_of_imported_extension_3/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.foobar).toList();
+
+
+
+
+org-dartlang-testcase:///foo.dart:
+enum Foo { a, b, c, d, e, }
+extension FooExtension on Foo {
+  Function(int) get foobar => (int value) => 42;
+}
+extension BarExtension on Bar {
+  Function(int) get foobar => (int value) => 42;
+}
+class Bar {}
diff --git a/pkg/front_end/test/outline_extractor_suite.dart b/pkg/front_end/test/outline_extractor_suite.dart
index 02be7f5..4548f0c 100644
--- a/pkg/front_end/test/outline_extractor_suite.dart
+++ b/pkg/front_end/test/outline_extractor_suite.dart
@@ -100,13 +100,12 @@
   @override
   Future<Result<TestDescription>> run(
       TestDescription description, Context context) async {
-    Uri? packages = description.uri.resolve(".dart_tool/package_config.json");
+    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(".");
@@ -142,7 +141,7 @@
   @override
   Future<Result<TestDescription>> run(
       TestDescription description, Context context) async {
-    Uri? packages = description.uri.resolve(".dart_tool/package_config.json");
+    Uri? packages = description.uri.resolve(".packages");
     if (!new File.fromUri(packages).existsSync()) {
       packages = null;
     }
diff --git a/pkg/front_end/test/unit_test_suites.dart b/pkg/front_end/test/unit_test_suites.dart
index c9acb09..20fd455 100644
--- a/pkg/front_end/test/unit_test_suites.dart
+++ b/pkg/front_end/test/unit_test_suites.dart
@@ -31,9 +31,10 @@
     show createContext;
 import 'incremental_suite.dart' as incremental show createContext;
 import 'lint_suite.dart' as lint show createContext;
-import 'parser_suite.dart' as parser show createContext;
-import 'parser_equivalence_suite.dart' as parserEquivalence show createContext;
+import 'outline_extractor_suite.dart' as outline_extractor show createContext;
 import 'parser_all_suite.dart' as parserAll show createContext;
+import 'parser_equivalence_suite.dart' as parserEquivalence show createContext;
+import 'parser_suite.dart' as parser show createContext;
 import 'spelling_test_not_src_suite.dart' as spelling_not_src
     show createContext;
 import 'spelling_test_src_suite.dart' as spelling_src show createContext;
@@ -473,6 +474,12 @@
     "../../testing.json",
     shardCount: 1,
   ),
+  const Suite(
+    "outline_extractor",
+    outline_extractor.createContext,
+    "../testing.json",
+    shardCount: 1,
+  ),
 ];
 
 const Duration timeoutDuration = Duration(minutes: 30);