// 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/ast.dart';
import 'package:meta/meta.dart';

/// A visitor used to write a source representation of a visited AST node (and
/// all of it's children) to a sink.
class ToSourceVisitor implements AstVisitor<void> {
  /// The sink to which the source is to be written.
  @protected
  final StringSink sink;

  /// Initialize a newly created visitor to write source code representing the
  /// visited nodes to the given [sink].
  ToSourceVisitor(this.sink);

  @override
  void visitAdjacentStrings(AdjacentStrings node) {
    _visitNodeList(node.strings, separator: ' ');
  }

  @override
  void visitAnnotation(Annotation node) {
    sink.write('@');
    _visitNode(node.name);
    _visitNode(node.typeArguments);
    _visitNode(node.constructorName, prefix: '.');
    _visitNode(node.arguments);
  }

  @override
  void visitArgumentList(ArgumentList node) {
    sink.write('(');
    _visitNodeList(node.arguments, separator: ', ');
    sink.write(')');
  }

  @override
  void visitAsExpression(AsExpression node) {
    _visitNode(node.expression);
    sink.write(' as ');
    _visitNode(node.type);
  }

  @override
  void visitAssertInitializer(AssertInitializer node) {
    sink.write('assert (');
    _visitNode(node.condition);
    if (node.message != null) {
      sink.write(', ');
      _visitNode(node.message);
    }
    sink.write(')');
  }

  @override
  void visitAssertStatement(AssertStatement node) {
    sink.write('assert (');
    _visitNode(node.condition);
    if (node.message != null) {
      sink.write(', ');
      _visitNode(node.message);
    }
    sink.write(');');
  }

  @override
  void visitAssignedVariablePattern(AssignedVariablePattern node) {
    sink.write(node.name.lexeme);
  }

  @override
  void visitAssignmentExpression(AssignmentExpression node) {
    _visitNode(node.leftHandSide);
    sink.write(' ');
    sink.write(node.operator.lexeme);
    sink.write(' ');
    _visitNode(node.rightHandSide);
  }

  @override
  void visitAwaitExpression(AwaitExpression node) {
    sink.write('await ');
    _visitNode(node.expression);
  }

  @override
  void visitBinaryExpression(BinaryExpression node) {
    _writeOperand(node, node.leftOperand);
    sink.write(' ');
    sink.write(node.operator.lexeme);
    sink.write(' ');
    _writeOperand(node, node.rightOperand);
  }

  @override
  void visitBlock(Block node) {
    sink.write('{');
    _visitNodeList(node.statements, separator: ' ');
    sink.write('}');
  }

  @override
  void visitBlockClassBody(BlockClassBody node) {
    sink.write('{');
    _visitNodeList(node.members, separator: ' ');
    sink.write('}');
  }

  @override
  void visitBlockFunctionBody(BlockFunctionBody node) {
    var keyword = node.keyword;
    if (keyword != null) {
      sink.write(keyword.lexeme);
      if (node.star != null) {
        sink.write('*');
      }
      sink.write(' ');
    }
    _visitNode(node.block);
  }

  @override
  void visitBooleanLiteral(BooleanLiteral node) {
    sink.write(node.literal.lexeme);
  }

  @override
  void visitBreakStatement(BreakStatement node) {
    sink.write('break');
    _visitNode(node.label, prefix: ' ');
    sink.write(';');
  }

  @override
  void visitCascadeExpression(CascadeExpression node) {
    _visitNode(node.target);
    _visitNodeList(node.cascadeSections);
  }

  @override
  void visitCaseClause(CaseClause node) {
    sink.write('case ');
    _visitNode(node.guardedPattern);
  }

  @override
  void visitCastPattern(CastPattern node) {
    _visitNode(node.pattern);
    sink.write(' as ');
    _visitNode(node.type);
  }

  @override
  void visitCatchClause(CatchClause node) {
    _visitNode(node.exceptionType, prefix: 'on ');
    if (node.catchKeyword != null) {
      if (node.exceptionType != null) {
        sink.write(' ');
      }
      sink.write('catch (');
      _visitNode(node.exceptionParameter);
      _visitNode(node.stackTraceParameter, prefix: ', ');
      sink.write(') ');
    } else {
      sink.write(' ');
    }
    _visitNode(node.body);
  }

  @override
  void visitCatchClauseParameter(CatchClauseParameter node) {
    _visitToken(node.name);
  }

  @override
  void visitClassDeclaration(covariant ClassDeclarationImpl node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.abstractKeyword, suffix: ' ');
    _visitToken(node.sealedKeyword, suffix: ' ');
    _visitToken(node.baseKeyword, suffix: ' ');
    _visitToken(node.interfaceKeyword, suffix: ' ');
    _visitToken(node.finalKeyword, suffix: ' ');
    _visitToken(node.mixinKeyword, suffix: ' ');
    sink.write('class ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.extendsClause, prefix: ' ');
    _visitNode(node.withClause, prefix: ' ');
    _visitNode(node.implementsClause, prefix: ' ');
    sink.write(' {');
    _visitNodeList(node.members, separator: ' ');
    sink.write('}');
  }

  @override
  void visitClassTypeAlias(covariant ClassTypeAliasImpl node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.abstractKeyword, suffix: ' ');
    _visitToken(node.sealedKeyword, suffix: ' ');
    _visitToken(node.baseKeyword, suffix: ' ');
    _visitToken(node.interfaceKeyword, suffix: ' ');
    _visitToken(node.finalKeyword, suffix: ' ');
    _visitToken(node.mixinKeyword, suffix: ' ');
    sink.write('class ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    sink.write(' = ');
    _visitNode(node.superclass);
    _visitNode(node.withClause, prefix: ' ');
    _visitNode(node.implementsClause, prefix: ' ');
    sink.write(';');
  }

  @override
  void visitComment(Comment node) {}

  @override
  void visitCommentReference(CommentReference node) {
    sink.write(node.newKeyword?.lexeme ?? '');
    _visitNode(prefix: '[', node.expression, suffix: ']');
  }

  @override
  void visitCompilationUnit(CompilationUnit node) {
    var scriptTag = node.scriptTag;
    NodeList<Directive> directives = node.directives;
    _visitNode(scriptTag);
    String prefix = scriptTag == null ? '' : ' ';
    _visitNodeList(directives, prefix: prefix, separator: ' ');
    prefix = scriptTag == null && directives.isEmpty ? '' : ' ';
    _visitNodeList(node.declarations, prefix: prefix, separator: ' ');
  }

  @override
  void visitConditionalExpression(ConditionalExpression node) {
    _visitNode(node.condition);
    sink.write(' ? ');
    _visitNode(node.thenExpression);
    sink.write(' : ');
    _visitNode(node.elseExpression);
  }

  @override
  void visitConfiguration(Configuration node) {
    sink.write('if (');
    _visitNode(node.name);
    _visitNode(node.value, prefix: ' == ');
    sink.write(') ');
    _visitNode(node.uri);
  }

  @override
  void visitConstantPattern(ConstantPattern node) {
    _visitToken(node.constKeyword, suffix: ' ');
    _visitNode(node.expression);
  }

  @override
  void visitConstructorDeclaration(ConstructorDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.externalKeyword, suffix: ' ');
    _visitToken(node.constKeyword, suffix: ' ');
    _visitToken(node.factoryKeyword, suffix: ' ');
    _visitNode(node.returnType);
    _visitToken(node.name, prefix: '.');
    _visitNode(node.parameters);
    _visitNodeList(node.initializers, prefix: ' : ', separator: ', ');
    _visitNode(node.redirectedConstructor, prefix: ' = ');
    _visitFunctionBody(node.body);
  }

  @override
  void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
    _visitToken(node.thisKeyword, suffix: '.');
    _visitNode(node.fieldName);
    sink.write(' = ');
    _visitNode(node.expression);
  }

  @override
  void visitConstructorName(ConstructorName node) {
    _visitNode(node.type);
    _visitNode(node.name, prefix: '.');
  }

  @override
  void visitConstructorReference(ConstructorReference node) {
    _visitNode(node.constructorName);
  }

  @override
  void visitConstructorSelector(ConstructorSelector node) {
    _visitToken(node.period);
    _visitNode(node.name);
  }

  @override
  void visitContinueStatement(ContinueStatement node) {
    sink.write('continue');
    _visitNode(node.label, prefix: ' ');
    sink.write(';');
  }

  @override
  void visitDeclaredIdentifier(DeclaredIdentifier node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type, suffix: ' ');
    _visitToken(node.name);
  }

  @override
  void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type, suffix: ' ');
    sink.write(node.name.lexeme);
  }

  @override
  void visitDefaultFormalParameter(DefaultFormalParameter node) {
    _visitNode(node.parameter);
    var separator = node.separator;
    if (separator != null) {
      if (separator.lexeme != ':') {
        sink.write(' ');
      }
      sink.write(separator.lexeme);
      _visitNode(node.defaultValue, prefix: ' ');
    }
  }

  @override
  void visitDoStatement(DoStatement node) {
    sink.write('do ');
    _visitNode(node.body);
    sink.write(' while (');
    _visitNode(node.condition);
    sink.write(');');
  }

  @override
  void visitDotShorthandConstructorInvocation(
    DotShorthandConstructorInvocation node,
  ) {
    _visitToken(node.constKeyword, suffix: ' ');
    _visitToken(node.period);
    _visitNode(node.constructorName);
    _visitNode(node.typeArguments);
    _visitNode(node.argumentList);
  }

  @override
  void visitDotShorthandInvocation(DotShorthandInvocation node) {
    _visitToken(node.period);
    _visitNode(node.memberName);
    _visitNode(node.typeArguments);
    _visitNode(node.argumentList);
  }

  @override
  void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
    _visitToken(node.period);
    _visitNode(node.propertyName);
  }

  @override
  void visitDottedName(DottedName node) {
    _visitNodeList(node.components, separator: '.');
  }

  @override
  void visitDoubleLiteral(DoubleLiteral node) {
    sink.write(node.literal.lexeme);
  }

  @override
  void visitEmptyClassBody(EmptyClassBody node) {
    sink.write(';');
  }

  @override
  void visitEmptyFunctionBody(EmptyFunctionBody node) {
    sink.write(';');
  }

  @override
  void visitEmptyStatement(EmptyStatement node) {
    sink.write(';');
  }

  @override
  void visitEnumBody(EnumBody node) {
    sink.write(' {');
    _visitNodeList(node.constants, separator: ', ');
    _visitToken(node.semicolon);
    _visitNodeList(node.members, prefix: ' ', separator: ' ');
    sink.write('}');
  }

  @override
  void visitEnumConstantArguments(EnumConstantArguments node) {
    _visitNode(node.typeArguments);
    _visitNode(node.constructorSelector);
    _visitNode(node.argumentList);
  }

  @override
  void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.arguments);
  }

  @override
  void visitEnumDeclaration(EnumDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('enum ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.withClause, prefix: ' ');
    _visitNode(node.implementsClause, prefix: ' ');
    sink.write(' {');
    _visitNodeList(node.constants, separator: ', ');
    _visitToken(node.semicolon);
    _visitNodeList(node.members, prefix: ' ', separator: ' ');
    sink.write('}');
  }

  @override
  void visitExportDirective(ExportDirective node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('export ');
    _visitNode(node.uri);
    _visitNodeList(node.configurations, prefix: ' ', separator: ' ');
    _visitNodeList(node.combinators, prefix: ' ', separator: ' ');
    sink.write(';');
  }

  @override
  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
    var keyword = node.keyword;
    if (keyword != null) {
      sink.write(keyword.lexeme);
      if (node.star != null) {
        sink.write('*');
      }
      sink.write(' ');
    }
    sink.write('${node.functionDefinition.lexeme} ');
    _visitNode(node.expression);
    if (node.semicolon != null) {
      sink.write(';');
    }
  }

  @override
  void visitExpressionStatement(ExpressionStatement node) {
    _visitNode(node.expression);
    sink.write(';');
  }

  @override
  void visitExtendsClause(ExtendsClause node) {
    sink.write('extends ');
    _visitNode(node.superclass);
  }

  @override
  void visitExtensionDeclaration(ExtensionDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.extensionKeyword, suffix: ' ');
    _visitToken(node.typeKeyword, suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    sink.write(' ');
    _visitNode(node.onClause, suffix: ' ');
    _visitToken(node.leftBracket);
    _visitNodeList(node.members, separator: ' ');
    _visitToken(node.rightBracket);
  }

  @override
  void visitExtensionOnClause(ExtensionOnClause node) {
    sink.write('on ');
    _visitNode(node.extendedType);
  }

  @override
  void visitExtensionOverride(ExtensionOverride node) {
    _visitNode(node.importPrefix);
    _visitToken(node.name);
    _visitNode(node.typeArguments);
    _visitNode(node.argumentList);
  }

  @override
  void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.extensionKeyword, suffix: ' ');
    _visitToken(node.typeKeyword, suffix: ' ');
    _visitToken(node.constKeyword, suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.representation, suffix: ' ');
    _visitNode(node.implementsClause, suffix: ' ');
    _visitToken(node.leftBracket);
    _visitNodeList(node.members, separator: ' ');
    _visitToken(node.rightBracket);
  }

  @override
  void visitFieldDeclaration(FieldDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.abstractKeyword, suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.externalKeyword, suffix: ' ');
    _visitToken(node.staticKeyword, suffix: ' ');
    _visitNode(node.fields);
    sink.write(';');
  }

  @override
  void visitFieldFormalParameter(FieldFormalParameter node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.requiredKeyword, suffix: ' ');
    _visitToken(node.covariantKeyword, suffix: ' ');
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type, suffix: ' ');
    sink.write('this.');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.parameters);
  }

  @override
  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
    _visitNode(node.loopVariable);
    sink.write(' in ');
    _visitNode(node.iterable);
  }

  @override
  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
    _visitNode(node.identifier);
    sink.write(' in ');
    _visitNode(node.iterable);
  }

  @override
  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.pattern);
    sink.write(' in ');
    _visitNode(node.iterable);
  }

  @override
  void visitForElement(ForElement node) {
    _visitToken(node.awaitKeyword, suffix: ' ');
    sink.write('for (');
    _visitNode(node.forLoopParts);
    sink.write(') ');
    _visitNode(node.body);
  }

  @override
  void visitFormalParameterList(FormalParameterList node) {
    String? groupEnd;
    sink.write('(');
    NodeList<FormalParameter> parameters = node.parameters;
    int size = parameters.length;
    for (int i = 0; i < size; i++) {
      FormalParameter parameter = parameters[i];
      if (i > 0) {
        sink.write(', ');
      }
      if (groupEnd == null && parameter is DefaultFormalParameter) {
        if (parameter.isNamed) {
          groupEnd = '}';
          sink.write('{');
        } else {
          groupEnd = ']';
          sink.write('[');
        }
      }
      parameter.accept(this);
    }
    if (groupEnd != null) {
      sink.write(groupEnd);
    }
    sink.write(')');
  }

  @override
  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
    _visitNode(node.variables);
    sink.write(';');
    _visitNode(node.condition, prefix: ' ');
    sink.write(';');
    _visitNodeList(node.updaters, prefix: ' ', separator: ', ');
  }

  @override
  void visitForPartsWithExpression(ForPartsWithExpression node) {
    _visitNode(node.initialization);
    sink.write(';');
    _visitNode(node.condition, prefix: ' ');
    sink.write(';');
    _visitNodeList(node.updaters, prefix: ' ', separator: ', ');
  }

  @override
  void visitForPartsWithPattern(ForPartsWithPattern node) {
    _visitNode(node.variables);
    sink.write('; ');
    _visitNode(node.condition);
    sink.write('; ');
    _visitNodeList(node.updaters, separator: ', ');
  }

  @override
  void visitForStatement(ForStatement node) {
    if (node.awaitKeyword != null) {
      sink.write('await ');
    }
    sink.write('for (');
    _visitNode(node.forLoopParts);
    sink.write(') ');
    _visitNode(node.body);
  }

  @override
  void visitFunctionDeclaration(FunctionDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.externalKeyword, suffix: ' ');
    _visitNode(node.returnType, suffix: ' ');
    _visitToken(node.propertyKeyword, suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.functionExpression);
  }

  @override
  void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
    _visitNode(node.functionDeclaration);
  }

  @override
  void visitFunctionExpression(FunctionExpression node) {
    _visitNode(node.typeParameters);
    _visitNode(node.parameters);
    _visitFunctionBody(node.body);
  }

  @override
  void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
    _visitNode(node.function);
    _visitNode(node.typeArguments);
    _visitNode(node.argumentList);
  }

  @override
  void visitFunctionReference(FunctionReference node) {
    _visitNode(node.function);
    _visitNode(node.typeArguments);
  }

  @override
  void visitFunctionTypeAlias(FunctionTypeAlias node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('typedef ');
    _visitNode(node.returnType, suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.parameters);
    sink.write(';');
  }

  @override
  void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.requiredKeyword, suffix: ' ');
    _visitToken(node.covariantKeyword, suffix: ' ');
    _visitNode(node.returnType, suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.parameters);
    if (node.question != null) {
      sink.write('?');
    }
  }

  @override
  void visitGenericFunctionType(GenericFunctionType node) {
    _visitNode(node.returnType);
    sink.write(' Function');
    _visitNode(node.typeParameters);
    _visitNode(node.parameters);
    if (node.question != null) {
      sink.write('?');
    }
  }

  @override
  void visitGenericTypeAlias(GenericTypeAlias node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    sink.write('typedef ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    sink.write(' = ');
    _visitNode(node.type);
    sink.write(';');
  }

  @override
  void visitGuardedPattern(GuardedPattern node) {
    _visitNode(node.pattern);
    _visitNode(node.whenClause, prefix: ' ');
  }

  @override
  void visitHideCombinator(HideCombinator node) {
    sink.write('hide ');
    _visitNodeList(node.hiddenNames, separator: ', ');
  }

  @override
  void visitIfElement(IfElement node) {
    sink.write('if (');
    _visitNode(node.expression);
    _visitNode(node.caseClause, prefix: ' ');
    sink.write(') ');
    _visitNode(node.thenElement);
    _visitNode(node.elseElement, prefix: ' else ');
  }

  @override
  void visitIfStatement(IfStatement node) {
    sink.write('if (');
    _visitNode(node.expression);
    _visitNode(node.caseClause, prefix: ' ');
    sink.write(') ');
    _visitNode(node.thenStatement);
    _visitNode(node.elseStatement, prefix: ' else ');
  }

  @override
  void visitImplementsClause(ImplementsClause node) {
    sink.write('implements ');
    _visitNodeList(node.interfaces, separator: ', ');
  }

  @override
  void visitImplicitCallReference(ImplicitCallReference node) {
    _visitNode(node.expression);
    _visitNode(node.typeArguments);
  }

  @override
  void visitImportDirective(ImportDirective node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('import ');
    _visitNode(node.uri);
    _visitNodeList(node.configurations, prefix: ' ', separator: ' ');
    if (node.deferredKeyword != null) {
      sink.write(' deferred');
    }
    _visitNode(node.prefix, prefix: ' as ');
    _visitNodeList(node.combinators, prefix: ' ', separator: ' ');
    sink.write(';');
  }

  @override
  void visitImportPrefixReference(ImportPrefixReference node) {
    sink.write(node.name.lexeme);
    sink.write('.');
  }

  @override
  void visitIndexExpression(IndexExpression node) {
    if (node.isCascaded) {
      _visitToken(node.period);
    } else {
      _visitNode(node.target);
    }
    _visitToken(node.question);
    _visitToken(node.leftBracket);
    _visitNode(node.index);
    _visitToken(node.rightBracket);
  }

  @override
  void visitInstanceCreationExpression(InstanceCreationExpression node) {
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.constructorName);
    _visitNode(node.argumentList);
  }

  @override
  void visitIntegerLiteral(IntegerLiteral node) {
    sink.write(node.literal.lexeme);
  }

  @override
  void visitInterpolationExpression(InterpolationExpression node) {
    if (node.rightBracket != null) {
      sink.write('\${');
      _visitNode(node.expression);
      sink.write('}');
    } else {
      sink.write('\$');
      _visitNode(node.expression);
    }
  }

  @override
  void visitInterpolationString(InterpolationString node) {
    sink.write(node.contents.lexeme);
  }

  @override
  void visitIsExpression(IsExpression node) {
    _visitNode(node.expression);
    if (node.notOperator == null) {
      sink.write(' is ');
    } else {
      sink.write(' is! ');
    }
    _visitNode(node.type);
  }

  @override
  void visitLabel(Label node) {
    _visitNode(node.label);
    sink.write(':');
  }

  @override
  void visitLabeledStatement(LabeledStatement node) {
    _visitNodeList(node.labels, separator: ' ', suffix: ' ');
    _visitNode(node.statement);
  }

  @override
  void visitLibraryDirective(LibraryDirective node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('library ');
    _visitNode(node.name);
    sink.write(';');
  }

  @override
  void visitLibraryIdentifier(LibraryIdentifier node) {
    sink.write(node.name);
  }

  @override
  void visitListLiteral(ListLiteral node) {
    _visitToken(node.constKeyword, suffix: ' ');
    _visitNode(node.typeArguments);
    sink.write('[');
    _visitNodeList(node.elements, separator: ', ');
    sink.write(']');
  }

  @override
  void visitListPattern(ListPattern node) {
    _visitNode(node.typeArguments);
    sink.write('[');
    _visitNodeList(node.elements, separator: ', ');
    sink.write(']');
  }

  @override
  void visitLogicalAndPattern(LogicalAndPattern node) {
    _visitNode(node.leftOperand);
    sink.write(' ');
    sink.write(node.operator.lexeme);
    sink.write(' ');
    _visitNode(node.rightOperand);
  }

  @override
  void visitLogicalOrPattern(LogicalOrPattern node) {
    _visitNode(node.leftOperand);
    sink.write(' ');
    sink.write(node.operator.lexeme);
    sink.write(' ');
    _visitNode(node.rightOperand);
  }

  @override
  void visitMapLiteralEntry(MapLiteralEntry node) {
    _visitNode(node.key);
    sink.write(' : ');
    _visitNode(node.value);
  }

  @override
  void visitMapPattern(MapPattern node) {
    _visitNode(node.typeArguments);
    sink.write('{');
    _visitNodeList(node.elements, separator: ', ');
    sink.write('}');
  }

  @override
  void visitMapPatternEntry(MapPatternEntry node) {
    _visitNode(node.key);
    sink.write(': ');
    _visitNode(node.value);
  }

  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.externalKeyword, suffix: ' ');
    _visitToken(node.modifierKeyword, suffix: ' ');
    _visitNode(node.returnType, suffix: ' ');
    _visitToken(node.propertyKeyword, suffix: ' ');
    _visitToken(node.operatorKeyword, suffix: ' ');
    _visitToken(node.name);
    if (!node.isGetter) {
      _visitNode(node.typeParameters);
      _visitNode(node.parameters);
    }
    _visitFunctionBody(node.body);
  }

  @override
  void visitMethodInvocation(MethodInvocation node) {
    _visitNode(node.target);
    _visitToken(node.operator);
    _visitNode(node.methodName);
    _visitNode(node.typeArguments);
    _visitNode(node.argumentList);
  }

  @override
  void visitMixinDeclaration(MixinDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.baseKeyword, suffix: ' ');
    sink.write('mixin ');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.onClause, prefix: ' ');
    _visitNode(node.implementsClause, prefix: ' ');
    sink.write(' {');
    _visitNodeList(node.members, separator: ' ');
    sink.write('}');
  }

  @override
  void visitMixinOnClause(MixinOnClause node) {
    sink.write('on ');
    _visitNodeList(node.superclassConstraints, separator: ', ');
  }

  @override
  void visitNamedExpression(NamedExpression node) {
    _visitNode(node.name);
    _visitNode(node.expression, prefix: ' ');
  }

  @override
  void visitNamedType(NamedType node) {
    _visitNode(node.importPrefix);
    _visitToken(node.name);
    _visitNode(node.typeArguments);
    if (node.question != null) {
      sink.write('?');
    }
  }

  @override
  void visitNameWithTypeParameters(NameWithTypeParameters node) {
    _visitToken(node.typeName);
    _visitNode(node.typeParameters);
  }

  @override
  void visitNativeClause(NativeClause node) {
    sink.write('native ');
    _visitNode(node.name);
  }

  @override
  void visitNativeFunctionBody(NativeFunctionBody node) {
    sink.write('native ');
    _visitNode(node.stringLiteral);
    sink.write(';');
  }

  @override
  void visitNullAssertPattern(NullAssertPattern node) {
    _visitNode(node.pattern);
    sink.write(node.operator.lexeme);
  }

  @override
  void visitNullAwareElement(NullAwareElement node) {
    sink.write(node.question.lexeme);
    _visitNode(node.value);
  }

  @override
  void visitNullCheckPattern(NullCheckPattern node) {
    _visitNode(node.pattern);
    sink.write(node.operator.lexeme);
  }

  @override
  void visitNullLiteral(NullLiteral node) {
    sink.write('null');
  }

  @override
  void visitObjectPattern(ObjectPattern node) {
    _visitNode(node.type);
    sink.write('(');
    _visitNodeList(node.fields, separator: ', ');
    sink.write(')');
  }

  @override
  void visitParenthesizedExpression(ParenthesizedExpression node) {
    sink.write('(');
    _visitNode(node.expression);
    sink.write(')');
  }

  @override
  void visitParenthesizedPattern(ParenthesizedPattern node) {
    sink.write('(');
    _visitNode(node.pattern);
    sink.write(')');
  }

  @override
  void visitPartDirective(PartDirective node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('part ');
    _visitNode(node.uri);
    sink.write(';');
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write('part of ');
    _visitNode(node.libraryName);
    _visitNode(node.uri);
    sink.write(';');
  }

  @override
  void visitPatternAssignment(PatternAssignment node) {
    _visitNode(node.pattern);
    sink.write(' = ');
    _visitNode(node.expression);
  }

  @override
  void visitPatternField(PatternField node) {
    _visitNode(node.name, suffix: ' ');
    _visitNode(node.pattern);
  }

  @override
  void visitPatternFieldName(PatternFieldName node) {
    _visitToken(node.name);
    sink.write(':');
  }

  @override
  void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    sink.write(node.keyword.lexeme);
    sink.write(' ');
    _visitNode(node.pattern);
    sink.write(' = ');
    _visitNode(node.expression);
  }

  @override
  void visitPatternVariableDeclarationStatement(
    PatternVariableDeclarationStatement node,
  ) {
    _visitNode(node.declaration);
    sink.write(';');
  }

  @override
  void visitPostfixExpression(PostfixExpression node) {
    _writeOperand(node, node.operand);
    sink.write(node.operator.lexeme);
  }

  @override
  void visitPrefixedIdentifier(PrefixedIdentifier node) {
    _visitNode(node.prefix);
    sink.write('.');
    _visitNode(node.identifier);
  }

  @override
  void visitPrefixExpression(PrefixExpression node) {
    sink.write(node.operator.lexeme);
    _writeOperand(node, node.operand);
  }

  @override
  void visitPrimaryConstructorDeclaration(PrimaryConstructorDeclaration node) {
    _visitToken(node.constKeyword);
    _visitToken(node.typeName, prefix: ' ');
    _visitNode(node.typeParameters);
    _visitNode(node.constructorName);
    _visitNode(node.formalParameters);
  }

  @override
  void visitPrimaryConstructorName(PrimaryConstructorName node) {
    _visitToken(node.period);
    _visitToken(node.name);
  }

  @override
  void visitPropertyAccess(PropertyAccess node) {
    if (node.isCascaded) {
      sink.write(node.operator.lexeme);
    } else {
      _visitNode(node.target);
      sink.write(node.operator.lexeme);
    }
    _visitNode(node.propertyName);
  }

  @override
  void visitRecordLiteral(RecordLiteral node) {
    _visitToken(node.leftParenthesis);
    _visitNodeList(node.fields, separator: ', ');
    _visitToken(node.rightParenthesis);
  }

  @override
  void visitRecordPattern(RecordPattern node) {
    var fields = node.fields;
    sink.write('(');
    _visitNodeList(fields, separator: ', ');
    if (fields.length == 1) {
      sink.write(',');
    }
    sink.write(')');
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    var positionalFields = node.positionalFields;
    var namedFields = node.namedFields;

    sink.write('(');
    if (positionalFields.isNotEmpty) {
      _visitNodeList(positionalFields, separator: ', ');
      if (namedFields != null) {
        sink.write(', ');
      }
    }
    _visitNode(namedFields);
    sink.write(')');
    if (node.question != null) {
      sink.write('?');
    }
  }

  @override
  void visitRecordTypeAnnotationNamedField(
    RecordTypeAnnotationNamedField node,
  ) {
    _visitNode(node.type);
    sink.write(' ');
    sink.write(node.name);
  }

  @override
  void visitRecordTypeAnnotationNamedFields(
    RecordTypeAnnotationNamedFields node,
  ) {
    sink.write('{');
    _visitNodeList(node.fields, separator: ', ');
    sink.write('}');
  }

  @override
  void visitRecordTypeAnnotationPositionalField(
    RecordTypeAnnotationPositionalField node,
  ) {
    _visitNode(node.type);
    if (node.name != null) {
      sink.write(' ');
      sink.write(node.name);
    }
  }

  @override
  void visitRedirectingConstructorInvocation(
    RedirectingConstructorInvocation node,
  ) {
    sink.write('this');
    _visitNode(node.constructorName, prefix: '.');
    _visitNode(node.argumentList);
  }

  @override
  void visitRelationalPattern(RelationalPattern node) {
    sink.write(node.operator.lexeme);
    sink.write(' ');
    _visitNode(node.operand);
  }

  @override
  void visitRepresentationConstructorName(RepresentationConstructorName node) {
    _visitToken(node.period);
    _visitToken(node.name);
  }

  @override
  void visitRepresentationDeclaration(RepresentationDeclaration node) {
    _visitNode(node.constructorName);
    _visitToken(node.leftParenthesis);
    _visitNodeList(node.fieldMetadata, separator: ' ', suffix: ' ');
    _visitNode(node.fieldType, suffix: ' ');
    _visitToken(node.fieldName);
    _visitToken(node.rightParenthesis);
  }

  @override
  void visitRestPatternElement(RestPatternElement node) {
    sink.write(node.operator.lexeme);
    _visitNode(node.pattern);
  }

  @override
  void visitRethrowExpression(RethrowExpression node) {
    sink.write('rethrow');
  }

  @override
  void visitReturnStatement(ReturnStatement node) {
    var expression = node.expression;
    if (expression == null) {
      sink.write('return;');
    } else {
      sink.write('return ');
      expression.accept(this);
      sink.write(';');
    }
  }

  @override
  void visitScriptTag(ScriptTag node) {
    sink.write(node.scriptTag.lexeme);
  }

  @override
  void visitSetOrMapLiteral(SetOrMapLiteral node) {
    _visitToken(node.constKeyword, suffix: ' ');
    _visitNode(node.typeArguments);
    sink.write('{');
    _visitNodeList(node.elements, separator: ', ');
    sink.write('}');
  }

  @override
  void visitShowCombinator(ShowCombinator node) {
    sink.write('show ');
    _visitNodeList(node.shownNames, separator: ', ');
  }

  @override
  void visitSimpleFormalParameter(SimpleFormalParameter node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.requiredKeyword, suffix: ' ');
    _visitToken(node.covariantKeyword, suffix: ' ');
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type);
    if (node.type != null && node.name != null) {
      sink.write(' ');
    }
    _visitToken(node.name);
  }

  @override
  void visitSimpleIdentifier(SimpleIdentifier node) {
    sink.write(node.token.lexeme);
  }

  @override
  void visitSimpleStringLiteral(SimpleStringLiteral node) {
    sink.write(node.literal.lexeme);
  }

  @override
  void visitSpreadElement(SpreadElement node) {
    sink.write(node.spreadOperator.lexeme);
    _visitNode(node.expression);
  }

  @override
  void visitStringInterpolation(StringInterpolation node) {
    _visitNodeList(node.elements);
  }

  @override
  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
    sink.write('super');
    _visitNode(node.constructorName, prefix: '.');
    _visitNode(node.argumentList);
  }

  @override
  void visitSuperExpression(SuperExpression node) {
    sink.write('super');
  }

  @override
  void visitSuperFormalParameter(SuperFormalParameter node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.requiredKeyword, suffix: ' ');
    _visitToken(node.covariantKeyword, suffix: ' ');
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type, suffix: ' ');
    sink.write('super.');
    _visitToken(node.name);
    _visitNode(node.typeParameters);
    _visitNode(node.parameters);
  }

  @override
  void visitSwitchCase(SwitchCase node) {
    _visitNodeList(node.labels, separator: ' ', suffix: ' ');
    sink.write('case ');
    _visitNode(node.expression);
    sink.write(': ');
    _visitNodeList(node.statements, separator: ' ');
  }

  @override
  void visitSwitchDefault(SwitchDefault node) {
    _visitNodeList(node.labels, separator: ' ', suffix: ' ');
    sink.write('default: ');
    _visitNodeList(node.statements, separator: ' ');
  }

  @override
  void visitSwitchExpression(SwitchExpression node) {
    sink.write('switch (');
    _visitNode(node.expression);
    sink.write(') {');
    _visitNodeList(node.cases, separator: ', ');
    sink.write('}');
  }

  @override
  void visitSwitchExpressionCase(SwitchExpressionCase node) {
    _visitNode(node.guardedPattern);
    sink.write(' => ');
    _visitNode(node.expression);
  }

  @override
  void visitSwitchPatternCase(SwitchPatternCase node) {
    _visitNodeList(node.labels, separator: ' ', suffix: ' ');
    sink.write('case ');
    _visitNode(node.guardedPattern);
    sink.write(': ');
    _visitNodeList(node.statements, separator: ' ');
  }

  @override
  void visitSwitchStatement(SwitchStatement node) {
    sink.write('switch (');
    _visitNode(node.expression);
    sink.write(') {');
    _visitNodeList(node.members, separator: ' ');
    sink.write('}');
  }

  @override
  void visitSymbolLiteral(SymbolLiteral node) {
    sink.write('#');
    List<Token> components = node.components;
    for (int i = 0; i < components.length; i++) {
      if (i > 0) {
        sink.write('.');
      }
      sink.write(components[i].lexeme);
    }
  }

  @override
  void visitThisExpression(ThisExpression node) {
    sink.write('this');
  }

  @override
  void visitThrowExpression(ThrowExpression node) {
    sink.write('throw ');
    _visitNode(node.expression);
  }

  @override
  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.augmentKeyword, suffix: ' ');
    _visitToken(node.externalKeyword, suffix: ' ');
    _visitNode(node.variables, suffix: ';');
  }

  @override
  void visitTryStatement(TryStatement node) {
    sink.write('try ');
    _visitNode(node.body);
    _visitNodeList(node.catchClauses, prefix: ' ', separator: ' ');
    _visitNode(node.finallyBlock, prefix: ' finally ');
  }

  @override
  void visitTypeArgumentList(TypeArgumentList node) {
    sink.write('<');
    _visitNodeList(node.arguments, separator: ', ');
    sink.write('>');
  }

  @override
  void visitTypeLiteral(TypeLiteral node) {
    _visitNode(node.type);
  }

  @override
  void visitTypeParameter(TypeParameter node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    // TODO(kallentu): : Clean up TypeParameterImpl casting once variance is
    // added to the interface.
    var varianceKeyword = (node as TypeParameterImpl).varianceKeyword;
    if (varianceKeyword != null) {
      sink.write('${varianceKeyword.lexeme} ');
    }
    _visitToken(node.name);
    _visitNode(node.bound, prefix: ' extends ');
  }

  @override
  void visitTypeParameterList(TypeParameterList node) {
    sink.write('<');
    _visitNodeList(node.typeParameters, separator: ', ');
    sink.write('>');
  }

  @override
  void visitVariableDeclaration(VariableDeclaration node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.name);
    _visitNode(node.initializer, prefix: ' = ');
  }

  @override
  void visitVariableDeclarationList(VariableDeclarationList node) {
    _visitNodeList(node.metadata, separator: ' ', suffix: ' ');
    _visitToken(node.lateKeyword, suffix: ' ');
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type, suffix: ' ');
    _visitNodeList(node.variables, separator: ', ');
  }

  @override
  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
    _visitNode(node.variables);
    sink.write(';');
  }

  @override
  void visitWhenClause(WhenClause node) {
    sink.write('when ');
    _visitNode(node.expression);
  }

  @override
  void visitWhileStatement(WhileStatement node) {
    sink.write('while (');
    _visitNode(node.condition);
    sink.write(') ');
    _visitNode(node.body);
  }

  @override
  void visitWildcardPattern(WildcardPattern node) {
    _visitToken(node.keyword, suffix: ' ');
    _visitNode(node.type, suffix: ' ');
    sink.write(node.name.lexeme);
  }

  @override
  void visitWithClause(WithClause node) {
    sink.write('with ');
    _visitNodeList(node.mixinTypes, separator: ', ');
  }

  @override
  void visitYieldStatement(YieldStatement node) {
    if (node.star != null) {
      sink.write('yield* ');
    } else {
      sink.write('yield ');
    }
    _visitNode(node.expression);
    sink.write(';');
  }

  /// Visit the given function [body], printing a prefix before if the body
  /// is not empty.
  void _visitFunctionBody(FunctionBody body) {
    if (body is! EmptyFunctionBody) {
      sink.write(' ');
    }
    _visitNode(body);
  }

  /// Print the given [node], printing the [prefix] before the node,
  /// and [suffix] after the node, if it is non-`null`.
  void _visitNode(AstNode? node, {String prefix = '', String suffix = ''}) {
    if (node != null) {
      sink.write(prefix);
      node.accept(this);
      sink.write(suffix);
    }
  }

  /// Print a list of [nodes], separated by the given [separator]; if the list
  /// is not empty print [prefix] before the first node, and [suffix] after
  /// the last node.
  void _visitNodeList(
    List<AstNode> nodes, {
    String prefix = '',
    String separator = '',
    String suffix = '',
  }) {
    var length = nodes.length;
    if (length > 0) {
      sink.write(prefix);
      for (int i = 0; i < length; i++) {
        if (i > 0) {
          sink.write(separator);
        }
        nodes[i].accept(this);
      }
      sink.write(suffix);
    }
  }

  /// Print the given [token].
  void _visitToken(Token? token, {String prefix = '', String suffix = ''}) {
    if (token != null) {
      sink.write(prefix);
      sink.write(token.lexeme);
      sink.write(suffix);
    }
  }

  void _writeOperand(Expression node, Expression operand) {
    bool needsParenthesis = operand.precedence < node.precedence;
    if (needsParenthesis) {
      sink.write('(');
    }
    operand.accept(this);
    if (needsParenthesis) {
      sink.write(')');
    }
  }
}
