New summaries work snapshot.

1. Extract TokensWriter and TokensContext.

2. Type parameters for more declarations.

Change-Id: Iaeeaa5c8da98c9fa5dce65244ccb28e66d3656a1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96224
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
index f68c463..019c4ad 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -8,12 +8,12 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/ast/token.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/generated/utilities_dart.dart';
 import 'package:analyzer/src/summary/idl.dart';
 import 'package:analyzer/src/summary2/reference.dart';
+import 'package:analyzer/src/summary2/tokens_context.dart';
 
 /// Deserializer of fully resolved ASTs from flat buffers.
 class AstBinaryReader {
@@ -21,12 +21,10 @@
   final LinkedNodeReferences _linkedReferences;
   final List<Reference> _references;
 
-  final UnlinkedTokens _tokensBinary;
-  final List<Token> _tokens;
+  final TokensContext _tokensContext;
 
-  AstBinaryReader(this._nameRoot, this._linkedReferences, this._tokensBinary)
-      : _references = List<Reference>(_linkedReferences.name.length),
-        _tokens = List<Token>(_tokensBinary.type.length);
+  AstBinaryReader(this._nameRoot, this._linkedReferences, this._tokensContext)
+      : _references = List<Reference>(_linkedReferences.name.length);
 
   AstNode readNode(LinkedNode data) {
     if (data == null) return null;
@@ -269,19 +267,6 @@
     }
   }
 
-  CommentToken _getCommentToken(int index) {
-    var result = _getToken(index);
-    var token = result;
-    while (true) {
-      index = _tokensBinary.next[index];
-      if (index == 0) return result;
-
-      var nextToken = _getToken(index);
-      token.next = nextToken;
-      token = nextToken;
-    }
-  }
-
   T _getElement<T extends Element>(int index) {
     return _getReferenceByIndex(index)?.element;
   }
@@ -316,42 +301,7 @@
   }
 
   Token _getToken(int index) {
-    var token = _tokens[index];
-    if (token == null) {
-      var kind = _tokensBinary.kind[index];
-      switch (kind) {
-        case UnlinkedTokenKind.nothing:
-          return null;
-        case UnlinkedTokenKind.comment:
-          return CommentToken(
-            _binaryToAstTokenType(_tokensBinary.type[index]),
-            _tokensBinary.lexeme[index],
-            _tokensBinary.offset[index],
-          );
-        case UnlinkedTokenKind.keyword:
-          return KeywordToken(
-            _binaryToAstTokenType(_tokensBinary.type[index]),
-            _tokensBinary.offset[index],
-            _getCommentToken(_tokensBinary.precedingComment[index]),
-          );
-        case UnlinkedTokenKind.simple:
-          return SimpleToken(
-            _binaryToAstTokenType(_tokensBinary.type[index]),
-            _tokensBinary.offset[index],
-            _getCommentToken(_tokensBinary.precedingComment[index]),
-          );
-        case UnlinkedTokenKind.string:
-          return StringToken(
-            _binaryToAstTokenType(_tokensBinary.type[index]),
-            _tokensBinary.lexeme[index],
-            _tokensBinary.offset[index],
-            _getCommentToken(_tokensBinary.precedingComment[index]),
-          );
-        default:
-          throw UnimplementedError('Token kind: $kind');
-      }
-    }
-    return token;
+    return _tokensContext.tokenOfIndex(index);
   }
 
   List<Token> _getTokens(List<int> indexList) {
@@ -1523,279 +1473,4 @@
     }
     return result;
   }
-
-  static TokenType _binaryToAstTokenType(UnlinkedTokenType type) {
-    switch (type) {
-      case UnlinkedTokenType.ABSTRACT:
-        return Keyword.ABSTRACT;
-      case UnlinkedTokenType.AMPERSAND:
-        return TokenType.AMPERSAND;
-      case UnlinkedTokenType.AMPERSAND_AMPERSAND:
-        return TokenType.AMPERSAND_AMPERSAND;
-      case UnlinkedTokenType.AMPERSAND_EQ:
-        return TokenType.AMPERSAND_EQ;
-      case UnlinkedTokenType.AS:
-        return TokenType.AS;
-      case UnlinkedTokenType.ASSERT:
-        return Keyword.ASSERT;
-      case UnlinkedTokenType.ASYNC:
-        return Keyword.ASYNC;
-      case UnlinkedTokenType.AT:
-        return TokenType.AT;
-      case UnlinkedTokenType.AWAIT:
-        return Keyword.AWAIT;
-      case UnlinkedTokenType.BACKPING:
-        return TokenType.BACKPING;
-      case UnlinkedTokenType.BACKSLASH:
-        return TokenType.BACKSLASH;
-      case UnlinkedTokenType.BANG:
-        return TokenType.BANG;
-      case UnlinkedTokenType.BANG_EQ:
-        return TokenType.BANG_EQ;
-      case UnlinkedTokenType.BAR:
-        return TokenType.BAR;
-      case UnlinkedTokenType.BAR_BAR:
-        return TokenType.BAR_BAR;
-      case UnlinkedTokenType.BAR_EQ:
-        return TokenType.BAR_EQ;
-      case UnlinkedTokenType.BREAK:
-        return Keyword.BREAK;
-      case UnlinkedTokenType.CARET:
-        return TokenType.CARET;
-      case UnlinkedTokenType.CARET_EQ:
-        return TokenType.CARET_EQ;
-      case UnlinkedTokenType.CASE:
-        return Keyword.CASE;
-      case UnlinkedTokenType.CATCH:
-        return Keyword.CATCH;
-      case UnlinkedTokenType.CLASS:
-        return Keyword.CLASS;
-      case UnlinkedTokenType.CLOSE_CURLY_BRACKET:
-        return TokenType.CLOSE_CURLY_BRACKET;
-      case UnlinkedTokenType.CLOSE_PAREN:
-        return TokenType.CLOSE_PAREN;
-      case UnlinkedTokenType.CLOSE_SQUARE_BRACKET:
-        return TokenType.CLOSE_SQUARE_BRACKET;
-      case UnlinkedTokenType.COLON:
-        return TokenType.COLON;
-      case UnlinkedTokenType.COMMA:
-        return TokenType.COMMA;
-      case UnlinkedTokenType.CONST:
-        return Keyword.CONST;
-      case UnlinkedTokenType.CONTINUE:
-        return Keyword.CONTINUE;
-      case UnlinkedTokenType.COVARIANT:
-        return Keyword.COVARIANT;
-      case UnlinkedTokenType.DEFAULT:
-        return Keyword.DEFAULT;
-      case UnlinkedTokenType.DEFERRED:
-        return Keyword.DEFERRED;
-      case UnlinkedTokenType.DO:
-        return Keyword.DO;
-      case UnlinkedTokenType.DOUBLE:
-        return TokenType.DOUBLE;
-      case UnlinkedTokenType.DYNAMIC:
-        return Keyword.DYNAMIC;
-      case UnlinkedTokenType.ELSE:
-        return Keyword.ELSE;
-      case UnlinkedTokenType.ENUM:
-        return Keyword.ENUM;
-      case UnlinkedTokenType.EOF:
-        return TokenType.EOF;
-      case UnlinkedTokenType.EQ:
-        return TokenType.EQ;
-      case UnlinkedTokenType.EQ_EQ:
-        return TokenType.EQ_EQ;
-      case UnlinkedTokenType.EXPORT:
-        return Keyword.EXPORT;
-      case UnlinkedTokenType.EXTENDS:
-        return Keyword.EXTENDS;
-      case UnlinkedTokenType.EXTERNAL:
-        return Keyword.EXTERNAL;
-      case UnlinkedTokenType.FACTORY:
-        return Keyword.FACTORY;
-      case UnlinkedTokenType.FALSE:
-        return Keyword.FALSE;
-      case UnlinkedTokenType.FINAL:
-        return Keyword.FINAL;
-      case UnlinkedTokenType.FINALLY:
-        return Keyword.FINALLY;
-      case UnlinkedTokenType.FOR:
-        return Keyword.FOR;
-      case UnlinkedTokenType.FUNCTION:
-        return TokenType.FUNCTION;
-      case UnlinkedTokenType.FUNCTION_KEYWORD:
-        return Keyword.FUNCTION;
-      case UnlinkedTokenType.GET:
-        return Keyword.GET;
-      case UnlinkedTokenType.GT:
-        return TokenType.GT;
-      case UnlinkedTokenType.GT_EQ:
-        return TokenType.GT_EQ;
-      case UnlinkedTokenType.GT_GT:
-        return TokenType.GT_GT;
-      case UnlinkedTokenType.GT_GT_EQ:
-        return TokenType.GT_GT_EQ;
-      case UnlinkedTokenType.HASH:
-        return TokenType.HASH;
-      case UnlinkedTokenType.HEXADECIMAL:
-        return TokenType.HEXADECIMAL;
-      case UnlinkedTokenType.HIDE:
-        return Keyword.HIDE;
-      case UnlinkedTokenType.IDENTIFIER:
-        return TokenType.IDENTIFIER;
-      case UnlinkedTokenType.IF:
-        return Keyword.IF;
-      case UnlinkedTokenType.IMPLEMENTS:
-        return Keyword.IMPLEMENTS;
-      case UnlinkedTokenType.IMPORT:
-        return Keyword.IMPORT;
-      case UnlinkedTokenType.IN:
-        return Keyword.IN;
-      case UnlinkedTokenType.INDEX:
-        return TokenType.INDEX;
-      case UnlinkedTokenType.INDEX_EQ:
-        return TokenType.INDEX_EQ;
-      case UnlinkedTokenType.INT:
-        return TokenType.INT;
-      case UnlinkedTokenType.INTERFACE:
-        return Keyword.INTERFACE;
-      case UnlinkedTokenType.IS:
-        return TokenType.IS;
-      case UnlinkedTokenType.LIBRARY:
-        return Keyword.LIBRARY;
-      case UnlinkedTokenType.LT:
-        return TokenType.LT;
-      case UnlinkedTokenType.LT_EQ:
-        return TokenType.LT_EQ;
-      case UnlinkedTokenType.LT_LT:
-        return TokenType.LT_LT;
-      case UnlinkedTokenType.LT_LT_EQ:
-        return TokenType.LT_LT_EQ;
-      case UnlinkedTokenType.MINUS:
-        return TokenType.MINUS;
-      case UnlinkedTokenType.MINUS_EQ:
-        return TokenType.MINUS_EQ;
-      case UnlinkedTokenType.MINUS_MINUS:
-        return TokenType.MINUS_MINUS;
-      case UnlinkedTokenType.MIXIN:
-        return Keyword.MIXIN;
-      case UnlinkedTokenType.MULTI_LINE_COMMENT:
-        return TokenType.MULTI_LINE_COMMENT;
-      case UnlinkedTokenType.NATIVE:
-        return Keyword.NATIVE;
-      case UnlinkedTokenType.NEW:
-        return Keyword.NEW;
-      case UnlinkedTokenType.NULL:
-        return Keyword.NULL;
-      case UnlinkedTokenType.OF:
-        return Keyword.OF;
-      case UnlinkedTokenType.ON:
-        return Keyword.ON;
-      case UnlinkedTokenType.OPEN_CURLY_BRACKET:
-        return TokenType.OPEN_CURLY_BRACKET;
-      case UnlinkedTokenType.OPEN_PAREN:
-        return TokenType.OPEN_PAREN;
-      case UnlinkedTokenType.OPEN_SQUARE_BRACKET:
-        return TokenType.OPEN_SQUARE_BRACKET;
-      case UnlinkedTokenType.OPERATOR:
-        return Keyword.OPERATOR;
-      case UnlinkedTokenType.PART:
-        return Keyword.PART;
-      case UnlinkedTokenType.PATCH:
-        return Keyword.PATCH;
-      case UnlinkedTokenType.PERCENT:
-        return TokenType.PERCENT;
-      case UnlinkedTokenType.PERCENT_EQ:
-        return TokenType.PERCENT_EQ;
-      case UnlinkedTokenType.PERIOD:
-        return TokenType.PERIOD;
-      case UnlinkedTokenType.PERIOD_PERIOD:
-        return TokenType.PERIOD_PERIOD;
-      case UnlinkedTokenType.PERIOD_PERIOD_PERIOD:
-        return TokenType.PERIOD_PERIOD_PERIOD;
-      case UnlinkedTokenType.PERIOD_PERIOD_PERIOD_QUESTION:
-        return TokenType.PERIOD_PERIOD_PERIOD_QUESTION;
-      case UnlinkedTokenType.PLUS:
-        return TokenType.PLUS;
-      case UnlinkedTokenType.PLUS_EQ:
-        return TokenType.PLUS_EQ;
-      case UnlinkedTokenType.PLUS_PLUS:
-        return TokenType.PLUS_PLUS;
-      case UnlinkedTokenType.QUESTION:
-        return TokenType.QUESTION;
-      case UnlinkedTokenType.QUESTION_PERIOD:
-        return TokenType.QUESTION_PERIOD;
-      case UnlinkedTokenType.QUESTION_QUESTION:
-        return TokenType.QUESTION_QUESTION;
-      case UnlinkedTokenType.QUESTION_QUESTION_EQ:
-        return TokenType.QUESTION_QUESTION_EQ;
-      case UnlinkedTokenType.RETHROW:
-        return Keyword.RETHROW;
-      case UnlinkedTokenType.RETURN:
-        return Keyword.RETURN;
-      case UnlinkedTokenType.SCRIPT_TAG:
-        return TokenType.SCRIPT_TAG;
-      case UnlinkedTokenType.SEMICOLON:
-        return TokenType.SEMICOLON;
-      case UnlinkedTokenType.SET:
-        return Keyword.SET;
-      case UnlinkedTokenType.SHOW:
-        return Keyword.SHOW;
-      case UnlinkedTokenType.SINGLE_LINE_COMMENT:
-        return TokenType.SINGLE_LINE_COMMENT;
-      case UnlinkedTokenType.SLASH:
-        return TokenType.SLASH;
-      case UnlinkedTokenType.SLASH_EQ:
-        return TokenType.SLASH_EQ;
-      case UnlinkedTokenType.SOURCE:
-        return Keyword.SOURCE;
-      case UnlinkedTokenType.STAR:
-        return TokenType.STAR;
-      case UnlinkedTokenType.STAR_EQ:
-        return TokenType.STAR_EQ;
-      case UnlinkedTokenType.STATIC:
-        return Keyword.STATIC;
-      case UnlinkedTokenType.STRING:
-        return TokenType.STRING;
-      case UnlinkedTokenType.STRING_INTERPOLATION_EXPRESSION:
-        return TokenType.STRING_INTERPOLATION_EXPRESSION;
-      case UnlinkedTokenType.STRING_INTERPOLATION_IDENTIFIER:
-        return TokenType.STRING_INTERPOLATION_IDENTIFIER;
-      case UnlinkedTokenType.SUPER:
-        return Keyword.SUPER;
-      case UnlinkedTokenType.SWITCH:
-        return Keyword.SWITCH;
-      case UnlinkedTokenType.SYNC:
-        return Keyword.SYNC;
-      case UnlinkedTokenType.THIS:
-        return Keyword.THIS;
-      case UnlinkedTokenType.THROW:
-        return Keyword.THROW;
-      case UnlinkedTokenType.TILDE:
-        return TokenType.TILDE;
-      case UnlinkedTokenType.TILDE_SLASH:
-        return TokenType.TILDE_SLASH;
-      case UnlinkedTokenType.TILDE_SLASH_EQ:
-        return TokenType.TILDE_SLASH_EQ;
-      case UnlinkedTokenType.TRUE:
-        return Keyword.TRUE;
-      case UnlinkedTokenType.TRY:
-        return Keyword.TRY;
-      case UnlinkedTokenType.TYPEDEF:
-        return Keyword.TYPEDEF;
-      case UnlinkedTokenType.VAR:
-        return Keyword.VAR;
-      case UnlinkedTokenType.VOID:
-        return Keyword.VOID;
-      case UnlinkedTokenType.WHILE:
-        return Keyword.WHILE;
-      case UnlinkedTokenType.WITH:
-        return Keyword.WITH;
-      case UnlinkedTokenType.YIELD:
-        return Keyword.YIELD;
-      default:
-        throw StateError('Unexpected type: $type');
-    }
-  }
 }
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
index 49d56ef..cf692e9 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -8,40 +8,28 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/ast/token.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/summary/format.dart';
 import 'package:analyzer/src/summary/idl.dart';
 import 'package:analyzer/src/summary2/reference.dart';
-import 'package:meta/meta.dart';
+import 'package:analyzer/src/summary2/tokens_context.dart';
 
 /// Serializer of fully resolved ASTs into flat buffers.
 class AstBinaryWriter extends ThrowingAstVisitor<LinkedNodeBuilder> {
+  final TokensContext tokens;
+
   final referenceRoot = Reference.root();
   final referencesBuilder = LinkedNodeReferencesBuilder();
   final _references = <Reference>[];
 
-  final UnlinkedTokensBuilder tokens = UnlinkedTokensBuilder();
-  final Map<Token, int> _tokenMap = Map.identity();
-  int _tokenIndex = 0;
-
   /// This field is set temporary while visiting [FieldDeclaration] or
   /// [TopLevelVariableDeclaration] to store data shared among all variables
   /// in these declarations.
   LinkedNodeVariablesDeclarationBuilder _variablesDeclaration;
 
-  AstBinaryWriter() {
+  AstBinaryWriter(this.tokens) {
     _references.add(referenceRoot);
-    _addToken(
-      isSynthetic: true,
-      kind: UnlinkedTokenKind.nothing,
-      length: 0,
-      lexeme: '',
-      offset: 0,
-      precedingComment: 0,
-      type: UnlinkedTokenType.NOTHING,
-    );
   }
 
   @override
@@ -825,6 +813,7 @@
       methodDeclaration_operatorKeyword: _getToken(node.operatorKeyword),
       methodDeclaration_propertyKeyword: _getToken(node.propertyKeyword),
       methodDeclaration_returnType: node.returnType?.accept(this),
+      methodDeclaration_typeParameters: node.typeParameters?.accept(this),
     );
     _storeClassMember(builder, node);
     return builder;
@@ -1276,7 +1265,6 @@
   }
 
   LinkedNodeBuilder writeNode(AstNode node) {
-    _writeTokens(node.beginToken, node.endToken);
     return node.accept(this);
   }
 
@@ -1288,27 +1276,6 @@
     }
   }
 
-  void _addToken({
-    @required bool isSynthetic,
-    @required UnlinkedTokenKind kind,
-    @required int length,
-    @required String lexeme,
-    @required int offset,
-    @required int precedingComment,
-    @required UnlinkedTokenType type,
-  }) {
-    tokens.endGroup.add(0);
-    tokens.isSynthetic.add(isSynthetic);
-    tokens.kind.add(kind);
-    tokens.length.add(length);
-    tokens.lexeme.add(lexeme);
-    tokens.next.add(0);
-    tokens.offset.add(offset);
-    tokens.precedingComment.add(precedingComment);
-    tokens.type.add(type);
-    _tokenIndex++;
-  }
-
   void _ensureReferenceIndex(Reference reference) {
     if (reference.index == null) {
       reference.index = _references.length;
@@ -1453,13 +1420,7 @@
   }
 
   int _getToken(Token token) {
-    if (token == null) return 0;
-
-    var index = _tokenMap[token];
-    if (index == null) {
-      throw StateError('Token must be written first: $token');
-    }
-    return index;
+    return tokens.indexOfToken(token);
   }
 
   List<int> _getTokens(List<Token> tokenList) {
@@ -1636,35 +1597,6 @@
       ..uriBasedDirective_uriElement = _getReference(node.uriElement).index;
   }
 
-  int _writeCommentToken(CommentToken token) {
-    if (token == null) return 0;
-    var firstIndex = _tokenIndex;
-
-    var previousIndex = 0;
-    while (token != null) {
-      var index = _tokenIndex;
-      _tokenMap[token] = index;
-      _addToken(
-        isSynthetic: false,
-        kind: UnlinkedTokenKind.comment,
-        length: token.length,
-        lexeme: token.lexeme,
-        offset: token.offset,
-        precedingComment: 0,
-        type: _astToBinaryTokenType(token.type),
-      );
-
-      if (previousIndex != 0) {
-        tokens.next[previousIndex] = index;
-      }
-      previousIndex = index;
-
-      token = token.next;
-    }
-
-    return firstIndex;
-  }
-
   List<LinkedNodeBuilder> _writeNodeList(List<AstNode> nodeList) {
     var result = List<LinkedNodeBuilder>.filled(
       nodeList.length,
@@ -1677,80 +1609,6 @@
     return result;
   }
 
-  int _writeToken(Token token) {
-    assert(_tokenMap[token] == null);
-
-    var commentIndex = _writeCommentToken(token.precedingComments);
-
-    var index = _tokenIndex;
-    _tokenMap[token] = index;
-
-    if (token is KeywordToken) {
-      _addToken(
-        isSynthetic: token.isSynthetic,
-        kind: UnlinkedTokenKind.keyword,
-        lexeme: token.lexeme,
-        offset: token.offset,
-        length: token.length,
-        precedingComment: commentIndex,
-        type: _astToBinaryTokenType(token.type),
-      );
-    } else if (token is StringToken) {
-      _addToken(
-        isSynthetic: token.isSynthetic,
-        kind: UnlinkedTokenKind.string,
-        lexeme: token.lexeme,
-        offset: token.offset,
-        length: token.length,
-        precedingComment: commentIndex,
-        type: _astToBinaryTokenType(token.type),
-      );
-    } else if (token is SimpleToken) {
-      _addToken(
-        isSynthetic: token.isSynthetic,
-        kind: UnlinkedTokenKind.simple,
-        lexeme: token.lexeme,
-        offset: token.offset,
-        length: token.length,
-        precedingComment: commentIndex,
-        type: _astToBinaryTokenType(token.type),
-      );
-    } else {
-      throw UnimplementedError('(${token.runtimeType}) $token');
-    }
-
-    return index;
-  }
-
-  /// Write all the tokens from the [first] to the [last] inclusively.
-  void _writeTokens(Token first, Token last) {
-    if (first is CommentToken) {
-      first = (first as CommentToken).parent;
-    }
-
-    var endGroupToBeginIndexMap = <Token, int>{};
-    var previousIndex = 0;
-    for (var token = first;; token = token.next) {
-      var index = _writeToken(token);
-
-      if (previousIndex != 0) {
-        tokens.next[previousIndex] = index;
-      }
-      previousIndex = index;
-
-      if (token.endGroup != null) {
-        endGroupToBeginIndexMap[token.endGroup] = index;
-      }
-
-      var beginIndex = endGroupToBeginIndexMap[token];
-      if (beginIndex != null) {
-        tokens.endGroup[beginIndex] = index;
-      }
-
-      if (token == last) break;
-    }
-  }
-
   LinkedNodeTypeBuilder _writeType(DartType type) {
     if (type == null) return null;
 
@@ -1788,278 +1646,4 @@
       throw UnimplementedError('(${type.runtimeType}) $type');
     }
   }
-
-  static UnlinkedTokenType _astToBinaryTokenType(TokenType type) {
-    if (type == Keyword.ABSTRACT) {
-      return UnlinkedTokenType.ABSTRACT;
-    } else if (type == TokenType.AMPERSAND) {
-      return UnlinkedTokenType.AMPERSAND;
-    } else if (type == TokenType.AMPERSAND_AMPERSAND) {
-      return UnlinkedTokenType.AMPERSAND_AMPERSAND;
-    } else if (type == TokenType.AMPERSAND_EQ) {
-      return UnlinkedTokenType.AMPERSAND_EQ;
-    } else if (type == TokenType.AS) {
-      return UnlinkedTokenType.AS;
-    } else if (type == Keyword.ASSERT) {
-      return UnlinkedTokenType.ASSERT;
-    } else if (type == Keyword.ASYNC) {
-      return UnlinkedTokenType.ASYNC;
-    } else if (type == TokenType.AT) {
-      return UnlinkedTokenType.AT;
-    } else if (type == Keyword.AWAIT) {
-      return UnlinkedTokenType.AWAIT;
-    } else if (type == TokenType.BACKPING) {
-      return UnlinkedTokenType.BACKPING;
-    } else if (type == TokenType.BACKSLASH) {
-      return UnlinkedTokenType.BACKSLASH;
-    } else if (type == TokenType.BANG) {
-      return UnlinkedTokenType.BANG;
-    } else if (type == TokenType.BANG_EQ) {
-      return UnlinkedTokenType.BANG_EQ;
-    } else if (type == TokenType.BAR) {
-      return UnlinkedTokenType.BAR;
-    } else if (type == TokenType.BAR_BAR) {
-      return UnlinkedTokenType.BAR_BAR;
-    } else if (type == TokenType.BAR_EQ) {
-      return UnlinkedTokenType.BAR_EQ;
-    } else if (type == Keyword.BREAK) {
-      return UnlinkedTokenType.BREAK;
-    } else if (type == TokenType.CARET) {
-      return UnlinkedTokenType.CARET;
-    } else if (type == TokenType.CARET_EQ) {
-      return UnlinkedTokenType.CARET_EQ;
-    } else if (type == Keyword.CASE) {
-      return UnlinkedTokenType.CASE;
-    } else if (type == Keyword.CATCH) {
-      return UnlinkedTokenType.CATCH;
-    } else if (type == Keyword.CLASS) {
-      return UnlinkedTokenType.CLASS;
-    } else if (type == TokenType.CLOSE_CURLY_BRACKET) {
-      return UnlinkedTokenType.CLOSE_CURLY_BRACKET;
-    } else if (type == TokenType.CLOSE_PAREN) {
-      return UnlinkedTokenType.CLOSE_PAREN;
-    } else if (type == TokenType.CLOSE_SQUARE_BRACKET) {
-      return UnlinkedTokenType.CLOSE_SQUARE_BRACKET;
-    } else if (type == TokenType.COLON) {
-      return UnlinkedTokenType.COLON;
-    } else if (type == TokenType.COMMA) {
-      return UnlinkedTokenType.COMMA;
-    } else if (type == Keyword.CONST) {
-      return UnlinkedTokenType.CONST;
-    } else if (type == Keyword.CONTINUE) {
-      return UnlinkedTokenType.CONTINUE;
-    } else if (type == Keyword.COVARIANT) {
-      return UnlinkedTokenType.COVARIANT;
-    } else if (type == Keyword.DEFAULT) {
-      return UnlinkedTokenType.DEFAULT;
-    } else if (type == Keyword.DEFERRED) {
-      return UnlinkedTokenType.DEFERRED;
-    } else if (type == Keyword.DO) {
-      return UnlinkedTokenType.DO;
-    } else if (type == TokenType.DOUBLE) {
-      return UnlinkedTokenType.DOUBLE;
-    } else if (type == Keyword.DYNAMIC) {
-      return UnlinkedTokenType.DYNAMIC;
-    } else if (type == Keyword.ELSE) {
-      return UnlinkedTokenType.ELSE;
-    } else if (type == Keyword.ENUM) {
-      return UnlinkedTokenType.ENUM;
-    } else if (type == TokenType.EOF) {
-      return UnlinkedTokenType.EOF;
-    } else if (type == TokenType.EQ) {
-      return UnlinkedTokenType.EQ;
-    } else if (type == TokenType.EQ_EQ) {
-      return UnlinkedTokenType.EQ_EQ;
-    } else if (type == Keyword.EXPORT) {
-      return UnlinkedTokenType.EXPORT;
-    } else if (type == Keyword.EXTENDS) {
-      return UnlinkedTokenType.EXTENDS;
-    } else if (type == Keyword.EXTERNAL) {
-      return UnlinkedTokenType.EXTERNAL;
-    } else if (type == Keyword.FACTORY) {
-      return UnlinkedTokenType.FACTORY;
-    } else if (type == Keyword.FALSE) {
-      return UnlinkedTokenType.FALSE;
-    } else if (type == Keyword.FINAL) {
-      return UnlinkedTokenType.FINAL;
-    } else if (type == Keyword.FINALLY) {
-      return UnlinkedTokenType.FINALLY;
-    } else if (type == Keyword.FOR) {
-      return UnlinkedTokenType.FOR;
-    } else if (type == Keyword.FUNCTION) {
-      return UnlinkedTokenType.FUNCTION_KEYWORD;
-    } else if (type == TokenType.FUNCTION) {
-      return UnlinkedTokenType.FUNCTION;
-    } else if (type == Keyword.GET) {
-      return UnlinkedTokenType.GET;
-    } else if (type == TokenType.GT) {
-      return UnlinkedTokenType.GT;
-    } else if (type == TokenType.GT_EQ) {
-      return UnlinkedTokenType.GT_EQ;
-    } else if (type == TokenType.GT_GT) {
-      return UnlinkedTokenType.GT_GT;
-    } else if (type == TokenType.GT_GT_EQ) {
-      return UnlinkedTokenType.GT_GT_EQ;
-    } else if (type == TokenType.HASH) {
-      return UnlinkedTokenType.HASH;
-    } else if (type == TokenType.HEXADECIMAL) {
-      return UnlinkedTokenType.HEXADECIMAL;
-    } else if (type == Keyword.HIDE) {
-      return UnlinkedTokenType.HIDE;
-    } else if (type == TokenType.IDENTIFIER) {
-      return UnlinkedTokenType.IDENTIFIER;
-    } else if (type == Keyword.IF) {
-      return UnlinkedTokenType.IF;
-    } else if (type == Keyword.IMPLEMENTS) {
-      return UnlinkedTokenType.IMPLEMENTS;
-    } else if (type == Keyword.IMPORT) {
-      return UnlinkedTokenType.IMPORT;
-    } else if (type == Keyword.IN) {
-      return UnlinkedTokenType.IN;
-    } else if (type == TokenType.INDEX) {
-      return UnlinkedTokenType.INDEX;
-    } else if (type == TokenType.INDEX_EQ) {
-      return UnlinkedTokenType.INDEX_EQ;
-    } else if (type == TokenType.INT) {
-      return UnlinkedTokenType.INT;
-    } else if (type == Keyword.INTERFACE) {
-      return UnlinkedTokenType.INTERFACE;
-    } else if (type == TokenType.IS) {
-      return UnlinkedTokenType.IS;
-    } else if (type == Keyword.LIBRARY) {
-      return UnlinkedTokenType.LIBRARY;
-    } else if (type == TokenType.LT) {
-      return UnlinkedTokenType.LT;
-    } else if (type == TokenType.LT_EQ) {
-      return UnlinkedTokenType.LT_EQ;
-    } else if (type == TokenType.LT_LT) {
-      return UnlinkedTokenType.LT_LT;
-    } else if (type == TokenType.LT_LT_EQ) {
-      return UnlinkedTokenType.LT_LT_EQ;
-    } else if (type == TokenType.MINUS) {
-      return UnlinkedTokenType.MINUS;
-    } else if (type == TokenType.MINUS_EQ) {
-      return UnlinkedTokenType.MINUS_EQ;
-    } else if (type == TokenType.MINUS_MINUS) {
-      return UnlinkedTokenType.MINUS_MINUS;
-    } else if (type == Keyword.MIXIN) {
-      return UnlinkedTokenType.MIXIN;
-    } else if (type == TokenType.MULTI_LINE_COMMENT) {
-      return UnlinkedTokenType.MULTI_LINE_COMMENT;
-    } else if (type == Keyword.NATIVE) {
-      return UnlinkedTokenType.NATIVE;
-    } else if (type == Keyword.NEW) {
-      return UnlinkedTokenType.NEW;
-    } else if (type == Keyword.NULL) {
-      return UnlinkedTokenType.NULL;
-    } else if (type == Keyword.OF) {
-      return UnlinkedTokenType.OF;
-    } else if (type == Keyword.ON) {
-      return UnlinkedTokenType.ON;
-    } else if (type == TokenType.OPEN_CURLY_BRACKET) {
-      return UnlinkedTokenType.OPEN_CURLY_BRACKET;
-    } else if (type == TokenType.OPEN_PAREN) {
-      return UnlinkedTokenType.OPEN_PAREN;
-    } else if (type == TokenType.OPEN_SQUARE_BRACKET) {
-      return UnlinkedTokenType.OPEN_SQUARE_BRACKET;
-    } else if (type == Keyword.OPERATOR) {
-      return UnlinkedTokenType.OPERATOR;
-    } else if (type == Keyword.PART) {
-      return UnlinkedTokenType.PART;
-    } else if (type == Keyword.PATCH) {
-      return UnlinkedTokenType.PATCH;
-    } else if (type == TokenType.PERCENT) {
-      return UnlinkedTokenType.PERCENT;
-    } else if (type == TokenType.PERCENT_EQ) {
-      return UnlinkedTokenType.PERCENT_EQ;
-    } else if (type == TokenType.PERIOD) {
-      return UnlinkedTokenType.PERIOD;
-    } else if (type == TokenType.PERIOD_PERIOD) {
-      return UnlinkedTokenType.PERIOD_PERIOD;
-    } else if (type == TokenType.PERIOD_PERIOD_PERIOD) {
-      return UnlinkedTokenType.PERIOD_PERIOD_PERIOD;
-    } else if (type == TokenType.PERIOD_PERIOD_PERIOD_QUESTION) {
-      return UnlinkedTokenType.PERIOD_PERIOD_PERIOD_QUESTION;
-    } else if (type == TokenType.PLUS) {
-      return UnlinkedTokenType.PLUS;
-    } else if (type == TokenType.PLUS_EQ) {
-      return UnlinkedTokenType.PLUS_EQ;
-    } else if (type == TokenType.PLUS_PLUS) {
-      return UnlinkedTokenType.PLUS_PLUS;
-    } else if (type == TokenType.QUESTION) {
-      return UnlinkedTokenType.QUESTION;
-    } else if (type == TokenType.QUESTION_PERIOD) {
-      return UnlinkedTokenType.QUESTION_PERIOD;
-    } else if (type == TokenType.QUESTION_QUESTION) {
-      return UnlinkedTokenType.QUESTION_QUESTION;
-    } else if (type == TokenType.QUESTION_QUESTION_EQ) {
-      return UnlinkedTokenType.QUESTION_QUESTION_EQ;
-    } else if (type == Keyword.RETHROW) {
-      return UnlinkedTokenType.RETHROW;
-    } else if (type == Keyword.RETURN) {
-      return UnlinkedTokenType.RETURN;
-    } else if (type == TokenType.SCRIPT_TAG) {
-      return UnlinkedTokenType.SCRIPT_TAG;
-    } else if (type == TokenType.SEMICOLON) {
-      return UnlinkedTokenType.SEMICOLON;
-    } else if (type == Keyword.SET) {
-      return UnlinkedTokenType.SET;
-    } else if (type == Keyword.SHOW) {
-      return UnlinkedTokenType.SHOW;
-    } else if (type == TokenType.SINGLE_LINE_COMMENT) {
-      return UnlinkedTokenType.SINGLE_LINE_COMMENT;
-    } else if (type == TokenType.SLASH) {
-      return UnlinkedTokenType.SLASH;
-    } else if (type == TokenType.SLASH_EQ) {
-      return UnlinkedTokenType.SLASH_EQ;
-    } else if (type == Keyword.SOURCE) {
-      return UnlinkedTokenType.SOURCE;
-    } else if (type == TokenType.STAR) {
-      return UnlinkedTokenType.STAR;
-    } else if (type == TokenType.STAR_EQ) {
-      return UnlinkedTokenType.STAR_EQ;
-    } else if (type == Keyword.STATIC) {
-      return UnlinkedTokenType.STATIC;
-    } else if (type == TokenType.STRING) {
-      return UnlinkedTokenType.STRING;
-    } else if (type == TokenType.STRING_INTERPOLATION_EXPRESSION) {
-      return UnlinkedTokenType.STRING_INTERPOLATION_EXPRESSION;
-    } else if (type == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
-      return UnlinkedTokenType.STRING_INTERPOLATION_IDENTIFIER;
-    } else if (type == Keyword.SUPER) {
-      return UnlinkedTokenType.SUPER;
-    } else if (type == Keyword.SWITCH) {
-      return UnlinkedTokenType.SWITCH;
-    } else if (type == Keyword.SYNC) {
-      return UnlinkedTokenType.SYNC;
-    } else if (type == Keyword.THIS) {
-      return UnlinkedTokenType.THIS;
-    } else if (type == Keyword.THROW) {
-      return UnlinkedTokenType.THROW;
-    } else if (type == TokenType.TILDE) {
-      return UnlinkedTokenType.TILDE;
-    } else if (type == TokenType.TILDE_SLASH) {
-      return UnlinkedTokenType.TILDE_SLASH;
-    } else if (type == TokenType.TILDE_SLASH_EQ) {
-      return UnlinkedTokenType.TILDE_SLASH_EQ;
-    } else if (type == Keyword.TRUE) {
-      return UnlinkedTokenType.TRUE;
-    } else if (type == Keyword.TRY) {
-      return UnlinkedTokenType.TRY;
-    } else if (type == Keyword.TYPEDEF) {
-      return UnlinkedTokenType.TYPEDEF;
-    } else if (type == Keyword.VAR) {
-      return UnlinkedTokenType.VAR;
-    } else if (type == Keyword.VOID) {
-      return UnlinkedTokenType.VOID;
-    } else if (type == Keyword.WHILE) {
-      return UnlinkedTokenType.WHILE;
-    } else if (type == Keyword.WITH) {
-      return UnlinkedTokenType.WITH;
-    } else if (type == Keyword.YIELD) {
-      return UnlinkedTokenType.YIELD;
-    } else {
-      throw StateError('Unexpected type: $type');
-    }
-  }
 }
diff --git a/pkg/analyzer/lib/src/summary2/builder/source_library_builder.dart b/pkg/analyzer/lib/src/summary2/builder/source_library_builder.dart
index 55637af..671e14e 100644
--- a/pkg/analyzer/lib/src/summary2/builder/source_library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/builder/source_library_builder.dart
@@ -14,6 +14,7 @@
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/summary2/reference_resolver.dart';
 import 'package:analyzer/src/summary2/scope.dart';
+import 'package:analyzer/src/summary2/tokens_writer.dart';
 import 'package:analyzer/src/summary2/top_level_inference.dart';
 
 class SourceLibraryBuilder {
@@ -221,10 +222,19 @@
       var unit = libraryUnits[unitSource];
       definingUnit ??= unit;
 
-      var writer = AstBinaryWriter();
+      var tokensResult = TokensWriter().writeTokens(
+        unit.beginToken,
+        unit.endToken,
+      );
+      var tokensContext = tokensResult.toContext();
+
+      var writer = AstBinaryWriter(tokensContext);
       var unitNode = writer.writeNode(unit);
 
-      var unitContext = LinkedUnitContext(linker.bundleContext, writer.tokens);
+      var unitContext = LinkedUnitContext(
+        linker.bundleContext,
+        tokensContext,
+      );
       builder.units.add(
         UnitBuilder(unitSource.uri, unitContext, unitNode),
       );
@@ -232,7 +242,7 @@
       libraryNode.units.add(
         LinkedNodeUnitBuilder(
           uriStr: '${unitSource.uri}',
-          tokens: writer.tokens,
+          tokens: tokensResult.tokens,
           node: unitNode,
         ),
       );
diff --git a/pkg/analyzer/lib/src/summary2/linked_element_factory.dart b/pkg/analyzer/lib/src/summary2/linked_element_factory.dart
index 1b4c3e9..19be9f6 100644
--- a/pkg/analyzer/lib/src/summary2/linked_element_factory.dart
+++ b/pkg/analyzer/lib/src/summary2/linked_element_factory.dart
@@ -10,6 +10,7 @@
 import 'package:analyzer/src/summary2/linked_bundle_context.dart';
 import 'package:analyzer/src/summary2/linked_unit_context.dart';
 import 'package:analyzer/src/summary2/reference.dart';
+import 'package:analyzer/src/summary2/tokens_context.dart';
 
 class LinkedElementFactory {
   final AnalysisContext analysisContext;
@@ -106,9 +107,10 @@
     var unitContainerRef = reference.getChild('@unit');
     for (var unitData in node.units) {
       var unitSource = sourceFactory.forUri(unitData.uriStr);
+      var tokensContext = TokensContext(unitData.tokens);
       var unitElement = CompilationUnitElementImpl.forLinkedNode(
         libraryElement,
-        LinkedUnitContext(libraryData.context, unitData.tokens),
+        LinkedUnitContext(libraryData.context, tokensContext),
         unitContainerRef.getChild(unitData.uriStr),
         unitData.node,
       );
diff --git a/pkg/analyzer/lib/src/summary2/linked_unit_context.dart b/pkg/analyzer/lib/src/summary2/linked_unit_context.dart
index d26e9b0..10a52bb 100644
--- a/pkg/analyzer/lib/src/summary2/linked_unit_context.dart
+++ b/pkg/analyzer/lib/src/summary2/linked_unit_context.dart
@@ -8,13 +8,14 @@
 import 'package:analyzer/src/summary2/ast_binary_reader.dart';
 import 'package:analyzer/src/summary2/linked_bundle_context.dart';
 import 'package:analyzer/src/summary2/reference.dart';
+import 'package:analyzer/src/summary2/tokens_context.dart';
 
 /// The context of a unit - the context of the bundle, and the unit tokens.
 class LinkedUnitContext {
   final LinkedBundleContext bundleContext;
-  final UnlinkedTokens tokens;
+  final TokensContext tokensContext;
 
-  LinkedUnitContext(this.bundleContext, this.tokens);
+  LinkedUnitContext(this.bundleContext, this.tokensContext);
 
   Iterable<LinkedNode> classFields(LinkedNode class_) sync* {
     for (var declaration in class_.classOrMixinDeclaration_members) {
@@ -98,11 +99,11 @@
   }
 
   int getSimpleOffset(LinkedNode node) {
-    return tokens.offset[node.simpleIdentifier_token];
+    return tokensContext.offset(node.simpleIdentifier_token);
   }
 
   String getTokenLexeme(int token) {
-    return tokens.lexeme[token];
+    return tokensContext.lexeme(token);
   }
 
   DartType getType(LinkedNodeType linkedType) {
@@ -173,7 +174,7 @@
   }
 
   bool isConstKeyword(int token) {
-    return tokens.type[token] == UnlinkedTokenType.CONST;
+    return tokensContext.type(token) == UnlinkedTokenType.CONST;
   }
 
   bool isConstVariableList(LinkedNode node) {
@@ -202,7 +203,7 @@
   }
 
   bool isFinalKeyword(int token) {
-    return tokens.type[token] == UnlinkedTokenType.FINAL;
+    return tokensContext.type(token) == UnlinkedTokenType.FINAL;
   }
 
   bool isFinalVariableList(LinkedNode node) {
@@ -236,7 +237,7 @@
   }
 
   bool isLibraryKeyword(int token) {
-    return tokens.type[token] == UnlinkedTokenType.LIBRARY;
+    return tokensContext.type(token) == UnlinkedTokenType.LIBRARY;
   }
 
   bool isMethod(LinkedNode node) {
@@ -305,7 +306,7 @@
     var reader = AstBinaryReader(
       bundleContext.elementFactory.rootReference,
       bundleContext.referencesData,
-      tokens,
+      tokensContext,
     );
     return reader.readNode(linkedNode.variableDeclaration_initializer);
   }
@@ -336,10 +337,10 @@
   }
 
   bool _isGetToken(int token) {
-    return tokens.type[token] == UnlinkedTokenType.GET;
+    return tokensContext.type(token) == UnlinkedTokenType.GET;
   }
 
   bool _isSetToken(int token) {
-    return tokens.type[token] == UnlinkedTokenType.SET;
+    return tokensContext.type(token) == UnlinkedTokenType.SET;
   }
 }
diff --git a/pkg/analyzer/lib/src/summary2/reference_resolver.dart b/pkg/analyzer/lib/src/summary2/reference_resolver.dart
index f5e0e76..016c987 100644
--- a/pkg/analyzer/lib/src/summary2/reference_resolver.dart
+++ b/pkg/analyzer/lib/src/summary2/reference_resolver.dart
@@ -41,53 +41,61 @@
   }
 
   void _classDeclaration(LinkedNodeBuilder node) {
-    _node(node.classOrMixinDeclaration_typeParameters);
+    var name = unit.context.getUnitMemberName(node);
+    reference = reference.getChild('@class').getChild(name);
 
-    var extendsClause = node.classDeclaration_extendsClause;
-    if (extendsClause != null) {
-      _typeName(extendsClause.extendsClause_superclass);
-    } else {
-      // TODO(scheglov) add synthetic
-    }
-
-    _nodeList(
-      node.classDeclaration_withClause?.withClause_mixinTypes,
-    );
-
-    _nodeList(
-      node.classOrMixinDeclaration_implementsClause
-          ?.implementsClause_interfaces,
-    );
-
-    for (var field in node.classOrMixinDeclaration_members) {
-      if (field.kind != LinkedNodeKind.constructorDeclaration) {
-        _node(field);
+    var typeParameters = node.classOrMixinDeclaration_typeParameters;
+    _withTypeParameters(typeParameters, () {
+      var extendsClause = node.classDeclaration_extendsClause;
+      if (extendsClause != null) {
+        _typeName(extendsClause.extendsClause_superclass);
       }
-    }
-    for (var field in node.classOrMixinDeclaration_members) {
-      if (field.kind == LinkedNodeKind.constructorDeclaration) {
-        _node(field);
+
+      _nodeList(
+        node.classDeclaration_withClause?.withClause_mixinTypes,
+      );
+
+      _nodeList(
+        node.classOrMixinDeclaration_implementsClause
+            ?.implementsClause_interfaces,
+      );
+
+      for (var field in node.classOrMixinDeclaration_members) {
+        if (field.kind != LinkedNodeKind.constructorDeclaration) {
+          _node(field);
+        }
       }
-    }
+      for (var field in node.classOrMixinDeclaration_members) {
+        if (field.kind == LinkedNodeKind.constructorDeclaration) {
+          _node(field);
+        }
+      }
+    });
+
+    reference = reference.parent.parent;
   }
 
   void _classTypeAlias(LinkedNodeBuilder node) {
-    _node(node.classTypeAlias_typeParameters);
+    var name = unit.context.getUnitMemberName(node);
+    reference = reference.getChild('@class').getChild(name);
 
-    var superclass = node.classTypeAlias_superclass;
-    if (superclass != null) {
-      _typeName(superclass);
-    } else {
-      // TODO(scheglov) add synthetic
-    }
+    var typeParameters = node.classTypeAlias_typeParameters;
+    _withTypeParameters(typeParameters, () {
+      var superclass = node.classTypeAlias_superclass;
+      if (superclass != null) {
+        _typeName(superclass);
+      }
 
-    _nodeList(
-      node.classTypeAlias_withClause?.withClause_mixinTypes,
-    );
+      _nodeList(
+        node.classTypeAlias_withClause?.withClause_mixinTypes,
+      );
 
-    _nodeList(
-      node.classTypeAlias_implementsClause?.implementsClause_interfaces,
-    );
+      _nodeList(
+        node.classTypeAlias_implementsClause?.implementsClause_interfaces,
+      );
+    });
+
+    reference = reference.parent.parent;
   }
 
   void _compilationUnit(LinkedNodeBuilder node) {
@@ -124,48 +132,46 @@
   }
 
   void _functionDeclaration(LinkedNodeBuilder node) {
-    var returnType = node.functionDeclaration_returnType;
-    if (returnType != null) {
-      _node(returnType);
-      node.functionDeclaration_returnType2 = _getTypeAnnotationType(returnType);
-    } else {
-      node.functionDeclaration_returnType2 = _dynamicType;
-    }
+    var function = node.functionDeclaration_functionExpression;
+    var typeParameters = function.functionExpression_typeParameters;
+    _withTypeParameters(typeParameters, () {
+      var returnType = node.functionDeclaration_returnType;
+      if (returnType != null) {
+        _node(returnType);
+        node.functionDeclaration_returnType2 =
+            _getTypeAnnotationType(returnType);
+      } else {
+        node.functionDeclaration_returnType2 = _dynamicType;
+      }
 
-    _node(node.functionDeclaration_functionExpression);
+      _node(function.functionExpression_formalParameters);
+    });
   }
 
   void _functionExpression(LinkedNodeBuilder node) {
-    _node(node.functionExpression_typeParameters);
-    _node(node.functionExpression_formalParameters);
+    var typeParameters = node.functionExpression_typeParameters;
+    _withTypeParameters(typeParameters, () {
+      _node(node.functionExpression_formalParameters);
+    });
   }
 
   void _functionTypeAlias(LinkedNodeBuilder node) {
-    var name = unit.context.getSimpleName(
-      node.namedCompilationUnitMember_name,
-    );
+    var name = unit.context.getUnitMemberName(node);
     reference = reference.getChild('@typeAlias').getChild(name);
 
     var typeParameters = node.functionTypeAlias_typeParameters;
-    if (typeParameters != null) {
-      _newScopeTypeParameters(typeParameters);
-    }
+    _withTypeParameters(typeParameters, () {
+      var returnType = node.functionTypeAlias_returnType;
+      if (returnType != null) {
+        _node(returnType);
+        node.functionTypeAlias_returnType2 = _getTypeAnnotationType(returnType);
+      } else {
+        node.functionTypeAlias_returnType2 = _dynamicType;
+      }
 
-    _node(typeParameters);
+      _node(node.functionTypeAlias_formalParameters);
+    });
 
-    var returnType = node.functionTypeAlias_returnType;
-    if (returnType != null) {
-      _node(returnType);
-      node.functionTypeAlias_returnType2 = _getTypeAnnotationType(returnType);
-    } else {
-      node.functionTypeAlias_returnType2 = _dynamicType;
-    }
-
-    _node(node.functionTypeAlias_formalParameters);
-
-    if (typeParameters != null) {
-      scope = scope.parent;
-    }
     reference = reference.parent.parent;
   }
 
@@ -176,25 +182,19 @@
     reference = reference.getChild(name);
 
     var typeParameters = node.genericFunctionType_typeParameters;
-    if (typeParameters != null) {
-      _newScopeTypeParameters(typeParameters);
-    }
+    _withTypeParameters(typeParameters, () {
+      var returnType = node.genericFunctionType_returnType;
+      if (returnType != null) {
+        _node(returnType);
+        node.genericFunctionType_returnType2 =
+            _getTypeAnnotationType(returnType);
+      } else {
+        node.genericFunctionType_returnType2 = _dynamicType;
+      }
 
-    _node(typeParameters);
+      _node(node.genericFunctionType_formalParameters);
+    });
 
-    var returnType = node.genericFunctionType_returnType;
-    if (returnType != null) {
-      _node(returnType);
-      node.genericFunctionType_returnType2 = _getTypeAnnotationType(returnType);
-    } else {
-      node.genericFunctionType_returnType2 = _dynamicType;
-    }
-
-    _node(node.genericFunctionType_formalParameters);
-
-    if (typeParameters != null) {
-      scope = scope.parent;
-    }
     reference = reference.parent.parent;
   }
 
@@ -205,18 +205,10 @@
     reference = reference.getChild('@typeAlias').getChild(name);
 
     var typeParameters = node.genericTypeAlias_typeParameters;
-    if (typeParameters != null) {
-      _newScopeTypeParameters(typeParameters);
-    }
+    _withTypeParameters(typeParameters, () {
+      _node(node.genericTypeAlias_functionType);
+    });
 
-    var function = node.genericTypeAlias_functionType;
-
-    _node(typeParameters);
-    _node(function);
-
-    if (typeParameters != null) {
-      scope = scope.parent;
-    }
     reference = reference.parent.parent;
   }
 
@@ -232,28 +224,21 @@
   void _libraryDirective(LinkedNodeBuilder node) {}
 
   void _methodDeclaration(LinkedNodeBuilder node) {
-    _node(node.methodDeclaration_typeParameters);
+    var name = unit.context.getMethodName(node);
+    reference = reference.getChild('@method').getChild(name);
 
-    var returnType = node.methodDeclaration_returnType;
-    if (returnType != null) {
-      _node(returnType);
-      node.methodDeclaration_returnType2 = _getTypeAnnotationType(returnType);
-    }
+    var typeParameters = node.methodDeclaration_typeParameters;
+    _withTypeParameters(typeParameters, () {
+      var returnType = node.methodDeclaration_returnType;
+      if (returnType != null) {
+        _node(returnType);
+        node.methodDeclaration_returnType2 = _getTypeAnnotationType(returnType);
+      }
 
-    _node(node.methodDeclaration_formalParameters);
-  }
+      _node(node.methodDeclaration_formalParameters);
+    });
 
-  void _newScopeTypeParameters(LinkedNode typeParameterList) {
-    scope = Scope(this.scope, {});
-
-    var containerRef = this.reference.getChild('@typeParameter');
-    var typeParameters = typeParameterList.typeParameterList_typeParameters;
-    for (var typeParameter in typeParameters) {
-      var name = unit.context.getSimpleName(typeParameter.typeParameter_name);
-      var reference = containerRef.getChild(name);
-      reference.node = typeParameter;
-      scope.declare(name, Declaration(name, reference));
-    }
+    reference = reference.parent.parent;
   }
 
   void _node(LinkedNodeBuilder node) {
@@ -418,4 +403,30 @@
       }
     }
   }
+
+  /// Enter the type parameters scope, visit them, and run [f].
+  void _withTypeParameters(LinkedNode typeParameterList, void f()) {
+    if (typeParameterList == null) {
+      f();
+      return;
+    }
+
+    scope = Scope(this.scope, {});
+
+    var containerRef = this.reference.getChild('@typeParameter');
+    var typeParameters = typeParameterList.typeParameterList_typeParameters;
+    for (var typeParameter in typeParameters) {
+      var name = unit.context.getSimpleName(typeParameter.typeParameter_name);
+      var reference = containerRef.getChild(name);
+      reference.node = typeParameter;
+      scope.declare(name, Declaration(name, reference));
+    }
+
+    _node(typeParameterList);
+    f();
+
+    if (typeParameterList != null) {
+      scope = scope.parent;
+    }
+  }
 }
diff --git a/pkg/analyzer/lib/src/summary2/tokens_context.dart b/pkg/analyzer/lib/src/summary2/tokens_context.dart
new file mode 100644
index 0000000..ba5ea17
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary2/tokens_context.dart
@@ -0,0 +1,377 @@
+// 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 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/src/dart/ast/token.dart';
+import 'package:analyzer/src/summary/idl.dart';
+import 'package:analyzer/src/summary2/tokens_writer.dart';
+
+/// The context for reading or writing tokens.
+///
+/// Tokens cannot be compared, so tokens for [indexOfToken] must be previously
+/// received from [tokenOfIndex], or the context must be created from a
+/// [TokensResult] (the result of writing previously parsed tokens).
+class TokensContext {
+  final UnlinkedTokens _tokens;
+  final List<Token> _indexToToken;
+  final Map<Token, int> _tokenToIndex;
+
+  TokensContext(this._tokens)
+      : _indexToToken = List<Token>(_tokens.type.length),
+        _tokenToIndex = Map.identity();
+
+  TokensContext.fromResult(
+      this._tokens, this._indexToToken, this._tokenToIndex);
+
+  int indexOfToken(Token token) {
+    if (token == null) return 0;
+
+    var index = _tokenToIndex[token];
+    if (index == null) {
+      throw StateError('Unexpected token: $token');
+    }
+    return index;
+  }
+
+  String lexeme(int index) {
+    return _tokens.lexeme[index];
+  }
+
+  int offset(int index) {
+    return _tokens.offset[index];
+  }
+
+  Token tokenOfIndex(int index) {
+    if (index == 0) return null;
+
+    var token = _indexToToken[index];
+    if (token == null) {
+      var kind = _tokens.kind[index];
+      switch (kind) {
+        case UnlinkedTokenKind.nothing:
+          return null;
+        case UnlinkedTokenKind.comment:
+          return CommentToken(
+            _binaryToAstTokenType(_tokens.type[index]),
+            _tokens.lexeme[index],
+            _tokens.offset[index],
+          );
+        case UnlinkedTokenKind.keyword:
+          return KeywordToken(
+            _binaryToAstTokenType(_tokens.type[index]),
+            _tokens.offset[index],
+            _getCommentToken(_tokens.precedingComment[index]),
+          );
+        case UnlinkedTokenKind.simple:
+          return SimpleToken(
+            _binaryToAstTokenType(_tokens.type[index]),
+            _tokens.offset[index],
+            _getCommentToken(_tokens.precedingComment[index]),
+          );
+        case UnlinkedTokenKind.string:
+          return StringToken(
+            _binaryToAstTokenType(_tokens.type[index]),
+            _tokens.lexeme[index],
+            _tokens.offset[index],
+            _getCommentToken(_tokens.precedingComment[index]),
+          );
+        default:
+          throw UnimplementedError('Token kind: $kind');
+      }
+    }
+    return token;
+  }
+
+  UnlinkedTokenType type(int index) {
+    return _tokens.type[index];
+  }
+
+  CommentToken _getCommentToken(int index) {
+    var result = tokenOfIndex(index);
+    var token = result;
+    while (true) {
+      index = _tokens.next[index];
+      if (index == 0) return result;
+
+      var nextToken = tokenOfIndex(index);
+      token.next = nextToken;
+      token = nextToken;
+    }
+  }
+
+  static TokenType _binaryToAstTokenType(UnlinkedTokenType type) {
+    switch (type) {
+      case UnlinkedTokenType.ABSTRACT:
+        return Keyword.ABSTRACT;
+      case UnlinkedTokenType.AMPERSAND:
+        return TokenType.AMPERSAND;
+      case UnlinkedTokenType.AMPERSAND_AMPERSAND:
+        return TokenType.AMPERSAND_AMPERSAND;
+      case UnlinkedTokenType.AMPERSAND_EQ:
+        return TokenType.AMPERSAND_EQ;
+      case UnlinkedTokenType.AS:
+        return TokenType.AS;
+      case UnlinkedTokenType.ASSERT:
+        return Keyword.ASSERT;
+      case UnlinkedTokenType.ASYNC:
+        return Keyword.ASYNC;
+      case UnlinkedTokenType.AT:
+        return TokenType.AT;
+      case UnlinkedTokenType.AWAIT:
+        return Keyword.AWAIT;
+      case UnlinkedTokenType.BACKPING:
+        return TokenType.BACKPING;
+      case UnlinkedTokenType.BACKSLASH:
+        return TokenType.BACKSLASH;
+      case UnlinkedTokenType.BANG:
+        return TokenType.BANG;
+      case UnlinkedTokenType.BANG_EQ:
+        return TokenType.BANG_EQ;
+      case UnlinkedTokenType.BAR:
+        return TokenType.BAR;
+      case UnlinkedTokenType.BAR_BAR:
+        return TokenType.BAR_BAR;
+      case UnlinkedTokenType.BAR_EQ:
+        return TokenType.BAR_EQ;
+      case UnlinkedTokenType.BREAK:
+        return Keyword.BREAK;
+      case UnlinkedTokenType.CARET:
+        return TokenType.CARET;
+      case UnlinkedTokenType.CARET_EQ:
+        return TokenType.CARET_EQ;
+      case UnlinkedTokenType.CASE:
+        return Keyword.CASE;
+      case UnlinkedTokenType.CATCH:
+        return Keyword.CATCH;
+      case UnlinkedTokenType.CLASS:
+        return Keyword.CLASS;
+      case UnlinkedTokenType.CLOSE_CURLY_BRACKET:
+        return TokenType.CLOSE_CURLY_BRACKET;
+      case UnlinkedTokenType.CLOSE_PAREN:
+        return TokenType.CLOSE_PAREN;
+      case UnlinkedTokenType.CLOSE_SQUARE_BRACKET:
+        return TokenType.CLOSE_SQUARE_BRACKET;
+      case UnlinkedTokenType.COLON:
+        return TokenType.COLON;
+      case UnlinkedTokenType.COMMA:
+        return TokenType.COMMA;
+      case UnlinkedTokenType.CONST:
+        return Keyword.CONST;
+      case UnlinkedTokenType.CONTINUE:
+        return Keyword.CONTINUE;
+      case UnlinkedTokenType.COVARIANT:
+        return Keyword.COVARIANT;
+      case UnlinkedTokenType.DEFAULT:
+        return Keyword.DEFAULT;
+      case UnlinkedTokenType.DEFERRED:
+        return Keyword.DEFERRED;
+      case UnlinkedTokenType.DO:
+        return Keyword.DO;
+      case UnlinkedTokenType.DOUBLE:
+        return TokenType.DOUBLE;
+      case UnlinkedTokenType.DYNAMIC:
+        return Keyword.DYNAMIC;
+      case UnlinkedTokenType.ELSE:
+        return Keyword.ELSE;
+      case UnlinkedTokenType.ENUM:
+        return Keyword.ENUM;
+      case UnlinkedTokenType.EOF:
+        return TokenType.EOF;
+      case UnlinkedTokenType.EQ:
+        return TokenType.EQ;
+      case UnlinkedTokenType.EQ_EQ:
+        return TokenType.EQ_EQ;
+      case UnlinkedTokenType.EXPORT:
+        return Keyword.EXPORT;
+      case UnlinkedTokenType.EXTENDS:
+        return Keyword.EXTENDS;
+      case UnlinkedTokenType.EXTERNAL:
+        return Keyword.EXTERNAL;
+      case UnlinkedTokenType.FACTORY:
+        return Keyword.FACTORY;
+      case UnlinkedTokenType.FALSE:
+        return Keyword.FALSE;
+      case UnlinkedTokenType.FINAL:
+        return Keyword.FINAL;
+      case UnlinkedTokenType.FINALLY:
+        return Keyword.FINALLY;
+      case UnlinkedTokenType.FOR:
+        return Keyword.FOR;
+      case UnlinkedTokenType.FUNCTION:
+        return TokenType.FUNCTION;
+      case UnlinkedTokenType.FUNCTION_KEYWORD:
+        return Keyword.FUNCTION;
+      case UnlinkedTokenType.GET:
+        return Keyword.GET;
+      case UnlinkedTokenType.GT:
+        return TokenType.GT;
+      case UnlinkedTokenType.GT_EQ:
+        return TokenType.GT_EQ;
+      case UnlinkedTokenType.GT_GT:
+        return TokenType.GT_GT;
+      case UnlinkedTokenType.GT_GT_EQ:
+        return TokenType.GT_GT_EQ;
+      case UnlinkedTokenType.HASH:
+        return TokenType.HASH;
+      case UnlinkedTokenType.HEXADECIMAL:
+        return TokenType.HEXADECIMAL;
+      case UnlinkedTokenType.HIDE:
+        return Keyword.HIDE;
+      case UnlinkedTokenType.IDENTIFIER:
+        return TokenType.IDENTIFIER;
+      case UnlinkedTokenType.IF:
+        return Keyword.IF;
+      case UnlinkedTokenType.IMPLEMENTS:
+        return Keyword.IMPLEMENTS;
+      case UnlinkedTokenType.IMPORT:
+        return Keyword.IMPORT;
+      case UnlinkedTokenType.IN:
+        return Keyword.IN;
+      case UnlinkedTokenType.INDEX:
+        return TokenType.INDEX;
+      case UnlinkedTokenType.INDEX_EQ:
+        return TokenType.INDEX_EQ;
+      case UnlinkedTokenType.INT:
+        return TokenType.INT;
+      case UnlinkedTokenType.INTERFACE:
+        return Keyword.INTERFACE;
+      case UnlinkedTokenType.IS:
+        return TokenType.IS;
+      case UnlinkedTokenType.LIBRARY:
+        return Keyword.LIBRARY;
+      case UnlinkedTokenType.LT:
+        return TokenType.LT;
+      case UnlinkedTokenType.LT_EQ:
+        return TokenType.LT_EQ;
+      case UnlinkedTokenType.LT_LT:
+        return TokenType.LT_LT;
+      case UnlinkedTokenType.LT_LT_EQ:
+        return TokenType.LT_LT_EQ;
+      case UnlinkedTokenType.MINUS:
+        return TokenType.MINUS;
+      case UnlinkedTokenType.MINUS_EQ:
+        return TokenType.MINUS_EQ;
+      case UnlinkedTokenType.MINUS_MINUS:
+        return TokenType.MINUS_MINUS;
+      case UnlinkedTokenType.MIXIN:
+        return Keyword.MIXIN;
+      case UnlinkedTokenType.MULTI_LINE_COMMENT:
+        return TokenType.MULTI_LINE_COMMENT;
+      case UnlinkedTokenType.NATIVE:
+        return Keyword.NATIVE;
+      case UnlinkedTokenType.NEW:
+        return Keyword.NEW;
+      case UnlinkedTokenType.NULL:
+        return Keyword.NULL;
+      case UnlinkedTokenType.OF:
+        return Keyword.OF;
+      case UnlinkedTokenType.ON:
+        return Keyword.ON;
+      case UnlinkedTokenType.OPEN_CURLY_BRACKET:
+        return TokenType.OPEN_CURLY_BRACKET;
+      case UnlinkedTokenType.OPEN_PAREN:
+        return TokenType.OPEN_PAREN;
+      case UnlinkedTokenType.OPEN_SQUARE_BRACKET:
+        return TokenType.OPEN_SQUARE_BRACKET;
+      case UnlinkedTokenType.OPERATOR:
+        return Keyword.OPERATOR;
+      case UnlinkedTokenType.PART:
+        return Keyword.PART;
+      case UnlinkedTokenType.PATCH:
+        return Keyword.PATCH;
+      case UnlinkedTokenType.PERCENT:
+        return TokenType.PERCENT;
+      case UnlinkedTokenType.PERCENT_EQ:
+        return TokenType.PERCENT_EQ;
+      case UnlinkedTokenType.PERIOD:
+        return TokenType.PERIOD;
+      case UnlinkedTokenType.PERIOD_PERIOD:
+        return TokenType.PERIOD_PERIOD;
+      case UnlinkedTokenType.PERIOD_PERIOD_PERIOD:
+        return TokenType.PERIOD_PERIOD_PERIOD;
+      case UnlinkedTokenType.PERIOD_PERIOD_PERIOD_QUESTION:
+        return TokenType.PERIOD_PERIOD_PERIOD_QUESTION;
+      case UnlinkedTokenType.PLUS:
+        return TokenType.PLUS;
+      case UnlinkedTokenType.PLUS_EQ:
+        return TokenType.PLUS_EQ;
+      case UnlinkedTokenType.PLUS_PLUS:
+        return TokenType.PLUS_PLUS;
+      case UnlinkedTokenType.QUESTION:
+        return TokenType.QUESTION;
+      case UnlinkedTokenType.QUESTION_PERIOD:
+        return TokenType.QUESTION_PERIOD;
+      case UnlinkedTokenType.QUESTION_QUESTION:
+        return TokenType.QUESTION_QUESTION;
+      case UnlinkedTokenType.QUESTION_QUESTION_EQ:
+        return TokenType.QUESTION_QUESTION_EQ;
+      case UnlinkedTokenType.RETHROW:
+        return Keyword.RETHROW;
+      case UnlinkedTokenType.RETURN:
+        return Keyword.RETURN;
+      case UnlinkedTokenType.SCRIPT_TAG:
+        return TokenType.SCRIPT_TAG;
+      case UnlinkedTokenType.SEMICOLON:
+        return TokenType.SEMICOLON;
+      case UnlinkedTokenType.SET:
+        return Keyword.SET;
+      case UnlinkedTokenType.SHOW:
+        return Keyword.SHOW;
+      case UnlinkedTokenType.SINGLE_LINE_COMMENT:
+        return TokenType.SINGLE_LINE_COMMENT;
+      case UnlinkedTokenType.SLASH:
+        return TokenType.SLASH;
+      case UnlinkedTokenType.SLASH_EQ:
+        return TokenType.SLASH_EQ;
+      case UnlinkedTokenType.SOURCE:
+        return Keyword.SOURCE;
+      case UnlinkedTokenType.STAR:
+        return TokenType.STAR;
+      case UnlinkedTokenType.STAR_EQ:
+        return TokenType.STAR_EQ;
+      case UnlinkedTokenType.STATIC:
+        return Keyword.STATIC;
+      case UnlinkedTokenType.STRING:
+        return TokenType.STRING;
+      case UnlinkedTokenType.STRING_INTERPOLATION_EXPRESSION:
+        return TokenType.STRING_INTERPOLATION_EXPRESSION;
+      case UnlinkedTokenType.STRING_INTERPOLATION_IDENTIFIER:
+        return TokenType.STRING_INTERPOLATION_IDENTIFIER;
+      case UnlinkedTokenType.SUPER:
+        return Keyword.SUPER;
+      case UnlinkedTokenType.SWITCH:
+        return Keyword.SWITCH;
+      case UnlinkedTokenType.SYNC:
+        return Keyword.SYNC;
+      case UnlinkedTokenType.THIS:
+        return Keyword.THIS;
+      case UnlinkedTokenType.THROW:
+        return Keyword.THROW;
+      case UnlinkedTokenType.TILDE:
+        return TokenType.TILDE;
+      case UnlinkedTokenType.TILDE_SLASH:
+        return TokenType.TILDE_SLASH;
+      case UnlinkedTokenType.TILDE_SLASH_EQ:
+        return TokenType.TILDE_SLASH_EQ;
+      case UnlinkedTokenType.TRUE:
+        return Keyword.TRUE;
+      case UnlinkedTokenType.TRY:
+        return Keyword.TRY;
+      case UnlinkedTokenType.TYPEDEF:
+        return Keyword.TYPEDEF;
+      case UnlinkedTokenType.VAR:
+        return Keyword.VAR;
+      case UnlinkedTokenType.VOID:
+        return Keyword.VOID;
+      case UnlinkedTokenType.WHILE:
+        return Keyword.WHILE;
+      case UnlinkedTokenType.WITH:
+        return Keyword.WITH;
+      case UnlinkedTokenType.YIELD:
+        return Keyword.YIELD;
+      default:
+        throw StateError('Unexpected type: $type');
+    }
+  }
+}
diff --git a/pkg/analyzer/lib/src/summary2/tokens_writer.dart b/pkg/analyzer/lib/src/summary2/tokens_writer.dart
new file mode 100644
index 0000000..3a9e989
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary2/tokens_writer.dart
@@ -0,0 +1,445 @@
+// 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 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/src/dart/ast/token.dart';
+import 'package:analyzer/src/summary/format.dart';
+import 'package:analyzer/src/summary/idl.dart';
+import 'package:analyzer/src/summary2/tokens_context.dart';
+import 'package:meta/meta.dart';
+
+/// The result of writing a sequence of tokens.
+class TokensResult {
+  final UnlinkedTokensBuilder tokens;
+  final List<Token> _indexToToken;
+  final Map<Token, int> _tokenToIndex;
+
+  TokensResult(this.tokens, this._indexToToken, this._tokenToIndex);
+
+  TokensContext toContext() {
+    return TokensContext.fromResult(tokens, _indexToToken, _tokenToIndex);
+  }
+}
+
+class TokensWriter {
+  final UnlinkedTokensBuilder _tokens = UnlinkedTokensBuilder();
+  final List<Token> _indexToToken = [];
+  final Map<Token, int> _tokenToIndex = Map.identity();
+
+  TokensWriter() {
+    _addToken(
+      null,
+      isSynthetic: true,
+      kind: UnlinkedTokenKind.nothing,
+      length: 0,
+      lexeme: '',
+      offset: 0,
+      precedingComment: 0,
+      type: UnlinkedTokenType.NOTHING,
+    );
+  }
+
+  /// Write all the tokens from the [first] to the [last] inclusively.
+  TokensResult writeTokens(Token first, Token last) {
+    if (first is CommentToken) {
+      first = (first as CommentToken).parent;
+    }
+
+    var endGroupToBeginIndexMap = <Token, int>{};
+    var previousIndex = 0;
+    for (var token = first;; token = token.next) {
+      var index = _writeToken(token);
+
+      if (previousIndex != 0) {
+        _tokens.next[previousIndex] = index;
+      }
+      previousIndex = index;
+
+      if (token.endGroup != null) {
+        endGroupToBeginIndexMap[token.endGroup] = index;
+      }
+
+      var beginIndex = endGroupToBeginIndexMap[token];
+      if (beginIndex != null) {
+        _tokens.endGroup[beginIndex] = index;
+      }
+
+      if (token == last) break;
+    }
+
+    return TokensResult(_tokens, _indexToToken, _tokenToIndex);
+  }
+
+  int _addToken(
+    Token token, {
+    @required bool isSynthetic,
+    @required UnlinkedTokenKind kind,
+    @required int length,
+    @required String lexeme,
+    @required int offset,
+    @required int precedingComment,
+    @required UnlinkedTokenType type,
+  }) {
+    _tokens.endGroup.add(0);
+    _tokens.isSynthetic.add(isSynthetic);
+    _tokens.kind.add(kind);
+    _tokens.length.add(length);
+    _tokens.lexeme.add(lexeme);
+    _tokens.next.add(0);
+    _tokens.offset.add(offset);
+    _tokens.precedingComment.add(precedingComment);
+    _tokens.type.add(type);
+
+    var index = _indexToToken.length;
+    _indexToToken.add(token);
+    _tokenToIndex[token] = index;
+    return index;
+  }
+
+  int _writeCommentToken(CommentToken token) {
+    if (token == null) return 0;
+
+    int firstIndex = null;
+    var previousIndex = 0;
+    while (token != null) {
+      var index = _addToken(
+        token,
+        isSynthetic: false,
+        kind: UnlinkedTokenKind.comment,
+        length: token.length,
+        lexeme: token.lexeme,
+        offset: token.offset,
+        precedingComment: 0,
+        type: _astToBinaryTokenType(token.type),
+      );
+      firstIndex ??= index;
+
+      if (previousIndex != 0) {
+        _tokens.next[previousIndex] = index;
+      }
+      previousIndex = index;
+
+      token = token.next;
+    }
+
+    return firstIndex;
+  }
+
+  int _writeToken(Token token) {
+    assert(_tokenToIndex[token] == null);
+
+    var commentIndex = _writeCommentToken(token.precedingComments);
+
+    if (token is KeywordToken) {
+      return _addToken(
+        token,
+        isSynthetic: token.isSynthetic,
+        kind: UnlinkedTokenKind.keyword,
+        lexeme: token.lexeme,
+        offset: token.offset,
+        length: token.length,
+        precedingComment: commentIndex,
+        type: _astToBinaryTokenType(token.type),
+      );
+    } else if (token is StringToken) {
+      return _addToken(
+        token,
+        isSynthetic: token.isSynthetic,
+        kind: UnlinkedTokenKind.string,
+        lexeme: token.lexeme,
+        offset: token.offset,
+        length: token.length,
+        precedingComment: commentIndex,
+        type: _astToBinaryTokenType(token.type),
+      );
+    } else if (token is SimpleToken) {
+      return _addToken(
+        token,
+        isSynthetic: token.isSynthetic,
+        kind: UnlinkedTokenKind.simple,
+        lexeme: token.lexeme,
+        offset: token.offset,
+        length: token.length,
+        precedingComment: commentIndex,
+        type: _astToBinaryTokenType(token.type),
+      );
+    } else {
+      throw UnimplementedError('(${token.runtimeType}) $token');
+    }
+  }
+
+  static UnlinkedTokenType _astToBinaryTokenType(TokenType type) {
+    if (type == Keyword.ABSTRACT) {
+      return UnlinkedTokenType.ABSTRACT;
+    } else if (type == TokenType.AMPERSAND) {
+      return UnlinkedTokenType.AMPERSAND;
+    } else if (type == TokenType.AMPERSAND_AMPERSAND) {
+      return UnlinkedTokenType.AMPERSAND_AMPERSAND;
+    } else if (type == TokenType.AMPERSAND_EQ) {
+      return UnlinkedTokenType.AMPERSAND_EQ;
+    } else if (type == TokenType.AS) {
+      return UnlinkedTokenType.AS;
+    } else if (type == Keyword.ASSERT) {
+      return UnlinkedTokenType.ASSERT;
+    } else if (type == Keyword.ASYNC) {
+      return UnlinkedTokenType.ASYNC;
+    } else if (type == TokenType.AT) {
+      return UnlinkedTokenType.AT;
+    } else if (type == Keyword.AWAIT) {
+      return UnlinkedTokenType.AWAIT;
+    } else if (type == TokenType.BACKPING) {
+      return UnlinkedTokenType.BACKPING;
+    } else if (type == TokenType.BACKSLASH) {
+      return UnlinkedTokenType.BACKSLASH;
+    } else if (type == TokenType.BANG) {
+      return UnlinkedTokenType.BANG;
+    } else if (type == TokenType.BANG_EQ) {
+      return UnlinkedTokenType.BANG_EQ;
+    } else if (type == TokenType.BAR) {
+      return UnlinkedTokenType.BAR;
+    } else if (type == TokenType.BAR_BAR) {
+      return UnlinkedTokenType.BAR_BAR;
+    } else if (type == TokenType.BAR_EQ) {
+      return UnlinkedTokenType.BAR_EQ;
+    } else if (type == Keyword.BREAK) {
+      return UnlinkedTokenType.BREAK;
+    } else if (type == TokenType.CARET) {
+      return UnlinkedTokenType.CARET;
+    } else if (type == TokenType.CARET_EQ) {
+      return UnlinkedTokenType.CARET_EQ;
+    } else if (type == Keyword.CASE) {
+      return UnlinkedTokenType.CASE;
+    } else if (type == Keyword.CATCH) {
+      return UnlinkedTokenType.CATCH;
+    } else if (type == Keyword.CLASS) {
+      return UnlinkedTokenType.CLASS;
+    } else if (type == TokenType.CLOSE_CURLY_BRACKET) {
+      return UnlinkedTokenType.CLOSE_CURLY_BRACKET;
+    } else if (type == TokenType.CLOSE_PAREN) {
+      return UnlinkedTokenType.CLOSE_PAREN;
+    } else if (type == TokenType.CLOSE_SQUARE_BRACKET) {
+      return UnlinkedTokenType.CLOSE_SQUARE_BRACKET;
+    } else if (type == TokenType.COLON) {
+      return UnlinkedTokenType.COLON;
+    } else if (type == TokenType.COMMA) {
+      return UnlinkedTokenType.COMMA;
+    } else if (type == Keyword.CONST) {
+      return UnlinkedTokenType.CONST;
+    } else if (type == Keyword.CONTINUE) {
+      return UnlinkedTokenType.CONTINUE;
+    } else if (type == Keyword.COVARIANT) {
+      return UnlinkedTokenType.COVARIANT;
+    } else if (type == Keyword.DEFAULT) {
+      return UnlinkedTokenType.DEFAULT;
+    } else if (type == Keyword.DEFERRED) {
+      return UnlinkedTokenType.DEFERRED;
+    } else if (type == Keyword.DO) {
+      return UnlinkedTokenType.DO;
+    } else if (type == TokenType.DOUBLE) {
+      return UnlinkedTokenType.DOUBLE;
+    } else if (type == Keyword.DYNAMIC) {
+      return UnlinkedTokenType.DYNAMIC;
+    } else if (type == Keyword.ELSE) {
+      return UnlinkedTokenType.ELSE;
+    } else if (type == Keyword.ENUM) {
+      return UnlinkedTokenType.ENUM;
+    } else if (type == TokenType.EOF) {
+      return UnlinkedTokenType.EOF;
+    } else if (type == TokenType.EQ) {
+      return UnlinkedTokenType.EQ;
+    } else if (type == TokenType.EQ_EQ) {
+      return UnlinkedTokenType.EQ_EQ;
+    } else if (type == Keyword.EXPORT) {
+      return UnlinkedTokenType.EXPORT;
+    } else if (type == Keyword.EXTENDS) {
+      return UnlinkedTokenType.EXTENDS;
+    } else if (type == Keyword.EXTERNAL) {
+      return UnlinkedTokenType.EXTERNAL;
+    } else if (type == Keyword.FACTORY) {
+      return UnlinkedTokenType.FACTORY;
+    } else if (type == Keyword.FALSE) {
+      return UnlinkedTokenType.FALSE;
+    } else if (type == Keyword.FINAL) {
+      return UnlinkedTokenType.FINAL;
+    } else if (type == Keyword.FINALLY) {
+      return UnlinkedTokenType.FINALLY;
+    } else if (type == Keyword.FOR) {
+      return UnlinkedTokenType.FOR;
+    } else if (type == Keyword.FUNCTION) {
+      return UnlinkedTokenType.FUNCTION_KEYWORD;
+    } else if (type == TokenType.FUNCTION) {
+      return UnlinkedTokenType.FUNCTION;
+    } else if (type == Keyword.GET) {
+      return UnlinkedTokenType.GET;
+    } else if (type == TokenType.GT) {
+      return UnlinkedTokenType.GT;
+    } else if (type == TokenType.GT_EQ) {
+      return UnlinkedTokenType.GT_EQ;
+    } else if (type == TokenType.GT_GT) {
+      return UnlinkedTokenType.GT_GT;
+    } else if (type == TokenType.GT_GT_EQ) {
+      return UnlinkedTokenType.GT_GT_EQ;
+    } else if (type == TokenType.HASH) {
+      return UnlinkedTokenType.HASH;
+    } else if (type == TokenType.HEXADECIMAL) {
+      return UnlinkedTokenType.HEXADECIMAL;
+    } else if (type == Keyword.HIDE) {
+      return UnlinkedTokenType.HIDE;
+    } else if (type == TokenType.IDENTIFIER) {
+      return UnlinkedTokenType.IDENTIFIER;
+    } else if (type == Keyword.IF) {
+      return UnlinkedTokenType.IF;
+    } else if (type == Keyword.IMPLEMENTS) {
+      return UnlinkedTokenType.IMPLEMENTS;
+    } else if (type == Keyword.IMPORT) {
+      return UnlinkedTokenType.IMPORT;
+    } else if (type == Keyword.IN) {
+      return UnlinkedTokenType.IN;
+    } else if (type == TokenType.INDEX) {
+      return UnlinkedTokenType.INDEX;
+    } else if (type == TokenType.INDEX_EQ) {
+      return UnlinkedTokenType.INDEX_EQ;
+    } else if (type == TokenType.INT) {
+      return UnlinkedTokenType.INT;
+    } else if (type == Keyword.INTERFACE) {
+      return UnlinkedTokenType.INTERFACE;
+    } else if (type == TokenType.IS) {
+      return UnlinkedTokenType.IS;
+    } else if (type == Keyword.LIBRARY) {
+      return UnlinkedTokenType.LIBRARY;
+    } else if (type == TokenType.LT) {
+      return UnlinkedTokenType.LT;
+    } else if (type == TokenType.LT_EQ) {
+      return UnlinkedTokenType.LT_EQ;
+    } else if (type == TokenType.LT_LT) {
+      return UnlinkedTokenType.LT_LT;
+    } else if (type == TokenType.LT_LT_EQ) {
+      return UnlinkedTokenType.LT_LT_EQ;
+    } else if (type == TokenType.MINUS) {
+      return UnlinkedTokenType.MINUS;
+    } else if (type == TokenType.MINUS_EQ) {
+      return UnlinkedTokenType.MINUS_EQ;
+    } else if (type == TokenType.MINUS_MINUS) {
+      return UnlinkedTokenType.MINUS_MINUS;
+    } else if (type == Keyword.MIXIN) {
+      return UnlinkedTokenType.MIXIN;
+    } else if (type == TokenType.MULTI_LINE_COMMENT) {
+      return UnlinkedTokenType.MULTI_LINE_COMMENT;
+    } else if (type == Keyword.NATIVE) {
+      return UnlinkedTokenType.NATIVE;
+    } else if (type == Keyword.NEW) {
+      return UnlinkedTokenType.NEW;
+    } else if (type == Keyword.NULL) {
+      return UnlinkedTokenType.NULL;
+    } else if (type == Keyword.OF) {
+      return UnlinkedTokenType.OF;
+    } else if (type == Keyword.ON) {
+      return UnlinkedTokenType.ON;
+    } else if (type == TokenType.OPEN_CURLY_BRACKET) {
+      return UnlinkedTokenType.OPEN_CURLY_BRACKET;
+    } else if (type == TokenType.OPEN_PAREN) {
+      return UnlinkedTokenType.OPEN_PAREN;
+    } else if (type == TokenType.OPEN_SQUARE_BRACKET) {
+      return UnlinkedTokenType.OPEN_SQUARE_BRACKET;
+    } else if (type == Keyword.OPERATOR) {
+      return UnlinkedTokenType.OPERATOR;
+    } else if (type == Keyword.PART) {
+      return UnlinkedTokenType.PART;
+    } else if (type == Keyword.PATCH) {
+      return UnlinkedTokenType.PATCH;
+    } else if (type == TokenType.PERCENT) {
+      return UnlinkedTokenType.PERCENT;
+    } else if (type == TokenType.PERCENT_EQ) {
+      return UnlinkedTokenType.PERCENT_EQ;
+    } else if (type == TokenType.PERIOD) {
+      return UnlinkedTokenType.PERIOD;
+    } else if (type == TokenType.PERIOD_PERIOD) {
+      return UnlinkedTokenType.PERIOD_PERIOD;
+    } else if (type == TokenType.PERIOD_PERIOD_PERIOD) {
+      return UnlinkedTokenType.PERIOD_PERIOD_PERIOD;
+    } else if (type == TokenType.PERIOD_PERIOD_PERIOD_QUESTION) {
+      return UnlinkedTokenType.PERIOD_PERIOD_PERIOD_QUESTION;
+    } else if (type == TokenType.PLUS) {
+      return UnlinkedTokenType.PLUS;
+    } else if (type == TokenType.PLUS_EQ) {
+      return UnlinkedTokenType.PLUS_EQ;
+    } else if (type == TokenType.PLUS_PLUS) {
+      return UnlinkedTokenType.PLUS_PLUS;
+    } else if (type == TokenType.QUESTION) {
+      return UnlinkedTokenType.QUESTION;
+    } else if (type == TokenType.QUESTION_PERIOD) {
+      return UnlinkedTokenType.QUESTION_PERIOD;
+    } else if (type == TokenType.QUESTION_QUESTION) {
+      return UnlinkedTokenType.QUESTION_QUESTION;
+    } else if (type == TokenType.QUESTION_QUESTION_EQ) {
+      return UnlinkedTokenType.QUESTION_QUESTION_EQ;
+    } else if (type == Keyword.RETHROW) {
+      return UnlinkedTokenType.RETHROW;
+    } else if (type == Keyword.RETURN) {
+      return UnlinkedTokenType.RETURN;
+    } else if (type == TokenType.SCRIPT_TAG) {
+      return UnlinkedTokenType.SCRIPT_TAG;
+    } else if (type == TokenType.SEMICOLON) {
+      return UnlinkedTokenType.SEMICOLON;
+    } else if (type == Keyword.SET) {
+      return UnlinkedTokenType.SET;
+    } else if (type == Keyword.SHOW) {
+      return UnlinkedTokenType.SHOW;
+    } else if (type == TokenType.SINGLE_LINE_COMMENT) {
+      return UnlinkedTokenType.SINGLE_LINE_COMMENT;
+    } else if (type == TokenType.SLASH) {
+      return UnlinkedTokenType.SLASH;
+    } else if (type == TokenType.SLASH_EQ) {
+      return UnlinkedTokenType.SLASH_EQ;
+    } else if (type == Keyword.SOURCE) {
+      return UnlinkedTokenType.SOURCE;
+    } else if (type == TokenType.STAR) {
+      return UnlinkedTokenType.STAR;
+    } else if (type == TokenType.STAR_EQ) {
+      return UnlinkedTokenType.STAR_EQ;
+    } else if (type == Keyword.STATIC) {
+      return UnlinkedTokenType.STATIC;
+    } else if (type == TokenType.STRING) {
+      return UnlinkedTokenType.STRING;
+    } else if (type == TokenType.STRING_INTERPOLATION_EXPRESSION) {
+      return UnlinkedTokenType.STRING_INTERPOLATION_EXPRESSION;
+    } else if (type == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
+      return UnlinkedTokenType.STRING_INTERPOLATION_IDENTIFIER;
+    } else if (type == Keyword.SUPER) {
+      return UnlinkedTokenType.SUPER;
+    } else if (type == Keyword.SWITCH) {
+      return UnlinkedTokenType.SWITCH;
+    } else if (type == Keyword.SYNC) {
+      return UnlinkedTokenType.SYNC;
+    } else if (type == Keyword.THIS) {
+      return UnlinkedTokenType.THIS;
+    } else if (type == Keyword.THROW) {
+      return UnlinkedTokenType.THROW;
+    } else if (type == TokenType.TILDE) {
+      return UnlinkedTokenType.TILDE;
+    } else if (type == TokenType.TILDE_SLASH) {
+      return UnlinkedTokenType.TILDE_SLASH;
+    } else if (type == TokenType.TILDE_SLASH_EQ) {
+      return UnlinkedTokenType.TILDE_SLASH_EQ;
+    } else if (type == Keyword.TRUE) {
+      return UnlinkedTokenType.TRUE;
+    } else if (type == Keyword.TRY) {
+      return UnlinkedTokenType.TRY;
+    } else if (type == Keyword.TYPEDEF) {
+      return UnlinkedTokenType.TYPEDEF;
+    } else if (type == Keyword.VAR) {
+      return UnlinkedTokenType.VAR;
+    } else if (type == Keyword.VOID) {
+      return UnlinkedTokenType.VOID;
+    } else if (type == Keyword.WHILE) {
+      return UnlinkedTokenType.WHILE;
+    } else if (type == Keyword.WITH) {
+      return UnlinkedTokenType.WITH;
+    } else if (type == Keyword.YIELD) {
+      return UnlinkedTokenType.YIELD;
+    } else {
+      throw StateError('Unexpected type: $type');
+    }
+  }
+}
diff --git a/pkg/analyzer/lib/src/summary2/top_level_inference.dart b/pkg/analyzer/lib/src/summary2/top_level_inference.dart
index 51dfa9f..bb1c32d 100644
--- a/pkg/analyzer/lib/src/summary2/top_level_inference.dart
+++ b/pkg/analyzer/lib/src/summary2/top_level_inference.dart
@@ -139,7 +139,7 @@
     var reader = AstBinaryReader(
       unit.context.bundleContext.elementFactory.rootReference,
       unit.context.bundleContext.referencesData,
-      unit.context.tokens,
+      unit.context.tokensContext,
     );
 
     // TODO(scheglov) This duplicates `readInitializer` in LinkedUnitContext
diff --git a/pkg/analyzer/test/src/summary/resynthesize_ast2_test.dart b/pkg/analyzer/test/src/summary/resynthesize_ast2_test.dart
index 69da6c9..945a2b3 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_ast2_test.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_ast2_test.dart
@@ -1272,12 +1272,6 @@
 
   @override
   @failingTest
-  test_generic_gClass_gMethodStatic() async {
-    await super.test_generic_gClass_gMethodStatic();
-  }
-
-  @override
-  @failingTest
   test_genericFunction_asFunctionReturnType() async {
     await super.test_genericFunction_asFunctionReturnType();
   }
@@ -1422,13 +1416,6 @@
 
   @override
   @failingTest
-  test_inferred_function_type_in_generic_class_in_generic_method() async {
-    await super
-        .test_inferred_function_type_in_generic_class_in_generic_method();
-  }
-
-  @override
-  @failingTest
   test_inferred_type_is_typedef() async {
     await super.test_inferred_type_is_typedef();
   }
@@ -1914,18 +1901,6 @@
 
   @override
   @failingTest
-  test_method_type_parameter() async {
-    await super.test_method_type_parameter();
-  }
-
-  @override
-  @failingTest
-  test_method_type_parameter_in_generic_class() async {
-    await super.test_method_type_parameter_in_generic_class();
-  }
-
-  @override
-  @failingTest
   test_method_type_parameter_with_function_typed_parameter() async {
     await super.test_method_type_parameter_with_function_typed_parameter();
   }
@@ -1976,20 +1951,6 @@
 
   @override
   @failingTest
-  test_nested_generic_functions_in_generic_class_with_function_typed_params() async {
-    await super
-        .test_nested_generic_functions_in_generic_class_with_function_typed_params();
-  }
-
-  @override
-  @failingTest
-  test_nested_generic_functions_in_generic_class_with_local_variables() async {
-    await super
-        .test_nested_generic_functions_in_generic_class_with_local_variables();
-  }
-
-  @override
-  @failingTest
   test_parameter_covariant_inherited() async {
     await super.test_parameter_covariant_inherited();
   }
diff --git a/pkg/analyzer/test/src/summary2/ast_binary_writer_test.dart b/pkg/analyzer/test/src/summary2/ast_binary_writer_test.dart
index 8d1ad46..3bcd3ea 100644
--- a/pkg/analyzer/test/src/summary2/ast_binary_writer_test.dart
+++ b/pkg/analyzer/test/src/summary2/ast_binary_writer_test.dart
@@ -7,6 +7,7 @@
 import 'package:analyzer/src/summary2/ast_binary_reader.dart';
 import 'package:analyzer/src/summary2/ast_binary_writer.dart';
 import 'package:analyzer/src/summary2/reference.dart';
+import 'package:analyzer/src/summary2/tokens_writer.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -122,7 +123,13 @@
     var originalUnit = parseResult.unit;
     var originalCode = originalUnit.toSource();
 
-    var writer = new AstBinaryWriter();
+    var tokensResult = TokensWriter().writeTokens(
+      originalUnit.beginToken,
+      originalUnit.endToken,
+    );
+    var tokensContext = tokensResult.toContext();
+
+    var writer = new AstBinaryWriter(tokensContext);
     var builder = writer.writeNode(originalUnit);
     writer.writeReferences();