// 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/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

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

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

@reflectiveTest
class UndefinedSetterTest extends PubPackageResolutionTest {
  test_functionAlias_typeInstantiated() async {
    await assertErrorsInCode(
      '''
typedef Fn<T> = void Function(T);

void bar() {
  Fn<int>.foo = 7;
}

extension E on Type {
  set foo(int value) {}
}
''',
      [error(CompileTimeErrorCode.undefinedSetterOnFunctionType, 58, 3)],
    );
  }

  test_functionAlias_typeInstantiated_parenthesized() async {
    await assertNoErrorsInCode('''
typedef Fn<T> = void Function(T);

void bar() {
  (Fn<int>).foo = 7;
}

extension E on Type {
  set foo(int value) {}
}
''');
  }

  test_importWithPrefix_defined() async {
    newFile('$testPackageLibPath/lib.dart', r'''
library lib;
set y(int value) {}''');
    await assertNoErrorsInCode(r'''
import 'lib.dart' as x;
main() {
  x.y = 0;
}
''');
  }

  test_instance_undefined() async {
    await assertErrorsInCode(
      r'''
class T {}
f(T e1) { e1.m = 0; }
''',
      [
        error(
          CompileTimeErrorCode.undefinedSetter,
          24,
          1,
          messageContains: ["the type 'T'"],
        ),
      ],
    );
  }

  test_instance_undefined_mixin() async {
    await assertErrorsInCode(
      r'''
mixin M {
  f() { this.m = 0; }
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 23, 1)],
    );
  }

  test_inSubtype() async {
    await assertErrorsInCode(
      r'''
class A {}
class B extends A {
  set b(x) {}
}
f(var a) {
  if (a is A) {
    a.b = 0;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 80, 1)],
    );
  }

  test_inType() async {
    await assertErrorsInCode(
      r'''
class A {}
f(var a) {
  if(a is A) {
    a.m = 0;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 43, 1)],
    );
  }

  test_new_cascade() async {
    await assertErrorsInCode(
      '''
class C {}

f(C? c) {
  c..new = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 27, 3)],
    );
  }

  test_new_dynamic() async {
    await assertErrorsInCode(
      '''
f(dynamic d) {
  d.new = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 19, 3)],
    );
  }

  test_new_instance() async {
    await assertErrorsInCode(
      '''
class C {}

f(C c) {
  c.new = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 25, 3)],
    );
  }

  test_new_interfaceType() async {
    await assertErrorsInCode(
      '''
class C {}

f() {
  C.new = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 22, 3)],
    );
  }

  test_new_nullAware() async {
    await assertErrorsInCode(
      '''
class C {}

f(C? c) {
  c?.new = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 27, 3)],
    );
  }

  test_new_typeVariable() async {
    await assertErrorsInCode(
      '''
f<T>(T t) {
  t.new = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 16, 3)],
    );
  }

  test_set_abstract_field_valid() async {
    await assertNoErrorsInCode('''
abstract class A {
  abstract int x;
}
void f(A a, int x) {
  a.x = x;
}
''');
  }

  test_set_external_field_valid() async {
    await assertNoErrorsInCode('''
class A {
  external int x;
}
void f(A a, int x) {
  a.x = x;
}
''');
  }

  test_set_external_static_field_valid() async {
    await assertNoErrorsInCode('''
class A {
  external static int x;
}
void f(int x) {
  A.x = x;
}
''');
  }

  test_static_conditionalAccess_defined() async {
    await assertErrorsInCode(
      '''
class A {
  static var x;
}
f() { A?.x = 1; }
''',
      [error(StaticWarningCode.invalidNullAwareOperator, 35, 2)],
    );
  }

  test_static_definedInSuperclass() async {
    await assertErrorsInCode(
      '''
class S {
  static set s(int i) {}
}
class C extends S {}
f(var p) {
  f(C.s = 1);
}''',
      [
        error(
          CompileTimeErrorCode.undefinedSetter,
          75,
          1,
          messageContains: ["type 'C'"],
        ),
      ],
    );
  }

  test_static_extension_instanceAccess() async {
    await assertErrorsInCode(
      '''
class C {}

extension E on C {
  static set a(int v) {}
}

f(C c) {
  c.a = 2;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 72, 1)],
    );

    assertResolvedNodeText(findNode.assignment('a ='), r'''
AssignmentExpression
  leftHandSide: PrefixedIdentifier
    prefix: SimpleIdentifier
      token: c
      element: <testLibrary>::@function::f::@formalParameter::c
      staticType: C
    period: .
    identifier: SimpleIdentifier
      token: a
      element: <null>
      staticType: null
    element: <null>
    staticType: null
  operator: =
  rightHandSide: IntegerLiteral
    literal: 2
    correspondingParameter: <null>
    staticType: int
  readElement: <null>
  readType: null
  writeElement: <null>
  writeType: InvalidType
  element: <null>
  staticType: int
''');
  }

  test_static_undefined() async {
    await assertErrorsInCode(
      r'''
class A {}
f() { A.B = 0;}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 19, 1)],
    );
  }

  test_typeLiteral_cascadeTarget() async {
    await assertErrorsInCode(
      r'''
class T {
  static void set foo(_) {}
}
main() {
  T..foo = 42;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 54, 3)],
    );
  }

  test_withExtension() async {
    await assertErrorsInCode(
      r'''
class C {}

extension E on C {}

f(C c) {
  c.a = 1;
}
''',
      [error(CompileTimeErrorCode.undefinedSetter, 46, 1)],
    );
  }
}
