// Copyright (c) 2017, 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/src/diagnostic/diagnostic.dart' as diag;
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../dart/resolution/node_text_expectations.dart';
import '../diagnostics/parser_diagnostics.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(AstBuilderTest);
    defineReflectiveTests(UpdateNodeTextExpectations);
  });
}

@reflectiveTest
class AstBuilderTest extends ParserDiagnosticsTest {
  void test_class_abstract_final_base() {
    var parseResult = parseStringWithErrors(r'''
/// text
abstract final base class A {}
''');
    parseResult.assertErrors([error(diag.abstractFinalBaseClass, 18, 10)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  abstractKeyword: abstract
  baseKeyword: base
  finalKeyword: final
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_abstract_final_interface() {
    var parseResult = parseStringWithErrors(r'''
/// text
abstract final interface class A {}
''');
    parseResult.assertErrors([error(diag.abstractFinalInterfaceClass, 18, 15)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  abstractKeyword: abstract
  interfaceKeyword: interface
  finalKeyword: final
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_abstract_sealed() {
    var parseResult = parseStringWithErrors(r'''
/// text
abstract sealed class A {}
''');
    parseResult.assertErrors([error(diag.abstractSealedClass, 18, 6)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  abstractKeyword: abstract
  sealedKeyword: sealed
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_base() {
    var parseResult = parseStringWithErrors(r'''
/// text
base class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  baseKeyword: base
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_commentReferences_beforeAbstract() {
    var parseResult = parseStringWithErrors(r'''
/** [String] */ abstract class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    references
      CommentReference
        expression: SimpleIdentifier
          token: String @5
    tokens
      /** [String] */
        offset: 0
  abstractKeyword: abstract @16
  classKeyword: class @25
  namePart: NameWithTypeParameters
    typeName: A @31
  body: BlockClassBody
    leftBracket: { @33
    rightBracket: } @34
''', withOffsets: true);
  }

  void test_class_commentReferences_beforeAnnotation() {
    var parseResult = parseStringWithErrors(r'''
/// See [int] and [String]
/// and [Object].
@Annotation
abstract class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    references
      CommentReference
        expression: SimpleIdentifier
          token: int @9
      CommentReference
        expression: SimpleIdentifier
          token: String @19
      CommentReference
        expression: SimpleIdentifier
          token: Object @36
    tokens
      /// See [int] and [String]
        offset: 0
      /// and [Object].
        offset: 27
  metadata
    Annotation
      atSign: @ @45
      name: SimpleIdentifier
        token: Annotation @46
  abstractKeyword: abstract @57
  classKeyword: class @66
  namePart: NameWithTypeParameters
    typeName: A @72
  body: BlockClassBody
    leftBracket: { @74
    rightBracket: } @75
''', withOffsets: true);
  }

  void test_class_commentReferences_complex() {
    var parseResult = parseStringWithErrors(r'''
/// This dartdoc comment [should] be ignored
@Annotation
/// This dartdoc comment is [included].
// a non dartdoc comment [inbetween]
/// See [int] and [String] but `not [a]`
/// ```
/// This [code] block should be ignored
/// ```
/// and [Object].
abstract class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    references
      CommentReference
        expression: SimpleIdentifier
          token: included @86
      CommentReference
        expression: SimpleIdentifier
          token: int @143
      CommentReference
        expression: SimpleIdentifier
          token: String @153
      CommentReference
        expression: SimpleIdentifier
          token: Object @240
    tokens
      /// This dartdoc comment is [included].
        offset: 57
      /// See [int] and [String] but `not [a]`
        offset: 134
      /// ```
        offset: 175
      /// This [code] block should be ignored
        offset: 183
      /// ```
        offset: 223
      /// and [Object].
        offset: 231
    codeBlocks
      MdCodeBlock
        infoString: <empty>
        type: CodeBlockType.fenced
        lines
          MdCodeBlockLine
            offset: 178
            length: 4
          MdCodeBlockLine
            offset: 186
            length: 36
          MdCodeBlockLine
            offset: 226
            length: 4
  metadata
    Annotation
      atSign: @ @45
      name: SimpleIdentifier
        token: Annotation @46
  abstractKeyword: abstract @249
  classKeyword: class @258
  namePart: NameWithTypeParameters
    typeName: A @264
  body: BlockClassBody
    leftBracket: { @266
    rightBracket: } @267
''', withOffsets: true);
  }

  void test_class_constructor_named() {
    var parseResult = parseStringWithErrors(r'''
class A {
  A.named();
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.constructor('A.named()');
    assertParsedNodeText(node, r'''
ConstructorDeclaration
  typeName: SimpleIdentifier
    token: A
  period: .
  name: named
  parameters: FormalParameterList
    leftParenthesis: (
    rightParenthesis: )
  body: EmptyFunctionBody
    semicolon: ;
''');
  }

  void test_class_constructor_unnamed() {
    var parseResult = parseStringWithErrors(r'''
class A {
  A();
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.constructor('A()');
    assertParsedNodeText(node, r'''
ConstructorDeclaration
  typeName: SimpleIdentifier
    token: A
  parameters: FormalParameterList
    leftParenthesis: (
    rightParenthesis: )
  body: EmptyFunctionBody
    semicolon: ;
''');
  }

  void test_class_extendsClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
class C extends (int, int) {}
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeExtends, 16, 10)]);

    var node = parseResult.findNode.classDeclaration('class C');
    assertParsedNodeText(node, r'''
ClassDeclaration
  classKeyword: class @0
  namePart: NameWithTypeParameters
    typeName: C @6
  body: BlockClassBody
    leftBracket: { @27
    rightBracket: } @28
''', withOffsets: true);
  }

  void test_class_final() {
    var parseResult = parseStringWithErrors(r'''
/// text
final class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  finalKeyword: final
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_final_mixin() {
    var parseResult = parseStringWithErrors(r'''
final mixin class A {}
''');
    parseResult.assertErrors([error(diag.finalMixinClass, 0, 5)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  finalKeyword: final
  mixinKeyword: mixin
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_implementsClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
class C implements A, (int, int), B {}
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeImplements, 22, 10)]);

    var node = parseResult.findNode.classDeclaration('class C');
    assertParsedNodeText(node, r'''
ClassDeclaration
  classKeyword: class @0
  namePart: NameWithTypeParameters
    typeName: C @6
  implementsClause: ImplementsClause
    implementsKeyword: implements @8
    interfaces
      NamedType
        name: A @19
      NamedType
        name: B @34
  body: BlockClassBody
    leftBracket: { @36
    rightBracket: } @37
''', withOffsets: true);
  }

  void test_class_interface() {
    var parseResult = parseStringWithErrors(r'''
/// text
interface class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  interfaceKeyword: interface
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_interface_mixin() {
    var parseResult = parseStringWithErrors(r'''
interface mixin class A {}
''');
    parseResult.assertErrors([error(diag.interfaceMixinClass, 0, 9)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  interfaceKeyword: interface
  mixinKeyword: mixin
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_mixin() {
    var parseResult = parseStringWithErrors(r'''
/// text
mixin class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  mixinKeyword: mixin
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_sealed() {
    var parseResult = parseStringWithErrors(r'''
/// text
sealed class A {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  sealedKeyword: sealed
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_sealed_abstract() {
    var parseResult = parseStringWithErrors(r'''
/// text
sealed abstract class A {}
''');
    parseResult.assertErrors([error(diag.abstractSealedClass, 9, 6)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  documentationComment: Comment
    tokens
      /// text
  abstractKeyword: abstract
  sealedKeyword: sealed
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_sealed_mixin() {
    var parseResult = parseStringWithErrors(r'''
sealed mixin class A {}
''');
    parseResult.assertErrors([error(diag.sealedMixinClass, 0, 6)]);

    var node = parseResult.findNode.classDeclaration('class A {}');
    assertParsedNodeText(node, r'''
ClassDeclaration
  sealedKeyword: sealed
  mixinKeyword: mixin
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: A
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_class_withClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
class C with A, (int, int), B {}
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeWith, 16, 10)]);

    var node = parseResult.findNode.classDeclaration('class C');
    assertParsedNodeText(node, r'''
ClassDeclaration
  classKeyword: class
  namePart: NameWithTypeParameters
    typeName: C
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: A
      NamedType
        name: B
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_classAlias_mixin() {
    var parseResult = parseStringWithErrors(r'''
mixin M {}
/// text
mixin class A = Object with M;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classTypeAlias('class A');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  documentationComment: Comment
    tokens
      /// text
  mixinKeyword: mixin
  typedefKeyword: class
  name: A
  equals: =
  superclass: NamedType
    name: Object
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: M
  semicolon: ;
''');
  }

  void test_classAlias_notNamedType() {
    var parseResult = parseStringWithErrors(r'''
class C = A Function() with M;
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeExtends, 10, 12)]);
    var node = parseResult.findNode.classTypeAlias('class');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  typedefKeyword: class
  name: C
  equals: =
  superclass: NamedType
    name: identifier <synthetic>
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: M
  semicolon: ;
''');
  }

  void test_classTypeAlias_base() {
    var parseResult = parseStringWithErrors(r'''
mixin M {}
/// text
base class A = Object with M;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classTypeAlias('class A');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  documentationComment: Comment
    tokens
      /// text
  baseKeyword: base
  typedefKeyword: class
  name: A
  equals: =
  superclass: NamedType
    name: Object
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: M
  semicolon: ;
''');
  }

  void test_classTypeAlias_final() {
    var parseResult = parseStringWithErrors(r'''
mixin M {}
/// text
final class A = Object with M;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classTypeAlias('class A');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  documentationComment: Comment
    tokens
      /// text
  finalKeyword: final
  typedefKeyword: class
  name: A
  equals: =
  superclass: NamedType
    name: Object
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: M
  semicolon: ;
''');
  }

  void test_classTypeAlias_implementsClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
class C = Object with M implements A, (int, int), B;
mixin M {}
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeImplements, 38, 10)]);

    var node = parseResult.findNode.classTypeAlias('class C');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  typedefKeyword: class @0
  name: C @6
  equals: = @8
  superclass: NamedType
    name: Object @10
  withClause: WithClause
    withKeyword: with @17
    mixinTypes
      NamedType
        name: M @22
  implementsClause: ImplementsClause
    implementsKeyword: implements @24
    interfaces
      NamedType
        name: A @35
      NamedType
        name: B @50
  semicolon: ; @51
''', withOffsets: true);
  }

  void test_classTypeAlias_interface() {
    var parseResult = parseStringWithErrors(r'''
mixin M {}
/// text
interface class A = Object with M;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classTypeAlias('class A');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  documentationComment: Comment
    tokens
      /// text
  interfaceKeyword: interface
  typedefKeyword: class
  name: A
  equals: =
  superclass: NamedType
    name: Object
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: M
  semicolon: ;
''');
  }

  void test_classTypeAlias_sealed() {
    var parseResult = parseStringWithErrors(r'''
mixin M {}
/// text
sealed class A = Object with M;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.classTypeAlias('class A');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  documentationComment: Comment
    tokens
      /// text
  sealedKeyword: sealed
  typedefKeyword: class
  name: A
  equals: =
  superclass: NamedType
    name: Object
  withClause: WithClause
    withKeyword: with
    mixinTypes
      NamedType
        name: M
  semicolon: ;
''');
  }

  void test_classTypeAlias_withClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
class C = Object with A, (int, int), B;
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeWith, 25, 10)]);

    var node = parseResult.findNode.classTypeAlias('class C');
    assertParsedNodeText(node, r'''
ClassTypeAlias
  typedefKeyword: class @0
  name: C @6
  equals: = @8
  superclass: NamedType
    name: Object @10
  withClause: WithClause
    withKeyword: with @17
    mixinTypes
      NamedType
        name: A @22
      NamedType
        name: B @37
  semicolon: ; @38
''', withOffsets: true);
  }

  void test_constructor_factory_misnamed_withoutPrimaryConstructors() {
    var parseResult = parseStringWithErrors(r'''
// @dart = 3.10
class A {
  factory B() => null;
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.constructor('B()');
    assertParsedNodeText(node, r'''
ConstructorDeclaration
  factoryKeyword: factory
  typeName: SimpleIdentifier
    token: B
  parameters: FormalParameterList
    leftParenthesis: (
    rightParenthesis: )
  body: ExpressionFunctionBody
    functionDefinition: =>
    expression: NullLiteral
      literal: null
    semicolon: ;
''');
  }

  void test_constructor_initilizer_assignmentWithSuperCallAsTarget() {
    var parseResult = parseStringWithErrors(r'''
class A {
  A() : super() = 0;
}
''');
    parseResult.assertErrors([
      error(diag.missingAssignableSelector, 18, 7),
      error(diag.invalidInitializer, 18, 11),
    ]);

    var node = parseResult.findNode.constructor('A()');
    assertParsedNodeText(node, r'''
ConstructorDeclaration
  typeName: SimpleIdentifier
    token: A
  parameters: FormalParameterList
    leftParenthesis: (
    rightParenthesis: )
  separator: :
  body: EmptyFunctionBody
    semicolon: ;
''');
  }

  void test_constructor_superParamAndSuperInitializer() {
    var parseResult = parseStringWithErrors(r'''
abstract class A {
  final String f1;

  final int f2;

  const A(this.f1, this.f2);
}

class B extends A {
  const B(super.f1): super(2);
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.constructor('B(');
    assertParsedNodeText(node, r'''
ConstructorDeclaration
  constKeyword: const
  typeName: SimpleIdentifier
    token: B
  parameters: FormalParameterList
    leftParenthesis: (
    parameter: SuperFormalParameter
      superKeyword: super
      period: .
      name: f1
    rightParenthesis: )
  separator: :
  initializers
    SuperConstructorInvocation
      superKeyword: super
      argumentList: ArgumentList
        leftParenthesis: (
        arguments
          IntegerLiteral
            literal: 2
        rightParenthesis: )
  body: EmptyFunctionBody
    semicolon: ;
''');
  }

  void test_constructor_wrongName() {
    var parseResult = parseStringWithErrors(r'''
class A {
  B() : super();
}
''');
    parseResult.assertErrors([error(diag.invalidConstructorName, 12, 1)]);

    var node = parseResult.findNode.constructor('B()');
    assertParsedNodeText(node, r'''
ConstructorDeclaration
  typeName: SimpleIdentifier
    token: B
  parameters: FormalParameterList
    leftParenthesis: (
    rightParenthesis: )
  separator: :
  initializers
    SuperConstructorInvocation
      superKeyword: super
      argumentList: ArgumentList
        leftParenthesis: (
        rightParenthesis: )
  body: EmptyFunctionBody
    semicolon: ;
''');
  }

  void test_dotShorthand_invocation() {
    var parseResult = parseStringWithErrors(r'''
class C {}

void main() {
  C c = .new();
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.dotShorthandInvocation('.new()');
    assertParsedNodeText(node, r'''
DotShorthandInvocation
  period: .
  memberName: SimpleIdentifier
    token: new
  argumentList: ArgumentList
    leftParenthesis: (
    rightParenthesis: )
  isDotShorthand: true
''');
  }

  void test_dotShorthand_propertyAccess() {
    var parseResult = parseStringWithErrors(r'''
enum E { a }

void main() {
  E e = .a;
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.dotShorthandPropertyAccess('.a');
    assertParsedNodeText(node, r'''
DotShorthandPropertyAccess
  period: .
  propertyName: SimpleIdentifier
    token: a
  isDotShorthand: true
''');
  }

  void test_enum_base() {
    var parseResult = parseStringWithErrors(r'''
base enum E { v }
''');
    parseResult.assertErrors([error(diag.baseEnum, 0, 4)]);

    var node = parseResult.findNode.enumDeclaration('enum E');
    assertParsedNodeText(node, r'''
EnumDeclaration
  enumKeyword: enum
  namePart: NameWithTypeParameters
    typeName: E
  body: EnumBody
    leftBracket: {
    constants
      EnumConstantDeclaration
        name: v
    rightBracket: }
''');
  }

  void test_enum_constant_name_dot() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v.
}
''');
    parseResult.assertErrors([
      error(diag.missingIdentifier, 14, 1),
      error(diag.expectedToken, 14, 1),
    ]);

    var node = parseResult.findNode.enumConstantDeclaration('v.');
    assertParsedNodeText(node, r'''
EnumConstantDeclaration
  name: v
  arguments: EnumConstantArguments
    constructorSelector: ConstructorSelector
      period: .
      name: SimpleIdentifier
        token: <empty> <synthetic>
    argumentList: ArgumentList
      leftParenthesis: ( <synthetic>
      rightParenthesis: ) <synthetic>
''');
  }

  void test_enum_constant_name_dot_identifier_semicolon() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v.named;
}
''');
    parseResult.assertErrors([error(diag.expectedToken, 13, 5)]);

    var node = parseResult.findNode.enumConstantDeclaration('v.');
    assertParsedNodeText(node, r'''
EnumConstantDeclaration
  name: v
  arguments: EnumConstantArguments
    constructorSelector: ConstructorSelector
      period: .
      name: SimpleIdentifier
        token: named
    argumentList: ArgumentList
      leftParenthesis: ( <synthetic>
      rightParenthesis: ) <synthetic>
''');
  }

  void test_enum_constant_name_dot_semicolon() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v.;
}
''');
    parseResult.assertErrors([
      error(diag.expectedToken, 13, 1),
      error(diag.missingIdentifier, 13, 1),
    ]);

    var node = parseResult.findNode.enumConstantDeclaration('v.');
    assertParsedNodeText(node, r'''
EnumConstantDeclaration
  name: v
  arguments: EnumConstantArguments
    constructorSelector: ConstructorSelector
      period: .
      name: SimpleIdentifier
        token: <empty> <synthetic>
    argumentList: ArgumentList
      leftParenthesis: ( <synthetic>
      rightParenthesis: ) <synthetic>
''');
  }

  void test_enum_constant_name_typeArguments_dot() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v<int>.
}
''');
    parseResult.assertErrors([
      error(diag.missingIdentifier, 19, 1),
      error(diag.expectedToken, 19, 1),
    ]);

    var node = parseResult.findNode.enumConstantDeclaration('v<int>.');
    assertParsedNodeText(node, r'''
EnumConstantDeclaration
  name: v
  arguments: EnumConstantArguments
    typeArguments: TypeArgumentList
      leftBracket: <
      arguments
        NamedType
          name: int
      rightBracket: >
    constructorSelector: ConstructorSelector
      period: .
      name: SimpleIdentifier
        token: <empty> <synthetic>
    argumentList: ArgumentList
      leftParenthesis: ( <synthetic>
      rightParenthesis: ) <synthetic>
''');
  }

  void test_enum_constant_name_typeArguments_dot_semicolon() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v<int>.;
}
''');
    parseResult.assertErrors([
      error(diag.missingIdentifier, 18, 1),
      error(diag.expectedToken, 18, 1),
    ]);

    var node = parseResult.findNode.enumConstantDeclaration('v<int>');
    assertParsedNodeText(node, r'''
EnumConstantDeclaration
  name: v
  arguments: EnumConstantArguments
    typeArguments: TypeArgumentList
      leftBracket: <
      arguments
        NamedType
          name: int
      rightBracket: >
    constructorSelector: ConstructorSelector
      period: .
      name: SimpleIdentifier
        token: <empty> <synthetic>
    argumentList: ArgumentList
      leftParenthesis: ( <synthetic>
      rightParenthesis: ) <synthetic>
''');
  }

  void test_enum_constant_withTypeArgumentsWithoutArguments() {
    var parseResult = parseStringWithErrors(r'''
enum E<T> {
  v<int>;
}
''');
    parseResult.assertErrors([error(diag.expectedToken, 19, 1)]);

    var node = parseResult.findNode.enumConstantDeclaration('v<int>');
    assertParsedNodeText(node, r'''
EnumConstantDeclaration
  name: v
  arguments: EnumConstantArguments
    typeArguments: TypeArgumentList
      leftBracket: <
      arguments
        NamedType
          name: int
      rightBracket: >
    argumentList: ArgumentList
      leftParenthesis: ( <synthetic>
      rightParenthesis: ) <synthetic>
''');
  }

  void test_enum_final() {
    var parseResult = parseStringWithErrors(r'''
final enum E { v }
''');
    parseResult.assertErrors([error(diag.finalEnum, 0, 5)]);

    var node = parseResult.findNode.enumDeclaration('enum E');
    assertParsedNodeText(node, r'''
EnumDeclaration
  enumKeyword: enum
  namePart: NameWithTypeParameters
    typeName: E
  body: EnumBody
    leftBracket: {
    constants
      EnumConstantDeclaration
        name: v
    rightBracket: }
''');
  }

  void test_enum_interface() {
    var parseResult = parseStringWithErrors(r'''
interface enum E { v }
''');
    parseResult.assertErrors([error(diag.interfaceEnum, 0, 9)]);

    var node = parseResult.findNode.enumDeclaration('enum E');
    assertParsedNodeText(node, r'''
EnumDeclaration
  enumKeyword: enum
  namePart: NameWithTypeParameters
    typeName: E
  body: EnumBody
    leftBracket: {
    constants
      EnumConstantDeclaration
        name: v
    rightBracket: }
''');
  }

  void test_enum_sealed() {
    var parseResult = parseStringWithErrors(r'''
sealed enum E { v }
''');
    parseResult.assertErrors([error(diag.sealedEnum, 0, 6)]);

    var node = parseResult.findNode.enumDeclaration('enum E');
    assertParsedNodeText(node, r'''
EnumDeclaration
  enumKeyword: enum
  namePart: NameWithTypeParameters
    typeName: E
  body: EnumBody
    leftBracket: {
    constants
      EnumConstantDeclaration
        name: v
    rightBracket: }
''');
  }

  void test_enum_semicolon_null() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.enumDeclaration('enum E');
    assertParsedNodeText(node, r'''
EnumDeclaration
  enumKeyword: enum
  namePart: NameWithTypeParameters
    typeName: E
  body: EnumBody
    leftBracket: {
    constants
      EnumConstantDeclaration
        name: v
    rightBracket: }
''');
  }

  void test_enum_semicolon_optional() {
    var parseResult = parseStringWithErrors(r'''
enum E {
  v;
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.enumDeclaration('enum E');
    assertParsedNodeText(node, r'''
EnumDeclaration
  enumKeyword: enum
  namePart: NameWithTypeParameters
    typeName: E
  body: EnumBody
    leftBracket: {
    constants
      EnumConstantDeclaration
        name: v
    semicolon: ;
    rightBracket: }
''');
  }

  void test_extension_onClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
extension E on (int, int) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.extensionDeclaration('extension E');
    assertParsedNodeText(node, r'''
ExtensionDeclaration
  extensionKeyword: extension @0
  name: E @10
  onClause: ExtensionOnClause
    onKeyword: on @12
    extendedType: RecordTypeAnnotation
      leftParenthesis: ( @15
      positionalFields
        RecordTypeAnnotationPositionalField
          type: NamedType
            name: int @16
        RecordTypeAnnotationPositionalField
          type: NamedType
            name: int @21
      rightParenthesis: ) @24
  body: BlockClassBody
    leftBracket: { @26
    rightBracket: } @27
''', withOffsets: true);
  }

  void test_getter_sameNameAsClass() {
    var parseResult = parseStringWithErrors(r'''
class A {
  get A => 0;
}
''');
    parseResult.assertErrors([error(diag.memberWithClassName, 16, 1)]);

    var node = parseResult.findNode.methodDeclaration('get A');
    assertParsedNodeText(node, r'''
MethodDeclaration
  propertyKeyword: get
  name: A
  body: ExpressionFunctionBody
    functionDefinition: =>
    expression: IntegerLiteral
      literal: 0
    semicolon: ;
''');
  }

  void test_library_with_name() {
    var parseResult = parseStringWithErrors(r'''
library name.and.dots;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.library('library');
    assertParsedNodeText(node, r'''
LibraryDirective
  libraryKeyword: library
  name: LibraryIdentifier
    components
      SimpleIdentifier
        token: name
      SimpleIdentifier
        token: and
      SimpleIdentifier
        token: dots
  semicolon: ;
''');
  }

  void test_library_without_name() {
    var parseResult = parseStringWithErrors(r'''
library;
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.library('library');
    assertParsedNodeText(node, r'''
LibraryDirective
  libraryKeyword: library
  semicolon: ;
''');
  }

  void test_mixin_base() {
    var parseResult = parseStringWithErrors(r'''
/// text
base mixin M {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.mixinDeclaration('mixin M');
    assertParsedNodeText(node, r'''
MixinDeclaration
  documentationComment: Comment
    tokens
      /// text
  baseKeyword: base
  mixinKeyword: mixin
  name: M
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_mixin_final() {
    var parseResult = parseStringWithErrors(r'''
final mixin M {}
''');
    parseResult.assertErrors([error(diag.finalMixin, 0, 5)]);

    var node = parseResult.findNode.mixinDeclaration('mixin M');
    assertParsedNodeText(node, r'''
MixinDeclaration
  mixinKeyword: mixin
  name: M
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_mixin_implementsClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
class C {}
mixin M on C implements A, (int, int), B {}
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeImplements, 38, 10)]);

    var node = parseResult.findNode.mixinDeclaration('mixin M');
    assertParsedNodeText(node, r'''
MixinDeclaration
  mixinKeyword: mixin @11
  name: M @17
  onClause: MixinOnClause
    onKeyword: on @19
    superclassConstraints
      NamedType
        name: C @22
  implementsClause: ImplementsClause
    implementsKeyword: implements @24
    interfaces
      NamedType
        name: A @35
      NamedType
        name: B @50
  body: BlockClassBody
    leftBracket: { @52
    rightBracket: } @53
''', withOffsets: true);
  }

  void test_mixin_interface() {
    var parseResult = parseStringWithErrors(r'''
interface mixin M {}
''');
    parseResult.assertErrors([error(diag.interfaceMixin, 0, 9)]);

    var node = parseResult.findNode.mixinDeclaration('mixin M');
    assertParsedNodeText(node, r'''
MixinDeclaration
  mixinKeyword: mixin
  name: M
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_mixin_onClause_recordType() {
    var parseResult = parseStringWithErrors(r'''
mixin M on A, (int, int), B {}
''');
    parseResult.assertErrors([error(diag.expectedNamedTypeOn, 14, 10)]);

    var node = parseResult.findNode.mixinDeclaration('mixin M');
    assertParsedNodeText(node, r'''
MixinDeclaration
  mixinKeyword: mixin @0
  name: M @6
  onClause: MixinOnClause
    onKeyword: on @8
    superclassConstraints
      NamedType
        name: A @11
      NamedType
        name: B @26
  body: BlockClassBody
    leftBracket: { @28
    rightBracket: } @29
''', withOffsets: true);
  }

  void test_mixin_sealed() {
    var parseResult = parseStringWithErrors(r'''
sealed mixin M {}
''');
    parseResult.assertErrors([error(diag.sealedMixin, 0, 6)]);

    var node = parseResult.findNode.mixinDeclaration('mixin M');
    assertParsedNodeText(node, r'''
MixinDeclaration
  mixinKeyword: mixin
  name: M
  body: BlockClassBody
    leftBracket: {
    rightBracket: }
''');
  }

  void test_recordLiteral() {
    var parseResult = parseStringWithErrors(r'''
final x = (0, a: 1);
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordLiteral('(0');
    assertParsedNodeText(node, r'''
RecordLiteral
  leftParenthesis: (
  fields
    IntegerLiteral
      literal: 0
    NamedExpression
      name: Label
        label: SimpleIdentifier
          token: a
        colon: :
      expression: IntegerLiteral
        literal: 1
  rightParenthesis: )
''');
  }

  void test_recordLiteral_named_trailingComma() {
    var parseResult = parseStringWithErrors(r'''
final x = (0, a: 1,);
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordLiteral('(0');
    assertParsedNodeText(node, r'''
RecordLiteral
  leftParenthesis: (
  fields
    IntegerLiteral
      literal: 0
    NamedExpression
      name: Label
        label: SimpleIdentifier
          token: a
        colon: :
      expression: IntegerLiteral
        literal: 1
  rightParenthesis: )
''');
  }

  void test_recordLiteral_positional_one_trailingComma() {
    var parseResult = parseStringWithErrors(r'''
final x = (0,);
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordLiteral('(0');
    assertParsedNodeText(node, r'''
RecordLiteral
  leftParenthesis: (
  fields
    IntegerLiteral
      literal: 0
  rightParenthesis: )
''');
  }

  void test_recordLiteral_positional_trailingComma() {
    var parseResult = parseStringWithErrors(r'''
final x = (0, 1,);
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordLiteral('(0');
    assertParsedNodeText(node, r'''
RecordLiteral
  leftParenthesis: (
  fields
    IntegerLiteral
      literal: 0
    IntegerLiteral
      literal: 1
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_empty() {
    var parseResult = parseStringWithErrors(r'''
() f() {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('() f');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_mixed() {
    var parseResult = parseStringWithErrors(r'''
void f((int, bool, {int a, bool b}) r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: bool
  namedFields: RecordTypeAnnotationNamedFields
    leftBracket: {
    fields
      RecordTypeAnnotationNamedField
        type: NamedType
          name: int
        name: a
      RecordTypeAnnotationNamedField
        type: NamedType
          name: bool
        name: b
    rightBracket: }
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_named() {
    var parseResult = parseStringWithErrors(r'''
void f(({int a, bool b}) r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('({int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  namedFields: RecordTypeAnnotationNamedFields
    leftBracket: {
    fields
      RecordTypeAnnotationNamedField
        type: NamedType
          name: int
        name: a
      RecordTypeAnnotationNamedField
        type: NamedType
          name: bool
        name: b
    rightBracket: }
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_named_trailingComma() {
    var parseResult = parseStringWithErrors(r'''
void f(({int a, bool b,}) r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('({int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  namedFields: RecordTypeAnnotationNamedFields
    leftBracket: {
    fields
      RecordTypeAnnotationNamedField
        type: NamedType
          name: int
        name: a
      RecordTypeAnnotationNamedField
        type: NamedType
          name: bool
        name: b
    rightBracket: }
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_nullable() {
    var parseResult = parseStringWithErrors(r'''
void f((int, bool)? r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: bool
  rightParenthesis: )
  question: ?
''');
  }

  void test_recordTypeAnnotation_positional() {
    var parseResult = parseStringWithErrors(r'''
void f((int, bool) r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: bool
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_positional_one() {
    var parseResult = parseStringWithErrors(r'''
void f((int) r) {}
''');
    parseResult.assertErrors([
      error(diag.recordTypeOnePositionalNoTrailingComma, 11, 1),
    ]);

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_positional_one_trailingComma() {
    var parseResult = parseStringWithErrors(r'''
void f((int, ) r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_positional_trailingComma() {
    var parseResult = parseStringWithErrors(r'''
void f((int, bool,) r) {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: bool
  rightParenthesis: )
''');
  }

  void test_recordTypeAnnotation_topFunction_returnType_withTypeParameter() {
    var parseResult = parseStringWithErrors(r'''
(int, T) f<T>() {}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.recordTypeAnnotation('(int');
    assertParsedNodeText(node, r'''
RecordTypeAnnotation
  leftParenthesis: (
  positionalFields
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: int
    RecordTypeAnnotationPositionalField
      type: NamedType
        name: T
  rightParenthesis: )
''');
  }

  void test_superFormalParameter() {
    var parseResult = parseStringWithErrors(r'''
class A {
  A(super.a);
}
''');
    parseResult.assertNoErrors();

    var node = parseResult.findNode.superFormalParameter('super.a');
    assertParsedNodeText(node, r'''
SuperFormalParameter
  superKeyword: super
  period: .
  name: a
''');
  }

  void test_switchStatement_withPatternCase_whenDisabled() {
    var parseResult = parseStringWithErrors(r'''
// @dart = 2.19
void f(Object value) {
  switch (value) {
    case (int a,) when a == 0:
  }
}
''');
    parseResult.assertErrors([error(diag.expectedToken, 72, 1)]);

    var node = parseResult.findNode.switchCase('case');
    assertParsedNodeText(node, r'''
SwitchCase
  keyword: case
  expression: ParenthesizedExpression
    leftParenthesis: (
    expression: SimpleIdentifier
      token: int
    rightParenthesis: )
  colon: :
''');
  }
}
