// Copyright (c) 2024, 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(ConstAnnotationTest);
  });
}

@reflectiveTest
class ConstAnnotationTest extends PubPackageResolutionTest {
  @override
  void setUp() {
    super.setUp();
    writeTestPackageConfigWithMeta();
  }

  test_adjacentLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final c = C('H' 'ello');

class C {
  C(@mustBeConst String s);
}
''');
  }

  test_binaryOperator_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(A a) {
  var b = A();
  a < b;
}

class A {
  bool operator <(@mustBeConst A other) => false;
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 86, 1),
    ]);
  }

  test_binaryOperator_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(A a) {
  const b = A();
  a < b;
}

class A {
  const A();

  bool operator <(@mustBeConst A other) => false;
}
''');
  }

  test_constExpression_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() {
  g(const C());
}

void g(@mustBeConst C c) {}

class C {
  const C();
}
''');
  }

  test_constructor_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final c = C(3);

class C {
  C(@mustBeConst int i);
}
''');
  }

  test_constructor_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

final c = C(v);

class C {
  C(@mustBeConst int i);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 77, 1),
    ]);
  }

  test_functionExpression_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() {
  var g = (@mustBeConst int i) {};
  g(3);
}
''');
  }

  test_functionExpression_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(int x) {
  var g = (@mustBeConst int i) {};
  g(x);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 106, 1),
    ]);
  }

  test_functionType_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(void g(@mustBeConst int i)) {
  g(3);
}
''');
  }

  test_functionType_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(void g(@mustBeConst int i), int x) {
  g(x);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 99, 1),
    ]);
  }

  test_indexExpression_constExpression_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(A a) {
  const b = A();
  a[1] = b;
}

class A {
  const A();

  void operator []=(@mustBeConst int i, @mustBeConst A v) {}
}
''');
  }

  test_indexExpression_nonConstant_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(A a) {
  a[1] = A();
}

class A {
  const A();

  void operator []=(@mustBeConst int i, @mustBeConst A v) {}
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 74, 3),
    ]);
  }

  test_interpolationLiteral_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const a = 'ello';

final c = C('H$a');

class C {
  C(@mustBeConst String s);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 82, 5),
    ]);
  }

  test_localFunction_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() {
  void g(@mustBeConst int i) {}
  g(3);
}
''');
  }

  test_localFunction_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(int x) {
  void g(@mustBeConst int i) {}
  g(x);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 103, 1),
    ]);
  }

  test_method_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(C c) => c.g(3);

class C {
  void g([@mustBeConst int? value]) {}
}
''');
  }

  test_method_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

void f(C c) => c.g(v);

class C {
  void g([@mustBeConst int? value]) {}
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 84, 1),
    ]);
  }

  test_optionalNamed_constVariable_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const v = 3;

void f() => g(value: v);

void g({@mustBeConst int? value}) {}
''');
  }

  test_optionalNamed_noArgument_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() => g();

void g({@mustBeConst int? value}) {}
''');
  }

  test_optionalNamed_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

void f() => g(value: v);

void g({@mustBeConst int? value}) {}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 79, 8),
    ]);
  }

  test_optionalPositional_constVariable_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const v = 3;

void f() => g(v);

void g([@mustBeConst int? value]) {}
''');
  }

  test_optionalPositional_noArgument_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() => g();

void g([@mustBeConst int? value]) {}
''');
  }

  test_optionalPositional_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

void f() => g(v);

void g([@mustBeConst int? value]) {}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 79, 1),
    ]);
  }

  test_redirectingConstructor_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

class A {
  A(@mustBeConst int i);
  A.named(int i) : this(i);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 110, 1),
    ]);
  }

  test_redirectingFactoryConstructor_variable_succeeds() async {
    // The call to `A.named(v)` is not considered a direct call to `A()`; while
    // the parameters declared in `A.named` must "match" those in `A.new`, they
    // are separate. For example, they can have separate annotations.
    // TODO(srawlins): It still seems that to be consistent, we should report
    // `A.named(int i)`.
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

class A {
  A(@mustBeConst int i);
  factory A.named(int i) = A;
}

final v = 3;
var a = A.named(v);
''');
  }

  test_requiredNamed_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() => g(value: 3);

void g({@mustBeConst required int value}) {}
''');
  }

  test_requiredNamed_constVariable_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const v = 3;

void f() => g(value: v);

void g({@mustBeConst required int value}) {}
''');
  }

  test_requiredPositional_constVariable_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const v = 3;

void f() => g(v);

void g(@mustBeConst int value) {}
''');
  }

  test_requiredPositional_list_constVariable_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const v = [3,4];

void f() => g(v);

void g(@mustBeConst List<int> value) {}
''');
  }

  test_requiredPositional_localVariable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f(int value) => g(value);

void g(@mustBeConst int value) {}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 74, 5),
    ]);
  }

  test_requiredPositional_map_constVariable_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

const v = {'k1': 3, 'k2': 4};

void f() => g(v);

void g(@mustBeConst Map<String, int> value) {}
''');
  }

  test_requiredPositional_topLevelVariable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

void f() => g(v);

void g(@mustBeConst int value) {}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 79, 1),
    ]);
  }

  test_setter_fails() async {
    await assertErrorsInCode(
      r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() {
  final v = 3;
  i = v;
}

set i(@mustBeConst int? value) {}
''',
      [error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 83, 1)],
    );
  }

  test_setter_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

void f() {
  i = 3;
}

set i(@mustBeConst int? value) {}
''');
  }

  test_subclassesDontInherit() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

final r = B().f(v);

abstract class A {
  void f(@mustBeConst int i);
}

class B extends A {
  @override
  void f(int i) {}
}
''');
  }

  test_superclassCanBeOverriden_cast_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

final r = (B() as A).f(v);

abstract class A {
  void f(int i);
}

class B extends A {
  @override
  void f(@mustBeConst int i) {}
}
''');
  }

  test_superclassCanBeOverriden_noCast_fails() async {
    await assertErrorsInCode(
      r'''
import 'package:meta/meta.dart' show mustBeConst;

final v = 3;

final r = B().f(v);

abstract class A {
  void f(int i);
}

class B extends A {
  @override
  void f(@mustBeConst int i) {}
}
''',
      [
        error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 81, 1),
      ],
    );
  }

  test_superConstructor_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

class A {
  A(@mustBeConst int i);
}

class B extends A {
  B(int i) : super(i);
}
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 128, 1),
    ]);
  }

  test_superParameter_variable_succeeds() async {
    // TODO(srawlins): It seems that to be consistent, we should `super.i`.
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

class A {
  A(@mustBeConst int i);
}

class B extends A {
  B(super.i);
}
''');
  }

  test_typedef_function_constantLiteral_succeeds() async {
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

typedef Td = void Function(@mustBeConst int);

void f(Td td) {
  td(3);
}
''');
  }

  test_typedef_function_variable_succeeds() async {
    // An annotation on a parameter in a function type is not supported.
    await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

typedef Td = void Function(@mustBeConst int);

void f(int x, Td td) {
  td(x);
}
''');
  }

  test_typedef_nonFunction_variable_fails() async {
    await assertErrorsInCode(r'''
import 'package:meta/meta.dart' show mustBeConst;

typedef T = C;

class C {
  C(@mustBeConst int i);
}

void g(int x) => T(x);
''', [
      error(WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER, 124, 1),
    ]);
  }
}
