// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:typed_data';

import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/binary/binary_reader.dart';
import 'package:analyzer/src/binary/binary_writer.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/not_serializable_nodes.dart';
import 'package:analyzer/src/util/collection.dart';
import 'package:analyzer/src/util/comment.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';

Uint8List writeUnitInformative(CompilationUnit unit) {
  var info = _InfoBuilder().build(unit);
  var writer = BinaryWriter();
  info.write(writer);
  return writer.takeBytes();
}

class InformativeDataApplier {
  bool _shouldDeferApplyMembersOffsets = true;

  void applyFromNode(LibraryFragmentImpl fragment, CompilationUnit node) {
    _shouldDeferApplyMembersOffsets = false;
    var unitInfo = _InfoBuilder().build(node);
    _applyFromInfo(fragment, unitInfo);
  }

  void applyToLibrary(
    LinkedElementFactory elementFactory,
    LibraryElementImpl libraryElement,
    Map<Uri, Uint8List> unitsInformativeBytes,
  ) {
    if (elementFactory.isApplyingInformativeData) {
      throw StateError('Unexpected recursion.');
    }
    elementFactory.isApplyingInformativeData = true;

    for (var libraryFragment in libraryElement.internal.fragments) {
      var uri = libraryFragment.source.uri;
      if (unitsInformativeBytes[uri] case var infoBytes?) {
        _applyFromBytes(libraryFragment, infoBytes);
      }
    }

    elementFactory.isApplyingInformativeData = false;
  }

  void _applyFromBytes(
    LibraryFragmentImpl libraryFragment,
    Uint8List infoBytes,
  ) {
    var unitReader = BinaryReader(infoBytes);
    var unitInfo = _InfoUnit.read(unitReader);
    _applyFromInfo(libraryFragment, unitInfo);
  }

  void _applyFromInfo(LibraryFragmentImpl libraryFragment, _InfoUnit unitInfo) {
    var libraryElement = libraryFragment.library;
    if (identical(libraryElement.internal.firstFragment, libraryFragment)) {
      _applyToLibrary(libraryElement, unitInfo);
    }

    libraryFragment.setCodeRange(unitInfo.codeOffset, unitInfo.codeLength);
    libraryFragment.lineInfo = LineInfo(unitInfo.lineStarts);

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToImports(libraryFragment.libraryImports, unitInfo);
      _applyToExports(libraryFragment.libraryExports, unitInfo);
      _applyToPartIncludes(libraryFragment.parts, unitInfo);
    });

    libraryFragment.deferConstantOffsets(unitInfo.libraryConstantOffsets, (
      applier,
    ) {
      applier.applyToImports(libraryFragment.libraryImports);
      applier.applyToExports(libraryFragment.libraryExports);
      applier.applyToParts(libraryFragment.parts);
    });

    _applyToAccessors(
      libraryFragment.getters.withOriginDeclaration,
      unitInfo.topLevelGetters,
    );
    _applyToAccessors(
      libraryFragment.setters.withOriginDeclaration,
      unitInfo.topLevelSetters,
    );

    forCorrespondingPairs(
      libraryFragment.classes
          .where((fragment) => !fragment.isMixinApplication)
          .toList(),
      unitInfo.classDeclarations,
      _applyToClassDeclaration,
    );

    forCorrespondingPairs(
      libraryFragment.classes
          .where((fragment) => fragment.isMixinApplication)
          .toList(),
      unitInfo.classTypeAliases,
      _applyToClassTypeAlias,
    );

    forCorrespondingPairs(
      libraryFragment.enums,
      unitInfo.enums,
      _applyToEnumDeclaration,
    );

    forCorrespondingPairs(
      libraryFragment.extensions,
      unitInfo.extensions,
      _applyToExtensionDeclaration,
    );

    forCorrespondingPairs(
      libraryFragment.extensionTypes,
      unitInfo.extensionTypes,
      _applyToExtensionTypeDeclaration,
    );

    forCorrespondingPairs(
      libraryFragment.functions,
      unitInfo.topLevelFunctions,
      _applyToFunctionDeclaration,
    );

    forCorrespondingPairs(
      libraryFragment.mixins,
      unitInfo.mixinDeclarations,
      _applyToMixinDeclaration,
    );

    forCorrespondingPairs(
      libraryFragment.topLevelVariables.withOriginDeclaration,
      unitInfo.topLevelVariable,
      _applyToTopLevelVariable,
    );

    forCorrespondingPairs(
      libraryFragment.typeAliases.cast<TypeAliasFragmentImpl>().toList(),
      unitInfo.typeAliases,
      _applyToTypeAlias,
    );
  }

  void _applyToAccessors(
    List<PropertyAccessorFragmentImpl> fragmentList,
    List<_InfoExecutableDeclaration> infoList,
  ) {
    forCorrespondingPairs(fragmentList.withOriginDeclaration, infoList, (
      fragment,
      info,
    ) {
      fragment.setCodeRange(info.codeOffset, info.codeLength);
      fragment.firstTokenOffset = info.firstTokenOffset;
      fragment.nameOffset = info.nameOffset;
      fragment.documentationComment = info.documentationComment;

      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToFormalParameters(fragment.formalParameters, info.parameters);
      });

      fragment.deferConstantOffsets(info.constantOffsets, (applier) {
        applier.applyToMetadata(fragment.metadata);
        applier.applyToTypeParameters(fragment.typeParameters);
        applier.applyToFormalParameters(fragment.formalParameters);
      });
    });
  }

  void _applyToClassDeclaration(
    ClassFragmentImpl fragment,
    _InfoClassDeclaration info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });

    _scheduleApplyMembersOffsets(fragment, () {
      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToConstructors(fragment.constructors, info.constructors);
        _applyToFields(fragment.fields, info.fields);
        _applyToAccessors(fragment.getters, info.getters);
        _applyToAccessors(fragment.setters, info.setters);
        _applyToMethods(fragment.methods, info.methods);
      });
    });
  }

  void _applyToClassTypeAlias(
    ClassFragmentImpl fragment,
    _InfoClassTypeAlias info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });
  }

  void _applyToCombinators(
    List<NamespaceCombinator> elementList,
    List<_InfoCombinator> infoList,
  ) {
    forCorrespondingPairs<NamespaceCombinator, _InfoCombinator>(
      elementList,
      infoList,
      (element, info) {
        if (element is ShowElementCombinatorImpl) {
          element.offset = info.offset;
          element.end = info.end;
        }
        if (element is HideElementCombinatorImpl) {
          element.offset = info.offset;
          element.end = info.end;
        }
      },
    );
  }

  void _applyToConstructors(
    List<ConstructorFragmentImpl> fragmentList,
    List<_InfoConstructorDeclaration> infoList,
  ) {
    forCorrespondingPairs(fragmentList, infoList, (fragment, info) {
      fragment.setCodeRange(info.codeOffset, info.codeLength);
      fragment.newKeywordOffset = info.newKeywordOffset;
      fragment.factoryKeywordOffset = info.factoryKeywordOffset;
      fragment.typeNameOffset = info.typeNameOffset;
      fragment.periodOffset = info.periodOffset;
      fragment.firstTokenOffset = info.firstTokenOffset;
      fragment.nameEnd = info.nameEnd;
      fragment.nameOffset = info.nameOffset;
      fragment.thisKeywordOffset = info.thisKeywordOffset;
      fragment.documentationComment = info.documentationComment;

      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToFormalParameters(fragment.formalParameters, info.parameters);
      });

      fragment.deferConstantOffsets(info.constantOffsets, (applier) {
        applier.applyToMetadata(fragment.metadata);
        applier.applyToFormalParameters(fragment.formalParameters);
        applier.applyToConstructorInitializers(fragment);
      });
    });
  }

  void _applyToEnumDeclaration(
    EnumFragmentImpl fragment,
    _InfoEnumDeclaration info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    _scheduleApplyMembersOffsets(fragment, () {
      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToConstructors(fragment.constructors, info.constructors);
        _applyToFields(fragment.fields, info.fields);
        _applyToAccessors(fragment.getters, info.getters);
        _applyToAccessors(fragment.setters, info.setters);
        _applyToMethods(fragment.methods, info.methods);
      });
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });
  }

  void _applyToExports(List<LibraryExportImpl> exports, _InfoUnit info) {
    forCorrespondingPairs(exports, info.exports, (element, info) {
      element.exportKeywordOffset = info.exportKeywordOffset;
      _applyToCombinators(element.combinators, info.combinators);
    });
  }

  void _applyToExtensionDeclaration(
    ExtensionFragmentImpl fragment,
    _InfoExtensionDeclaration info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    _scheduleApplyMembersOffsets(fragment, () {
      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToFields(fragment.fields, info.fields);
        _applyToAccessors(fragment.getters, info.getters);
        _applyToAccessors(fragment.setters, info.setters);
        _applyToMethods(fragment.methods, info.methods);
      });
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });
  }

  void _applyToExtensionTypeDeclaration(
    ExtensionTypeFragmentImpl fragment,
    _InfoExtensionTypeDeclaration info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    _scheduleApplyMembersOffsets(fragment, () {
      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToFields(fragment.fields, info.fields);
        _applyToConstructors(fragment.constructors, info.constructors);
        _applyToAccessors(fragment.getters, info.getters);
        _applyToAccessors(fragment.setters, info.setters);
        _applyToMethods(fragment.methods, info.methods);
      });
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });
  }

  void _applyToFields(
    List<FieldFragmentImpl> fragmentList,
    List<_InfoFieldDeclaration> infoList,
  ) {
    forCorrespondingPairs(fragmentList.withOriginDeclaration, infoList, (
      fragment,
      info,
    ) {
      fragment.setCodeRange(info.codeOffset, info.codeLength);
      fragment.firstTokenOffset = info.firstTokenOffset;
      fragment.nameOffset = info.nameOffset;
      fragment.documentationComment = info.documentationComment;

      fragment.deferConstantOffsets(info.constantOffsets, (applier) {
        applier.applyToMetadata(fragment.metadata);
        applier.applyToConstantInitializer(fragment);
      });
    });
  }

  void _applyToFormalParameters(
    List<FormalParameterFragmentImpl> parameters,
    List<_InfoFormalParameter> infoList,
  ) {
    parameters = parameters.where((p) => p.isOriginDeclaration).toList();
    forCorrespondingPairs(parameters, infoList, (fragment, info) {
      fragment.setCodeRange(info.codeOffset, info.codeLength);
      fragment.firstTokenOffset = info.firstTokenOffset;
      fragment.nameOffset = info.nameOffset;
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
      _applyToFormalParameters(fragment.formalParameters, info.parameters);
    });
  }

  void _applyToFunctionDeclaration(
    TopLevelFunctionFragmentImpl fragment,
    _InfoExecutableDeclaration info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
      _applyToFormalParameters(fragment.formalParameters, info.parameters);
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
      applier.applyToFormalParameters(fragment.formalParameters);
    });
  }

  void _applyToImports(List<LibraryImportImpl> imports, _InfoUnit info) {
    forCorrespondingPairs(imports, info.imports, (element, info) {
      element.importKeywordOffset = info.importKeywordOffset;
      if (element.prefix case var prefixFragment?) {
        prefixFragment.nameOffset = info.prefixOffset;
        prefixFragment.offset = info.prefixOffset ?? info.importKeywordOffset;
      }
      _applyToCombinators(element.combinators, info.combinators);
    });
  }

  void _applyToLibrary(LibraryElementImpl element, _InfoUnit info) {
    element.nameOffset = info.libraryName.offset;
    element.nameLength = info.libraryName.length;
    element.documentationComment = info.docComment;

    element.deferConstantOffsets(info.libraryConstantOffsets, (applier) {
      applier.applyToMetadata(element.metadata);
    });
  }

  void _applyToMethods(
    List<MethodFragmentImpl> fragmentList,
    List<_InfoExecutableDeclaration> infoList,
  ) {
    forCorrespondingPairs(fragmentList, infoList, (fragment, info) {
      fragment.setCodeRange(info.codeOffset, info.codeLength);
      fragment.firstTokenOffset = info.firstTokenOffset;
      fragment.nameOffset = info.nameOffset;
      fragment.documentationComment = info.documentationComment;

      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
        _applyToFormalParameters(fragment.formalParameters, info.parameters);
      });

      fragment.deferConstantOffsets(info.constantOffsets, (applier) {
        applier.applyToMetadata(fragment.metadata);
        applier.applyToTypeParameters(fragment.typeParameters);
        applier.applyToFormalParameters(fragment.formalParameters);
      });
    });
  }

  void _applyToMixinDeclaration(
    MixinFragmentImpl fragment,
    _InfoMixinDeclaration info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    _scheduleApplyMembersOffsets(fragment, () {
      DeferredResolutionReadingHelper.withoutLoadingResolution(() {
        _applyToConstructors(fragment.constructors, info.constructors);
        _applyToFields(fragment.fields, info.fields);
        _applyToAccessors(fragment.getters, info.getters);
        _applyToAccessors(fragment.setters, info.setters);
        _applyToMethods(fragment.methods, info.methods);
      });
    });

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });
  }

  void _applyToPartIncludes(List<PartIncludeImpl> imports, _InfoUnit info) {
    forCorrespondingPairs(imports, info.parts, (element, info) {
      element.partKeywordOffset = info.partKeywordOffset;
    });
  }

  void _applyToTopLevelVariable(
    TopLevelVariableFragmentImpl fragment,
    _InfoTopLevelVariable info,
  ) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    fragment.deferConstantOffsets(info.constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToConstantInitializer(fragment);
    });
  }

  void _applyToTypeAlias(TypeAliasFragmentImpl fragment, _InfoTypeAlias info) {
    fragment.setCodeRange(info.codeOffset, info.codeLength);
    fragment.firstTokenOffset = info.firstTokenOffset;
    fragment.nameOffset = info.nameOffset;
    fragment.documentationComment = info.documentationComment;

    DeferredResolutionReadingHelper.withoutLoadingResolution(() {
      _applyToTypeParameters(fragment.typeParameters, info.typeParameters);
    });

    _setupApplyConstantOffsetsForTypeAlias(fragment, info.constantOffsets);
  }

  void _applyToTypeParameters(
    List<TypeParameterFragmentImpl> fragmentList,
    List<_InfoTypeParameter> infoList,
  ) {
    forCorrespondingPairs(fragmentList, infoList, (fragment, info) {
      fragment.setCodeRange(info.codeOffset, info.codeLength);
      fragment.firstTokenOffset = info.firstTokenOffset;
      fragment.nameOffset = info.nameOffset;
    });
  }

  /// Either defer, or eagerly invoke [callback].
  void _scheduleApplyMembersOffsets(
    InstanceFragmentImpl fragment,
    void Function() callback,
  ) {
    if (_shouldDeferApplyMembersOffsets) {
      fragment.deferApplyMembersOffsets(callback);
    } else {
      callback();
    }
  }

  void _setupApplyConstantOffsetsForTypeAlias(
    TypeAliasFragmentImpl fragment,
    Uint32List constantOffsets,
  ) {
    fragment.deferConstantOffsets(constantOffsets, (applier) {
      applier.applyToMetadata(fragment.metadata);
      applier.applyToTypeParameters(fragment.typeParameters);
    });
  }
}

class _InfoBuilder {
  _InfoUnit build(CompilationUnit unit) {
    return _InfoUnit(
      codeOffset: unit.offset,
      codeLength: unit.length,
      lineStarts: unit.lineInfo.lineStarts,
      libraryName: _buildLibraryName(unit),
      libraryConstantOffsets: _buildLibraryConstantOffsets(unit),
      docComment: _getDocumentationComment(unit.directives.firstOrNull),
      imports: _buildImports(unit),
      exports: _buildExports(unit),
      parts: _buildParts(unit),
      classDeclarations: _buildClasses(unit),
      classTypeAliases: _buildClassTypeAliases(unit),
      enums: _buildEnums(unit),
      extensions: _buildExtensions(unit),
      extensionTypes: _buildExtensionTypes(unit),
      mixinDeclarations: _buildMixins(unit),
      topLevelFunctions: _buildTopLevelFunctions(unit),
      topLevelGetters: _buildTopLevelGetters(unit),
      topLevelSetters: _buildTopLevelSetters(unit),
      topLevelVariable: _buildTopLevelVariables(unit),
      typeAliases: _buildTypeAliases(unit),
    );
  }

  _InfoClassDeclaration _buildClass(ClassDeclaration node) {
    return _InfoClassDeclaration(
      data: _buildInterfaceData(
        node,
        name: node.namePart.typeName,
        typeParameters: node.namePart.typeParameters,
        primaryConstructor: node.namePart.ifTypeOrNull(),
        members: node.body.ifTypeOrNull<BlockClassBody>()?.members ?? [],
      ),
    );
  }

  List<_InfoClassDeclaration> _buildClasses(CompilationUnit unit) {
    return unit.declarations
        .whereType<ClassDeclaration>()
        .map(_buildClass)
        .toList();
  }

  _InfoClassTypeAlias _buildClassTypeAlias(ClassTypeAlias node) {
    return _InfoClassTypeAlias(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      typeParameters: _buildTypeParameters(node.typeParameters),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        typeParameters: node.typeParameters,
      ),
    );
  }

  List<_InfoClassTypeAlias> _buildClassTypeAliases(CompilationUnit unit) {
    return unit.declarations
        .whereType<ClassTypeAlias>()
        .map(_buildClassTypeAlias)
        .toList();
  }

  _InfoCombinator _buildCombinator(Combinator node) {
    return _InfoCombinator(offset: node.offset, end: node.end);
  }

  List<_InfoCombinator> _buildCombinators(List<Combinator> combinators) {
    return combinators.map(_buildCombinator).toList();
  }

  Uint32List _buildConstantOffsets({
    NodeList<Annotation>? metadata,
    Iterable<ImportDirective>? importDirectives,
    Iterable<ExportDirective>? exportDirectives,
    Iterable<PartDirective>? partDirectives,
    TypeParameterList? typeParameters,
    FormalParameterList? formalParameters,
    Expression? constantInitializer,
    NodeList<ConstructorInitializer>? constructorInitializers,
    NodeList<EnumConstantDeclaration>? enumConstants,
    TypeAnnotation? aliasedType,
    EnumConstantArguments? enumConstantArguments,
  }) {
    var collector = _OffsetsCollector();

    void addDirectives(Iterable<Directive>? directives) {
      if (directives != null) {
        for (var directive in directives) {
          directive.metadata.accept(collector);
        }
      }
    }

    void addTypeParameters(TypeParameterList? typeParameters) {
      if (typeParameters != null) {
        for (var typeParameter in typeParameters.typeParameters) {
          typeParameter.metadata.accept(collector);
        }
      }
    }

    void addFormalParameters(FormalParameterList? formalParameters) {
      if (formalParameters != null) {
        for (var parameter in formalParameters.parameters) {
          parameter.metadata.accept(collector);
          addFormalParameters(
            parameter is FunctionTypedFormalParameter
                ? parameter.parameters
                : null,
          );
          if (parameter is DefaultFormalParameter) {
            parameter.defaultValue?.accept(collector);
          }
        }
      }
    }

    metadata?.accept(collector);
    addDirectives(importDirectives);
    addDirectives(exportDirectives);
    addDirectives(partDirectives);
    addTypeParameters(typeParameters);
    addFormalParameters(formalParameters);
    constantInitializer?.accept(collector);
    constructorInitializers?.accept(collector);
    if (enumConstants != null) {
      for (var enumConstant in enumConstants) {
        enumConstant.metadata.accept(collector);
      }
    }
    if (aliasedType is GenericFunctionType) {
      addTypeParameters(aliasedType.typeParameters);
      addFormalParameters(aliasedType.parameters);
    }
    enumConstantArguments?.typeArguments?.accept(collector);
    enumConstantArguments?.argumentList.accept(collector);
    return Uint32List.fromList(collector.offsets);
  }

  _InfoConstructorDeclaration _buildConstructor(ConstructorDeclaration node) {
    return _InfoConstructorDeclaration(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name?.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      typeParameters: const [],
      parameters: _buildFormalParameters(node.parameters),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        formalParameters: node.parameters,
        constructorInitializers: node.initializers,
      ),
      newKeywordOffset: node.newKeyword?.offset,
      factoryKeywordOffset: node.factoryKeyword?.offset,
      typeNameOffset: node.typeName?.offset,
      periodOffset: node.period?.offset,
      nameEnd: (node.name ?? node.typeName)?.end,
      thisKeywordOffset: null,
    );
  }

  _InfoEnumDeclaration _buildEnum(EnumDeclaration node) {
    return _InfoEnumDeclaration(
      data: _buildInterfaceData(
        node,
        name: node.namePart.typeName,
        typeParameters: node.namePart.typeParameters,
        primaryConstructor: node.namePart.ifTypeOrNull(),
        members: node.body.members,
        fields: [
          ...node.body.constants.map(_buildEnumConstant),
          ...node.body.members
              .whereType<FieldDeclaration>()
              .expand((node) => node.fields.variables)
              .map((node) => _buildField(node)),
        ],
      ),
    );
  }

  _InfoFieldDeclaration _buildEnumConstant(EnumConstantDeclaration node) {
    var codeOffset = node.offset;
    return _InfoFieldDeclaration(
      firstTokenOffset: node.offset,
      codeOffset: codeOffset,
      codeLength: node.end - codeOffset,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        enumConstantArguments: node.arguments,
      ),
    );
  }

  List<_InfoEnumDeclaration> _buildEnums(CompilationUnit unit) {
    return unit.declarations
        .whereType<EnumDeclaration>()
        .map(_buildEnum)
        .toList();
  }

  _InfoExport _buildExport(ExportDirective node) {
    return _InfoExport(
      exportKeywordOffset: node.exportKeyword.offset,
      combinators: _buildCombinators(node.combinators),
    );
  }

  List<_InfoExport> _buildExports(CompilationUnit unit) {
    return unit.directives
        .whereType<ExportDirective>()
        .map(_buildExport)
        .toList();
  }

  _InfoExtensionDeclaration _buildExtension(ExtensionDeclaration node) {
    return _InfoExtensionDeclaration(
      data: _buildInstanceData(
        node,
        name: node.name,
        typeParameters: node.typeParameters,
        members: node.body.members,
      ),
    );
  }

  List<_InfoExtensionDeclaration> _buildExtensions(CompilationUnit unit) {
    return unit.declarations
        .whereType<ExtensionDeclaration>()
        .map(_buildExtension)
        .toList();
  }

  _InfoExtensionTypeDeclaration _buildExtensionType(
    ExtensionTypeDeclaration node,
  ) {
    return _InfoExtensionTypeDeclaration(
      data: _buildInterfaceData(
        node,
        name: node.primaryConstructor.typeName,
        typeParameters: node.primaryConstructor.typeParameters,
        primaryConstructor: node.primaryConstructor,
        members: node.body.ifTypeOrNull<BlockClassBody>()?.members ?? [],
      ),
    );
  }

  List<_InfoExtensionTypeDeclaration> _buildExtensionTypes(
    CompilationUnit unit,
  ) {
    return unit.declarations
        .whereType<ExtensionTypeDeclaration>()
        .map(_buildExtensionType)
        .toList();
  }

  _InfoFieldDeclaration _buildField(VariableDeclaration node) {
    var codeOffset = _codeOffsetForVariable(node);
    var declaration = node.parent!.parent as FieldDeclaration;
    return _InfoFieldDeclaration(
      firstTokenOffset: node.offset,
      codeOffset: codeOffset,
      codeLength: node.end - codeOffset,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      constantOffsets: _buildConstantOffsets(
        metadata: declaration.metadata,
        constantInitializer: node.initializer,
      ),
    );
  }

  _InfoFormalParameter _buildFormalParameter(FormalParameter node) {
    var notDefault = node.notDefault;

    var (typeParameters, parameters) = switch (notDefault) {
      FunctionTypedFormalParameter p => (p.typeParameters, p.parameters),
      FieldFormalParameter p => (p.typeParameters, p.parameters),
      SuperFormalParameter p => (p.typeParameters, p.parameters),
      _ => (null, null),
    };

    return _InfoFormalParameter(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name?.offsetIfNotEmpty,
      typeParameters: _buildTypeParameters(typeParameters),
      parameters: _buildFormalParameters(parameters),
    );
  }

  List<_InfoFormalParameter> _buildFormalParameters(FormalParameterList? node) {
    if (node == null) {
      return [];
    }
    var parameters = node.parameters;
    return List.generate(
      parameters.length,
      (index) => _buildFormalParameter(parameters[index]),
    );
  }

  _InfoTypeAlias _buildFunctionTypeAlias(FunctionTypeAlias node) {
    return _InfoTypeAlias(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      typeParameters: _buildTypeParameters(node.typeParameters),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        typeParameters: node.typeParameters,
        aliasedType: node.returnType,
      ),
    );
  }

  _InfoTypeAlias _buildGenericTypeAlias(GenericTypeAlias node) {
    return _InfoTypeAlias(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      typeParameters: _buildTypeParameters(node.typeParameters),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        typeParameters: node.typeParameters,
        aliasedType: node.type,
      ),
    );
  }

  _InfoImport _buildImport(ImportDirective node) {
    return _InfoImport(
      importKeywordOffset: node.importKeyword.offset,
      prefixOffset: node.prefix?.token.offsetIfNotEmpty,
      combinators: _buildCombinators(node.combinators),
    );
  }

  List<_InfoImport> _buildImports(CompilationUnit unit) {
    return unit.directives
        .whereType<ImportDirective>()
        .map(_buildImport)
        .toList();
  }

  _InstanceData _buildInstanceData(
    Declaration node, {
    required Token? name,
    required TypeParameterList? typeParameters,
    required List<ClassMember> members,
    List<_InfoFieldDeclaration>? fields,
  }) {
    var annotatedNode = node as AnnotatedNode;
    return _InstanceData(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: name?.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(annotatedNode),
      typeParameters: _buildTypeParameters(typeParameters),
      fields:
          fields ??
          members
              .whereType<FieldDeclaration>()
              .expand((declaration) => declaration.fields.variables)
              .map((node) => _buildField(node))
              .toList(),
      getters: members
          .whereType<MethodDeclaration>()
          .where((node) => node.isGetter)
          .map(_buildMethodDeclaration)
          .toList(),
      setters: members
          .whereType<MethodDeclaration>()
          .where((node) => node.isSetter)
          .map(_buildMethodDeclaration)
          .toList(),
      methods: members
          .whereType<MethodDeclaration>()
          .where((node) => !node.isGetter && !node.isSetter)
          .map(_buildMethodDeclaration)
          .toList(),
      constantOffsets: _buildConstantOffsets(
        metadata: annotatedNode.metadata,
        typeParameters: typeParameters,
      ),
    );
  }

  _InterfaceData _buildInterfaceData(
    Declaration node, {
    required Token? name,
    required TypeParameterList? typeParameters,
    PrimaryConstructorDeclaration? primaryConstructor,
    required List<ClassMember> members,
    List<_InfoFieldDeclaration>? fields,
  }) {
    var instanceData = _buildInstanceData(
      node,
      name: name,
      typeParameters: typeParameters,
      members: members,
      fields: fields,
    );

    var primaryConstructorBody = members
        .whereType<PrimaryConstructorBody>()
        .firstOrNull;

    return _InterfaceData(
      instanceData: instanceData,
      constructors: [
        if (primaryConstructor != null)
          _buildPrimaryConstructor(
            primaryConstructor,
            body: primaryConstructorBody,
          ),
        ...members.whereType<ConstructorDeclaration>().map(_buildConstructor),
      ],
    );
  }

  Uint32List _buildLibraryConstantOffsets(CompilationUnit unit) {
    Directive? firstDirective;
    for (var directive in unit.directives) {
      firstDirective ??= directive;
      if (directive is LibraryDirective) {
        break;
      }
    }
    return _buildConstantOffsets(
      metadata: firstDirective?.metadata,
      importDirectives: unit.directives.whereType<ImportDirective>(),
      exportDirectives: unit.directives.whereType<ExportDirective>(),
      partDirectives: unit.directives.whereType<PartDirective>(),
    );
  }

  _InfoLibraryName _buildLibraryName(CompilationUnit unit) {
    var nameOffset = -1;
    var nameLength = 0;
    for (var directive in unit.directives) {
      if (directive is LibraryDirective) {
        var libraryName = directive.name;
        if (libraryName != null) {
          nameOffset = libraryName.offset;
          nameLength = libraryName.length;
        }
        break;
      }
    }
    return _InfoLibraryName(offset: nameOffset, length: nameLength);
  }

  _InfoExecutableDeclaration _buildMethodDeclaration(MethodDeclaration node) {
    return _InfoExecutableDeclaration(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      typeParameters: _buildTypeParameters(node.typeParameters),
      parameters: _buildFormalParameters(node.parameters),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        typeParameters: node.typeParameters,
        formalParameters: node.parameters,
      ),
    );
  }

  _InfoMixinDeclaration _buildMixin(MixinDeclaration node) {
    return _InfoMixinDeclaration(
      data: _buildInterfaceData(
        node,
        name: node.name,
        typeParameters: node.typeParameters,
        members: node.body.members,
      ),
    );
  }

  List<_InfoMixinDeclaration> _buildMixins(CompilationUnit unit) {
    return unit.declarations
        .whereType<MixinDeclaration>()
        .map(_buildMixin)
        .toList();
  }

  _InfoPart _buildPart(PartDirective node) {
    return _InfoPart(partKeywordOffset: node.partKeyword.offset);
  }

  List<_InfoPart> _buildParts(CompilationUnit unit) {
    return unit.directives.whereType<PartDirective>().map(_buildPart).toList();
  }

  _InfoConstructorDeclaration _buildPrimaryConstructor(
    PrimaryConstructorDeclaration node, {
    required PrimaryConstructorBody? body,
  }) {
    return _InfoConstructorDeclaration(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.constructorName?.name.offsetIfNotEmpty,
      documentationComment: null,
      typeParameters: const [],
      parameters: _buildFormalParameters(node.formalParameters),
      constantOffsets: _buildConstantOffsets(
        metadata: body?.metadata,
        formalParameters: node.formalParameters,
        constructorInitializers: body?.initializers,
      ),
      newKeywordOffset: null,
      factoryKeywordOffset: null,
      typeNameOffset: node.typeName.offset,
      periodOffset: node.constructorName?.period.offset,
      nameEnd: (node.constructorName?.name ?? node.typeName).end,
      thisKeywordOffset: body?.thisKeyword.offset,
    );
  }

  _InfoExecutableDeclaration _buildTopLevelFunction(FunctionDeclaration node) {
    return _InfoExecutableDeclaration(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      typeParameters: _buildTypeParameters(
        node.functionExpression.typeParameters,
      ),
      parameters: _buildFormalParameters(node.functionExpression.parameters),
      constantOffsets: _buildConstantOffsets(
        metadata: node.metadata,
        typeParameters: node.functionExpression.typeParameters,
        formalParameters: node.functionExpression.parameters,
      ),
    );
  }

  List<_InfoExecutableDeclaration> _buildTopLevelFunctions(
    CompilationUnit unit,
  ) {
    return unit.declarations
        .whereType<FunctionDeclaration>()
        .where((node) => !(node.isGetter || node.isSetter))
        .map(_buildTopLevelFunction)
        .toList();
  }

  List<_InfoExecutableDeclaration> _buildTopLevelGetters(CompilationUnit unit) {
    return unit.declarations
        .whereType<FunctionDeclaration>()
        .where((node) => node.isGetter)
        .map(_buildTopLevelFunction)
        .toList();
  }

  List<_InfoExecutableDeclaration> _buildTopLevelSetters(CompilationUnit unit) {
    return unit.declarations
        .whereType<FunctionDeclaration>()
        .where((node) => node.isSetter)
        .map(_buildTopLevelFunction)
        .toList();
  }

  _InfoTopLevelVariable _buildTopLevelVariable(VariableDeclaration node) {
    var codeOffset = _codeOffsetForVariable(node);
    var declaration = node.parent!.parent as TopLevelVariableDeclaration;
    return _InfoTopLevelVariable(
      firstTokenOffset: node.offset,
      codeOffset: codeOffset,
      codeLength: node.end - codeOffset,
      nameOffset: node.name.offsetIfNotEmpty,
      documentationComment: _getDocumentationComment(node),
      constantOffsets: _buildConstantOffsets(
        metadata: declaration.metadata,
        constantInitializer: node.initializer,
      ),
    );
  }

  List<_InfoTopLevelVariable> _buildTopLevelVariables(CompilationUnit unit) {
    return unit.declarations
        .whereType<TopLevelVariableDeclaration>()
        .expand((declaration) => declaration.variables.variables)
        .map(_buildTopLevelVariable)
        .toList();
  }

  List<_InfoTypeAlias> _buildTypeAliases(CompilationUnit unit) {
    return unit.declarations
        .map((declaration) {
          switch (declaration) {
            case FunctionTypeAlias():
              return _buildFunctionTypeAlias(declaration);
            case GenericTypeAlias():
              return _buildGenericTypeAlias(declaration);
            default:
              return null;
          }
        })
        .nonNulls
        .toList();
  }

  _InfoTypeParameter _buildTypeParameter(TypeParameter node) {
    return _InfoTypeParameter(
      firstTokenOffset: node.offset,
      codeOffset: node.offset,
      codeLength: node.length,
      nameOffset: node.name.offsetIfNotEmpty,
    );
  }

  List<_InfoTypeParameter> _buildTypeParameters(TypeParameterList? node) {
    if (node == null) {
      return [];
    }
    var typeParameters = node.typeParameters;
    return List.generate(
      typeParameters.length,
      (index) => _buildTypeParameter(typeParameters[index]),
    );
  }

  int _codeOffsetForVariable(VariableDeclaration node) {
    var codeOffset = node.offset;
    var variableList = node.parent as VariableDeclarationList;
    if (variableList.variables[0] == node) {
      codeOffset = variableList.parent!.offset;
    }
    return codeOffset;
  }

  String? _getDocumentationComment(AnnotatedNode? node) {
    var comment = node?.documentationComment;
    return getCommentNodeRawText(comment);
  }
}

class _InfoClassDeclaration extends _InfoInterfaceDeclaration {
  _InfoClassDeclaration({required super.data});

  _InfoClassDeclaration.read(super.reader) : super.read();
}

class _InfoClassTypeAlias extends _InfoNode {
  final List<_InfoTypeParameter> typeParameters;
  final Uint32List constantOffsets;

  _InfoClassTypeAlias({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required super.documentationComment,
    required this.typeParameters,
    required this.constantOffsets,
  }) : super();

  _InfoClassTypeAlias.read(super.reader)
    : typeParameters = reader.readList(_InfoTypeParameter.read),
      constantOffsets = reader.readUint30List(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeList(typeParameters, (v) => v.write(writer));
    writer.writeUint30List(constantOffsets);
    super.write(writer);
  }
}

class _InfoCombinator {
  final int offset;
  final int end;

  _InfoCombinator({required this.offset, required this.end});

  factory _InfoCombinator.read(BinaryReader reader) {
    return _InfoCombinator(
      offset: reader.readUint30(),
      end: reader.readUint30(),
    );
  }

  void write(BinaryWriter writer) {
    writer.writeUint30(offset);
    writer.writeUint30(end);
  }
}

class _InfoConstructorDeclaration extends _InfoExecutableDeclaration {
  final int? newKeywordOffset;
  final int? factoryKeywordOffset;
  final int? typeNameOffset;
  final int? periodOffset;
  final int? nameEnd;
  final int? thisKeywordOffset;

  _InfoConstructorDeclaration({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required super.documentationComment,
    required super.typeParameters,
    required super.parameters,
    required super.constantOffsets,
    required this.newKeywordOffset,
    required this.factoryKeywordOffset,
    required this.typeNameOffset,
    required this.periodOffset,
    required this.nameEnd,
    required this.thisKeywordOffset,
  });

  _InfoConstructorDeclaration.read(super.reader)
    : newKeywordOffset = reader.readOptionalUint30(),
      factoryKeywordOffset = reader.readOptionalUint30(),
      typeNameOffset = reader.readOptionalUint30(),
      periodOffset = reader.readOptionalUint30(),
      nameEnd = reader.readOptionalUint30(),
      thisKeywordOffset = reader.readOptionalUint30(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeOptionalUint30(newKeywordOffset);
    writer.writeOptionalUint30(factoryKeywordOffset);
    writer.writeOptionalUint30(typeNameOffset);
    writer.writeOptionalUint30(periodOffset);
    writer.writeOptionalUint30(nameEnd);
    writer.writeOptionalUint30(thisKeywordOffset);
    super.write(writer);
  }
}

class _InfoEnumDeclaration extends _InfoInterfaceDeclaration {
  _InfoEnumDeclaration({required super.data});

  _InfoEnumDeclaration.read(super.reader) : super.read();
}

class _InfoExecutableDeclaration extends _InfoNode {
  final List<_InfoTypeParameter> typeParameters;
  final List<_InfoFormalParameter> parameters;
  final Uint32List constantOffsets;

  _InfoExecutableDeclaration({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required super.documentationComment,
    required this.typeParameters,
    required this.parameters,
    required this.constantOffsets,
  }) : super();

  _InfoExecutableDeclaration.read(super.reader)
    : typeParameters = reader.readList(_InfoTypeParameter.read),
      parameters = reader.readList(_InfoFormalParameter.read),
      constantOffsets = reader.readUint30List(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeList(typeParameters, (v) => v.write(writer));
    writer.writeList(parameters, (v) => v.write(writer));
    writer.writeUint30List(constantOffsets);
    super.write(writer);
  }
}

class _InfoExport {
  final int exportKeywordOffset;
  final List<_InfoCombinator> combinators;

  _InfoExport({required this.exportKeywordOffset, required this.combinators});

  factory _InfoExport.read(BinaryReader reader) {
    return _InfoExport(
      exportKeywordOffset: reader.readUint30(),
      combinators: reader.readList(_InfoCombinator.read),
    );
  }

  void write(BinaryWriter writer) {
    writer.writeUint30(exportKeywordOffset);
    writer.writeList(combinators, (v) => v.write(writer));
  }
}

class _InfoExtensionDeclaration extends _InfoInstanceDeclaration {
  _InfoExtensionDeclaration({required super.data});

  _InfoExtensionDeclaration.read(super.reader) : super.read();
}

class _InfoExtensionTypeDeclaration extends _InfoInterfaceDeclaration {
  _InfoExtensionTypeDeclaration({required super.data});

  _InfoExtensionTypeDeclaration.read(super.reader) : super.read();
}

class _InfoFieldDeclaration extends _InfoNode {
  final Uint32List constantOffsets;

  _InfoFieldDeclaration({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required super.documentationComment,
    required this.constantOffsets,
  }) : super();

  _InfoFieldDeclaration.read(super.reader)
    : constantOffsets = reader.readUint30List(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeUint30List(constantOffsets);
    super.write(writer);
  }
}

class _InfoFormalParameter extends _InfoNode {
  final List<_InfoTypeParameter> typeParameters;
  final List<_InfoFormalParameter> parameters;

  _InfoFormalParameter({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required this.typeParameters,
    required this.parameters,
  }) : super(documentationComment: null);

  _InfoFormalParameter.read(super.reader)
    : typeParameters = reader.readList(_InfoTypeParameter.read),
      parameters = reader.readList(_InfoFormalParameter.read),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeList(typeParameters, (v) => v.write(writer));
    writer.writeList(parameters, (v) => v.write(writer));
    super.write(writer);
  }
}

class _InfoImport {
  final int importKeywordOffset;
  final int? prefixOffset;
  final List<_InfoCombinator> combinators;

  _InfoImport({
    required this.importKeywordOffset,
    required this.prefixOffset,
    required this.combinators,
  });

  factory _InfoImport.read(BinaryReader reader) {
    return _InfoImport(
      importKeywordOffset: reader.readUint30(),
      prefixOffset: reader.readOptionalUint30(),
      combinators: reader.readList(_InfoCombinator.read),
    );
  }

  void write(BinaryWriter writer) {
    writer.writeUint30(importKeywordOffset);
    writer.writeOptionalUint30(prefixOffset);
    writer.writeList(combinators, (v) => v.write(writer));
  }
}

abstract class _InfoInstanceDeclaration extends _InfoNode {
  final List<_InfoTypeParameter> typeParameters;
  final List<_InfoFieldDeclaration> fields;
  final List<_InfoExecutableDeclaration> getters;
  final List<_InfoExecutableDeclaration> setters;
  final List<_InfoExecutableDeclaration> methods;
  final Uint32List constantOffsets;

  _InfoInstanceDeclaration({required _InstanceData data})
    : typeParameters = data.typeParameters,
      fields = data.fields,
      getters = data.getters,
      setters = data.setters,
      methods = data.methods,
      constantOffsets = data.constantOffsets,
      super(
        codeOffset: data.codeOffset,
        codeLength: data.codeLength,
        firstTokenOffset: data.firstTokenOffset,
        nameOffset: data.nameOffset,
        documentationComment: data.documentationComment,
      );

  _InfoInstanceDeclaration.read(super.reader)
    : typeParameters = reader.readList(_InfoTypeParameter.read),
      fields = reader.readList(_InfoFieldDeclaration.read),
      getters = reader.readList(_InfoExecutableDeclaration.read),
      setters = reader.readList(_InfoExecutableDeclaration.read),
      methods = reader.readList(_InfoExecutableDeclaration.read),
      constantOffsets = reader.readUint30List(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeList(typeParameters, (v) => v.write(writer));
    writer.writeList(fields, (v) => v.write(writer));
    writer.writeList(getters, (v) => v.write(writer));
    writer.writeList(setters, (v) => v.write(writer));
    writer.writeList(methods, (v) => v.write(writer));
    writer.writeUint30List(constantOffsets);
    super.write(writer);
  }
}

abstract class _InfoInterfaceDeclaration extends _InfoInstanceDeclaration {
  final List<_InfoConstructorDeclaration> constructors;

  _InfoInterfaceDeclaration({required _InterfaceData data})
    : constructors = data.constructors,
      super(data: data.instanceData);

  _InfoInterfaceDeclaration.read(super.reader)
    : constructors = reader.readList(_InfoConstructorDeclaration.read),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeList(constructors, (v) => v.write(writer));
    super.write(writer);
  }
}

class _InfoLibraryName {
  final int offset;
  final int length;

  _InfoLibraryName({required this.offset, required this.length});

  factory _InfoLibraryName.read(BinaryReader reader) {
    return _InfoLibraryName(
      offset: reader.readUint30() - 1,
      length: reader.readUint30(),
    );
  }

  void write(BinaryWriter writer) {
    writer.writeUint30(offset + 1);
    writer.writeUint30(length);
  }
}

class _InfoMixinDeclaration extends _InfoInterfaceDeclaration {
  _InfoMixinDeclaration({required super.data});

  _InfoMixinDeclaration.read(super.reader) : super.read();
}

abstract class _InfoNode {
  final int firstTokenOffset;
  final int codeOffset;
  final int codeLength;
  final int? nameOffset;
  final String? documentationComment;

  _InfoNode({
    required this.firstTokenOffset,
    required this.codeOffset,
    required this.codeLength,
    required this.nameOffset,
    required this.documentationComment,
  });

  _InfoNode.read(BinaryReader reader)
    : firstTokenOffset = reader.readUint30(),
      codeOffset = reader.readUint30(),
      codeLength = reader.readUint30(),
      nameOffset = reader.readOptionalUint30(),
      documentationComment = reader.readStringUtf8().nullIfEmpty;

  void write(BinaryWriter writer) {
    writer.writeUint30(firstTokenOffset);
    writer.writeUint30(codeOffset);
    writer.writeUint30(codeLength);
    writer.writeOptionalUint30(nameOffset);
    writer.writeStringUtf8(documentationComment ?? '');
  }
}

class _InfoPart {
  final int partKeywordOffset;

  _InfoPart({required this.partKeywordOffset});

  factory _InfoPart.read(BinaryReader reader) {
    return _InfoPart(partKeywordOffset: reader.readUint30());
  }

  void write(BinaryWriter writer) {
    writer.writeUint30(partKeywordOffset);
  }
}

class _InfoTopLevelVariable extends _InfoNode {
  final Uint32List constantOffsets;

  _InfoTopLevelVariable({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required super.documentationComment,
    required this.constantOffsets,
  }) : super();

  _InfoTopLevelVariable.read(super.reader)
    : constantOffsets = reader.readUint30List(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeUint30List(constantOffsets);
    super.write(writer);
  }
}

class _InfoTypeAlias extends _InfoNode {
  final List<_InfoTypeParameter> typeParameters;
  final Uint32List constantOffsets;

  _InfoTypeAlias({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
    required super.documentationComment,
    required this.typeParameters,
    required this.constantOffsets,
  }) : super();

  _InfoTypeAlias.read(super.reader)
    : typeParameters = reader.readList(_InfoTypeParameter.read),
      constantOffsets = reader.readUint30List(),
      super.read();

  @override
  void write(BinaryWriter writer) {
    writer.writeList(typeParameters, (v) => v.write(writer));
    writer.writeUint30List(constantOffsets);
    super.write(writer);
  }
}

class _InfoTypeParameter extends _InfoNode {
  _InfoTypeParameter({
    required super.firstTokenOffset,
    required super.codeOffset,
    required super.codeLength,
    required super.nameOffset,
  }) : super(documentationComment: null);

  _InfoTypeParameter.read(super.reader) : super.read();
}

class _InfoUnit {
  final int codeOffset;
  final int codeLength;
  final List<int> lineStarts;
  final _InfoLibraryName libraryName;
  final Uint32List libraryConstantOffsets;
  final String? docComment;
  final List<_InfoImport> imports;
  final List<_InfoExport> exports;
  final List<_InfoPart> parts;
  final List<_InfoClassDeclaration> classDeclarations;
  final List<_InfoClassTypeAlias> classTypeAliases;
  final List<_InfoEnumDeclaration> enums;
  final List<_InfoExtensionDeclaration> extensions;
  final List<_InfoExtensionTypeDeclaration> extensionTypes;
  final List<_InfoMixinDeclaration> mixinDeclarations;
  final List<_InfoExecutableDeclaration> topLevelFunctions;
  final List<_InfoExecutableDeclaration> topLevelGetters;
  final List<_InfoExecutableDeclaration> topLevelSetters;
  final List<_InfoTopLevelVariable> topLevelVariable;
  final List<_InfoTypeAlias> typeAliases;

  _InfoUnit({
    required this.codeOffset,
    required this.codeLength,
    required this.lineStarts,
    required this.libraryName,
    required this.libraryConstantOffsets,
    required this.docComment,
    required this.imports,
    required this.exports,
    required this.parts,
    required this.classDeclarations,
    required this.classTypeAliases,
    required this.enums,
    required this.extensions,
    required this.extensionTypes,
    required this.mixinDeclarations,
    required this.topLevelFunctions,
    required this.topLevelGetters,
    required this.topLevelSetters,
    required this.topLevelVariable,
    required this.typeAliases,
  });

  _InfoUnit.read(BinaryReader reader)
    : codeOffset = reader.readUint30(),
      codeLength = reader.readUint30(),
      lineStarts = reader.readUint30List(),
      libraryName = _InfoLibraryName.read(reader),
      libraryConstantOffsets = reader.readUint30List(),
      docComment = reader.readOptionalStringUtf8(),
      imports = reader.readList(_InfoImport.read),
      exports = reader.readList(_InfoExport.read),
      parts = reader.readList(_InfoPart.read),
      classDeclarations = reader.readList(_InfoClassDeclaration.read),
      classTypeAliases = reader.readList(_InfoClassTypeAlias.read),
      enums = reader.readList(_InfoEnumDeclaration.read),
      extensions = reader.readList(_InfoExtensionDeclaration.read),
      extensionTypes = reader.readList(_InfoExtensionTypeDeclaration.read),
      mixinDeclarations = reader.readList(_InfoMixinDeclaration.read),
      topLevelFunctions = reader.readList(_InfoExecutableDeclaration.read),
      topLevelGetters = reader.readList(_InfoExecutableDeclaration.read),
      topLevelSetters = reader.readList(_InfoExecutableDeclaration.read),
      topLevelVariable = reader.readList(_InfoTopLevelVariable.read),
      typeAliases = reader.readList(_InfoTypeAlias.read);

  void write(BinaryWriter writer) {
    writer.writeUint30(codeOffset);
    writer.writeUint30(codeLength);
    writer.writeUint30List(lineStarts);
    libraryName.write(writer);
    writer.writeUint30List(libraryConstantOffsets);
    writer.writeOptionalStringUtf8(docComment);
    writer.writeList(imports, (v) => v.write(writer));
    writer.writeList(exports, (v) => v.write(writer));
    writer.writeList(parts, (v) => v.write(writer));
    writer.writeList(classDeclarations, (v) => v.write(writer));
    writer.writeList(classTypeAliases, (v) => v.write(writer));
    writer.writeList(enums, (v) => v.write(writer));
    writer.writeList(extensions, (v) => v.write(writer));
    writer.writeList(extensionTypes, (v) => v.write(writer));
    writer.writeList(mixinDeclarations, (v) => v.write(writer));
    writer.writeList(topLevelFunctions, (v) => v.write(writer));
    writer.writeList(topLevelGetters, (v) => v.write(writer));
    writer.writeList(topLevelSetters, (v) => v.write(writer));
    writer.writeList(topLevelVariable, (v) => v.write(writer));
    writer.writeList(typeAliases, (v) => v.write(writer));
  }
}

class _InstanceData {
  final int firstTokenOffset;
  final int codeOffset;
  final int codeLength;
  final int? nameOffset;
  final String? documentationComment;
  final List<_InfoTypeParameter> typeParameters;
  final List<_InfoFieldDeclaration> fields;
  final List<_InfoExecutableDeclaration> getters;
  final List<_InfoExecutableDeclaration> setters;
  final List<_InfoExecutableDeclaration> methods;
  final Uint32List constantOffsets;

  _InstanceData({
    required this.firstTokenOffset,
    required this.codeOffset,
    required this.codeLength,
    required this.nameOffset,
    required this.documentationComment,
    required this.typeParameters,
    required this.fields,
    required this.getters,
    required this.setters,
    required this.methods,
    required this.constantOffsets,
  });
}

class _InterfaceData {
  final _InstanceData instanceData;
  final List<_InfoConstructorDeclaration> constructors;

  _InterfaceData({required this.instanceData, required this.constructors});
}

class _OffsetsApplier extends _OffsetsAstVisitor {
  final _SafeListIterator<int> _iterator;

  _OffsetsApplier(this._iterator);

  void applyToConstantInitializer(FragmentImpl fragment) {
    if (fragment is FieldFragmentImpl && fragment.isEnumConstant) {
      _applyToEnumConstantInitializer(fragment);
    } else if (fragment is VariableFragmentImpl) {
      fragment.constantInitializer?.accept(this);
    }
  }

  void applyToConstructorInitializers(ConstructorFragmentImpl fragment) {
    for (var initializer in fragment.constantInitializers) {
      initializer.accept(this);
    }
  }

  void applyToEnumConstants(List<FieldFragmentImpl> constants) {
    for (var constant in constants) {
      applyToMetadata(constant.metadata);
    }
  }

  void applyToExports(List<LibraryExportImpl> elements) {
    for (var element in elements) {
      applyToMetadata(element.metadata);
    }
  }

  void applyToFormalParameters(
    List<FormalParameterFragmentImpl> formalParameters,
  ) {
    for (var formalParameters in formalParameters) {
      applyToMetadata(formalParameters.metadata);
      applyToFormalParameters(formalParameters.formalParameters);
      applyToConstantInitializer(formalParameters);
    }
  }

  void applyToImports(List<LibraryImportImpl> elements) {
    for (var element in elements) {
      applyToMetadata(element.metadata);
    }
  }

  void applyToMetadata(MetadataImpl metadata) {
    for (var annotation in metadata.annotations) {
      var node = annotation.annotationAst;
      node.accept(this);
    }
  }

  void applyToParts(List<PartIncludeImpl> elements) {
    for (var element in elements) {
      applyToMetadata(element.metadata);
    }
  }

  void applyToTypeParameters(List<TypeParameterFragmentImpl> typeParameters) {
    for (var typeParameter in typeParameters) {
      applyToMetadata(typeParameter.metadata);
    }
  }

  @override
  void handleToken(Token token) {
    var offset = _iterator.take();
    if (offset != null) {
      token.offset = offset;
    }
  }

  @override
  void visitFunctionExpression(FunctionExpression node) {
    // We store FunctionExpression(s) as empty stubs: `() {}`.
    // We just need it to have right code range, so we apply 2 offsets.
    node.parameters?.leftParenthesis.offset = _iterator.take() ?? 0;

    var body = node.body;
    if (body is BlockFunctionBody) {
      body.block.rightBracket.offset = _iterator.take() ?? 0;
    }
  }

  @override
  void visitSimpleFormalParameter(SimpleFormalParameter node) {
    super.visitSimpleFormalParameter(node);

    var fragment = node.declaredFragment;
    var identifier = node.name;
    if (fragment is FormalParameterFragmentImpl && identifier != null) {
      fragment.firstTokenOffset = identifier.offset;
      fragment.nameOffset = identifier.offsetIfNotEmpty;
    }
  }

  @override
  void visitSimpleIdentifier(SimpleIdentifier node) {
    if (isNotSerializableMarker(node)) {
      return;
    }

    super.visitSimpleIdentifier(node);
  }

  void _applyToEnumConstantInitializer(FieldFragmentImpl fragment) {
    var initializer = fragment.constantInitializer;
    if (initializer is InstanceCreationExpressionImpl) {
      initializer.constructorName.type.typeArguments?.accept(this);
      initializer.argumentList.accept(this);
    }
  }
}

abstract class _OffsetsAstVisitor extends RecursiveAstVisitor<void> {
  void handleToken(Token token);

  @override
  void visitAnnotation(Annotation node) {
    _tokenOrNull(node.atSign);
    _tokenOrNull(node.period);
    super.visitAnnotation(node);
  }

  @override
  void visitArgumentList(ArgumentList node) {
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.rightParenthesis);
    super.visitArgumentList(node);
  }

  @override
  void visitAsExpression(AsExpression node) {
    _tokenOrNull(node.asOperator);
    super.visitAsExpression(node);
  }

  @override
  void visitAssertInitializer(AssertInitializer node) {
    _tokenOrNull(node.assertKeyword);
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.comma);
    _tokenOrNull(node.rightParenthesis);
    super.visitAssertInitializer(node);
  }

  @override
  void visitAssignmentExpression(AssignmentExpression node) {
    _tokenOrNull(node.operator);
    super.visitAssignmentExpression(node);
  }

  @override
  void visitBinaryExpression(BinaryExpression node) {
    _tokenOrNull(node.operator);
    super.visitBinaryExpression(node);
  }

  @override
  void visitBooleanLiteral(BooleanLiteral node) {
    _tokenOrNull(node.literal);
  }

  @override
  void visitConditionalExpression(ConditionalExpression node) {
    _tokenOrNull(node.question);
    _tokenOrNull(node.colon);
    super.visitConditionalExpression(node);
  }

  @override
  void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
    _tokenOrNull(node.thisKeyword);
    _tokenOrNull(node.equals);
    super.visitConstructorFieldInitializer(node);
  }

  @override
  void visitConstructorName(ConstructorName node) {
    node.type.accept(this);
    _tokenOrNull(node.period);
    node.name?.accept(this);
  }

  @override
  void visitDotShorthandConstructorInvocation(
    DotShorthandConstructorInvocation node,
  ) {
    _tokenOrNull(node.constKeyword);
    _tokenOrNull(node.period);
    node.constructorName.accept(this);
    node.argumentList.accept(this);
  }

  /// When we read from bytes, [DotShorthandInvocation]s are not rewritten to
  /// [DotShorthandConstructorInvocation]s when they're resolved to be
  /// constructor invocations. However, since the tokens happen to be the same
  /// between the two in this case, we have the same offsets.
  @override
  void visitDotShorthandInvocation(DotShorthandInvocation node) {
    _tokenOrNull(node.period);
    node.memberName.accept(this);
    node.typeArguments?.accept(this);
    node.argumentList.accept(this);
  }

  @override
  void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
    _tokenOrNull(node.period);
    node.propertyName.accept(this);
  }

  @override
  void visitDoubleLiteral(DoubleLiteral node) {
    _tokenOrNull(node.literal);
  }

  @override
  void visitFormalParameterList(FormalParameterList node) {
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.leftDelimiter);
    _tokenOrNull(node.rightDelimiter);
    _tokenOrNull(node.rightParenthesis);
    super.visitFormalParameterList(node);
  }

  @override
  void visitGenericFunctionType(GenericFunctionType node) {
    _tokenOrNull(node.functionKeyword);
    super.visitGenericFunctionType(node);
  }

  @override
  void visitIfElement(IfElement node) {
    _tokenOrNull(node.ifKeyword);
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.rightParenthesis);
    _tokenOrNull(node.elseKeyword);
    super.visitIfElement(node);
  }

  @override
  void visitImportPrefixReference(ImportPrefixReference node) {
    _tokenOrNull(node.name);
    _tokenOrNull(node.period);
  }

  @override
  void visitIndexExpression(IndexExpression node) {
    _tokenOrNull(node.leftBracket);
    _tokenOrNull(node.rightBracket);
    super.visitIndexExpression(node);
  }

  @override
  void visitInstanceCreationExpression(InstanceCreationExpression node) {
    _tokenOrNull(node.keyword);
    node.constructorName.accept(this);
    node.argumentList.accept(this);
  }

  @override
  void visitIntegerLiteral(IntegerLiteral node) {
    _tokenOrNull(node.literal);
  }

  @override
  void visitInterpolationExpression(InterpolationExpression node) {
    _tokenOrNull(node.leftBracket);
    _tokenOrNull(node.rightBracket);
    super.visitInterpolationExpression(node);
  }

  @override
  void visitInterpolationString(InterpolationString node) {
    _tokenOrNull(node.contents);
  }

  @override
  void visitIsExpression(IsExpression node) {
    _tokenOrNull(node.isOperator);
    super.visitIsExpression(node);
  }

  @override
  void visitLabel(Label node) {
    _tokenOrNull(node.colon);
    super.visitLabel(node);
  }

  @override
  void visitListLiteral(ListLiteral node) {
    _tokenOrNull(node.constKeyword);
    _tokenOrNull(node.leftBracket);
    _tokenOrNull(node.rightBracket);
    super.visitListLiteral(node);
  }

  @override
  void visitMapLiteralEntry(MapLiteralEntry node) {
    _tokenOrNull(node.separator);
    super.visitMapLiteralEntry(node);
  }

  @override
  void visitMethodInvocation(MethodInvocation node) {
    node.target?.accept(this);
    _tokenOrNull(node.operator);
    node.methodName.accept(this);
    node.typeArguments?.accept(this);
    node.argumentList.accept(this);
  }

  @override
  void visitNamedType(NamedType node) {
    node.importPrefix?.accept(this);
    _tokenOrNull(node.name);
    node.typeArguments?.accept(this);
    _tokenOrNull(node.question);
  }

  @override
  void visitNullLiteral(NullLiteral node) {
    _tokenOrNull(node.literal);
  }

  @override
  void visitParenthesizedExpression(ParenthesizedExpression node) {
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.rightParenthesis);
    super.visitParenthesizedExpression(node);
  }

  @override
  void visitPostfixExpression(PostfixExpression node) {
    _tokenOrNull(node.operator);
    super.visitPostfixExpression(node);
  }

  @override
  void visitPrefixedIdentifier(PrefixedIdentifier node) {
    node.prefix.accept(this);
    _tokenOrNull(node.period);
    node.identifier.accept(this);
  }

  @override
  void visitPrefixExpression(PrefixExpression node) {
    _tokenOrNull(node.operator);
    super.visitPrefixExpression(node);
  }

  @override
  void visitPropertyAccess(PropertyAccess node) {
    node.target?.accept(this);
    _tokenOrNull(node.operator);
    node.propertyName.accept(this);
  }

  @override
  void visitRecordLiteral(RecordLiteral node) {
    _tokenOrNull(node.constKeyword);
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.rightParenthesis);
    super.visitRecordLiteral(node);
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    _tokenOrNull(node.leftParenthesis);
    _tokenOrNull(node.rightParenthesis);
    _tokenOrNull(node.question);
    super.visitRecordTypeAnnotation(node);
  }

  @override
  void visitRecordTypeAnnotationNamedField(
    RecordTypeAnnotationNamedField node,
  ) {
    _tokenOrNull(node.name);
    super.visitRecordTypeAnnotationNamedField(node);
  }

  @override
  void visitRecordTypeAnnotationNamedFields(
    RecordTypeAnnotationNamedFields node,
  ) {
    _tokenOrNull(node.leftBracket);
    _tokenOrNull(node.rightBracket);
    super.visitRecordTypeAnnotationNamedFields(node);
  }

  @override
  void visitRecordTypeAnnotationPositionalField(
    RecordTypeAnnotationPositionalField node,
  ) {
    _tokenOrNull(node.name);
    super.visitRecordTypeAnnotationPositionalField(node);
  }

  @override
  void visitRedirectingConstructorInvocation(
    RedirectingConstructorInvocation node,
  ) {
    _tokenOrNull(node.thisKeyword);
    _tokenOrNull(node.period);
    super.visitRedirectingConstructorInvocation(node);
  }

  @override
  void visitSetOrMapLiteral(SetOrMapLiteral node) {
    _tokenOrNull(node.constKeyword);
    _tokenOrNull(node.leftBracket);
    _tokenOrNull(node.rightBracket);
    super.visitSetOrMapLiteral(node);
  }

  @override
  void visitSimpleFormalParameter(SimpleFormalParameter node) {
    _tokenOrNull(node.requiredKeyword);
    _tokenOrNull(node.name);
    super.visitSimpleFormalParameter(node);
  }

  @override
  void visitSimpleIdentifier(SimpleIdentifier node) {
    _tokenOrNull(node.token);
  }

  @override
  void visitSimpleStringLiteral(SimpleStringLiteral node) {
    _tokenOrNull(node.literal);
  }

  @override
  void visitSpreadElement(SpreadElement node) {
    _tokenOrNull(node.spreadOperator);
    super.visitSpreadElement(node);
  }

  @override
  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
    _tokenOrNull(node.superKeyword);
    _tokenOrNull(node.period);
    super.visitSuperConstructorInvocation(node);
  }

  @override
  void visitSuperExpression(SuperExpression node) {
    _tokenOrNull(node.superKeyword);
  }

  @override
  void visitSymbolLiteral(SymbolLiteral node) {
    _tokenOrNull(node.poundSign);
    node.components.forEach(_tokenOrNull);
  }

  @override
  void visitThisExpression(ThisExpression node) {
    _tokenOrNull(node.thisKeyword);
  }

  @override
  void visitThrowExpression(ThrowExpression node) {
    _tokenOrNull(node.throwKeyword);
    super.visitThrowExpression(node);
  }

  @override
  void visitTypeArgumentList(TypeArgumentList node) {
    _tokenOrNull(node.leftBracket);
    _tokenOrNull(node.rightBracket);
    super.visitTypeArgumentList(node);
  }

  void _tokenOrNull(Token? token) {
    if (token != null) {
      handleToken(token);
    }
  }
}

class _OffsetsCollector extends _OffsetsAstVisitor {
  final List<int> offsets = [];

  @override
  void handleToken(Token token) {
    offsets.add(token.offset);
  }

  @override
  void visitFunctionExpression(FunctionExpression node) {
    offsets.add(node.offset);
    offsets.add(node.end - 1);
  }
}

class _SafeListIterator<T> {
  final List<T> _elements;
  int _index = 0;

  _SafeListIterator(this._elements);

  bool get hasNext {
    return _index < _elements.length;
  }

  T? take() {
    if (hasNext) {
      return _elements[_index++];
    } else {
      return null;
    }
  }
}

extension on BinaryReader {
  List<T> readList<T>(T Function(BinaryReader) read) {
    return readTypedList(() => read(this));
  }
}

extension on Token {
  int? get offsetIfNotEmpty {
    return lexeme.isNotEmpty ? offset : null;
  }
}

extension on String {
  String? get nullIfEmpty {
    return isNotEmpty ? this : null;
  }
}

extension on DeferredResolutionReadingMixin {
  /// We want to have actual offsets for tokens of various constants in the
  /// element model, such as metadata and constant initializers. But we read
  /// these additional pieces of resolution data later, on demand. So, these
  /// offsets are different from `nameOffset` for example, which are applied
  /// directly after creating corresponding elements during a library loading.
  void deferConstantOffsets(
    Uint32List constantOffsets,
    void Function(_OffsetsApplier applier) callback,
  ) {
    deferResolutionConstantOffsets(() {
      var applier = _OffsetsApplier(_SafeListIterator(constantOffsets));
      callback(applier);
    });
  }
}

extension _ListOfPropertyAccessorFragment<
  T extends PropertyAccessorFragmentImpl
>
    on List<T> {
  List<T> get withOriginDeclaration {
    return where((e) => e.isOriginDeclaration).toList();
  }
}

extension _ListOfPropertyInducingFragment<
  T extends PropertyInducingFragmentImpl
>
    on List<T> {
  List<T> get withOriginDeclaration {
    return where((e) => e.isOriginDeclaration).toList();
  }
}
