// 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/dart/element/nullability_suffix.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

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

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

/// Tests for various end-to-end cases when invalid code caused exceptions
/// in one or another Analyzer subsystem. We are not interested not in specific
/// errors generated, but we want to make sure that there is at least one,
/// and analysis finishes without exceptions.
@reflectiveTest
class InvalidCodeTest extends PubPackageResolutionTest {
  // TODO(srawlins): Use null safety in test cases.
  // https://github.com/dart-lang/sdk/issues/44666
  test_const_AwaitExpression() async {
    await _assertCanBeAnalyzed(r'''
const a = await b();
''');
  }

  test_const_ForPartsWithExpression() async {
    await _assertCanBeAnalyzed(r'''
@A([for (;;) 0])
void f() {}
''');
  }

  /// This code results in a method with the empty name, and the default
  /// constructor, which also has the empty name. The `Map` in `f` initializer
  /// references the empty name.
  test_constructorAndMethodNameCollision() async {
    await _assertCanBeAnalyzed('''
class C {
  var f = { : };
  @ ();
}
''');
  }

  test_constructorDeclaration_named_missingName() async {
    await _assertCanBeAnalyzed('''
class C {
  C.();
}
''');
  }

  test_constructorDeclaration_named_missingName_factory() async {
    await _assertCanBeAnalyzed('''
class C {
  factory C.();
}
''');
  }

  test_duplicateName_class_enum() async {
    await _assertCanBeAnalyzed('''
class A<T> {
  void foo(B b) {
    b.bar(this);
  }
}

class B {
  void bar(A a) {}
}

enum A {
  a, b, c
}
''');
  }

  test_extensionOverrideInAnnotationContext() async {
    await _assertCanBeAnalyzed('''
class R {
  const R(int x);
}

@R(E(null).f())
extension E on Object {
  int f() => 0;
}
''');
  }

  test_extensionOverrideInAnnotationContext_importedWithPrefix() async {
    newFile('$testPackageLibPath/a.dart', r'''
extension E on Object {
  int f() => 0;
}
''');
    await _assertCanBeAnalyzed('''
import 'a.dart' as prefix;

class A {
  const A(int x);
}

@R(prefix.E(null).f())
void g() {}
}
''');
  }

  test_extensionOverrideInConstContext() async {
    await _assertCanBeAnalyzed('''
extension E on Object {
  int f() => 0;
}

const e = E(null).f();
''');
  }

  test_fieldFormalParameter_annotation_localFunction() async {
    await _assertCanBeAnalyzed(r'''
void main() {
  void foo(@deprecated this.bar) {}
}
''');
  }

  test_functionExpression_emptyBody() async {
    await _assertCanBeAnalyzed(r'''
var v = <T>();
''');
  }

  test_functionExpressionInvocation_mustBeNullShortingTerminated() async {
    // It looks like MethodInvocation, but because `8` is not SimpleIdentifier,
    // we parse it as FunctionExpressionInvocation.
    await _assertCanBeAnalyzed(r'''
var v = a?.8(b);
''');
  }

  test_fuzz_01() async {
    await _assertCanBeAnalyzed(r'''
typedef F = void Function(bool, int a(double b));
''');
    var alias = findElement2.typeAlias('F');
    assertType(
      alias.instantiate(
        typeArguments: const [],
        nullabilitySuffix: NullabilitySuffix.none,
      ),
      'void Function(bool, int Function(double))',
    );
  }

  test_fuzz_02() async {
    await _assertCanBeAnalyzed(r'''
class G<class G{d
''');
  }

  test_fuzz_03() async {
    await _assertCanBeAnalyzed('''
class{const():super.{n
''');
  }

  test_fuzz_04() async {
    await _assertCanBeAnalyzed('''
f({a: ({b = 0}) {}}) {}
''');
  }

  test_fuzz_05() async {
    // Here 'v' is used as both the local variable name, and its type.
    // This triggers "reference before declaration" diagnostics.
    // It attempts to ask the enclosing unit element for "v".
    // Every (not library or unit) element must have the enclosing unit.
    await _assertCanBeAnalyzed('''
f({a = [for (v v in [])]}) {}
''');
  }

  test_fuzz_06() async {
    await _assertCanBeAnalyzed(r'''
class C {
  int f;
  set f() {}
}
''');
  }

  test_fuzz_07() async {
    // typedef v(<T extends T>(e
    await _assertCanBeAnalyzed(r'''
typedef F(a<TT extends TT>(e));
''');
  }

  test_fuzz_08() async {
//    class{const v
//    v=((){try catch
    // When we resolve initializers of typed constant variables,
    // we should build locale elements.
    await _assertCanBeAnalyzed(r'''
class C {
  const Object v = () { var a = 0; };
}
''');
  }

  test_fuzz_09() async {
    await _assertCanBeAnalyzed(r'''
typedef void F(int a, this.b);
''');
    var alias = findElement2.typeAlias('F');
    assertType(
      alias.instantiate(
        typeArguments: const [],
        nullabilitySuffix: NullabilitySuffix.none,
      ),
      'void Function(int, dynamic)',
    );
  }

  test_fuzz_10() async {
    await _assertCanBeAnalyzed(r'''
void f<@A(() { Function() v; }) T>() {}
''');
  }

  test_fuzz_12() async {
    // This code crashed with summary2 because usually AST reader is lazy,
    // so we did not read metadata `@b` for `c`. But default values must be
    // read fully.
    // Fixed 2020-11-12.
    await _assertCanBeAnalyzed(r'''
void f({a = [for (@b c = 0;;)]}) {}
''');
  }

  test_fuzz_13() async {
    // `x is int` promotes the type of `x` to `S extends int`, and the
    // underlying element is `TypeParameterMember`, which by itself is
    // questionable.  But this is not a valid constant anyway, so we should
    // not even try to serialize it.
    await _assertCanBeAnalyzed(r'''
const v = [<S extends num>(S x) => x is int ? x : 0];
''');
  }

  test_fuzz_14() async {
    // This crashed because parser produces `ConstructorDeclaration`.
    // So, we try to create `ConstructorElement` for it, and it wants
    // `ClassElement` as the enclosing element. But we have `ExtensionElement`.
    await _assertCanBeAnalyzed(r'''
extension E {
  factory S() {}
}
''');
  }

  test_fuzz_15() async {
    // `@A` is not a valid annotation, it is missing arguments.
    // There was a bug that we did not check for arguments being missing.
    await _assertCanBeAnalyzed(r'''
class A<T> {}

@A
class B {}
''');
  }

  test_fuzz_16() async {
    // The default constructor of `A` does not have formal parameters.
    // But we give it arguments.
    // There was a bug that we did not check for this mismatch.
    await _assertCanBeAnalyzed(r'''
class A<T> {}

@A(0)
class B {}
''');
  }

  test_fuzz_38091() async {
    // https://github.com/dart-lang/sdk/issues/38091
    // this caused an infinite loop in parser recovery
    await _assertCanBeAnalyzed(r'c(=k(<)>');
  }

  test_fuzz_38506() async {
    // https://github.com/dart-lang/sdk/issues/38506
    // We have only one LibraryElement to get resolved annotations.
    // Leave annotations node of other LibraryDirective(s) unresolved.
    await _assertCanBeAnalyzed(r'''
library c;
@foo
library c;
''');
  }

  test_fuzz_38878() async {
    // We should not attempt to resolve `super` in annotations.
    await _assertCanBeAnalyzed(r'''
class C {
  @A(super.f())
  f(int x) {}
}
''');
  }

  test_fuzz_38953() async {
    // When we enter a directive, we should stop using the element walker
    // of the unit, just like when we enter a method body. Even though using
    // interpolation is not allowed in any directives.
    await _assertCanBeAnalyzed(r'''
import '${[for(var v = 0;;) v]}';
export '${[for(var v = 0;;) v]}';
part '${[for(var v = 0;;) v]}';
''');
  }

  test_genericFunction_asTypeArgument_ofUnresolvedClass() async {
    await _assertCanBeAnalyzed(r'''
C<int Function()> c;
''');
  }

  test_inAnnotation_noFlow_labeledStatement() async {
    await _assertCanBeAnalyzed('''
@A(() { label: })
typedef F = void Function();
''');
  }

  test_inDefaultValue_noFlow_ifExpression() async {
    await _assertCanBeAnalyzed('''
typedef void F({a = [if (true) 0]});
''');
  }

  test_inDefaultValue_noFlow_ifStatement() async {
    await _assertCanBeAnalyzed('''
typedef void F([a = () { if (true) 0; }]);
''');
  }

  test_invalidPart_withPart() async {
    await _assertCanBeAnalyzed('''
part of a;
part 'test.dart';
''');
  }

  test_issue_40837() async {
    await _assertCanBeAnalyzed('''
class A {
  const A(_);
}

@A(() => 0)
class B {}
''');
  }

  test_issue_48688() async {
    // During parsing we recover as `<synthetic>.bar.baz()`.
    // So, we have a synthetic empty identifier.
    // There was a bug - we considered it a reference to the unnamed extension.
    await _assertCanBeAnalyzed(r'''
void f() {
  final foo.bar.baz();
}

extension on int {}
''');
  }

  test_issue_52363() async {
    await _assertCanBeAnalyzed(r'''
class A {
  const a = (var b,) = (0,);
}
''');
  }

  test_issue_52432() async {
    await _assertCanBeAnalyzed(r'''
void f() {
  void g([super.foo]) {}
}
''');
  }

  test_keywordInConstructorInitializer_assert() async {
    await _assertCanBeAnalyzed('''
class C {
  C() : assert = 0;
}
''');
  }

  test_keywordInConstructorInitializer_null() async {
    await _assertCanBeAnalyzed('''
class C {
  C() : null = 0;
}
''');
  }

  test_keywordInConstructorInitializer_super() async {
    await _assertCanBeAnalyzed('''
class C {
  C() : super = 0;
}
''');
  }

  test_keywordInConstructorInitializer_this() async {
    await _assertCanBeAnalyzed('''
class C {
  C() : this = 0;
}
''');
  }

  test_libraryAfterImport() async {
    await _assertCanBeAnalyzed(r'''
import 'dart:async';
@foo
library my;
''');
  }

  test_localFunction_defaultFieldFormalParameter_metadata() async {
    await _assertCanBeAnalyzed(r'''
const my = 0;

void foo() {
  // ignore:unused_element
  void bar({@my this.x}) {}
}
''');
  }

  test_syntheticImportPrefix() async {
    await _assertCanBeAnalyzed('''
import 'dart:math' as;
''');
  }

  test_typeBeforeAnnotation() async {
    await _assertCanBeAnalyzed('''
class A {
  const A([x]);
}
class B {
  dynamic @A(const A()) x;
}
''');
  }

  Future<void> _assertCanBeAnalyzed(String text) async {
    await resolveTestCode(text);
    assertHasTestErrors();
  }
}
