Version 2.10.0-50.0.dev

Merge commit 'f190a4ae276dbebaa7aa261da74c13f9d1d6434d' into 'dev'
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index 01fc714..d2d1a31 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -93,7 +93,7 @@
 
 import 'util/experiment_environment_getter.dart' show getExperimentEnvironment;
 
-import 'util/textual_outline.dart' show textualOutline;
+import 'util/textual_outline_v2.dart' show textualOutline;
 
 import 'fasta_codes.dart'
     show
diff --git a/pkg/front_end/lib/src/fasta/util/textual_outline.dart b/pkg/front_end/lib/src/fasta/util/textual_outline.dart
deleted file mode 100644
index e165729..0000000
--- a/pkg/front_end/lib/src/fasta/util/textual_outline.dart
+++ /dev/null
@@ -1,579 +0,0 @@
-// Copyright (c) 2019, 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:typed_data' show Uint8List;
-
-import 'dart:io' show File;
-
-import 'package:_fe_analyzer_shared/src/parser/class_member_parser.dart'
-    show ClassMemberParser;
-
-import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
-    show ErrorToken, LanguageVersionToken, Scanner;
-
-import 'package:_fe_analyzer_shared/src/scanner/utf8_bytes_scanner.dart'
-    show Utf8BytesScanner;
-
-import '../../fasta/source/directive_listener.dart' show DirectiveListener;
-
-import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
-
-class _TextualOutlineState {
-  bool prevTokenKnown = false;
-  Token currentElementEnd;
-  List<String> currentChunk = new List<String>();
-  List<String> outputLines = new List<String>();
-
-  final bool performModelling;
-  final bool addMarkerForUnknownForTest;
-  String indent = "";
-  _TextualOutlineState(this.performModelling, this.addMarkerForUnknownForTest);
-}
-
-// TODO(jensj): Better support for show/hide on imports/exports.
-
-String textualOutline(List<int> rawBytes,
-    {bool throwOnUnexpected: false,
-    bool performModelling: false,
-    bool addMarkerForUnknownForTest: false}) {
-  // TODO(jensj): We need to specify the scanner settings to match that of the
-  // compiler!
-  Uint8List bytes = new Uint8List(rawBytes.length + 1);
-  bytes.setRange(0, rawBytes.length, rawBytes);
-
-  // Idea:
-  // * Chunks are entities, e.g. whole classes, whole procedures etc.
-  // * It could also be an "unknown" batch of tokens.
-  // * currentChunk is a temporary buffer where we add chunks in a "run".
-  // * Depending on whether we know what the previous token (and thus chunk) is
-  //   or not, and what the current token (and thus chunk) is, we either flush
-  //   the currentChunk buffer or not.
-  // * The idea being, that if we add 3 chunks we know and then are about to add
-  //   one we don't know, we can sort the 3 chunks we do know and output them.
-  //   But when we go from unknown to known, we don't sort before outputting.
-
-  _TextualOutlineState state =
-      new _TextualOutlineState(performModelling, addMarkerForUnknownForTest);
-
-  TokenPrinter tokenPrinter = new TokenPrinter();
-
-  Utf8BytesScanner scanner = new Utf8BytesScanner(bytes, includeComments: false,
-      languageVersionChanged:
-          (Scanner scanner, LanguageVersionToken languageVersion) {
-    flush(state, isSortable: false, isKnown: false);
-    state.prevTokenKnown = false;
-    state.outputLines
-        .add("// @dart = ${languageVersion.major}.${languageVersion.minor}");
-  });
-  Token firstToken = scanner.tokenize();
-  if (firstToken == null) {
-    if (throwOnUnexpected) throw "firstToken is null";
-    return null;
-  }
-
-  TextualOutlineListener listener = new TextualOutlineListener();
-  ClassMemberParser classMemberParser = new ClassMemberParser(listener);
-  classMemberParser.parseUnit(firstToken);
-
-  Token token = firstToken;
-  while (token != null) {
-    if (token is ErrorToken) {
-      return null;
-    }
-    if (token.isEof) break;
-
-    if (listener.classStartToFinish.containsKey(token)) {
-      if (state.prevTokenKnown) {
-        // TODO: Assert this instead.
-        if (!tokenPrinter.isEmpty) {
-          throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-        }
-      } else if (!tokenPrinter.isEmpty) {
-        // We're ending a streak of unknown: Output, and flush,
-        // but it's not sortable.
-        tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-        flush(state, isSortable: false, isKnown: false);
-      }
-
-      Token currentClassEnd = listener.classStartToFinish[token];
-      String classContent = _textualizeClass(
-          listener, token, currentClassEnd, state,
-          throwOnUnexpected: throwOnUnexpected);
-      if (classContent == null) return null;
-      state.currentChunk.add(classContent);
-      token = currentClassEnd.next;
-      state.prevTokenKnown = true;
-      assert(tokenPrinter.isEmpty);
-      continue;
-    }
-
-    bool isImportExport =
-        listener.importExportsStartToFinish.containsKey(token);
-    bool isKnownUnsortable = isImportExport ||
-        listener.unsortableElementStartToFinish.containsKey(token);
-
-    if (isKnownUnsortable) {
-      // We know about imports/exports, and internally they can be sorted,
-      // but the import/export block should be thought of as unknown, i.e.
-      // it should not moved around.
-      // We also know about other (e.g. library and parts) - those cannot be
-      // sorted though.
-      if (state.prevTokenKnown) {
-        // TODO: Assert this instead.
-        if (!tokenPrinter.isEmpty) {
-          throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-        }
-        flush(state, isSortable: true, isKnown: true);
-      } else {
-        tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-        flush(state, isSortable: false, isKnown: false);
-      }
-      if (isImportExport) {
-        TextualizedImportExport importResult =
-            _textualizeImportsAndExports(listener, token, state);
-        if (importResult == null) return null;
-        state.currentChunk.add(importResult.text);
-        token = importResult.token;
-      } else {
-        Token endToken = listener.unsortableElementStartToFinish[token];
-        while (token != endToken) {
-          tokenPrinter.print(token);
-          token = token.next;
-        }
-        tokenPrinter.print(endToken);
-        token = token.next;
-        tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-      }
-      state.prevTokenKnown = true;
-      flush(state, isSortable: false, isKnown: true);
-      assert(tokenPrinter.isEmpty);
-      continue;
-    }
-
-    token = _textualizeNonClassEntriesInsideLoop(
-        listener, token, state, throwOnUnexpected, tokenPrinter);
-    if (token == null) return null;
-  }
-  _textualizeAfterLoop(state, tokenPrinter);
-  return state.outputLines.join("\n\n");
-}
-
-Token _textualizeNonClassEntriesInsideLoop(
-    TextualOutlineListener listener,
-    Token token,
-    _TextualOutlineState state,
-    bool throwOnUnexpected,
-    TokenPrinter tokenPrinter) {
-  if (listener.elementStartToFinish.containsKey(token)) {
-    if (state.currentElementEnd != null) {
-      if (throwOnUnexpected) throw "Element in element";
-      return null;
-    }
-    if (state.prevTokenKnown) {
-      if (!tokenPrinter.isEmpty) {
-        throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-      }
-    } else if (!tokenPrinter.isEmpty) {
-      // We're ending a streak of unknown: Output, and flush,
-      // but it's not sortable.
-      tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-      flush(state, isSortable: false, isKnown: false);
-    }
-    state.currentElementEnd = listener.elementStartToFinish[token];
-    state.prevTokenKnown = true;
-  } else if (state.currentElementEnd == null &&
-      listener.metadataStartToFinish.containsKey(token)) {
-    if (state.prevTokenKnown) {
-      if (!tokenPrinter.isEmpty) {
-        throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-      }
-    } else if (!tokenPrinter.isEmpty) {
-      // We're ending a streak of unknown: Output, and flush,
-      // but it's not sortable.
-      tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-      flush(state, isSortable: false, isKnown: false);
-    }
-    state.currentElementEnd = listener.metadataStartToFinish[token];
-    state.prevTokenKnown = true;
-  }
-
-  if (state.currentElementEnd == null && state.prevTokenKnown) {
-    // We're ending a streak of known stuff.
-    if (!tokenPrinter.isEmpty) {
-      throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-    }
-    flush(state, isSortable: true, isKnown: true);
-    state.prevTokenKnown = false;
-  } else {
-    if (state.currentElementEnd == null) {
-      if (state.prevTokenKnown) {
-        // known -> unknown.
-        throw "This case was apparently not handled above.";
-      } else {
-        // OK: Streak of unknown.
-      }
-    } else {
-      if (state.prevTokenKnown) {
-        // OK: Streak of known.
-      } else {
-        // unknown -> known: This should have been flushed above.
-        if (!tokenPrinter.isEmpty) {
-          throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-        }
-      }
-    }
-  }
-
-  tokenPrinter.print(token);
-
-  if (token == state.currentElementEnd) {
-    state.currentElementEnd = null;
-    tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-  }
-
-  if (token.endGroup != null &&
-      listener.nonClassEndOffsets.contains(token.endGroup.offset)) {
-    token = token.endGroup;
-    tokenPrinter.nextTokenIsEndGroup = true;
-  } else {
-    token = token.next;
-  }
-  return token;
-}
-
-void _textualizeAfterLoop(
-    _TextualOutlineState state, TokenPrinter tokenPrinter) {
-  // We're done, so we're logically at an unknown token.
-  if (state.prevTokenKnown) {
-    // We're ending a streak of known stuff.
-    if (!tokenPrinter.isEmpty) {
-      throw new StateError("Expected empty, was '${tokenPrinter.content}'");
-    }
-    flush(state, isSortable: true, isKnown: true);
-    state.prevTokenKnown = false;
-  } else {
-    // Streak of unknown.
-    tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-    flush(state, isSortable: false, isKnown: false);
-    state.prevTokenKnown = false;
-  }
-}
-
-void flush(_TextualOutlineState state, {bool isSortable, bool isKnown}) {
-  assert(isSortable != null);
-  assert(isKnown != null);
-  if (state.currentChunk.isEmpty) return;
-  if (isSortable) {
-    state.currentChunk = mergeAndSort(state.currentChunk, state.indent,
-        isModelling: state.performModelling);
-  }
-  if (state.addMarkerForUnknownForTest && !isKnown) {
-    state.outputLines.add("---- unknown chunk starts ----");
-  }
-  if (state.indent == "") {
-    state.outputLines.addAll(state.currentChunk);
-  } else {
-    for (int i = 0; i < state.currentChunk.length; i++) {
-      state.outputLines.add("${state.indent}${state.currentChunk[i]}");
-    }
-  }
-  if (state.addMarkerForUnknownForTest && !isKnown) {
-    state.outputLines.add("---- unknown chunk ends ----");
-  }
-  state.currentChunk.clear();
-}
-
-List<String> mergeAndSort(List<String> data, String indent,
-    {bool isModelling}) {
-  assert(isModelling != null);
-  // If not modelling, don't sort.
-  if (!isModelling) return data;
-
-  bool hasAnnotations = false;
-  for (int i = 0; i < data.length - 1; i++) {
-    String element = data[i];
-    if (element.startsWith("@")) {
-      hasAnnotations = true;
-      break;
-    }
-  }
-  if (!hasAnnotations) {
-    data.sort();
-    return data;
-  }
-
-  // There's annotations: Merge them with the owner.
-  List<String> merged = new List<String>();
-  StringBuffer sb = new StringBuffer();
-  for (int i = 0; i < data.length; i++) {
-    String element = data[i];
-    if (element.startsWith("@")) {
-      if (sb.length > 0) sb.write(indent);
-      sb.writeln(element);
-    } else {
-      if (sb.length > 0) sb.write(indent);
-      sb.write(element);
-      merged.add(sb.toString());
-      sb.clear();
-    }
-  }
-  if (sb.length > 0) {
-    merged.add(sb.toString());
-    sb.clear();
-  }
-
-  merged.sort();
-  return merged;
-}
-
-class TokenPrinter {
-  bool nextTokenIsEndGroup = false;
-  int _endOfLast = -1;
-  StringBuffer _sb = new StringBuffer();
-
-  String get content => _sb.toString();
-
-  bool get isEmpty => _sb.isEmpty;
-
-  void clear() {
-    _endOfLast = -1;
-    _sb.clear();
-  }
-
-  void addAndClearIfHasContent(List<String> list) {
-    if (_sb.length > 0) {
-      list.add(_sb.toString());
-      clear();
-    }
-  }
-
-  void print(Token token) {
-    if (_sb.isNotEmpty && token.offset > _endOfLast && !nextTokenIsEndGroup) {
-      _sb.write(" ");
-    }
-
-    _sb.write(token.lexeme);
-    _endOfLast = token.end;
-    nextTokenIsEndGroup = false;
-  }
-
-  String toString() {
-    throw new UnsupportedError("toString");
-  }
-}
-
-String _textualizeClass(TextualOutlineListener listener, Token beginToken,
-    Token endToken, _TextualOutlineState originalState,
-    {bool throwOnUnexpected: false}) {
-  Token token = beginToken;
-  TokenPrinter tokenPrinter = new TokenPrinter();
-  // Class header.
-  while (token != endToken) {
-    tokenPrinter.print(token);
-    if (token.endGroup == endToken) {
-      token = token.next;
-      break;
-    }
-    token = token.next;
-  }
-  _TextualOutlineState state = new _TextualOutlineState(
-      originalState.performModelling, originalState.addMarkerForUnknownForTest);
-  if (token == endToken) {
-    // This for instance happens on named mixins, e.g.
-    // class C<T> = Object with A<Function(T)>;
-    // or when the class has no content, e.g.
-    // class C { }
-    // either way, output the end token right away to avoid a weird line break.
-    tokenPrinter.nextTokenIsEndGroup = true;
-    tokenPrinter.print(token);
-    tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-    flush(state, isSortable: false, isKnown: true);
-  } else {
-    tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-    flush(state, isSortable: false, isKnown: true);
-
-    state.indent = "  ";
-    while (token != endToken) {
-      token = _textualizeNonClassEntriesInsideLoop(
-          listener, token, state, throwOnUnexpected, tokenPrinter);
-      if (token == null) return null;
-    }
-    _textualizeAfterLoop(state, tokenPrinter);
-
-    state.indent = "";
-    tokenPrinter.nextTokenIsEndGroup = true;
-    tokenPrinter.print(token);
-    tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-    flush(state, isSortable: false, isKnown: true);
-  }
-  return state.outputLines.join("\n");
-}
-
-class TextualizedImportExport {
-  final String text;
-  final Token token;
-
-  TextualizedImportExport(this.text, this.token);
-}
-
-TextualizedImportExport _textualizeImportsAndExports(
-    TextualOutlineListener listener,
-    Token beginToken,
-    _TextualOutlineState originalState) {
-  TokenPrinter tokenPrinter = new TokenPrinter();
-  Token token = beginToken;
-  Token thisImportEnd = listener.importExportsStartToFinish[token];
-  _TextualOutlineState state = new _TextualOutlineState(
-      originalState.performModelling, originalState.addMarkerForUnknownForTest);
-  // TODO(jensj): Sort show and hide entries.
-  while (thisImportEnd != null) {
-    while (token != thisImportEnd) {
-      tokenPrinter.print(token);
-      token = token.next;
-    }
-    tokenPrinter.print(thisImportEnd);
-    token = token.next;
-    tokenPrinter.addAndClearIfHasContent(state.currentChunk);
-    thisImportEnd = listener.importExportsStartToFinish[token];
-  }
-
-  // End of imports. Sort them and return the sorted text.
-  flush(state, isSortable: true, isKnown: true);
-  return new TextualizedImportExport(state.outputLines.join("\n"), token);
-}
-
-main(List<String> args) {
-  File f = new File(args[0]);
-  String outline = textualOutline(f.readAsBytesSync(),
-      throwOnUnexpected: true, performModelling: true);
-  if (args.length > 1 && args[1] == "--overwrite") {
-    f.writeAsStringSync(outline);
-  } else {
-    print(outline);
-  }
-}
-
-class TextualOutlineListener extends DirectiveListener {
-  Set<int> nonClassEndOffsets = new Set<int>();
-  Map<Token, Token> classStartToFinish = {};
-  Map<Token, Token> elementStartToFinish = {};
-  Map<Token, Token> metadataStartToFinish = {};
-  Map<Token, Token> importExportsStartToFinish = {};
-  Map<Token, Token> unsortableElementStartToFinish = {};
-
-  @override
-  void endClassMethod(Token getOrSet, Token beginToken, Token beginParam,
-      Token beginInitializers, Token endToken) {
-    nonClassEndOffsets.add(endToken.offset);
-    elementStartToFinish[beginToken] = endToken;
-  }
-
-  @override
-  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
-    nonClassEndOffsets.add(endToken.offset);
-    elementStartToFinish[beginToken] = endToken;
-  }
-
-  @override
-  void endClassFactoryMethod(
-      Token beginToken, Token factoryKeyword, Token endToken) {
-    nonClassEndOffsets.add(endToken.offset);
-    elementStartToFinish[beginToken] = endToken;
-  }
-
-  @override
-  void handleNativeFunctionBodySkipped(Token nativeToken, Token semicolon) {
-    // Allow native functions.
-  }
-
-  @override
-  void endClassFields(
-      Token abstractToken,
-      Token externalToken,
-      Token staticToken,
-      Token covariantToken,
-      Token lateToken,
-      Token varFinalOrConst,
-      int count,
-      Token beginToken,
-      Token endToken) {
-    elementStartToFinish[beginToken] = endToken;
-  }
-
-  @override
-  void endTopLevelFields(
-      Token externalToken,
-      Token staticToken,
-      Token covariantToken,
-      Token lateToken,
-      Token varFinalOrConst,
-      int count,
-      Token beginToken,
-      Token endToken) {
-    elementStartToFinish[beginToken] = endToken;
-  }
-
-  void endFunctionTypeAlias(
-      Token typedefKeyword, Token equals, Token endToken) {
-    elementStartToFinish[typedefKeyword] = endToken;
-  }
-
-  void endEnum(Token enumKeyword, Token leftBrace, int count) {
-    elementStartToFinish[enumKeyword] = leftBrace.endGroup;
-  }
-
-  @override
-  void endLibraryName(Token libraryKeyword, Token semicolon) {
-    unsortableElementStartToFinish[libraryKeyword] = semicolon;
-  }
-
-  @override
-  void endPart(Token partKeyword, Token semicolon) {
-    unsortableElementStartToFinish[partKeyword] = semicolon;
-  }
-
-  @override
-  void endPartOf(
-      Token partKeyword, Token ofKeyword, Token semicolon, bool hasName) {
-    unsortableElementStartToFinish[partKeyword] = semicolon;
-  }
-
-  @override
-  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
-    // Metadata's endToken is the one *after* the actual end of the metadata.
-    metadataStartToFinish[beginToken] = endToken.previous;
-  }
-
-  @override
-  void endClassDeclaration(Token beginToken, Token endToken) {
-    classStartToFinish[beginToken] = endToken;
-  }
-
-  @override
-  void endMixinDeclaration(Token mixinKeyword, Token endToken) {
-    classStartToFinish[mixinKeyword] = endToken;
-  }
-
-  @override
-  void endExtensionDeclaration(
-      Token extensionKeyword, Token onKeyword, Token endToken) {
-    classStartToFinish[extensionKeyword] = endToken;
-  }
-
-  @override
-  void endNamedMixinApplication(Token beginToken, Token classKeyword,
-      Token equals, Token implementsKeyword, Token endToken) {
-    classStartToFinish[beginToken] = endToken;
-  }
-
-  @override
-  void endImport(Token importKeyword, Token semicolon) {
-    importExportsStartToFinish[importKeyword] = semicolon;
-  }
-
-  @override
-  void endExport(Token exportKeyword, Token semicolon) {
-    importExportsStartToFinish[exportKeyword] = semicolon;
-  }
-}
diff --git a/pkg/front_end/lib/src/fasta/util/textual_outline_v2.dart b/pkg/front_end/lib/src/fasta/util/textual_outline_v2.dart
new file mode 100644
index 0000000..d808874
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/util/textual_outline_v2.dart
@@ -0,0 +1,686 @@
+// Copyright (c) 2020, 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:typed_data' show Uint8List;
+
+import 'dart:io' show File;
+
+import 'package:_fe_analyzer_shared/src/parser/class_member_parser.dart'
+    show ClassMemberParser;
+
+import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
+    show ErrorToken, LanguageVersionToken, Scanner;
+
+import 'package:_fe_analyzer_shared/src/scanner/utf8_bytes_scanner.dart'
+    show Utf8BytesScanner;
+
+import '../../fasta/source/directive_listener.dart' show DirectiveListener;
+
+import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
+
+abstract class _Chunk implements Comparable<_Chunk> {
+  final int originalPosition;
+
+  List<_MetadataChunk> metadata;
+
+  _Chunk(this.originalPosition);
+
+  void printOn(StringBuffer sb, String indent);
+
+  void printMetadata(StringBuffer sb, String indent) {
+    if (metadata != null) {
+      for (_MetadataChunk m in metadata) {
+        m.printMetadataOn(sb, indent);
+      }
+    }
+  }
+
+  void internalMergeAndSort();
+
+  @override
+  int compareTo(_Chunk other) {
+    // Generally we compare according to the original position.
+    if (originalPosition < other.originalPosition) return -1;
+    return 1;
+  }
+}
+
+class _LanguageVersionChunk extends _Chunk {
+  final int major;
+  final int minor;
+
+  _LanguageVersionChunk(int originalPosition, this.major, this.minor)
+      : super(originalPosition);
+
+  @override
+  void printOn(StringBuffer sb, String indent) {
+    if (sb.isNotEmpty) {
+      sb.write("\n\n");
+    }
+    printMetadata(sb, indent);
+    sb.write("// @dart = ${major}.${minor}");
+  }
+
+  @override
+  void internalMergeAndSort() {
+    // Cannot be sorted.
+  }
+}
+
+abstract class _TokenChunk extends _Chunk {
+  final Token startToken;
+  final Token endToken;
+
+  _TokenChunk(int originalPosition, this.startToken, this.endToken)
+      : super(originalPosition);
+
+  void printOn(StringBuffer sb, String indent) {
+    int endOfLast = startToken.end;
+    if (sb.isNotEmpty) {
+      sb.write("\n");
+      if (indent.isEmpty && this is! _SingleImportExportChunk) {
+        // Hack to imitate v1.
+        sb.write("\n");
+      }
+    }
+    printMetadata(sb, indent);
+    sb.write(indent);
+
+    Token token = startToken;
+    Token afterEnd = endToken.next;
+    while (token != afterEnd) {
+      if (token.offset > endOfLast) {
+        sb.write(" ");
+      }
+
+      sb.write(token.lexeme);
+      endOfLast = token.end;
+      token = token.next;
+    }
+  }
+
+  @override
+  void internalMergeAndSort() {
+    // Generally cannot be sorted.
+  }
+}
+
+abstract class _SortableChunk extends _TokenChunk {
+  _SortableChunk(int originalPosition, Token startToken, Token endToken)
+      : super(originalPosition, startToken, endToken);
+
+  @override
+  int compareTo(_Chunk o) {
+    if (o is! _SortableChunk) return super.compareTo(o);
+
+    _SortableChunk other = o;
+
+    // Compare lexemes from startToken and at most the next 10 tokens.
+    // For valid code this should be more than enough. Note that this won't
+    // sort as a text-sort would as for instance "C<Foo>" and "C2<Foo>" will
+    // say "C" < "C2" where a text-sort would say "C<" > "C2". This doesn't
+    // really matter as long as the sorting is consistent (i.e. the textual
+    // outline always sorts like this).
+    Token thisToken = startToken;
+    Token otherToken = other.startToken;
+    int steps = 0;
+    while (thisToken.lexeme == otherToken.lexeme) {
+      if (steps++ > 10) break;
+      thisToken = thisToken.next;
+      otherToken = otherToken.next;
+    }
+    if (thisToken.lexeme == otherToken.lexeme) return super.compareTo(o);
+    return thisToken.lexeme.compareTo(otherToken.lexeme);
+  }
+}
+
+class _ImportExportChunk extends _Chunk {
+  final List<_SingleImportExportChunk> content =
+      new List<_SingleImportExportChunk>();
+
+  _ImportExportChunk(int originalPosition) : super(originalPosition);
+
+  @override
+  void printOn(StringBuffer sb, String indent) {
+    if (sb.isNotEmpty) {
+      sb.write("\n");
+    }
+    printMetadata(sb, indent);
+
+    for (_SingleImportExportChunk chunk in content) {
+      chunk.printOn(sb, indent);
+    }
+  }
+
+  @override
+  void internalMergeAndSort() {
+    content.sort();
+  }
+}
+
+class _SingleImportExportChunk extends _SortableChunk {
+  _SingleImportExportChunk(
+      int originalPosition, Token startToken, Token endToken)
+      : super(originalPosition, startToken, endToken);
+}
+
+class _KnownUnsortableChunk extends _TokenChunk {
+  _KnownUnsortableChunk(int originalPosition, Token startToken, Token endToken)
+      : super(originalPosition, startToken, endToken);
+}
+
+class _ClassChunk extends _SortableChunk {
+  List<_Chunk> content = new List<_Chunk>();
+  Token headerEnd;
+  Token footerStart;
+
+  _ClassChunk(int originalPosition, Token startToken, Token endToken)
+      : super(originalPosition, startToken, endToken);
+
+  void printOn(StringBuffer sb, String indent) {
+    int endOfLast = startToken.end;
+    if (sb.isNotEmpty) {
+      sb.write("\n\n");
+      sb.write(indent);
+    }
+
+    printMetadata(sb, indent);
+
+    // Header.
+    Token token = startToken;
+    Token afterEnd = headerEnd.next;
+    while (token != afterEnd) {
+      if (token.offset > endOfLast) {
+        sb.write(" ");
+      }
+
+      sb.write(token.lexeme);
+      endOfLast = token.end;
+
+      token = token.next;
+    }
+
+    // Content.
+    for (_Chunk chunk in content) {
+      chunk.printOn(sb, "  $indent");
+    }
+
+    // Footer.
+    if (footerStart != null) {
+      if (content.isNotEmpty) {
+        sb.write("\n");
+        sb.write(indent);
+      }
+      endOfLast = footerStart.end;
+      token = footerStart;
+      afterEnd = endToken.next;
+      while (token != afterEnd) {
+        if (token.offset > endOfLast) {
+          sb.write(" ");
+        }
+
+        sb.write(token.lexeme);
+        endOfLast = token.end;
+
+        token = token.next;
+      }
+    }
+  }
+
+  @override
+  void internalMergeAndSort() {
+    content = _mergeAndSort(content);
+  }
+}
+
+class _ProcedureEtcChunk extends _SortableChunk {
+  final Set<int> nonClassEndOffsets;
+  _ProcedureEtcChunk(int originalPosition, Token startToken, Token endToken,
+      this.nonClassEndOffsets)
+      : super(originalPosition, startToken, endToken);
+
+  void printOn(StringBuffer sb, String indent) {
+    int endOfLast = startToken.end;
+    if (sb.isNotEmpty) {
+      sb.write("\n");
+      if (indent.isEmpty) {
+        // Hack to imitate v1.
+        sb.write("\n");
+      }
+    }
+    printMetadata(sb, indent);
+    sb.write(indent);
+
+    Token token = startToken;
+    Token afterEnd = endToken.next;
+    bool nextTokenIsEndGroup = false;
+    while (token != afterEnd) {
+      if (token.offset > endOfLast && !nextTokenIsEndGroup) {
+        sb.write(" ");
+      }
+
+      sb.write(token.lexeme);
+      endOfLast = token.end;
+
+      if (token.endGroup != null &&
+          nonClassEndOffsets.contains(token.endGroup.offset)) {
+        token = token.endGroup;
+        nextTokenIsEndGroup = true;
+      } else {
+        token = token.next;
+        nextTokenIsEndGroup = false;
+      }
+    }
+  }
+}
+
+class _MetadataChunk extends _TokenChunk {
+  _MetadataChunk(int originalPosition, Token startToken, Token endToken)
+      : super(originalPosition, startToken, endToken);
+
+  void printMetadataOn(StringBuffer sb, String indent) {
+    int endOfLast = startToken.end;
+    sb.write(indent);
+    Token token = startToken;
+    Token afterEnd = endToken.next;
+    while (token != afterEnd) {
+      if (token.offset > endOfLast) {
+        sb.write(" ");
+      }
+
+      sb.write(token.lexeme);
+      endOfLast = token.end;
+      token = token.next;
+    }
+    sb.write("\n");
+  }
+}
+
+class _UnknownChunk extends _TokenChunk {
+  _UnknownChunk(int originalPosition, Token startToken, Token endToken)
+      : super(originalPosition, startToken, endToken);
+}
+
+class _UnknownTokenBuilder {
+  Token start;
+  Token interimEnd;
+}
+
+class BoxedInt {
+  int value;
+  BoxedInt(this.value);
+}
+
+// TODO(jensj): Better support for show/hide on imports/exports.
+
+String textualOutline(List<int> rawBytes,
+    {bool throwOnUnexpected: false,
+    bool performModelling: false,
+    bool addMarkerForUnknownForTest: false}) {
+  // TODO(jensj): We need to specify the scanner settings to match that of the
+  // compiler!
+  Uint8List bytes = new Uint8List(rawBytes.length + 1);
+  bytes.setRange(0, rawBytes.length, rawBytes);
+
+  List<_Chunk> parsedChunks = new List<_Chunk>();
+
+  BoxedInt originalPosition = new BoxedInt(0);
+
+  Utf8BytesScanner scanner = new Utf8BytesScanner(bytes, includeComments: false,
+      languageVersionChanged:
+          (Scanner scanner, LanguageVersionToken languageVersion) {
+    parsedChunks.add(new _LanguageVersionChunk(originalPosition.value++,
+        languageVersion.major, languageVersion.minor));
+  });
+  Token firstToken = scanner.tokenize();
+  if (firstToken == null) {
+    if (throwOnUnexpected) throw "firstToken is null";
+    return null;
+  }
+
+  TextualOutlineListener listener = new TextualOutlineListener();
+  ClassMemberParser classMemberParser = new ClassMemberParser(listener);
+  classMemberParser.parseUnit(firstToken);
+
+  Token nextToken = firstToken;
+  _UnknownTokenBuilder currentUnknown = new _UnknownTokenBuilder();
+  while (nextToken != null) {
+    if (nextToken is ErrorToken) {
+      return null;
+    }
+    if (nextToken.isEof) break;
+
+    nextToken = _textualizeTokens(
+        listener, nextToken, currentUnknown, parsedChunks, originalPosition);
+  }
+  outputUnknownChunk(currentUnknown, parsedChunks, originalPosition);
+
+  if (nextToken == null) return null;
+
+  if (performModelling) {
+    parsedChunks = _mergeAndSort(parsedChunks);
+  }
+
+  StringBuffer sb = new StringBuffer();
+  for (_Chunk chunk in parsedChunks) {
+    chunk.printOn(sb, "");
+  }
+
+  return sb.toString();
+}
+
+List<_Chunk> _mergeAndSort(List<_Chunk> chunks) {
+  // TODO(jensj): Only put into new list of there's metadata.
+  List<_Chunk> result =
+      new List<_Chunk>.filled(chunks.length, null, growable: true);
+  List<_MetadataChunk> metadataChunks;
+  int outSize = 0;
+  for (_Chunk chunk in chunks) {
+    if (chunk is _MetadataChunk) {
+      metadataChunks ??= new List<_MetadataChunk>();
+      metadataChunks.add(chunk);
+    } else {
+      chunk.metadata = metadataChunks;
+      metadataChunks = null;
+      chunk.internalMergeAndSort();
+      result[outSize++] = chunk;
+    }
+  }
+  if (metadataChunks != null) {
+    for (_MetadataChunk metadata in metadataChunks) {
+      result[outSize++] = metadata;
+    }
+  }
+  result.length = outSize;
+
+  result.sort();
+  return result;
+}
+
+/// Parses a chunk of tokens and returns the next - unparsed - token or null
+/// on error.
+Token _textualizeTokens(
+    TextualOutlineListener listener,
+    Token token,
+    _UnknownTokenBuilder currentUnknown,
+    List<_Chunk> parsedChunks,
+    BoxedInt originalPosition) {
+  Token classEndToken = listener.classStartToFinish[token];
+  if (classEndToken != null) {
+    outputUnknownChunk(currentUnknown, parsedChunks, originalPosition);
+
+    _ClassChunk classChunk =
+        new _ClassChunk(originalPosition.value++, token, classEndToken);
+    parsedChunks.add(classChunk);
+    return _textualizeClass(listener, classChunk, originalPosition);
+  }
+
+  Token isImportExportEndToken = listener.importExportsStartToFinish[token];
+  if (isImportExportEndToken != null) {
+    outputUnknownChunk(currentUnknown, parsedChunks, originalPosition);
+
+    _ImportExportChunk importExportChunk =
+        new _ImportExportChunk(originalPosition.value++);
+    parsedChunks.add(importExportChunk);
+    return _textualizeImportExports(listener, token, importExportChunk);
+  }
+
+  Token isKnownUnsortableEndToken =
+      listener.unsortableElementStartToFinish[token];
+  if (isKnownUnsortableEndToken != null) {
+    outputUnknownChunk(currentUnknown, parsedChunks, originalPosition);
+
+    Token beginToken = token;
+    parsedChunks.add(new _KnownUnsortableChunk(
+        originalPosition.value++, beginToken, isKnownUnsortableEndToken));
+    return isKnownUnsortableEndToken.next;
+  }
+
+  Token elementEndToken = listener.elementStartToFinish[token];
+  if (elementEndToken != null) {
+    outputUnknownChunk(currentUnknown, parsedChunks, originalPosition);
+
+    Token beginToken = token;
+    parsedChunks.add(new _ProcedureEtcChunk(originalPosition.value++,
+        beginToken, elementEndToken, listener.nonClassEndOffsets));
+    return elementEndToken.next;
+  }
+
+  Token metadataEndToken = listener.metadataStartToFinish[token];
+  if (metadataEndToken != null) {
+    outputUnknownChunk(currentUnknown, parsedChunks, originalPosition);
+
+    Token beginToken = token;
+    parsedChunks.add(new _MetadataChunk(
+        originalPosition.value++, beginToken, metadataEndToken));
+    return metadataEndToken.next;
+  }
+
+  // This token --- and whatever else tokens until we reach a start token we
+  // know is an unknown chunk. We don't yet know the end.
+  if (currentUnknown.start == null) {
+    // Start of unknown chunk.
+    currentUnknown.start = token;
+    currentUnknown.interimEnd = token;
+  } else {
+    // Continued unknown chunk.
+    currentUnknown.interimEnd = token;
+  }
+  return token.next;
+}
+
+Token _textualizeImportExports(TextualOutlineListener listener, Token token,
+    _ImportExportChunk importExportChunk) {
+  int originalPosition = 0;
+  Token endToken = listener.importExportsStartToFinish[token];
+  while (endToken != null) {
+    importExportChunk.content
+        .add(new _SingleImportExportChunk(originalPosition++, token, endToken));
+    token = endToken.next;
+    endToken = listener.importExportsStartToFinish[token];
+  }
+
+  return token;
+}
+
+Token _textualizeClass(TextualOutlineListener listener, _ClassChunk classChunk,
+    BoxedInt originalPosition) {
+  Token token = classChunk.startToken;
+  // Class header.
+  while (token != classChunk.endToken) {
+    if (token.endGroup == classChunk.endToken) {
+      break;
+    }
+    token = token.next;
+  }
+  classChunk.headerEnd = token;
+
+  if (token == classChunk.endToken) {
+    // This for instance happens on named mixins, e.g.
+    // class C<T> = Object with A<Function(T)>;
+    // or when the class has no content, e.g.
+    // class C { }
+    // either way, output the end token right away to avoid a weird line break.
+  } else {
+    token = token.next;
+    // "Normal" class with (possibly) content.
+    _UnknownTokenBuilder currentUnknown = new _UnknownTokenBuilder();
+    while (token != classChunk.endToken) {
+      token = _textualizeTokens(listener, token, currentUnknown,
+          classChunk.content, originalPosition);
+    }
+    outputUnknownChunk(currentUnknown, classChunk.content, originalPosition);
+    classChunk.footerStart = classChunk.endToken;
+  }
+
+  return classChunk.endToken.next;
+}
+
+/// Outputs an unknown chunk if one has been started.
+///
+/// Resets the given builder.
+void outputUnknownChunk(_UnknownTokenBuilder _currentUnknown,
+    List<_Chunk> parsedChunks, BoxedInt originalPosition) {
+  if (_currentUnknown.start == null) return;
+  parsedChunks.add(new _UnknownChunk(
+    originalPosition.value++,
+    _currentUnknown.start,
+    _currentUnknown.interimEnd,
+  ));
+  _currentUnknown.start = null;
+  _currentUnknown.interimEnd = null;
+}
+
+main(List<String> args) {
+  File f = new File(args[0]);
+  Uint8List data = f.readAsBytesSync();
+  String outline =
+      textualOutline(data, throwOnUnexpected: true, performModelling: true);
+  if (args.length > 1 && args[1] == "--overwrite") {
+    f.writeAsStringSync(outline);
+  } else if (args.length > 1 && args[1] == "--benchmark") {
+    Stopwatch stopwatch = new Stopwatch()..start();
+    for (int i = 0; i < 100; i++) {
+      String outline2 =
+          textualOutline(data, throwOnUnexpected: true, performModelling: true);
+      if (outline2 != outline) throw "Not the same result every time";
+    }
+    stopwatch.stop();
+    print("First 100 took ${stopwatch.elapsedMilliseconds} ms");
+    stopwatch = new Stopwatch()..start();
+    for (int i = 0; i < 10000; i++) {
+      String outline2 =
+          textualOutline(data, throwOnUnexpected: true, performModelling: true);
+      if (outline2 != outline) throw "Not the same result every time";
+    }
+    stopwatch.stop();
+    print("Next 10,000 took ${stopwatch.elapsedMilliseconds} ms");
+  } else {
+    print(outline);
+  }
+}
+
+class TextualOutlineListener extends DirectiveListener {
+  Set<int> nonClassEndOffsets = new Set<int>();
+  Map<Token, Token> classStartToFinish = {};
+  Map<Token, Token> elementStartToFinish = {};
+  Map<Token, Token> metadataStartToFinish = {};
+  Map<Token, Token> importExportsStartToFinish = {};
+  Map<Token, Token> unsortableElementStartToFinish = {};
+
+  @override
+  void endClassMethod(Token getOrSet, Token beginToken, Token beginParam,
+      Token beginInitializers, Token endToken) {
+    nonClassEndOffsets.add(endToken.offset);
+    elementStartToFinish[beginToken] = endToken;
+  }
+
+  @override
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+    nonClassEndOffsets.add(endToken.offset);
+    elementStartToFinish[beginToken] = endToken;
+  }
+
+  @override
+  void endClassFactoryMethod(
+      Token beginToken, Token factoryKeyword, Token endToken) {
+    nonClassEndOffsets.add(endToken.offset);
+    elementStartToFinish[beginToken] = endToken;
+  }
+
+  @override
+  void handleNativeFunctionBodySkipped(Token nativeToken, Token semicolon) {
+    // Allow native functions.
+  }
+
+  @override
+  void endClassFields(
+      Token abstractToken,
+      Token externalToken,
+      Token staticToken,
+      Token covariantToken,
+      Token lateToken,
+      Token varFinalOrConst,
+      int count,
+      Token beginToken,
+      Token endToken) {
+    elementStartToFinish[beginToken] = endToken;
+  }
+
+  @override
+  void endTopLevelFields(
+      Token externalToken,
+      Token staticToken,
+      Token covariantToken,
+      Token lateToken,
+      Token varFinalOrConst,
+      int count,
+      Token beginToken,
+      Token endToken) {
+    elementStartToFinish[beginToken] = endToken;
+  }
+
+  void endFunctionTypeAlias(
+      Token typedefKeyword, Token equals, Token endToken) {
+    elementStartToFinish[typedefKeyword] = endToken;
+  }
+
+  void endEnum(Token enumKeyword, Token leftBrace, int count) {
+    elementStartToFinish[enumKeyword] = leftBrace.endGroup;
+  }
+
+  @override
+  void endLibraryName(Token libraryKeyword, Token semicolon) {
+    unsortableElementStartToFinish[libraryKeyword] = semicolon;
+  }
+
+  @override
+  void endPart(Token partKeyword, Token semicolon) {
+    unsortableElementStartToFinish[partKeyword] = semicolon;
+  }
+
+  @override
+  void endPartOf(
+      Token partKeyword, Token ofKeyword, Token semicolon, bool hasName) {
+    unsortableElementStartToFinish[partKeyword] = semicolon;
+  }
+
+  @override
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    // Metadata's endToken is the one *after* the actual end of the metadata.
+    metadataStartToFinish[beginToken] = endToken.previous;
+  }
+
+  @override
+  void endClassDeclaration(Token beginToken, Token endToken) {
+    classStartToFinish[beginToken] = endToken;
+  }
+
+  @override
+  void endMixinDeclaration(Token mixinKeyword, Token endToken) {
+    classStartToFinish[mixinKeyword] = endToken;
+  }
+
+  @override
+  void endExtensionDeclaration(
+      Token extensionKeyword, Token onKeyword, Token endToken) {
+    classStartToFinish[extensionKeyword] = endToken;
+  }
+
+  @override
+  void endNamedMixinApplication(Token beginToken, Token classKeyword,
+      Token equals, Token implementsKeyword, Token endToken) {
+    classStartToFinish[beginToken] = endToken;
+  }
+
+  @override
+  void endImport(Token importKeyword, Token semicolon) {
+    importExportsStartToFinish[importKeyword] = semicolon;
+  }
+
+  @override
+  void endExport(Token exportKeyword, Token semicolon) {
+    importExportsStartToFinish[exportKeyword] = semicolon;
+  }
+}
diff --git a/pkg/front_end/test/fasta/textual_outline_suite.dart b/pkg/front_end/test/fasta/textual_outline_suite.dart
index db5dda5..c622db4 100644
--- a/pkg/front_end/test/fasta/textual_outline_suite.dart
+++ b/pkg/front_end/test/fasta/textual_outline_suite.dart
@@ -9,7 +9,7 @@
 
 import 'package:dart_style/dart_style.dart' show DartFormatter;
 
-import 'package:front_end/src/fasta/util/textual_outline.dart';
+import 'package:front_end/src/fasta/util/textual_outline_v2.dart';
 import 'package:testing/testing.dart'
     show
         Chain,
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 2c43748..32e6192 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -124,6 +124,7 @@
 boolean
 bother
 boundness
+boxed
 breadcrumbs
 brianwilkerson
 bs
@@ -425,6 +426,7 @@
 fo
 foo
 foobar
+footer
 foreign
 formed
 former
@@ -508,6 +510,7 @@
 ids
 iff
 il
+imitate
 immutability
 impl
 implementers
@@ -538,6 +541,7 @@
 instanceof
 instantiator
 intentionally
+interim
 interior
 interleaved
 intermediate
@@ -751,6 +755,7 @@
 orphans
 ors
 os
+outputs
 outputting
 overlap
 overloader
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index c74ced0..bf3dc81 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -508,6 +508,8 @@
 resource
 respected
 response
+result1
+result2
 retaining
 retainingpath
 retains
@@ -552,6 +554,7 @@
 stashed
 stat
 stats
+stay
 std
 stdio
 strip
@@ -625,6 +628,8 @@
 uses8
 usual
 uuid
+v1
+v2
 val
 vars
 verbatim
diff --git a/pkg/front_end/test/textual_outline_test.dart b/pkg/front_end/test/textual_outline_test.dart
index f95915f..59bf5eb 100644
--- a/pkg/front_end/test/textual_outline_test.dart
+++ b/pkg/front_end/test/textual_outline_test.dart
@@ -1,5 +1,5 @@
 import "dart:convert";
-import "package:front_end/src/fasta/util/textual_outline.dart";
+import "package:front_end/src/fasta/util/textual_outline_v2.dart";
 
 main() {
   // Doesn't sort if not asked to perform modelling.
@@ -107,12 +107,12 @@
   if (result !=
       """
 @a
-@A(2)
-typedef void F1();
+@A(3)
+int f1, f2;
 
 @a
-@A(3)
-int f1, f2;""") {
+@A(2)
+typedef void F1();""") {
     throw "Unexpected result: $result";
   }
 
@@ -131,12 +131,12 @@
   if (result !=
       """
 @a
-@A(2)
-typedef void F1();
+@A(3)
+int f1, f2;
 
 @a
-@A(3)
-int f1, f2;""") {
+@A(2)
+typedef void F1();""") {
     throw "Unexpected result: $result";
   }
 
@@ -269,4 +269,72 @@
 bar() {}""") {
     throw "Unexpected result: $result";
   }
+
+  // Ending metadata (not associated with anything) is still present.
+  // TODO: The extra metadata should actually stay at the bottom as it's not
+  // associated with anything and it will now basically be associated with foo.
+  result = textualOutline(utf8.encode("""
+@Object2()
+foo() {
+  // hello
+}
+
+@Object1()
+"""),
+      throwOnUnexpected: true,
+      performModelling: true,
+      addMarkerForUnknownForTest: true);
+  if (result !=
+      """
+@Object2()
+foo() {}
+
+@Object1()""") {
+    throw "Unexpected result: $result";
+  }
+
+  // Sorting of question mark types.
+  result = textualOutline(utf8.encode("""
+class Class1 {
+  Class1? get nullable1 => property1;
+  Class2? get property => null;
+  Class1 get nonNullable1 => property1;
+  Class2 get property1 => new Class1();
+}
+"""),
+      throwOnUnexpected: true,
+      performModelling: true,
+      addMarkerForUnknownForTest: true);
+  if (result !=
+      """
+class Class1 {
+  Class1? get nullable1 => property1;
+  Class1 get nonNullable1 => property1;
+  Class2? get property => null;
+  Class2 get property1 => new Class1();
+}""") {
+    throw "Unexpected result: $result";
+  }
+
+  // Sorting of various classes with numbers and less than.
+  result = textualOutline(utf8.encode("""
+class C2<V> = Super<V> with Mixin<V>;
+class C<V> extends Super<V> with Mixin<V> {}
+class D extends Super with Mixin {}
+class D2 = Super with Mixin;
+"""),
+      throwOnUnexpected: true,
+      performModelling: true,
+      addMarkerForUnknownForTest: true);
+  if (result !=
+      """
+class C<V> extends Super<V> with Mixin<V> {}
+
+class C2<V> = Super<V> with Mixin<V>;
+
+class D extends Super with Mixin {}
+
+class D2 = Super with Mixin;""") {
+    throw "Unexpected result: $result";
+  }
 }
diff --git a/pkg/front_end/testcases/general/annotation_top.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/annotation_top.dart.textual_outline_modelled.expect
index faefe48..11150ed 100644
--- a/pkg/front_end/testcases/general/annotation_top.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/annotation_top.dart.textual_outline_modelled.expect
@@ -2,13 +2,15 @@
 @A(1)
 library test;
 
+class A {
+  const A(int value);
+}
+
 @a
 @A(2)
 class C {}
 
-@a
-@A(2)
-typedef void F1();
+const Object a = const Object();
 @a
 @A(3)
 int f1, f2;
@@ -16,11 +18,8 @@
 @A(3)
 typedef F2 = void Function();
 @a
+@A(2)
+typedef void F1();
+@a
 @A(4)
 void main() {}
-
-class A {
-  const A(int value);
-}
-
-const Object a = const Object();
diff --git a/pkg/front_end/testcases/general/ffi_sample.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/ffi_sample.dart.textual_outline_modelled.expect
index f9d72da..2b9a9f6 100644
--- a/pkg/front_end/testcases/general/ffi_sample.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/ffi_sample.dart.textual_outline_modelled.expect
@@ -2,11 +2,11 @@
 import 'dart:ffi';
 
 class Coordinate extends Struct {
+  Pointer<Coordinate> next;
   @Double()
   double x;
   @Double()
   double y;
-  Pointer<Coordinate> next;
   factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {}
 }
 
diff --git a/pkg/front_end/testcases/general/generic_function_typedef.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/generic_function_typedef.dart.textual_outline_modelled.expect
index 5df2b1e..40fbff9 100644
--- a/pkg/front_end/testcases/general/generic_function_typedef.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/generic_function_typedef.dart.textual_outline_modelled.expect
@@ -24,9 +24,9 @@
 typedef H4 = void Function(void Function<T extends num, S extends num>());
 typedef H5 = void Function(void Function<T extends S, S extends num>());
 typedef H6 = void Function(void Function<T extends num, S extends T>());
+void Function<T, S>() f3;
+void Function<T>() f1;
 void Function<T extends S, S extends num>() f5;
 void Function<T extends num, S extends T>() f6;
 void Function<T extends num, S extends num>() f4;
 void Function<T extends num>() f2;
-void Function<T, S>() f3;
-void Function<T>() f1;
diff --git a/pkg/front_end/testcases/general/infer_field_from_multiple.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/infer_field_from_multiple.dart.textual_outline_modelled.expect
index b2f2254..9e2619e 100644
--- a/pkg/front_end/testcases/general/infer_field_from_multiple.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/infer_field_from_multiple.dart.textual_outline_modelled.expect
@@ -62,6 +62,7 @@
       this.field18);
   int field12;
   int field14;
+  var field1;
   var field10;
   var field11;
   var field13 = 0;
@@ -69,7 +70,6 @@
   var field16;
   var field17;
   var field18;
-  var field1;
   var field2;
   var field3 = 0;
   var field4 = 0;
@@ -102,6 +102,7 @@
       this.field18);
   T field12;
   T field14;
+  var field1;
   var field10;
   var field11;
   var field13 = null;
@@ -109,7 +110,6 @@
   var field16;
   var field17;
   var field18;
-  var field1;
   var field2;
   var field3 = 0;
   var field4 = 0;
diff --git a/pkg/front_end/testcases/general/metadata_enum.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/metadata_enum.dart.textual_outline_modelled.expect
index 6cb51de..ab7b226 100644
--- a/pkg/front_end/testcases/general/metadata_enum.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/metadata_enum.dart.textual_outline_modelled.expect
@@ -1,4 +1,4 @@
+const a = null;
 @a
 enum E { E1, E2, E3 }
-const a = null;
 main() {}
diff --git a/pkg/front_end/testcases/general/non_covariant_checks.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/non_covariant_checks.dart.textual_outline_modelled.expect
index fc7f6db..8ac3b96 100644
--- a/pkg/front_end/testcases/general/non_covariant_checks.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/non_covariant_checks.dart.textual_outline_modelled.expect
@@ -18,28 +18,28 @@
   S Function<S extends T>() get getter12 => field12;
   S Function<S extends T>(S) field14;
   S Function<S extends T>(S) get getter14 => field14;
-  T Function(T Function()) field7;
-  T Function(T Function()) get getter7 => field7;
-  T Function(T Function(T)) field11;
-  T Function(T Function(T)) get getter11 => field11;
-  T Function(T) field4;
-  T Function(T) get getter4 => field4;
   T Function() Function() field5;
   T Function() Function() get getter5 => field5;
   T Function() field2;
   T Function() get getter2 => field2;
+  T Function(T) field4;
+  T Function(T) get getter4 => field4;
+  T Function(T Function()) field7;
+  T Function(T Function()) get getter7 => field7;
+  T Function(T Function(T)) field11;
+  T Function(T Function(T)) get getter11 => field11;
   T Function(void Function(T)) field9;
   T Function(void Function(T)) get getter9 => field9;
   T field1;
   T get getter1 => field1;
+  void Function(S Function<S extends T>()) field15;
+  void Function(S Function<S extends T>()) get getter15 => field15;
+  void Function(T) field3;
+  void Function(T) get getter3 => field3;
   void Function(T Function()) field6;
   void Function(T Function()) get getter6 => field6;
   void Function(T Function(T)) field10;
   void Function(T Function(T)) get getter10 => field10;
-  void Function(T) field3;
-  void Function(T) get getter3 => field3;
-  void Function(S Function<S extends T>()) field15;
-  void Function(S Function<S extends T>()) get getter15 => field15;
   void Function(void Function(T)) field8;
   void Function(void Function(T)) get getter8 => field8;
   void Function<S extends T>(S) field13;
diff --git a/pkg/front_end/testcases/general/redirecting_factory.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/redirecting_factory.dart.textual_outline_modelled.expect
index ca2e3d3..3f02151 100644
--- a/pkg/front_end/testcases/general/redirecting_factory.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/redirecting_factory.dart.textual_outline_modelled.expect
@@ -26,10 +26,10 @@
   factory SimpleCase() = SimpleCaseImpl<A, B>;
 }
 
-class SimpleCaseImpl2<Ai2, Bi2> implements SimpleCaseImpl<Ai2, Bi2> {}
-
 class SimpleCaseImpl<Ai, Bi> implements SimpleCase<Ai, Bi> {
   factory SimpleCaseImpl() = SimpleCaseImpl2<Ai, Bi>;
 }
 
+class SimpleCaseImpl2<Ai2, Bi2> implements SimpleCaseImpl<Ai2, Bi2> {}
+
 main() {}
diff --git a/pkg/front_end/testcases/general/redirecting_factory_metadata.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/redirecting_factory_metadata.dart.textual_outline_modelled.expect
index 5455e6c..72b47ec 100644
--- a/pkg/front_end/testcases/general/redirecting_factory_metadata.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/redirecting_factory_metadata.dart.textual_outline_modelled.expect
@@ -1,7 +1,7 @@
 class Foo {
+  Foo.named(p);
   @forFactoryItself
   factory Foo(@forParameter @anotherForParameter p) = Foo.named;
-  Foo.named(p);
 }
 
 const anotherForParameter = 3;
diff --git a/pkg/front_end/testcases/general/type_literal_as_metadata.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/type_literal_as_metadata.dart.textual_outline_modelled.expect
index 4127317..54f0474 100644
--- a/pkg/front_end/testcases/general/type_literal_as_metadata.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/type_literal_as_metadata.dart.textual_outline_modelled.expect
@@ -1,8 +1,8 @@
-@A
-class B {}
-
 class A {
   const A();
 }
 
+@A
+class B {}
+
 main() {}
diff --git a/pkg/front_end/testcases/general/vm_type_ops.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/vm_type_ops.dart.textual_outline_modelled.expect
index 96d4e65..e28a446 100644
--- a/pkg/front_end/testcases/general/vm_type_ops.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general/vm_type_ops.dart.textual_outline_modelled.expect
@@ -13,8 +13,8 @@
 
 class D<P, Q> extends C<int, Q, P> {
   D(tt) : foo = tt;
-  Map<P, Q> foo4(w) {}
   Map<P, Q> foo;
+  Map<P, Q> foo4(w) {}
   foo2(y) {}
   foo3<T1, T2>(z) {}
 }
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/annotation_top.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/annotation_top.dart.textual_outline_modelled.expect
index 093fb5b..1184734 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/annotation_top.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/annotation_top.dart.textual_outline_modelled.expect
@@ -3,13 +3,15 @@
 @A(1)
 library test;
 
+class A {
+  const A(int value);
+}
+
 @a
 @A(2)
 class C {}
 
-@a
-@A(2)
-typedef void F1();
+const Object a = const Object();
 @a
 @A(3)
 int f1, f2;
@@ -17,11 +19,8 @@
 @A(3)
 typedef F2 = void Function();
 @a
+@A(2)
+typedef void F1();
+@a
 @A(4)
 void main() {}
-
-class A {
-  const A(int value);
-}
-
-const Object a = const Object();
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/ffi_sample.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/ffi_sample.dart.textual_outline_modelled.expect
index ed74cca..a7af7c2 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/ffi_sample.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/ffi_sample.dart.textual_outline_modelled.expect
@@ -3,11 +3,11 @@
 import 'dart:ffi';
 
 class Coordinate extends Struct {
+  Pointer<Coordinate> next;
   @Double()
   double x;
   @Double()
   double y;
-  Pointer<Coordinate> next;
   factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {}
 }
 
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/metadata_enum.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/metadata_enum.dart.textual_outline_modelled.expect
index 591a2e5..0bccd4a 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/metadata_enum.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/metadata_enum.dart.textual_outline_modelled.expect
@@ -1,5 +1,5 @@
 // @dart = 2.6
+const a = null;
 @a
 enum E { E1, E2, E3 }
-const a = null;
 main() {}
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/non_covariant_checks.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/non_covariant_checks.dart.textual_outline_modelled.expect
index 6969145..d3208cf 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/non_covariant_checks.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/non_covariant_checks.dart.textual_outline_modelled.expect
@@ -19,28 +19,28 @@
   S Function<S extends T>() get getter12 => field12;
   S Function<S extends T>(S) field14;
   S Function<S extends T>(S) get getter14 => field14;
-  T Function(T Function()) field7;
-  T Function(T Function()) get getter7 => field7;
-  T Function(T Function(T)) field11;
-  T Function(T Function(T)) get getter11 => field11;
-  T Function(T) field4;
-  T Function(T) get getter4 => field4;
   T Function() Function() field5;
   T Function() Function() get getter5 => field5;
   T Function() field2;
   T Function() get getter2 => field2;
+  T Function(T) field4;
+  T Function(T) get getter4 => field4;
+  T Function(T Function()) field7;
+  T Function(T Function()) get getter7 => field7;
+  T Function(T Function(T)) field11;
+  T Function(T Function(T)) get getter11 => field11;
   T Function(void Function(T)) field9;
   T Function(void Function(T)) get getter9 => field9;
   T field1;
   T get getter1 => field1;
+  void Function(S Function<S extends T>()) field15;
+  void Function(S Function<S extends T>()) get getter15 => field15;
+  void Function(T) field3;
+  void Function(T) get getter3 => field3;
   void Function(T Function()) field6;
   void Function(T Function()) get getter6 => field6;
   void Function(T Function(T)) field10;
   void Function(T Function(T)) get getter10 => field10;
-  void Function(T) field3;
-  void Function(T) get getter3 => field3;
-  void Function(S Function<S extends T>()) field15;
-  void Function(S Function<S extends T>()) get getter15 => field15;
   void Function(void Function(T)) field8;
   void Function(void Function(T)) get getter8 => field8;
   void Function<S extends T>(S) field13;
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory.dart.textual_outline_modelled.expect
index 78d09fc..85db97c 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory.dart.textual_outline_modelled.expect
@@ -27,10 +27,10 @@
   factory SimpleCase() = SimpleCaseImpl<A, B>;
 }
 
-class SimpleCaseImpl2<Ai2, Bi2> implements SimpleCaseImpl<Ai2, Bi2> {}
-
 class SimpleCaseImpl<Ai, Bi> implements SimpleCase<Ai, Bi> {
   factory SimpleCaseImpl() = SimpleCaseImpl2<Ai, Bi>;
 }
 
+class SimpleCaseImpl2<Ai2, Bi2> implements SimpleCaseImpl<Ai2, Bi2> {}
+
 main() {}
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory_metadata.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory_metadata.dart.textual_outline_modelled.expect
index 465cd5b..0b5eb06 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory_metadata.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/redirecting_factory_metadata.dart.textual_outline_modelled.expect
@@ -1,8 +1,8 @@
 // @dart = 2.6
 class Foo {
+  Foo.named(p);
   @forFactoryItself
   factory Foo(@forParameter @anotherForParameter p) = Foo.named;
-  Foo.named(p);
 }
 
 const anotherForParameter = 3;
diff --git a/pkg/front_end/testcases/general_nnbd_opt_out/type_literal_as_metadata.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general_nnbd_opt_out/type_literal_as_metadata.dart.textual_outline_modelled.expect
index ed301d3..83562c7 100644
--- a/pkg/front_end/testcases/general_nnbd_opt_out/type_literal_as_metadata.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/general_nnbd_opt_out/type_literal_as_metadata.dart.textual_outline_modelled.expect
@@ -1,9 +1,9 @@
 // @dart = 2.6
-@A
-class B {}
-
 class A {
   const A();
 }
 
+@A
+class B {}
+
 main() {}
diff --git a/pkg/front_end/testcases/inference/downwards_inference_annotations_typedef.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/inference/downwards_inference_annotations_typedef.dart.textual_outline_modelled.expect
index 9b53595..7d31ff1 100644
--- a/pkg/front_end/testcases/inference/downwards_inference_annotations_typedef.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/inference/downwards_inference_annotations_typedef.dart.textual_outline_modelled.expect
@@ -1,10 +1,9 @@
 library test;
 
-@Foo(const [])
-typedef void F();
-
 class Foo {
   const Foo(List<String> l);
 }
 
 main() {}
+@Foo(const [])
+typedef void F();
diff --git a/pkg/front_end/testcases/inference/future_union_downwards_3.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/inference/future_union_downwards_3.dart.textual_outline_modelled.expect
index 9307472..f86b37e 100644
--- a/pkg/front_end/testcases/inference/future_union_downwards_3.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/inference/future_union_downwards_3.dart.textual_outline_modelled.expect
@@ -2,11 +2,11 @@
 
 import 'dart:async';
 
-Future f;
 Future<List<int>> g2() async {}
 Future<List<int>> g3() async {}
 Future<List<int>> t2 = f.then((_) => [3]);
 Future<int> t1 = f.then((_) => new Future.value('hi'));
+Future f;
 
 class MyFuture<T> implements Future<T> {
   MyFuture() {}
diff --git a/pkg/front_end/testcases/inference/future_union_downwards_4.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/inference/future_union_downwards_4.dart.textual_outline_modelled.expect
index 3d8b7c8..d0adb06 100644
--- a/pkg/front_end/testcases/inference/future_union_downwards_4.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/inference/future_union_downwards_4.dart.textual_outline_modelled.expect
@@ -2,11 +2,11 @@
 
 import 'dart:async';
 
-Future f;
 Future<List<int>> g2() async {}
 Future<List<int>> g3() async {}
 Future<List<int>> t2 = f.then((_) => [3]);
 Future<int> t1 = f.then((_) => new MyFuture.value('hi'));
+Future f;
 
 class MyFuture<T> implements Future<T> {
   MyFuture() {}
diff --git a/pkg/front_end/testcases/late_lowering/return_late.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/late_lowering/return_late.dart.textual_outline_modelled.expect
index e1ef11f..e7887f2 100644
--- a/pkg/front_end/testcases/late_lowering/return_late.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/late_lowering/return_late.dart.textual_outline_modelled.expect
@@ -5,6 +5,6 @@
 }
 
 expect(expected, actual) {}
-int returnNonNullable(int value) {}
 int? returnNullable(int? value) {}
+int returnNonNullable(int value) {}
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/constant_null_check.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/constant_null_check.dart.textual_outline_modelled.expect
index c4a2339..392f4f1 100644
--- a/pkg/front_end/testcases/nnbd/constant_null_check.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/constant_null_check.dart.textual_outline_modelled.expect
@@ -5,9 +5,9 @@
 
 const Class e = const Class(a);
 const Class f = const Class(c);
-const int b = a!;
 const int? a = 42;
 const int? c = null;
 const int? d = c!;
+const int b = a!;
 expect(expected, actual) {}
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/function_types.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/function_types.dart.textual_outline_modelled.expect
index 8a2c7a1..0b0019c 100644
--- a/pkg/front_end/testcases/nnbd/function_types.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/function_types.dart.textual_outline_modelled.expect
@@ -1,5 +1,5 @@
-F bar() => foo;
 F? baz() => foo;
+F bar() => foo;
 Function()? foobar(Function()? x) => null;
 
 class A<T> {}
@@ -10,6 +10,6 @@
 
 main() {}
 typedef F = void Function();
-void Function() hest() => foo;
 void Function()? fisk() => foo;
+void Function() hest() => foo;
 void foo() {}
diff --git a/pkg/front_end/testcases/nnbd/future_or_variables.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/future_or_variables.dart.textual_outline_modelled.expect
index ff98b1c..82b623e 100644
--- a/pkg/front_end/testcases/nnbd/future_or_variables.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/future_or_variables.dart.textual_outline_modelled.expect
@@ -1,16 +1,16 @@
 import 'dart:async';
 
-FutureOr topLevelField1;
 FutureOr<FutureOr> topLevelField3;
 FutureOr<int?> topLevelField2;
+FutureOr topLevelField1;
 
 class Class1 {
-  FutureOr instanceField1;
   FutureOr<FutureOr> instanceField3;
   FutureOr<int?> instanceField2;
-  static FutureOr staticField1;
+  FutureOr instanceField1;
   static FutureOr<FutureOr> staticField3;
   static FutureOr<int?> staticField2;
+  static FutureOr staticField1;
   static void staticMethod1(
       [FutureOr parameter1,
       FutureOr<int?> parameter2,
@@ -33,9 +33,9 @@
   Class2.constructor1(
       this.instanceField1, this.instanceField2, this.instanceField3);
   Class2.constructor2();
-  FutureOr instanceField1;
   FutureOr<FutureOr> instanceField3;
   FutureOr<int?> instanceField2;
+  FutureOr instanceField1;
 }
 
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/issue41102.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/issue41102.dart.textual_outline_modelled.expect
index dee803a..35fb63f 100644
--- a/pkg/front_end/testcases/nnbd/issue41102.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/issue41102.dart.textual_outline_modelled.expect
@@ -24,6 +24,6 @@
 final t = StreamTransformer.fromHandlers(
     handleData: (data, sink) => Future.microtask(() => sink.add(data)),
     handleDone: (sink) => Future.microtask(() => sink.close()));
-int Function()? s13;
 int? s5;
+int Function()? s13;
 void main() {}
diff --git a/pkg/front_end/testcases/nnbd/issue42579_2.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/issue42579_2.dart.textual_outline_modelled.expect
index 5cdcfec..26937e5 100644
--- a/pkg/front_end/testcases/nnbd/issue42579_2.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/issue42579_2.dart.textual_outline_modelled.expect
@@ -6,5 +6,5 @@
 }
 
 main() {}
-wrap2<R>(A<R> Function() f) {}
 wrap<R>(R Function() f) {}
+wrap2<R>(A<R> Function() f) {}
diff --git a/pkg/front_end/testcases/nnbd/null_access.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/null_access.dart.textual_outline_modelled.expect
index 849bf32..0f1cebe 100644
--- a/pkg/front_end/testcases/nnbd/null_access.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/null_access.dart.textual_outline_modelled.expect
@@ -2,9 +2,9 @@
   Class call() => this;
   Class get nonNullableClass => this;
   NullableIndexClass get nonNullableNullableIndexClass => NullableIndexClass();
+  int? nullableField;
   int nonNullableField = 0;
   int operator [](int key) => key;
-  int? nullableField;
   void operator []=(int key, int value) {}
 }
 
diff --git a/pkg/front_end/testcases/nnbd/null_aware_chain.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/null_aware_chain.dart.textual_outline_modelled.expect
index f928776..156fbdd 100644
--- a/pkg/front_end/testcases/nnbd/null_aware_chain.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/null_aware_chain.dart.textual_outline_modelled.expect
@@ -1,8 +1,8 @@
 class Class {
-  Class get getter1 => this;
   Class([this.field]);
   Class? field;
   Class? get getter2 => field;
+  Class get getter1 => this;
 }
 
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/null_shorting.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/null_shorting.dart.textual_outline_modelled.expect
index 99a428a..aaf9215 100644
--- a/pkg/front_end/testcases/nnbd/null_shorting.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/null_shorting.dart.textual_outline_modelled.expect
@@ -1,12 +1,12 @@
 class Class1 {
-  Class1 get nonNullable1 => property1;
-  Class1 get property1 => new Class1();
-  Class1 nonNullable1Method() => nonNullable1;
   Class1? get nullable1 => property1;
   Class1? get property => null;
   Class1? operator +(int value) => nullable1;
   Class1? operator -() => nullable1;
   Class1? operator [](Class1? key) => nullable1;
+  Class1 get nonNullable1 => property1;
+  Class1 get property1 => new Class1();
+  Class1 nonNullable1Method() => nonNullable1;
   Class2 get nonNullable2 => property2;
   Class2 get property2 => new Class2();
   void operator []=(Class1? key, Class1? value) {}
diff --git a/pkg/front_end/testcases/nnbd/override_checks.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/override_checks.dart.textual_outline_modelled.expect
index c0742df..7ba713a 100644
--- a/pkg/front_end/testcases/nnbd/override_checks.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/override_checks.dart.textual_outline_modelled.expect
@@ -7,8 +7,8 @@
 }
 
 class B2 extends B1 {
-  num bar = 3.14;
   num? get baz => null;
+  num bar = 3.14;
   void hest(num value) {}
 }
 
diff --git a/pkg/front_end/testcases/nnbd/redundant_type_casts.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/redundant_type_casts.dart.textual_outline_modelled.expect
index 268c668..f56a0d9 100644
--- a/pkg/front_end/testcases/nnbd/redundant_type_casts.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/redundant_type_casts.dart.textual_outline_modelled.expect
@@ -1,6 +1,6 @@
 class A<T> {
-  T get current => _current as T;
   T? _current;
+  T get current => _current as T;
 }
 
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/return_late.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/return_late.dart.textual_outline_modelled.expect
index e1ef11f..e7887f2 100644
--- a/pkg/front_end/testcases/nnbd/return_late.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/return_late.dart.textual_outline_modelled.expect
@@ -5,6 +5,6 @@
 }
 
 expect(expected, actual) {}
-int returnNonNullable(int value) {}
 int? returnNullable(int? value) {}
+int returnNonNullable(int value) {}
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/return_null.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/return_null.dart.textual_outline_modelled.expect
index 5d7cbcb6..cc610a6 100644
--- a/pkg/front_end/testcases/nnbd/return_null.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/return_null.dart.textual_outline_modelled.expect
@@ -2,12 +2,12 @@
 
 Enum caseReturn1(Enum e) {}
 Enum caseReturn2(Enum e) {}
-Future returnAsync1() async {}
 Future<int?> returnAsync6() async {}
 Future<int?> returnAsync7() async {}
-FutureOr returnAsync2() async {}
+Future returnAsync1() async {}
 FutureOr<int> returnAsync3() async {}
 FutureOr<int?> returnAsync4() async {}
+FutureOr returnAsync2() async {}
 Iterable yieldSync() sync* {}
 Stream yieldAsync() async* {}
 String returnExplicit() {}
diff --git a/pkg/front_end/testcases/nnbd/simple_never.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/simple_never.dart.textual_outline_modelled.expect
index a729e48..df6d2c7 100644
--- a/pkg/front_end/testcases/nnbd/simple_never.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/simple_never.dart.textual_outline_modelled.expect
@@ -1,3 +1,3 @@
-Never foo() {}
 Never? bar() => null;
+Never foo() {}
 main() {}
diff --git a/pkg/front_end/testcases/nnbd/type_parameter_types.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/type_parameter_types.dart.textual_outline_modelled.expect
index 6350615..a4acdf6 100644
--- a/pkg/front_end/testcases/nnbd/type_parameter_types.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd/type_parameter_types.dart.textual_outline_modelled.expect
@@ -1,8 +1,8 @@
 Never never() => throw "Never";
 
 class A<X extends Object, Y extends Object?> {
-  X foo() => never();
   X? bar() => null;
+  X foo() => never();
   Y baz() => never();
 }
 
diff --git a/pkg/front_end/testcases/nnbd_mixed/member_inheritance_from_opt_out.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd_mixed/member_inheritance_from_opt_out.dart.textual_outline_modelled.expect
index 860366f..42c9fb7 100644
--- a/pkg/front_end/testcases/nnbd_mixed/member_inheritance_from_opt_out.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd_mixed/member_inheritance_from_opt_out.dart.textual_outline_modelled.expect
@@ -3,18 +3,6 @@
 import 'member_inheritance_from_opt_out_lib.dart';
 
 abstract class Interface {
-  int field1 = 0;
-  int get field3;
-  int get getter1;
-  int get property1;
-  int method1();
-  int method3a(int a, int b);
-  int method3b(int a, [int b]);
-  int method3c([int a, int b]);
-  int method5a(int a, {int b: 0});
-  int method5b({int a: 0, int b: 0});
-  int method5c({required int a, required int b});
-  int property3 = 0;
   int? field2;
   int? get field4;
   int? get getter2;
@@ -27,6 +15,18 @@
   int? method6b({int? a, int? b});
   int? method6c({required int? a, required int? b});
   int? property4;
+  int field1 = 0;
+  int get field3;
+  int get getter1;
+  int get property1;
+  int method1();
+  int method3a(int a, int b);
+  int method3b(int a, [int b]);
+  int method3c([int a, int b]);
+  int method5a(int a, {int b: 0});
+  int method5b({int a: 0, int b: 0});
+  int method5c({required int a, required int b});
+  int property3 = 0;
   void set field3(int value);
   void set field4(int? value);
   void set property1(int value);
@@ -40,18 +40,6 @@
 class Class2a extends LegacyClass implements Interface {}
 
 class Class2b extends LegacyClass implements Interface {
-  int field1 = 0;
-  int get field3 => 0;
-  int get getter1 => 0;
-  int get property1 => 0;
-  int method1() => 0;
-  int method3a(int a, int b) => 0;
-  int method3b(int a, [int b = 42]) => 0;
-  int method3c([int a = 42, int b = 42]) => 0;
-  int method5a(int a, {int b: 0}) => 0;
-  int method5b({int a: 0, int b: 0}) => 0;
-  int method5c({required int a, required int b}) => 0;
-  int property3 = 0;
   int? field2;
   int? get field4 => 0;
   int? get getter2 => 0;
@@ -64,6 +52,18 @@
   int? method6b({int? a, int? b}) => 0;
   int? method6c({required int? a, required int? b}) => 0;
   int? property4;
+  int field1 = 0;
+  int get field3 => 0;
+  int get getter1 => 0;
+  int get property1 => 0;
+  int method1() => 0;
+  int method3a(int a, int b) => 0;
+  int method3b(int a, [int b = 42]) => 0;
+  int method3c([int a = 42, int b = 42]) => 0;
+  int method5a(int a, {int b: 0}) => 0;
+  int method5b({int a: 0, int b: 0}) => 0;
+  int method5c({required int a, required int b}) => 0;
+  int property3 = 0;
   void set field3(int value) {}
   void set field4(int? value) {}
   void set property1(int value) {}
diff --git a/pkg/front_end/testcases/nnbd_mixed/messages_with_types_opt_in.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd_mixed/messages_with_types_opt_in.dart.textual_outline_modelled.expect
index 0f30fad..c612c35 100644
--- a/pkg/front_end/testcases/nnbd_mixed/messages_with_types_opt_in.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/nnbd_mixed/messages_with_types_opt_in.dart.textual_outline_modelled.expect
@@ -1,24 +1,24 @@
 import 'messages_with_types_opt_out.dart';
 
 class SubInIn extends SuperIn {
-  String nonNullableSame() => "bar";
   String? nullableSame() => "foo";
-  T nonNullableBad<T>(T t) => t;
+  String nonNullableSame() => "bar";
   T? nullableBad<T>(T t) => null;
+  T nonNullableBad<T>(T t) => t;
 }
 
 class SubOutIn extends SuperOut {
-  String nonNullableSame() => "bar";
   String? nullableSame() => "foo";
-  T nonNullableBad<T>(T t) => t;
+  String nonNullableSame() => "bar";
   T? nullableBad<T>(T t) => null;
+  T nonNullableBad<T>(T t) => t;
 }
 
 class SuperIn {
-  String nonNullableSame() => "bar";
   String? nullableSame() => "foo";
-  int nonNullableBad<T>(T t) => 2;
+  String nonNullableSame() => "bar";
   int? nullableBad<T>(T t) => 1;
+  int nonNullableBad<T>(T t) => 2;
 }
 
 double nonNullableVar = 4.0;
diff --git a/pkg/front_end/testcases/rasta/generic_factory.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/rasta/generic_factory.dart.textual_outline_modelled.expect
index 0bec190..449bfaf 100644
--- a/pkg/front_end/testcases/rasta/generic_factory.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/rasta/generic_factory.dart.textual_outline_modelled.expect
@@ -11,14 +11,14 @@
   factory B.b() = C<C2>;
 }
 
+class C<U> extends B<U> {
+  C() : super.internal();
+}
+
 class C1 {}
 
 class C2 {}
 
 class C3 {}
 
-class C<U> extends B<U> {
-  C() : super.internal();
-}
-
 main() {}
diff --git a/pkg/front_end/testcases/rasta/super_mixin.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/rasta/super_mixin.dart.textual_outline_modelled.expect
index 831a329..526090a 100644
--- a/pkg/front_end/testcases/rasta/super_mixin.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/rasta/super_mixin.dart.textual_outline_modelled.expect
@@ -1,9 +1,9 @@
 import "mixin_library.dart" show Mixin;
 
-class C2<V> = Super<V> with Mixin<V>;
-
 class C<V> extends Super<V> with Mixin<V> {}
 
+class C2<V> = Super<V> with Mixin<V>;
+
 class D extends Super with Mixin {}
 
 class D2 = Super with Mixin;
diff --git a/pkg/front_end/testcases/regress/issue_36793.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/regress/issue_36793.dart.textual_outline_modelled.expect
index aefe023..b3d1993 100644
--- a/pkg/front_end/testcases/regress/issue_36793.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/regress/issue_36793.dart.textual_outline_modelled.expect
@@ -1,6 +1,6 @@
+const int y = 42;
 @y
 int x = 1;
 @y
 int x = 2;
-const int y = 42;
 main() {}
diff --git a/pkg/front_end/testcases/value_class/explicit_mixin.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/value_class/explicit_mixin.dart.textual_outline_modelled.expect
index 9b4d462..bc846e0 100644
--- a/pkg/front_end/testcases/value_class/explicit_mixin.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/value_class/explicit_mixin.dart.textual_outline_modelled.expect
@@ -1,14 +1,13 @@
 @valueClass
 class A {}
 
-@valueClass
-class F = B with C;
-
 class B {}
 
 class C {}
 
 class D = A with B;
 class E = B with A;
+@valueClass
+class F = B with C;
 const String valueClass = "valueClass";
 main() {}
diff --git a/pkg/front_end/testcases/value_class/value_extends_non_value.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/value_class/value_extends_non_value.dart.textual_outline_modelled.expect
index 89b6414..e50bc0f 100644
--- a/pkg/front_end/testcases/value_class/value_extends_non_value.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/value_class/value_extends_non_value.dart.textual_outline_modelled.expect
@@ -1,9 +1,9 @@
-@valueClass
-class Cat extends Animal {}
-
 class Animal {
   final int numLegs;
 }
 
+@valueClass
+class Cat extends Animal {}
+
 const String valueClass = "valueClass";
 main() {}
diff --git a/pkg/front_end/testcases/value_class/value_extends_non_value_error.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/value_class/value_extends_non_value_error.dart.textual_outline_modelled.expect
index 9ee23d7..d7e499c 100644
--- a/pkg/front_end/testcases/value_class/value_extends_non_value_error.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/value_class/value_extends_non_value_error.dart.textual_outline_modelled.expect
@@ -1,9 +1,9 @@
-@valueClass
-class Cat extends Animal {}
-
 class Animal {
   int numLegs;
 }
 
+@valueClass
+class Cat extends Animal {}
+
 const String valueClass = "valueClass";
 main() {}
diff --git a/pkg/front_end/testcases/value_class/value_implements_non_value.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/value_class/value_implements_non_value.dart.textual_outline_modelled.expect
index ebcb028..650aa4b 100644
--- a/pkg/front_end/testcases/value_class/value_implements_non_value.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/value_class/value_implements_non_value.dart.textual_outline_modelled.expect
@@ -1,9 +1,9 @@
-@valueClass
-class Cat implements Animal {
+class Animal {
   final int numLegs;
 }
 
-class Animal {
+@valueClass
+class Cat implements Animal {
   final int numLegs;
 }
 
diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart
index e0a2ce2..b0ee0ae 100644
--- a/sdk/lib/async/stream.dart
+++ b/sdk/lib/async/stream.dart
@@ -2344,6 +2344,8 @@
    * Delivery can be delayed if other previously added events are
    * still pending delivery, if the subscription is paused,
    * or if the subscription isn't listening yet.
+   * If it's necessary to know whether the "done" event has been delievered,
+   * [done] future will complete when that has happend.
    */
   void closeSync();
 }
diff --git a/sdk/lib/async/stream_impl.dart b/sdk/lib/async/stream_impl.dart
index 942f4a7..52e5ca3 100644
--- a/sdk/lib/async/stream_impl.dart
+++ b/sdk/lib/async/stream_impl.dart
@@ -1122,15 +1122,22 @@
   _MultiStreamController() : super(null, null, null, null);
 
   void addSync(T data) {
-    _subscription._add(data);
+    if (!_mayAddEvent) throw _badEventState();
+    if (hasListener) _subscription._add(data);
   }
 
   void addErrorSync(Object error, [StackTrace? stackTrace]) {
-    _subscription._addError(error, stackTrace ?? StackTrace.empty);
+    if (!_mayAddEvent) throw _badEventState();
+    if (hasListener) {
+      _subscription._addError(error, stackTrace ?? StackTrace.empty);
+    }
   }
 
   void closeSync() {
-    _subscription._close();
+    if (isClosed) return;
+    if (!_mayAddEvent) throw _badEventState();
+    _state |= _StreamController._STATE_CLOSED;
+    if (hasListener) _subscription._close();
   }
 
   Stream<T> get stream {
diff --git a/tests/lib/async/stream_multi_test.dart b/tests/lib/async/stream_multi_test.dart
index e57c4f4..b2beb44 100644
--- a/tests/lib/async/stream_multi_test.dart
+++ b/tests/lib/async/stream_multi_test.dart
@@ -44,6 +44,7 @@
   testStreamsIndependent();
   asyncTest(testStreamNonOverlap);
   asyncTest(testRepeatLatest);
+  asyncTest(testIncorrectUse);
   asyncEnd();
 }
 
@@ -138,3 +139,79 @@
   var l3 = await f3;
   Expect.listEquals([2, 3], l3);
 }
+
+// Test that errors are thrown when required,
+// and use after cancel is ignored.
+Future<void> testIncorrectUse() async {
+  {
+    var lock = Completer();
+    var lock2 = Completer();
+    var stream = Stream.multi((c) async {
+      c.add(2);
+      await lock.future;
+      Expect.isTrue(!c.hasListener);
+      c.add(2);
+      c.addError("Error");
+      c.close();
+      // No adding after close.
+      Expect.throws<StateError>(() => c.add(3));
+      Expect.throws<StateError>(() => c.addSync(3));
+      Expect.throws<StateError>(() => c.addError("E"));
+      Expect.throws<StateError>(() => c.addErrorSync("E"));
+      Expect.throws<StateError>(() => c.addStream(Stream.empty()));
+      lock2.complete();
+    });
+    await for (var v in stream) {
+      Expect.equals(2, v);
+      break; // Cancels subscription.
+    }
+    lock.complete();
+    await lock2.future;
+  }
+
+  {
+    var lock = Completer();
+    var lock2 = Completer();
+    var stream = Stream.multi((c) async {
+      c.add(2);
+      await lock.future;
+      Expect.isTrue(!c.hasListener);
+      c.addSync(2);
+      c.addErrorSync("Error");
+      c.closeSync();
+      // No adding after close.
+      Expect.throws<StateError>(() => c.add(3));
+      Expect.throws<StateError>(() => c.addSync(3));
+      Expect.throws<StateError>(() => c.addError("E"));
+      Expect.throws<StateError>(() => c.addErrorSync("E"));
+      Expect.throws<StateError>(() => c.addStream(Stream.empty()));
+      lock2.complete();
+    });
+    await for (var v in stream) {
+      Expect.equals(2, v);
+      break; // Cancels subscription.
+    }
+    lock.complete();
+    await lock2.future;
+  }
+
+  {
+    var stream = Stream.multi((c) async {
+      var c2 = StreamController();
+      c.addStream(c2.stream);
+      // Now adding stream, cannot add events at the same time (for now!).
+      Expect.throws<StateError>(() => c.add(1));
+      Expect.throws<StateError>(() => c.addSync(1));
+      Expect.throws<StateError>(() => c.addError("Error"));
+      Expect.throws<StateError>(() => c.addErrorSync("Error"));
+      Expect.throws<StateError>(() => c.close());
+      Expect.throws<StateError>(() => c.closeSync());
+      await c2.close();
+      c.add(42);
+      c.close();
+    });
+    await for (var v in stream) {
+      Expect.equals(42, v);
+    }
+  }
+}
diff --git a/tools/VERSION b/tools/VERSION
index f256226..87839ca 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 49
+PRERELEASE 50
 PRERELEASE_PATCH 0
\ No newline at end of file