// Copyright (c) 2016, 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/ast.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../src/dart/resolution/context_collection_resolution.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(SimpleResolverTest);
  });
}

@reflectiveTest
class SimpleResolverTest extends PubPackageResolutionTest {
  test_argumentResolution_required_matching() async {
    await resolveTestCode(r'''
class A {
  void f() {
    g(1, 2, 3);
  }
  void g(a, b, c) {}
}''');
    await _validateArgumentResolution([0, 1, 2]);
  }

  test_argumentResolution_required_tooFew() async {
    await resolveTestCode(r'''
class A {
  void f() {
    g(1, 2);
  }
  void g(a, b, c) {}
}''');
    await _validateArgumentResolution([0, 1]);
  }

  test_argumentResolution_required_tooMany() async {
    await resolveTestCode(r'''
class A {
  void f() {
    g(1, 2, 3);
  }
  void g(a, b) {}
}''');
    await _validateArgumentResolution([0, 1, -1]);
  }

  test_argumentResolution_requiredAndNamed_extra() async {
    await resolveTestCode('''
class A {
  void f() {
    g(1, 2, c: 3, d: 4);
  }
  void g(a, b, {c}) {}
}''');
    await _validateArgumentResolution([0, 1, 2, -1]);
  }

  test_argumentResolution_requiredAndNamed_matching() async {
    await resolveTestCode(r'''
class A {
  void f() {
    g(1, 2, c: 3);
  }
  void g(a, b, {c}) {}
}''');
    await _validateArgumentResolution([0, 1, 2]);
  }

  test_argumentResolution_requiredAndNamed_missing() async {
    await resolveTestCode('''
class A {
  void f() {
    g(1, 2, d: 3);
  }
  void g(a, b, {c, d}) {}
}''');
    await _validateArgumentResolution([0, 1, 3]);
  }

  test_argumentResolution_requiredAndPositional_fewer() async {
    await resolveTestCode('''
class A {
  void f() {
    g(1, 2, 3);
  }
  void g(a, b, [c, d]) {}
}''');
    await _validateArgumentResolution([0, 1, 2]);
  }

  test_argumentResolution_requiredAndPositional_matching() async {
    await resolveTestCode(r'''
class A {
  void f() {
    g(1, 2, 3, 4);
  }
  void g(a, b, [c, d]) {}
}''');
    await _validateArgumentResolution([0, 1, 2, 3]);
  }

  test_argumentResolution_requiredAndPositional_more() async {
    await resolveTestCode(r'''
class A {
  void f() {
    g(1, 2, 3, 4);
  }
  void g(a, b, [c]) {}
}''');
    await _validateArgumentResolution([0, 1, 2, -1]);
  }

  test_argumentResolution_setter_propagated() async {
    await resolveTestCode(r'''
main() {
  var a = new A();
  a.sss = 0;
}
class A {
  set sss(x) {}
}''');
    var rhs = findNode.assignment(' = 0;').rightHandSide;
    expect(
      rhs.correspondingParameter,
      findElement2.parameter('x'),
    );
  }

  test_argumentResolution_setter_propagated_propertyAccess() async {
    await resolveTestCode(r'''
main() {
  var a = new A();
  a.b.sss = 0;
}
class A {
  B b = new B();
}
class B {
  set sss(x) {}
}''');
    var rhs = findNode.assignment(' = 0;').rightHandSide;
    expect(
      rhs.correspondingParameter,
      findElement2.parameter('x'),
    );
  }

  test_argumentResolution_setter_static() async {
    await resolveTestCode(r'''
main() {
  A a = new A();
  a.sss = 0;
}
class A {
  set sss(x) {}
}''');
    var rhs = findNode.assignment(' = 0;').rightHandSide;
    expect(
      rhs.correspondingParameter,
      findElement2.parameter('x'),
    );
  }

  test_argumentResolution_setter_static_propertyAccess() async {
    await resolveTestCode(r'''
main() {
  A a = new A();
  a.b.sss = 0;
}
class A {
  B b = new B();
}
class B {
  set sss(x) {}
}''');
    var rhs = findNode.assignment(' = 0;').rightHandSide;
    expect(
      rhs.correspondingParameter,
      findElement2.parameter('x'),
    );
  }

  test_breakTarget_labeled() async {
    // Verify that the target of the label is correctly found and is recorded
    // as the unlabeled portion of the statement.
    await resolveTestCode(r'''
void f() {
  loop1: while (true) {
    loop2: for (int i = 0; i < 10; i++) {
      break loop1;
      break loop2;
    }
  }
}
''');
    var break1 = findNode.breakStatement('break loop1;');
    var whileStatement = findNode.whileStatement('while (');
    expect(break1.target, same(whileStatement));

    var break2 = findNode.breakStatement('break loop2;');
    var forStatement = findNode.forStatement('for (');
    expect(break2.target, same(forStatement));
  }

  test_breakTarget_unlabeledBreakFromDo() async {
    await resolveTestCode('''
void f() {
  do {
    break;
  } while (true);
}
''');
    var doStatement = findNode.doStatement('do {');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, same(doStatement));
  }

  test_breakTarget_unlabeledBreakFromFor() async {
    await resolveTestCode(r'''
void f() {
  for (int i = 0; i < 10; i++) {
    break;
  }
}
''');
    var forStatement = findNode.forStatement('for (');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, same(forStatement));
  }

  test_breakTarget_unlabeledBreakFromForEach() async {
    await resolveTestCode('''
void f() {
  for (x in []) {
    break;
  }
}
''');
    var forStatement = findNode.forStatement('for (');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, same(forStatement));
  }

  test_breakTarget_unlabeledBreakFromSwitch() async {
    await resolveTestCode(r'''
void f() {
  while (true) {
    switch (0) {
      case 0:
        break;
    }
  }
}
''');
    var switchStatement = findNode.switchStatement('switch (');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, same(switchStatement));
  }

  test_breakTarget_unlabeledBreakFromSwitch_language219() async {
    await resolveTestCode(r'''
// @dart = 2.19
void f() {
  while (true) {
    switch (0) {
      case 0:
        break;
    }
  }
}
''');
    var switchStatement = findNode.switchStatement('switch (');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, same(switchStatement));
  }

  test_breakTarget_unlabeledBreakFromWhile() async {
    await resolveTestCode(r'''
void f() {
  while (true) {
    break;
  }
}
''');
    var whileStatement = findNode.whileStatement('while (');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, same(whileStatement));
  }

  test_breakTarget_unlabeledBreakToOuterFunction() async {
    // Verify that unlabeled break statements can't resolve to loops in an
    // outer function.
    await resolveTestCode(r'''
void f() {
  while (true) {
    void g() {
      break;
    }
  }
}
''');
    var breakStatement = findNode.breakStatement('break;');
    expect(breakStatement.target, isNull);
  }

  test_class_definesCall() async {
    await assertNoErrorsInCode(r'''
class A {
  int call(int x) { return x; }
}
int f(A a) {
  return a(0);
}''');
  }

  test_class_extends_implements() async {
    await assertNoErrorsInCode(r'''
class A extends B implements C {}
class B {}
class C {}''');
  }

  test_continueTarget_labeled() async {
    // Verify that the target of the label is correctly found and is recorded
    // as the unlabeled portion of the statement.
    await resolveTestCode('''
void f() {
  loop1: while (true) {
    loop2: for (int i = 0; i < 10; i++) {
      continue loop1;
      continue loop2;
    }
  }
}
''');
    var continue1 = findNode.continueStatement('continue loop1');
    var whileStatement = findNode.whileStatement('while (');
    expect(continue1.target, same(whileStatement));

    var continue2 = findNode.continueStatement('continue loop2');
    var forStatement = findNode.forStatement('for (');
    expect(continue2.target, same(forStatement));
  }

  test_continueTarget_unlabeledContinueFromDo() async {
    await resolveTestCode('''
void f() {
  do {
    continue;
  } while (true);
}
''');
    var doStatement = findNode.doStatement('do {');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, same(doStatement));
  }

  test_continueTarget_unlabeledContinueFromFor() async {
    await resolveTestCode('''
void f() {
  for (int i = 0; i < 10; i++) {
    continue;
  }
}
''');
    var forStatement = findNode.forStatement('for (');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, same(forStatement));
  }

  test_continueTarget_unlabeledContinueFromForEach() async {
    await resolveTestCode(r'''
void f() {
  for (x in []) {
    continue;
  }
}
''');
    var forStatement = findNode.forStatement('for (');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, same(forStatement));
  }

  test_continueTarget_unlabeledContinueFromWhile() async {
    await resolveTestCode(r'''
void f() {
  while (true) {
    continue;
  }
}
''');
    var whileStatement = findNode.whileStatement('while (');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, same(whileStatement));
  }

  test_continueTarget_unlabeledContinueSkipsSwitch() async {
    await resolveTestCode(r'''
void f() {
  while (true) {
    switch (0) {
      case 0:
        continue;
    }
  }
}
''');
    var whileStatement = findNode.whileStatement('while (');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, same(whileStatement));
  }

  test_continueTarget_unlabeledContinueSkipsSwitch_language219() async {
    await resolveTestCode(r'''
// @dart = 2.19
void f() {
  while (true) {
    switch (0) {
      case 0:
        continue;
    }
  }
}
''');
    var whileStatement = findNode.whileStatement('while (');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, same(whileStatement));
  }

  test_continueTarget_unlabeledContinueToOuterFunction() async {
    // Verify that unlabeled continue statements can't resolve to loops in an
    // outer function.
    await resolveTestCode(r'''
void f() {
  while (true) {
    void g() {
      continue;
    }
  }
}
''');
    var continueStatement = findNode.continueStatement('continue;');
    expect(continueStatement.target, isNull);
  }

  test_empty() async {
    await assertNoErrorsInCode('');
  }

  test_entryPoint_exported() async {
    newFile('$testPackageLibPath/a.dart', r'''
main() {}
''');

    await assertNoErrorsInCode(r'''
export 'a.dart';
''');

    var library = result.libraryElement2;
    var main = library.entryPoint2!;

    expect(main, isNotNull);
    expect(main.library2, isNot(same(library)));
  }

  test_entryPoint_local() async {
    await assertNoErrorsInCode(r'''
main() {}
''');

    var library = result.libraryElement2;
    var main = library.entryPoint2!;

    expect(main, isNotNull);
    expect(main.library2, same(library));
  }

  test_entryPoint_none() async {
    await assertNoErrorsInCode('');

    var library = result.libraryElement2;
    expect(library.entryPoint2, isNull);
  }

  test_enum_externalLibrary() async {
    newFile('$testPackageLibPath/a.dart', r'''
enum EEE {A, B, C}
''');
    await assertNoErrorsInCode(r'''
import 'a.dart';

void f(EEE e) {}
''');
  }

  test_extractedMethodAsConstant() async {
    await assertNoErrorsInCode(r'''
abstract class Comparable<T> {
  int compareTo(T other);
  static int compare(Comparable a, Comparable b) => a.compareTo(b);
}
class A {
  void sort([compare = Comparable.compare]) {}
}''');
  }

  test_fieldFormalParameter() async {
    await assertNoErrorsInCode(r'''
class A {
  int x;
  int y;
  A(this.x) : y = x {}
}''');

    var xParameter = findNode.fieldFormalParameter('this.x');

    var xParameterElement = xParameter.declaredFragment!.element;
    expect(xParameterElement.field2, findElement2.field('x'));

    assertResolvedNodeText(findNode.simple('x {}'), r'''
SimpleIdentifier
  token: x
  element: <testLibraryFragment>::@class::A::@constructor::new::@parameter::x#element
  staticType: int
''');
  }

  test_forEachLoops_nonConflicting() async {
    await assertErrorsInCode(r'''
f() {
  List list = [1,2,3];
  for (int x in list) {}
  for (int x in list) {}
}
''', [
      error(WarningCode.UNUSED_LOCAL_VARIABLE, 40, 1),
      error(WarningCode.UNUSED_LOCAL_VARIABLE, 65, 1),
    ]);
  }

  test_forLoops_nonConflicting() async {
    await assertNoErrorsInCode(r'''
f() {
  for (int i = 0; i < 3; i++) {
  }
  for (int i = 0; i < 3; i++) {
  }
}''');
  }

  test_functionTypeAlias() async {
    await assertNoErrorsInCode(r'''
typedef bool P(e);
class A {
  late P p;
  m(e) {
    if (p(e)) {}
  }
}''');
  }

  test_getter_fromMixins_bare_identifier() async {
    await assertNoErrorsInCode('''
class B {}
mixin M1 {
  get x => null;
}
mixin M2 {
  get x => null;
}
class C extends B with M1, M2 {
  f() {
    return x;
  }
}
''');

    // Verify that the getter for "x" in C.f() refers to the getter defined in
    // M2.
    assertResolvedNodeText(findNode.simple('x;'), r'''
SimpleIdentifier
  token: x
  element: <testLibraryFragment>::@mixin::M2::@getter::x#element
  staticType: dynamic
''');
  }

  test_getter_fromMixins_property_access() async {
    await assertErrorsInCode('''
class B {}
mixin M1 {
  get x => null;
}
mixin M2 {
  get x => null;
}
class C extends B with M1, M2 {}
void main() {
  var y = new C().x;
}
''', [
      error(WarningCode.UNUSED_LOCAL_VARIABLE, 124, 1),
    ]);

    // Verify that the getter for "x" in "new C().x" refers to the getter
    // defined in M2.
    assertResolvedNodeText(findNode.simple('x;'), r'''
SimpleIdentifier
  token: x
  element: <testLibraryFragment>::@mixin::M2::@getter::x#element
  staticType: dynamic
''');
  }

  test_import_hide() async {
    newFile('$testPackageLibPath/lib1.dart', r'''
set foo(value) {}
class A {}''');

    newFile('$testPackageLibPath/lib2.dart', r'''
set foo(value) {}''');

    await assertNoErrorsInCode(r'''
import 'lib1.dart' hide foo;
import 'lib2.dart';

main() {
  foo = 0;
}
A a = A();''');
  }

  test_import_prefix() async {
    newFile('$testPackageLibPath/a.dart', r'''
f(int x) {
  return x * x;
}''');

    await assertNoErrorsInCode(r'''
import 'a.dart' as _a;
main() {
  _a.f(0);
}''');
  }

  test_import_prefix_doesNotExist() async {
    //
    // The primary purpose of this test is to ensure that we are only getting a
    // single error generated when the only problem is that an imported file
    // does not exist.
    //
    await assertErrorsInCode('''
import 'missing.dart' as p;
int a = p.q + p.r.s;
String b = p.t(a) + p.u(v: 0);
p.T c = new p.T();
class D<E> extends p.T {
  D(int i) : super(i);
  p.U f = new p.V();
}
class F implements p.T {
  p.T m(p.U u) => null;
}
class G extends Object with p.V {}
class H extends D<p.W> {
  H(int i) : super(i);
}
''', [
      error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 7, 14),
    ]);
  }

  test_import_show_doesNotExist() async {
    //
    // The primary purpose of this test is to ensure that we are only getting a
    // single error generated when the only problem is that an imported file
    // does not exist.
    //
    await assertErrorsInCode('''
import 'missing.dart' show q, r, t, u, T, U, V, W;
int a = q + r.s;
String b = t(a) + u(v: 0);
T c = new T();
class D<E> extends T {
  D(int i) : super(i);
  U f = new V();
}
class F implements T {
  T m(U u) => null;
}
class G extends Object with V {}
class H extends D<W> {
  H(int i) : super(i);
}
''', [
      error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 7, 14),
    ]);
  }

  test_import_spaceInUri() async {
    newFile('$testPackageLibPath/sub folder/a.dart', r'''
foo() {}''');

    await assertNoErrorsInCode(r'''
import 'sub folder/a.dart';

main() {
  foo();
}''');
  }

  test_indexExpression_typeParameters() async {
    await assertNoErrorsInCode(r'''
f() {
  List<int> a = [];
  a[0];
  List<List<int>> b = [];
  b[0][0];
  List<List<List<int>>> c = [];
  c[0][0][0];
}''');
  }

  test_indexExpression_typeParameters_invalidAssignmentWarning() async {
    await assertErrorsInCode(r'''
f() {
  List<List<int>> b = [];
  b[0][0] = 'hi';
}''', [
      error(CompileTimeErrorCode.INVALID_ASSIGNMENT, 44, 4),
    ]);
  }

  test_indirectOperatorThroughCall() async {
    await assertNoErrorsInCode(r'''
class A {
  B call() { return new B(); }
}

class B {
  int operator [](int i) { return i; }
}

A f = new A();

g(int x) {}

main() {
  g(f()[0]);
}''');
  }

  test_invoke_dynamicThroughGetter() async {
    await assertNoErrorsInCode(r'''
class A {
  List get X => [() => 0];
  m(A a) {
    X.last;
  }
}''');
  }

  test_isValidMixin_badSuperclass() async {
    await assertErrorsInCode(r'''
class A extends B {}
class B {}
class C = Object with A;''', [
      error(CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT, 54, 1),
    ]);

    var a = findElement2.class_('A');
    expect(a.isValidMixin, isFalse);
  }

  test_isValidMixin_constructor() async {
    await assertErrorsInCode(r'''
class A {
  A() {}
}
class C = Object with A;''', [
      error(CompileTimeErrorCode.MIXIN_CLASS_DECLARES_CONSTRUCTOR, 43, 1),
    ]);

    var a = findElement2.class_('A');
    expect(a.isValidMixin, isFalse);
  }

  test_isValidMixin_factoryConstructor() async {
    await assertNoErrorsInCode(r'''
mixin class A {
  factory A() => throw 0;
}
class C = Object with A;''');

    var a = findElement2.class_('A');
    expect(a.isValidMixin, isTrue);
  }

  test_isValidMixin_super_toString() async {
    await assertNoErrorsInCode(r'''
mixin class A {
  toString() {
    return super.toString();
  }
}
class C = Object with A;''');

    var a = findElement2.class_('A');
    expect(a.isValidMixin, isTrue);
  }

  test_isValidMixin_valid() async {
    await assertNoErrorsInCode('''
mixin class A {}
class C = Object with A;''');

    var a = findElement2.class_('A');
    expect(a.isValidMixin, isTrue);
  }

  test_labels_switch() async {
    await assertNoErrorsInCode(r'''
void doSwitch(int target) {
  switch (target) {
    l0: case 0:
      continue l1;
    l1: case 1:
      continue l0;
    default:
      continue l1;
  }
}''');
  }

  test_labels_switch_language219() async {
    await assertNoErrorsInCode(r'''
// @dart = 2.19
void doSwitch(int target) {
  switch (target) {
    l0: case 0:
      continue l1;
    l1: case 1:
      continue l0;
    default:
      continue l1;
  }
}''');
  }

  test_localVariable_types_invoked() async {
    await resolveTestCode(r'''
const A = null;
main() {
  var myVar = (int p) => 'foo';
  myVar(42);
}''');
    var node = findNode.simple('myVar(42)');
    assertType(node, 'String Function(int)');
  }

  test_metadata_class() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A class C<A> {}''');

    var annotations = findElement2.class_('C').metadata2.annotations;
    expect(annotations, hasLength(1));

    var cDeclaration = findNode.classDeclaration('C<A>');
    assertResolvedNodeText(cDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_classTypeAlias() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A class C<A> = D with E;
class D {}
mixin E {}
''');

    var annotations = findElement2.class_('C').metadata2.annotations;
    expect(annotations, hasLength(1));

    var cDeclaration = findNode.classTypeAlias('C<A>');
    assertResolvedNodeText(cDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_enum() async {
    await assertNoErrorsInCode('''
const A = null;
@A enum E { A, B }
''');

    var annotations = findElement2.enum_('E').metadata2.annotations;
    expect(annotations, hasLength(1));

    var eDeclaration = findNode.enumDeclaration('E');
    assertResolvedNodeText(eDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_extension() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A extension E<A> on List<A> {}''');

    var annotations = findElement2.extension_('E').metadata2.annotations;
    expect(annotations, hasLength(1));

    var cDeclaration = findNode.extensionDeclaration('E<A>');
    assertResolvedNodeText(cDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_field() async {
    await assertNoErrorsInCode(r'''
const A = null;
class C {
  @A int f = 1;
}''');

    var metadata = findElement2.field('f').metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_fieldFormalParameter() async {
    await assertNoErrorsInCode(r'''
const A = null;
class C {
  int f;
  C(@A this.f);
}''');

    var metadata = findElement2.fieldFormalParameter('f').metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_function() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A f() {}''');

    var annotations = findElement2.topFunction('f').metadata2.annotations;
    expect(annotations, hasLength(1));
  }

  test_metadata_function_generic() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A f<A>() {}''');

    var annotations = findElement2.topFunction('f').metadata2.annotations;
    expect(annotations, hasLength(1));

    var fDeclaration = findNode.functionDeclaration('f<A>');
    assertResolvedNodeText(fDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_functionTypeAlias() async {
    await assertNoErrorsInCode('''
const A = null;
@A typedef F<A>(int A);
''');

    var annotations = findElement2.typeAlias('F').metadata2.annotations;
    expect(annotations, hasLength(1));

    var fDeclaration = findNode.functionTypeAlias('F');
    assertResolvedNodeText(fDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_functionTypedParameter() async {
    await assertNoErrorsInCode(r'''
const A = null;
f(@A int p(int x)) {}''');

    var metadata = findElement2.parameter('p').metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_functionTypedParameter_generic() async {
    await assertNoErrorsInCode(r'''
const A = null;
f(@A int p<A>(int x)) {}''');

    var annotations = findElement2.parameter('p').metadata2.annotations;
    expect(annotations, hasLength(1));

    var pDeclaration = findNode.functionTypedFormalParameter('p<A>');
    assertResolvedNodeText(pDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_genericTypeAlias() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A typedef F<A> = A Function();
''');

    var annotations = findElement2.typeAlias('F').metadata2.annotations;
    expect(annotations, hasLength(1));

    var fDeclaration = findNode.genericTypeAlias('F<A>');
    assertResolvedNodeText(fDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_libraryDirective() async {
    await assertNoErrorsInCode(r'''
@A library lib;
const A = null;''');

    var metadata = result.libraryElement2.metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_method() async {
    await assertNoErrorsInCode(r'''
const A = null;
class C {
  @A void m() {}
}''');

    var metadata = findElement2.method('m').metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_method_generic() async {
    await assertNoErrorsInCode(r'''
const A = null;
class C {
  @A void m<A>() {}
}''');

    var annotations = findElement2.method('m').metadata2.annotations;
    expect(annotations, hasLength(1));

    var mDeclaration = findNode.methodDeclaration('m<A>');
    assertResolvedNodeText(mDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_mixin() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A mixin M<A> on Object {}''');

    var annotations = findElement2.mixin('M').metadata2.annotations;
    expect(annotations, hasLength(1));

    var mDeclaration = findNode.mixinDeclaration('M<A>');
    assertResolvedNodeText(mDeclaration.metadata[0], r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_metadata_namedParameter() async {
    await assertNoErrorsInCode(r'''
const A = null;
f({@A int p = 0}) {}''');

    var metadata = findElement2.parameter('p').metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_positionalParameter() async {
    await assertNoErrorsInCode(r'''
const A = null;
f([@A int p = 0]) {}''');

    var metadata = findElement2.parameter('p').metadata2.annotations;
    expect(metadata, hasLength(1));
  }

  test_metadata_simpleParameter() async {
    await assertNoErrorsInCode(r'''
const A = null;
f(@A p1, @A int p2) {}''');

    expect(findElement2.parameter('p1').metadata2.annotations, hasLength(1));
    expect(findElement2.parameter('p2').metadata2.annotations, hasLength(1));
  }

  test_metadata_typedef() async {
    await assertNoErrorsInCode(r'''
const A = null;
@A typedef F<A>();''');

    expect(
      findElement2.typeAlias('F').metadata2.annotations,
      hasLength(1),
    );

    var annotation = findNode.annotation('@A');
    assertResolvedNodeText(annotation, r'''
Annotation
  atSign: @
  name: SimpleIdentifier
    token: A
    element: <testLibraryFragment>::@getter::A#element
    staticType: null
  element2: <testLibraryFragment>::@getter::A#element
''');
  }

  test_method_fromMixin() async {
    await assertNoErrorsInCode(r'''
class B {
  bar() => 1;
}
mixin class A {
  foo() => 2;
}

class C extends B with A {
  bar() => super.bar();
  foo() => super.foo();
}''');
  }

  test_method_fromMixins() async {
    await assertNoErrorsInCode('''
class B {}
mixin M1 {
  void f() {}
}
mixin M2 {
  void f() {}
}
class C extends B with M1, M2 {}
void main() {
  new C().f();
}
''');

    assertResolvedNodeText(findNode.simple('f();'), r'''
SimpleIdentifier
  token: f
  element: <testLibraryFragment>::@mixin::M2::@method::f#element
  staticType: void Function()
''');
  }

  test_method_fromMixins_bare_identifier() async {
    await assertNoErrorsInCode('''
class B {}
mixin M1 {
  void f() {}
}
mixin M2 {
  void f() {}
}
class C extends B with M1, M2 {
  void g() {
    f();
  }
}
''');

    assertResolvedNodeText(findNode.simple('f();'), r'''
SimpleIdentifier
  token: f
  element: <testLibraryFragment>::@mixin::M2::@method::f#element
  staticType: void Function()
''');
  }

  test_method_fromMixins_invoked_from_outside_class() async {
    await assertNoErrorsInCode('''
class B {}
mixin M1 {
  void f() {}
}
mixin M2 {
  void f() {}
}
class C extends B with M1, M2 {}
void main() {
  new C().f();
}
''');

    assertResolvedNodeText(findNode.simple('f();'), r'''
SimpleIdentifier
  token: f
  element: <testLibraryFragment>::@mixin::M2::@method::f#element
  staticType: void Function()
''');
  }

  test_method_fromSuperclassMixin() async {
    await assertNoErrorsInCode(r'''
mixin A {
  void m1() {}
}
class B extends Object with A {
}
class C extends B {
}
f(C c) {
  c.m1();
}''');
  }

  test_methodCascades() async {
    await assertNoErrorsInCode(r'''
class A {
  void m1() {}
  void m2() {}
  void m() {
    A a = new A();
    a..m1()
     ..m2();
  }
}''');
  }

  test_methodCascades_withSetter() async {
    await assertNoErrorsInCode(r'''
class A {
  String name = '';
  void m1() {}
  void m2() {}
  void m() {
    A a = new A();
    a..m1()
     ..name = 'name'
     ..m2();
  }
}''');
  }

  test_resolveAgainstNull() async {
    await assertNoErrorsInCode(r'''
f(var p) {
  return null == p;
}''');
  }

  test_setter_static() async {
    await assertNoErrorsInCode(r'''
set s(x) {
}

main() {
  s = 123;
}''');
  }

  /// Resolve the test file and verify that the arguments in a specific method
  /// invocation were correctly resolved.
  ///
  /// The file is expected to define a method named `g`, and has exactly one
  /// [MethodInvocation] in a statement ending with `);`. It is the arguments to
  /// that method invocation that are tested. The method invocation can contain
  /// errors.
  ///
  /// The arguments were resolved correctly if the number of expressions in the
  /// list matches the length of the array of indices and if, for each index in
  /// the array of indices, the parameter to which the argument expression was
  /// resolved is the parameter in the invoked method's list of parameters at
  /// that index. Arguments that should not be resolved to a parameter because
  /// of an error can be denoted by including a negative index in the array of
  /// indices.
  ///
  /// @param indices the array of indices used to associate arguments with
  ///          parameters
  /// @throws Exception if the source could not be resolved or if the structure
  ///           of the source is not valid
  Future<void> _validateArgumentResolution(List<int> indices) async {
    var g = findElement2.method('g');
    var parameters = g.formalParameters;

    var invocation = findNode.methodInvocation(');');

    var arguments = invocation.argumentList.arguments;

    var argumentCount = arguments.length;
    expect(argumentCount, indices.length);

    for (var i = 0; i < argumentCount; i++) {
      var argument = arguments[i];
      var actualParameter = argument.correspondingParameter;

      var index = indices[i];
      if (index < 0) {
        expect(actualParameter, isNull);
      } else {
        var expectedParameter = parameters[index];
        expect(actualParameter, same(expectedParameter));
      }
    }
  }
}
