[CFE] Update parser ast and utils

Change-Id: I616b597826903dce4d4b38144eb8b729f5699bbc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354229
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
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 a19dd7a..ae43e2d 100644
--- a/pkg/front_end/lib/src/fasta/util/parser_ast.dart
+++ b/pkg/front_end/lib/src/fasta/util/parser_ast.dart
@@ -410,7 +410,86 @@
       MetadataEnd node, Token startInclusive, Token endInclusive) {}
 }
 
+enum MemberContentType {
+  ClassConstructor,
+  ClassFactoryMethod,
+  ClassFields,
+  ClassMethod,
+  ClassRecoverableError,
+  EnumConstructor,
+  EnumFactoryMethod,
+  EnumFields,
+  EnumMethod,
+  ExperimentNotEnabled,
+  ExtensionConstructor,
+  ExtensionFactoryMethod,
+  ExtensionFields,
+  ExtensionMethod,
+  ExtensionTypeConstructor,
+  ExtensionTypeFactoryMethod,
+  ExtensionTypeFields,
+  ExtensionTypeMethod,
+  MixinConstructor,
+  MixinFactoryMethod,
+  MixinFields,
+  MixinMethod,
+  Unknown,
+}
+
+enum GeneralAstContentType {
+  Class,
+  Import,
+  Export,
+  Unknown,
+  Enum,
+  Typedef,
+  Script,
+  Extension,
+  ExtensionType,
+  InvalidTopLevelDeclaration,
+  RecoverableError,
+  RecoverImport,
+  MixinDeclaration,
+  NamedMixinDeclaration,
+  TopLevelMethod,
+  TopLevelFields,
+  LibraryName,
+  Part,
+  PartOf,
+  Metadata,
+  FunctionBody,
+}
+
 extension GeneralASTContentExtension on ParserAstNode {
+  GeneralAstContentType getType() {
+    if (isClass()) return GeneralAstContentType.Class;
+    if (isImport()) return GeneralAstContentType.Import;
+    if (isExport()) return GeneralAstContentType.Export;
+    if (isExport()) return GeneralAstContentType.Export;
+    if (isEnum()) return GeneralAstContentType.Enum;
+    if (isTypedef()) return GeneralAstContentType.Typedef;
+    if (isScript()) return GeneralAstContentType.Script;
+    if (isExtension()) return GeneralAstContentType.Extension;
+    if (isExtensionType()) return GeneralAstContentType.ExtensionType;
+    if (isInvalidTopLevelDeclaration()) {
+      return GeneralAstContentType.InvalidTopLevelDeclaration;
+    }
+    if (isRecoverableError()) return GeneralAstContentType.RecoverableError;
+    if (isRecoverImport()) return GeneralAstContentType.RecoverImport;
+    if (isMixinDeclaration()) return GeneralAstContentType.MixinDeclaration;
+    if (isNamedMixinDeclaration()) {
+      return GeneralAstContentType.NamedMixinDeclaration;
+    }
+    if (isTopLevelMethod()) return GeneralAstContentType.TopLevelMethod;
+    if (isTopLevelFields()) return GeneralAstContentType.TopLevelFields;
+    if (isLibraryName()) return GeneralAstContentType.LibraryName;
+    if (isPart()) return GeneralAstContentType.Part;
+    if (isPartOf()) return GeneralAstContentType.PartOf;
+    if (isMetadata()) return GeneralAstContentType.Metadata;
+    if (isFunctionBody()) return GeneralAstContentType.FunctionBody;
+    return GeneralAstContentType.Unknown;
+  }
+
   bool isClass() {
     if (this is! TopLevelDeclarationEnd) {
       return false;
@@ -539,6 +618,25 @@
     return children!.last as ExtensionDeclarationEnd;
   }
 
+  bool isExtensionType() {
+    if (this is! TopLevelDeclarationEnd) {
+      return false;
+    }
+    if (children!.first is! ExtensionDeclarationPreludeBegin) {
+      return false;
+    }
+    if (children!.last is! ExtensionTypeDeclarationEnd) {
+      return false;
+    }
+
+    return true;
+  }
+
+  ExtensionTypeDeclarationEnd asExtensionType() {
+    if (!isExtensionType()) throw "Not extension type";
+    return children!.last as ExtensionTypeDeclarationEnd;
+  }
+
   bool isInvalidTopLevelDeclaration() {
     if (this is! TopLevelDeclarationEnd) {
       return false;
@@ -782,6 +880,18 @@
   }
 }
 
+extension MetadataExtension on MetadataEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      }
+    }
+    return result;
+  }
+}
+
 extension CompilationUnitExtension on CompilationUnitEnd {
   List<TopLevelDeclarationEnd> getClasses() {
     List<TopLevelDeclarationEnd> result = [];
@@ -899,6 +1009,20 @@
     }
     throw "Not found.";
   }
+
+  IdentifierHandle getMixinIdentifier() {
+    ParserAstNode? parent = this.parent;
+    if (parent is! TopLevelDeclarationEnd) throw "Now nested as expected";
+    return parent.getIdentifier();
+  }
+}
+
+extension NamedMixinApplicationExtension on NamedMixinApplicationEnd {
+  IdentifierHandle getMixinIdentifier() {
+    ParserAstNode? parent = this.parent;
+    if (parent is! TopLevelDeclarationEnd) throw "Now nested as expected";
+    return parent.getIdentifier();
+  }
 }
 
 extension ClassDeclarationExtension on ClassDeclarationEnd {
@@ -935,6 +1059,12 @@
     }
     return null;
   }
+
+  IdentifierHandle getClassIdentifier() {
+    ParserAstNode? parent = this.parent;
+    if (parent is! TopLevelDeclarationEnd) throw "Now nested as expected";
+    return parent.getIdentifier();
+  }
 }
 
 extension ClassOrMixinBodyExtension on ClassOrMixinOrExtensionBodyEnd {
@@ -950,6 +1080,46 @@
 }
 
 extension MemberExtension on MemberEnd {
+  MemberContentType getMemberType() {
+    if (isClassConstructor()) return MemberContentType.ClassConstructor;
+    if (isClassFactoryMethod()) return MemberContentType.ClassFactoryMethod;
+    if (isClassFields()) return MemberContentType.ClassFields;
+    if (isClassMethod()) return MemberContentType.ClassMethod;
+
+    if (isMixinConstructor()) return MemberContentType.MixinConstructor;
+    if (isMixinFactoryMethod()) return MemberContentType.MixinFactoryMethod;
+    if (isMixinFields()) return MemberContentType.MixinFields;
+    if (isMixinMethod()) return MemberContentType.MixinMethod;
+
+    if (isExtensionConstructor()) return MemberContentType.ExtensionConstructor;
+    if (isExtensionFactoryMethod()) {
+      return MemberContentType.ExtensionFactoryMethod;
+    }
+    if (isExtensionFields()) return MemberContentType.ExtensionFields;
+    if (isExtensionMethod()) return MemberContentType.ExtensionMethod;
+
+    if (isExtensionTypeConstructor()) {
+      return MemberContentType.ExtensionTypeConstructor;
+    }
+    if (isExtensionTypeFactoryMethod()) {
+      return MemberContentType.ExtensionTypeFactoryMethod;
+    }
+    if (isExtensionTypeFields()) return MemberContentType.ExtensionTypeFields;
+    if (isExtensionTypeMethod()) return MemberContentType.ExtensionTypeMethod;
+
+    if (isEnumConstructor()) return MemberContentType.EnumConstructor;
+    if (isEnumFactoryMethod()) return MemberContentType.EnumFactoryMethod;
+    if (isEnumFields()) return MemberContentType.EnumFields;
+    if (isEnumMethod()) return MemberContentType.EnumMethod;
+
+    if (isClassRecoverableError()) {
+      return MemberContentType.ClassRecoverableError;
+    }
+    if (isExperimentNotEnabled()) return MemberContentType.ExperimentNotEnabled;
+
+    return MemberContentType.Unknown;
+  }
+
   bool isClassConstructor() {
     ParserAstNode child = children![1];
     if (child is ClassConstructorEnd) return true;
@@ -1051,6 +1221,156 @@
     if (child is RecoverableErrorHandle) return true;
     return false;
   }
+
+  bool isExperimentNotEnabled() {
+    ParserAstNode child = children![1];
+    if (child is ExperimentNotEnabledHandle) return true;
+    return false;
+  }
+
+  bool isExtensionMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionMethodEnd) return true;
+    return false;
+  }
+
+  ExtensionMethodEnd getExtensionMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionMethodEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionFields() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionFieldsEnd) return true;
+    return false;
+  }
+
+  ExtensionFieldsEnd getExtensionFields() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionFieldsEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionConstructor() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionConstructorEnd) return true;
+    return false;
+  }
+
+  ExtensionConstructorEnd getExtensionConstructor() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionConstructorEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionFactoryMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionFactoryMethodEnd) return true;
+    return false;
+  }
+
+  ExtensionFactoryMethodEnd getExtensionFactoryMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionFactoryMethodEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionTypeMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeMethodEnd) return true;
+    return false;
+  }
+
+  ExtensionTypeMethodEnd getExtensionTypeMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeMethodEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionTypeFields() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeFieldsEnd) return true;
+    return false;
+  }
+
+  ExtensionTypeFieldsEnd getExtensionTypeFields() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeFieldsEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionTypeConstructor() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeConstructorEnd) return true;
+    return false;
+  }
+
+  ExtensionTypeConstructorEnd getExtensionTypeConstructor() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeConstructorEnd) return child;
+    throw "Not found";
+  }
+
+  bool isExtensionTypeFactoryMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeFactoryMethodEnd) return true;
+    return false;
+  }
+
+  ExtensionTypeFactoryMethodEnd getExtensionTypeFactoryMethod() {
+    ParserAstNode child = children![1];
+    if (child is ExtensionTypeFactoryMethodEnd) return child;
+    throw "Not found";
+  }
+
+  bool isEnumMethod() {
+    ParserAstNode child = children![1];
+    if (child is EnumMethodEnd) return true;
+    return false;
+  }
+
+  EnumMethodEnd getEnumMethod() {
+    ParserAstNode child = children![1];
+    if (child is EnumMethodEnd) return child;
+    throw "Not found";
+  }
+
+  bool isEnumFields() {
+    ParserAstNode child = children![1];
+    if (child is EnumFieldsEnd) return true;
+    return false;
+  }
+
+  EnumFieldsEnd getEnumFields() {
+    ParserAstNode child = children![1];
+    if (child is EnumFieldsEnd) return child;
+    throw "Not found";
+  }
+
+  bool isEnumConstructor() {
+    ParserAstNode child = children![1];
+    if (child is EnumConstructorEnd) return true;
+    return false;
+  }
+
+  EnumConstructorEnd getEnumConstructor() {
+    ParserAstNode child = children![1];
+    if (child is EnumConstructorEnd) return child;
+    throw "Not found";
+  }
+
+  bool isEnumFactoryMethod() {
+    ParserAstNode child = children![1];
+    if (child is EnumFactoryMethodEnd) return true;
+    return false;
+  }
+
+  EnumFactoryMethodEnd getEnumFactoryMethod() {
+    ParserAstNode child = children![1];
+    if (child is EnumFactoryMethodEnd) return child;
+    throw "Not found";
+  }
 }
 
 extension MixinFieldsExtension on MixinFieldsEnd {
@@ -1097,6 +1417,50 @@
   }
 }
 
+extension ExtensionTypeFieldsExtension on ExtensionTypeFieldsEnd {
+  List<IdentifierHandle> getFieldIdentifiers() {
+    int countLeft = count;
+    List<IdentifierHandle>? identifiers;
+    for (int i = children!.length - 1; i >= 0; i--) {
+      ParserAstNode child = children![i];
+      if (child is IdentifierHandle &&
+          child.context == IdentifierContext.fieldDeclaration) {
+        countLeft--;
+        if (identifiers == null) {
+          identifiers = new List<IdentifierHandle>.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 EnumFieldsExtension on EnumFieldsEnd {
+  List<IdentifierHandle> getFieldIdentifiers() {
+    int countLeft = count;
+    List<IdentifierHandle>? identifiers;
+    for (int i = children!.length - 1; i >= 0; i--) {
+      ParserAstNode child = children![i];
+      if (child is IdentifierHandle &&
+          child.context == IdentifierContext.fieldDeclaration) {
+        countLeft--;
+        if (identifiers == null) {
+          identifiers = new List<IdentifierHandle>.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 ClassFieldsEnd {
   List<IdentifierHandle> getFieldIdentifiers() {
     int countLeft = count;
@@ -1141,6 +1505,22 @@
     }
     return ids;
   }
+
+  IdentifierHandle getEnumIdentifier() {
+    ParserAstNode? parent = this.parent;
+    if (parent is! TopLevelDeclarationEnd) throw "Now nested as expected";
+    return parent.getIdentifier();
+  }
+
+  List<MemberEnd> getMembers() {
+    List<MemberEnd> members = [];
+    for (ParserAstNode child in children!) {
+      if (child is MemberEnd) {
+        members.add(child);
+      }
+    }
+    return members;
+  }
 }
 
 extension ExtensionDeclarationExtension on ExtensionDeclarationEnd {
@@ -1151,6 +1531,38 @@
     }
     return ids;
   }
+
+  Token? getExtensionName() {
+    ExtensionDeclarationBegin begin =
+        children!.first as ExtensionDeclarationBegin;
+    return begin.name;
+  }
+
+  ClassOrMixinOrExtensionBodyEnd getClassOrMixinOrExtensionBody() {
+    for (ParserAstNode child in children!) {
+      if (child is ClassOrMixinOrExtensionBodyEnd) {
+        return child;
+      }
+    }
+    throw "Not found.";
+  }
+}
+
+extension ExtensionTypeDeclarationExtension on ExtensionTypeDeclarationEnd {
+  Token? getExtensionTypeName() {
+    ExtensionTypeDeclarationBegin begin =
+        children!.first as ExtensionTypeDeclarationBegin;
+    return begin.name;
+  }
+
+  ClassOrMixinOrExtensionBodyEnd getClassOrMixinOrExtensionBody() {
+    for (ParserAstNode child in children!) {
+      if (child is ClassOrMixinOrExtensionBodyEnd) {
+        return child;
+      }
+    }
+    throw "Not found.";
+  }
 }
 
 extension TopLevelMethodExtension on TopLevelMethodEnd {
@@ -1362,6 +1774,7 @@
     bool foundType = false;
     for (ParserAstNode child in children!) {
       if (child is TypeHandle ||
+          child is RecordTypeEnd ||
           child is NoTypeHandle ||
           child is VoidKeywordHandle ||
           child is FunctionTypeEnd) {
@@ -1382,6 +1795,7 @@
     bool foundType = false;
     for (ParserAstNode child in children!) {
       if (child is TypeHandle ||
+          child is RecordTypeEnd ||
           child is NoTypeHandle ||
           child is VoidKeywordHandle ||
           child is FunctionTypeEnd) {
@@ -1402,6 +1816,49 @@
     bool foundType = false;
     for (ParserAstNode child in children!) {
       if (child is TypeHandle ||
+          child is RecordTypeEnd ||
+          child is NoTypeHandle ||
+          child is VoidKeywordHandle ||
+          child is FunctionTypeEnd) {
+        foundType = true;
+      }
+      if (foundType && child is IdentifierHandle) {
+        return child.token.lexeme;
+      } else if (foundType && child is OperatorNameHandle) {
+        return child.token.lexeme;
+      }
+    }
+    throw "No identifier found: $children";
+  }
+}
+
+extension ExtensionTypeMethodExtension on ExtensionTypeMethodEnd {
+  String getNameIdentifier() {
+    bool foundType = false;
+    for (ParserAstNode child in children!) {
+      if (child is TypeHandle ||
+          child is RecordTypeEnd ||
+          child is NoTypeHandle ||
+          child is VoidKeywordHandle ||
+          child is FunctionTypeEnd) {
+        foundType = true;
+      }
+      if (foundType && child is IdentifierHandle) {
+        return child.token.lexeme;
+      } else if (foundType && child is OperatorNameHandle) {
+        return child.token.lexeme;
+      }
+    }
+    throw "No identifier found: $children";
+  }
+}
+
+extension EnumMethodExtension on EnumMethodEnd {
+  String getNameIdentifier() {
+    bool foundType = false;
+    for (ParserAstNode child in children!) {
+      if (child is TypeHandle ||
+          child is RecordTypeEnd ||
           child is NoTypeHandle ||
           child is VoidKeywordHandle ||
           child is FunctionTypeEnd) {
@@ -1431,6 +1888,62 @@
   }
 }
 
+extension MixinFactoryMethodExtension on MixinFactoryMethodEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      } else if (child is FormalParametersEnd) {
+        break;
+      }
+    }
+    return result;
+  }
+}
+
+extension ExtensionFactoryMethodExtension on ExtensionFactoryMethodEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      } else if (child is FormalParametersEnd) {
+        break;
+      }
+    }
+    return result;
+  }
+}
+
+extension ExtensionTypeFactoryMethodExtension on ExtensionTypeFactoryMethodEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      } else if (child is FormalParametersEnd) {
+        break;
+      }
+    }
+    return result;
+  }
+}
+
+extension EnumFactoryMethodExtension on EnumFactoryMethodEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      } else if (child is FormalParametersEnd) {
+        break;
+      }
+    }
+    return result;
+  }
+}
+
 extension ClassConstructorExtension on ClassConstructorEnd {
   FormalParametersEnd getFormalParameters() {
     for (ParserAstNode child in children!) {
@@ -1470,6 +1983,54 @@
   }
 }
 
+extension ExtensionConstructorExtension on ExtensionConstructorEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      }
+    }
+    return result;
+  }
+}
+
+extension ExtensionTypeConstructorExtension on ExtensionTypeConstructorEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      }
+    }
+    return result;
+  }
+}
+
+extension EnumConstructorExtension on EnumConstructorEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      }
+    }
+    return result;
+  }
+}
+
+extension MixinConstructorExtension on MixinConstructorEnd {
+  List<IdentifierHandle> getIdentifiers() {
+    List<IdentifierHandle> result = [];
+    for (ParserAstNode child in children!) {
+      if (child is IdentifierHandle) {
+        result.add(child);
+      }
+    }
+    return result;
+  }
+}
+
 extension FormalParametersExtension on FormalParametersEnd {
   List<FormalParameterEnd> getFormalParameters() {
     List<FormalParameterEnd> result = [];
diff --git a/pkg/front_end/test/fasta/util/parser_ast_test.dart b/pkg/front_end/test/fasta/util/parser_ast_test.dart
index 2fd7170..e000031 100644
--- a/pkg/front_end/test/fasta/util/parser_ast_test.dart
+++ b/pkg/front_end/test/fasta/util/parser_ast_test.dart
@@ -24,6 +24,7 @@
 }
 
 void canParseTopLevelIshOfAllFrontendFiles() {
+  Stopwatch stopwatch = new Stopwatch()..start();
   Directory directory = new Directory.fromUri(base.resolve("../../../"));
   int processed = 0;
   int errors = 0;
@@ -33,11 +34,14 @@
       try {
         processed++;
         List<int> data = entry.readAsBytesSync();
-        CompilationUnitEnd ast = getAST(data,
-            includeBody: true,
-            includeComments: true,
-            enableExtensionMethods: true,
-            enableNonNullable: false);
+        CompilationUnitEnd ast = getAST(
+          data,
+          includeBody: true,
+          includeComments: true,
+          enableExtensionMethods: true,
+          enableNonNullable: true,
+          enableTripleShift: true,
+        );
         splitIntoChunks(ast, data);
         for (ParserAstNode child in ast.children!) {
           if (child.isClass()) {
@@ -47,6 +51,16 @@
             splitIntoChunks(
                 child.asMixinDeclaration().getClassOrMixinOrExtensionBody(),
                 data);
+          } else if (child.isExtension()) {
+            splitIntoChunks(
+                child.asExtension().getClassOrMixinOrExtensionBody(), data);
+          } else if (child.isExtensionType()) {
+            splitIntoChunks(
+                child.asExtensionType().getClassOrMixinOrExtensionBody(), data);
+          } else if (child.isEnum()) {
+            for (MemberEnd member in child.asEnum().getMembers()) {
+              processItem(member, data);
+            }
           }
         }
       } catch (e, st) {
@@ -55,8 +69,11 @@
       }
     }
   }
-  print("Processed $processed files in $directory. "
+  print("Processed $processed files in $directory in ${stopwatch.elapsed}. "
       "Encountered $errors errors.");
+  if (errors != 0) {
+    throw "Got errors.";
+  }
 }
 
 void testTopLevelStuff() {
@@ -72,7 +89,7 @@
   expect(2, ast.getExports().length);
 
   List<String> foundChunks = splitIntoChunks(ast, data);
-  expect(22, foundChunks.length);
+  expect(23, foundChunks.length);
   expect("library top_level_stuff;", foundChunks[0]);
   expect('import "top_level_stuff_helper.dart";', foundChunks[1]);
   expect('export "top_level_stuff_helper.dart";', foundChunks[2]);
@@ -87,34 +104,35 @@
   expect("part 'top_level_stuff_helper.dart';", foundChunks[5]);
   expect('@metadataOneOnThisOne("bla")\n', foundChunks[6]);
   expect("@metadataTwoOnThisOne\n", foundChunks[7]);
+  expect('@metadataThree.OnThisOne<int>("hello")\n', foundChunks[8]);
   expect("""void toplevelMethod() {
   // no content
-}""", foundChunks[8]);
+}""", foundChunks[9]);
   expect("""List<E> anotherTopLevelMethod<E>() {
   return null;
-}""", foundChunks[9]);
-  expect("enum FooEnum { A, B, Bla }", foundChunks[10]);
+}""", foundChunks[10]);
+  expect("enum FooEnum { A, B, Bla }", foundChunks[11]);
   expect("""class FooClass {
   // no content.
-}""", foundChunks[11]);
+}""", foundChunks[12]);
   expect("""mixin FooMixin {
   // no content.
-}""", foundChunks[12]);
+}""", foundChunks[13]);
   expect("""class A<T> {
   // no content.
-}""", foundChunks[13]);
-  expect("typedef B = Function();", foundChunks[14]);
+}""", foundChunks[14]);
+  expect("typedef B = Function();", foundChunks[15]);
   expect("""mixin C<T> on A<T> {
   // no content.
-}""", foundChunks[15]);
+}""", foundChunks[16]);
   expect("""extension D<T> on A<T> {
   // no content.
-}""", foundChunks[16]);
-  expect("class E = A with FooClass;", foundChunks[17]);
-  expect("int field1;", foundChunks[18]);
-  expect("int field2, field3;", foundChunks[19]);
-  expect("int field4 = 42;", foundChunks[20]);
-  expect("@AnnotationAtEOF", foundChunks[21]);
+}""", foundChunks[17]);
+  expect("class E = A with FooClass;", foundChunks[18]);
+  expect("int field1;", foundChunks[19]);
+  expect("int field2, field3;", foundChunks[20]);
+  expect("int field4 = 42;", foundChunks[21]);
+  expect("@AnnotationAtEOF", foundChunks[22]);
 
   file = new File.fromUri(
       base.resolve("parser_ast_test_data/top_level_stuff_helper.txt"));
@@ -277,6 +295,8 @@
 List<String> processItem(ParserAstNode item, List<int> data) {
   if (item.isClass()) {
     ClassDeclarationEnd cls = item.asClass();
+    // Check that we can get the identifier without throwing.
+    cls.getClassIdentifier();
     return [
       getCutContent(data, cls.beginToken.offset,
           cls.endToken.offset + cls.endToken.length)
@@ -287,6 +307,8 @@
     if (entries.isNotEmpty) {
       List<String> chunks = [];
       for (MetadataEnd metadata in entries) {
+        // Check that we can get the identifiers without throwing.
+        metadata.getIdentifiers();
         chunks.add(getCutContent(
             data, metadata.beginToken.offset, metadata.endToken.offset));
       }
@@ -325,18 +347,24 @@
     ];
   } else if (item.isTopLevelMethod()) {
     TopLevelMethodEnd method = item.asTopLevelMethod();
+    // Check that we can get the identifier without throwing.
+    method.getNameIdentifier();
     return [
       getCutContent(data, method.beginToken.offset,
           method.endToken.offset + method.endToken.length)
     ];
   } else if (item.isTopLevelFields()) {
     TopLevelFieldsEnd fields = item.asTopLevelFields();
+    // Check that we can get the identifiers without throwing.
+    fields.getFieldIdentifiers();
     return [
       getCutContent(data, fields.beginToken.offset,
           fields.endToken.offset + fields.endToken.length)
     ];
   } else if (item.isEnum()) {
     EnumEnd declaration = item.asEnum();
+    // Check that we can get the identifier without throwing.
+    declaration.getEnumIdentifier();
     return [
       getCutContent(
           data,
@@ -346,28 +374,44 @@
     ];
   } else if (item.isMixinDeclaration()) {
     MixinDeclarationEnd mixinDecl = item.asMixinDeclaration();
+    // Check that we can get the identifier without throwing.
+    mixinDecl.getMixinIdentifier();
     return [
       getCutContent(data, mixinDecl.beginToken.offset,
           mixinDecl.endToken.offset + mixinDecl.endToken.length)
     ];
   } else if (item.isNamedMixinDeclaration()) {
     NamedMixinApplicationEnd namedMixinDecl = item.asNamedMixinDeclaration();
+    // Check that we can get the identifier without throwing.
+    namedMixinDecl.getMixinIdentifier();
     return [
       getCutContent(data, namedMixinDecl.begin.offset,
           namedMixinDecl.endToken.offset + namedMixinDecl.endToken.length)
     ];
   } else if (item.isTypedef()) {
     TypedefEnd typedefDecl = item.asTypedef();
+    // Check that we can get the identifier without throwing.
+    typedefDecl.getNameIdentifier();
     return [
       getCutContent(data, typedefDecl.typedefKeyword.offset,
           typedefDecl.endToken.offset + typedefDecl.endToken.length)
     ];
   } else if (item.isExtension()) {
     ExtensionDeclarationEnd extensionDecl = item.asExtension();
+    // Check that we can get the identifier without throwing.
+    extensionDecl.getExtensionName();
     return [
       getCutContent(data, extensionDecl.extensionKeyword.offset,
           extensionDecl.endToken.offset + extensionDecl.endToken.length)
     ];
+  } else if (item.isExtensionType()) {
+    ExtensionTypeDeclarationEnd extensionTypeDecl = item.asExtensionType();
+    // Check that we can get the identifier without throwing.
+    extensionTypeDecl.getExtensionTypeName();
+    return [
+      getCutContent(data, extensionTypeDecl.extensionKeyword.offset,
+          extensionTypeDecl.endToken.offset + extensionTypeDecl.endToken.length)
+    ];
   } else if (item.isScript()) {
     ScriptHandle script = item.asScript();
     return [
@@ -377,54 +421,160 @@
   } else if (item is MemberEnd) {
     if (item.isClassConstructor()) {
       ClassConstructorEnd decl = item.getClassConstructor();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isClassFactoryMethod()) {
       ClassFactoryMethodEnd decl = item.getClassFactoryMethod();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isClassMethod()) {
       ClassMethodEnd decl = item.getClassMethod();
+      // Check that we can get the identifier without throwing.
+      decl.getNameIdentifier();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isClassFields()) {
       ClassFieldsEnd decl = item.getClassFields();
-      return [
-        getCutContent(data, decl.beginToken.offset,
-            decl.endToken.offset + decl.endToken.length)
-      ];
-    } else if (item.isClassFields()) {
-      ClassFieldsEnd decl = item.getClassFields();
+      // Check that we can get the identifiers without throwing.
+      decl.getFieldIdentifiers();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isMixinFields()) {
       MixinFieldsEnd decl = item.getMixinFields();
+      // Check that we can get the identifiers without throwing.
+      decl.getFieldIdentifiers();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isMixinMethod()) {
       MixinMethodEnd decl = item.getMixinMethod();
+      // Check that we can get the identifier without throwing.
+      decl.getNameIdentifier();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isMixinFactoryMethod()) {
       MixinFactoryMethodEnd decl = item.getMixinFactoryMethod();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
       ];
     } else if (item.isMixinConstructor()) {
       MixinConstructorEnd decl = item.getMixinConstructor();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionMethod()) {
+      ExtensionMethodEnd decl = item.getExtensionMethod();
+      // Check that we can get the identifier without throwing.
+      decl.getNameIdentifier();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionFields()) {
+      ExtensionFieldsEnd decl = item.getExtensionFields();
+      // Check that we can get the identifiers without throwing.
+      decl.getFieldIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionConstructor()) {
+      ExtensionConstructorEnd decl = item.getExtensionConstructor();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionFactoryMethod()) {
+      ExtensionFactoryMethodEnd decl = item.getExtensionFactoryMethod();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionTypeMethod()) {
+      ExtensionTypeMethodEnd decl = item.getExtensionTypeMethod();
+      // Check that we can get the identifier without throwing.
+      decl.getNameIdentifier();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionTypeFields()) {
+      ExtensionTypeFieldsEnd decl = item.getExtensionTypeFields();
+      // Check that we can get the identifiers without throwing.
+      decl.getFieldIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionTypeConstructor()) {
+      ExtensionTypeConstructorEnd decl = item.getExtensionTypeConstructor();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isExtensionTypeFactoryMethod()) {
+      ExtensionTypeFactoryMethodEnd decl = item.getExtensionTypeFactoryMethod();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isEnumMethod()) {
+      EnumMethodEnd decl = item.getEnumMethod();
+      // Check that we can get the identifier without throwing.
+      decl.getNameIdentifier();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isEnumFields()) {
+      EnumFieldsEnd decl = item.getEnumFields();
+      // Check that we can get the identifiers without throwing.
+      decl.getFieldIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isEnumConstructor()) {
+      EnumConstructorEnd decl = item.getEnumConstructor();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
+      return [
+        getCutContent(data, decl.beginToken.offset,
+            decl.endToken.offset + decl.endToken.length)
+      ];
+    } else if (item.isEnumFactoryMethod()) {
+      EnumFactoryMethodEnd decl = item.getEnumFactoryMethod();
+      // Check that we can get the identifiers without throwing.
+      decl.getIdentifiers();
       return [
         getCutContent(data, decl.beginToken.offset,
             decl.endToken.offset + decl.endToken.length)
@@ -433,9 +583,10 @@
       if (item.type == ParserAstType.BEGIN) return const [];
       if (item.type == ParserAstType.HANDLE) return const [];
       if (item.isClassRecoverableError()) return const [];
+      if (item.isExperimentNotEnabled()) return const [];
       if (item.isRecoverableError()) return const [];
       if (item.isRecoverImport()) return const [];
-      throw "Unknown: $item --- ${item.children}";
+      throw "Unknown member: $item --- ${item.children}";
     }
   } else if (item.isFunctionBody()) {
     BlockFunctionBodyEnd decl = item.asFunctionBody();
diff --git a/pkg/front_end/test/fasta/util/parser_ast_test_data/top_level_stuff.txt b/pkg/front_end/test/fasta/util/parser_ast_test_data/top_level_stuff.txt
index 3cd024b..666ad80 100644
--- a/pkg/front_end/test/fasta/util/parser_ast_test_data/top_level_stuff.txt
+++ b/pkg/front_end/test/fasta/util/parser_ast_test_data/top_level_stuff.txt
@@ -12,6 +12,7 @@
 
 @metadataOneOnThisOne("bla")
 @metadataTwoOnThisOne
+@metadataThree.OnThisOne<int>("hello")
 void toplevelMethod() {
   // no content
 }
diff --git a/pkg/front_end/tool/parser_direct_ast/console_helper.dart b/pkg/front_end/tool/parser_direct_ast/console_helper.dart
index ad90ce7..c0b8b8f 100644
--- a/pkg/front_end/tool/parser_direct_ast/console_helper.dart
+++ b/pkg/front_end/tool/parser_direct_ast/console_helper.dart
@@ -63,6 +63,8 @@
   void quit() {
     _gotoMainScreenBuffer();
     _showCursor();
+    stdin.echoMode = true;
+    stdin.lineMode = true;
     _timer!.cancel();
     _preventClose.close();
     _stdinListen.cancel();
diff --git a/pkg/front_end/tool/parser_direct_ast/viewer.dart b/pkg/front_end/tool/parser_direct_ast/viewer.dart
index 627cac1..9a3f8bc 100644
--- a/pkg/front_end/tool/parser_direct_ast/viewer.dart
+++ b/pkg/front_end/tool/parser_direct_ast/viewer.dart
@@ -80,10 +80,19 @@
         "${element.deprecatedArguments.toString()}${extra}";
   }
 
+  int printLineFrom = 0;
+
   @override
   void print(WriteOnlyOutput output) {
-    for (int row = 0; row < shown.length; row++) {
-      if (row >= output.rows) break;
+    if (selected - printLineFrom >= output.rows) {
+      // going down -- "scroll" so the selected line is at the bottom.
+      printLineFrom = selected - output.rows + 1;
+    } else if (selected - printLineFrom < 0) {
+      // going up -- "scroll" so the selected line is at the top.
+      printLineFrom = selected;
+    }
+    for (int row = printLineFrom; row < shown.length; row++) {
+      if ((row - printLineFrom) >= output.rows) break;
 
       PrintedLine element = shown[row];
       String line = element.text;
@@ -91,7 +100,8 @@
       if (selected == row) {
         // Mark line with blue background.
         for (int column = 0; column < output.columns; column++) {
-          output.setCell(row, column, backgroundColor: BackgroundColor.Blue);
+          output.setCell(row - printLineFrom, column,
+              backgroundColor: BackgroundColor.Blue);
         }
       }
 
@@ -101,7 +111,7 @@
         length = output.columns;
       }
       for (int column = 0; column < length; column++) {
-        output.setCell(row, column, char: line[column]);
+        output.setCell(row - printLineFrom, column, char: line[column]);
       }
     }
   }