// 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 'recovery_test_support.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(ListLiteralTest);
    defineReflectiveTests(MapLiteralTest);
    defineReflectiveTests(MissingCodeTest);
    defineReflectiveTests(ParameterListTest);
    defineReflectiveTests(TypedefTest);
  });
}

/// Test how well the parser recovers when tokens are missing in a list literal.
@reflectiveTest
class ListLiteralTest extends AbstractRecoveryTest {
  void test_extraComma() {
    testRecovery(
      '''
f() => [a, , b];
''',
      [diag.missingIdentifier],
      '''
f() => [a, _s_, b];
''',
    );
  }

  void test_missingComma() {
    testRecovery(
      '''
f() => [a, b c];
''',
      [diag.expectedToken],
      '''
f() => [a, b, c];
''',
    );
  }

  void test_missingComma_afterIf() {
    testRecovery(
      '''
f() => [a, if (x) b c];
''',
      [diag.expectedElseOrComma],
      '''
f() => [a, if (x) b, c];
''',
    );
  }

  void test_missingComma_afterIfElse() {
    testRecovery(
      '''
f() => [a, if (x) b else y c];
''',
      [diag.expectedToken],
      '''
f() => [a, if (x) b else y, c];
''',
    );
  }
}

/// Test how well the parser recovers when tokens are missing in a map literal.
@reflectiveTest
class MapLiteralTest extends AbstractRecoveryTest {
  void test_missingComma() {
    testRecovery(
      '''
f() => {a: b, c: d e: f};
''',
      [diag.expectedToken],
      '''
f() => {a: b, c: d, e: f};
''',
    );
  }

  void test_missingComma_afterIf() {
    testRecovery(
      '''
f() => {a: b, if (x) c: d e: f};
''',
      [diag.expectedElseOrComma],
      '''
f() => {a: b, if (x) c: d, e: f};
''',
    );
  }

  void test_missingComma_afterIfElse() {
    testRecovery(
      '''
f() => {a: b, if (x) c: d else y: z e: f};
''',
      [diag.expectedToken],
      '''
f() => {a: b, if (x) c: d else y: z, e: f};
''',
    );
  }

  void test_missingKey() {
    testRecovery(
      '''
f() => {: b};
''',
      [diag.missingIdentifier],
      '''
f() => {_s_: b};
''',
    );
  }

  void test_missingValue_last() {
    testRecovery(
      '''
f() => {a: };
''',
      [diag.missingIdentifier],
      '''
f() => {a: _s_};
''',
    );
  }

  void test_missingValue_notLast() {
    testRecovery(
      '''
f() => {a: , b: c};
''',
      [diag.missingIdentifier],
      '''
f() => {a: _s_, b: c};
''',
    );
  }
}

/// Test how well the parser recovers when non-paired tokens are missing.
@reflectiveTest
class MissingCodeTest extends AbstractRecoveryTest {
  void test_ampersand() {
    testBinaryExpression('&');
  }

  void test_ampersand_super() {
    testUserDefinableOperatorWithSuper('&');
  }

  @failingTest
  void test_asExpression_missingLeft() {
    testRecovery(
      '''
convert(x) => as T;
''',
      [diag.expectedTypeName],
      '''
convert(x) => _s_ as T;
''',
    );
  }

  void test_asExpression_missingRight() {
    testRecovery(
      '''
convert(x) => x as ;
''',
      [diag.expectedTypeName],
      '''
convert(x) => x as _s_;
''',
    );
  }

  void test_assignmentExpression() {
    testRecovery(
      '''
f() {
  var x;
  x =
}
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
f() {
  var x;
  x = _s_;
}
''',
    );
  }

  void test_bar() {
    testBinaryExpression('|');
  }

  void test_bar_super() {
    testUserDefinableOperatorWithSuper('|');
  }

  void test_cascade_missingRight() {
    testRecovery(
      '''
f(x) {
  x..
}
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
f(x) {
  x.. _s_;
}
''',
    );
  }

  void test_classDeclaration_missingName() {
    testRecovery(
      '''
class {}
''',
      [diag.missingIdentifier],
      '''
class _s_ {}
''',
    );
  }

  @failingTest
  void test_combinatorsBeforePrefix() {
    //Expected 1 errors of type ParserErrorCode.MISSING_PREFIX_IN_DEFERRED_IMPORT, found 0
    testRecovery(
      '''
import 'bar.dart' deferred;
''',
      [diag.missingPrefixInDeferredImport],
      '''
import 'bar.dart' deferred as _s_;
''',
    );
  }

  void test_comma_missing() {
    testRecovery(
      '''
f(int a int b) { }
''',
      [diag.expectedToken],
      '''
f(int a, int b) { }
''',
    );
  }

  void test_conditionalExpression_else() {
    testRecovery(
      '''
f() => x ? y :
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
f() => x ? y : _s_;
''',
    );
  }

  void test_conditionalExpression_then() {
    testRecovery(
      '''
f() => x ? : z
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
f() => x ? _s_ : z;
''',
    );
  }

  void test_equalEqual() {
    testBinaryExpression('==');
  }

  void test_equalEqual_super() {
    testUserDefinableOperatorWithSuper('==');
  }

  void test_expressionBody_missingGt() {
    testRecovery(
      '''
f(x) = x;
''',
      [diag.missingFunctionBody],
      '''
f(x) => x;
''',
    );
  }

  void test_expressionBody_return() {
    testRecovery(
      '''
f(x) return x;
''',
      [diag.missingFunctionBody],
      '''
f(x) => x;
''',
    );
  }

  void test_greaterThan() {
    testBinaryExpression('>');
  }

  void test_greaterThan_super() {
    testUserDefinableOperatorWithSuper('>');
  }

  void test_greaterThanGreaterThan() {
    testBinaryExpression('>>');
  }

  void test_greaterThanGreaterThan_super() {
    testUserDefinableOperatorWithSuper('>>');
  }

  void test_greaterThanOrEqual() {
    testBinaryExpression('>=');
  }

  void test_greaterThanOrEqual_super() {
    testUserDefinableOperatorWithSuper('>=');
  }

  void test_hat() {
    testBinaryExpression('^');
  }

  void test_hat_super() {
    testUserDefinableOperatorWithSuper('^');
  }

  void test_initializerList_missingComma_assert() {
    // https://github.com/dart-lang/sdk/issues/33241
    testRecovery(
      '''
class Test {
  Test()
    : assert(true)
      assert(true);
}
''',
      [diag.expectedToken],
      '''
class Test {
  Test()
    : assert(true),
      assert(true);
}
''',
    );
  }

  void test_initializerList_missingComma_field() {
    // https://github.com/dart-lang/sdk/issues/33241
    testRecovery(
      '''
class Test {
  Test()
    : assert(true)
      x = 2;
}
''',
      [diag.expectedToken],
      '''
class Test {
  Test()
    : assert(true),
      x = 2;
}
''',
    );
  }

  void test_initializerList_missingComma_thisField() {
    // https://github.com/dart-lang/sdk/issues/33241
    testRecovery(
      '''
class Test {
  Test()
    : assert(true)
      this.x = 2;
}
''',
      [diag.expectedToken],
      '''
class Test {
  Test()
    : assert(true),
      this.x = 2;
}
''',
    );
  }

  void test_isExpression_missingLeft() {
    testRecovery(
      '''
f() {
  if (is String) {
  }
}
''',
      [diag.missingIdentifier],
      '''
f() {
  if (_s_ is String) {
  }
}
''',
    );
  }

  void test_isExpression_missingRight() {
    testRecovery(
      '''
f(x) {
  if (x is ) {}
}
''',
      [diag.expectedTypeName],
      '''
f(x) {
  if (x is _s_) {}
}
''',
    );
  }

  void test_lessThan() {
    testBinaryExpression('<');
  }

  void test_lessThan_super() {
    testUserDefinableOperatorWithSuper('<');
  }

  void test_lessThanLessThan() {
    testBinaryExpression('<<');
  }

  void test_lessThanLessThan_super() {
    testUserDefinableOperatorWithSuper('<<');
  }

  void test_lessThanOrEqual() {
    testBinaryExpression('<=');
  }

  void test_lessThanOrEqual_super() {
    testUserDefinableOperatorWithSuper('<=');
  }

  void test_minus() {
    testBinaryExpression('-');
  }

  void test_minus_super() {
    testUserDefinableOperatorWithSuper('-');
  }

  @failingTest
  void test_missingGet() {
    testRecovery(
      '''
class Bar {
  int foo => 0;
}
''',
      [diag.missingGet],
      '''
class Bar {
  int get foo => 0;
}
''',
    );
  }

  @failingTest
  void test_parameterList_leftParen() {
    // https://github.com/dart-lang/sdk/issues/22938
    testRecovery(
      '''
int f int x, int y) {}
''',
      [diag.expectedToken],
      '''
int f (int x, int y) {}
''',
    );
  }

  @failingTest
  void test_parentheses_aroundThrow() {
    // https://github.com/dart-lang/sdk/issues/24892
    testRecovery(
      '''
f(x) => x ?? throw 0;
''',
      [diag.expectedToken, diag.expectedToken],
      '''
f(x) => x ?? (throw 0);
''',
    );
  }

  void test_percent() {
    testBinaryExpression('%');
  }

  void test_percent_super() {
    testUserDefinableOperatorWithSuper('%');
  }

  void test_plus() {
    testBinaryExpression('+');
  }

  void test_plus_super() {
    testUserDefinableOperatorWithSuper('+');
  }

  void test_prefixedIdentifier() {
    testRecovery(
      '''
f() {
  var v = 'String';
  v.
}
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
f() {
  var v = 'String';
  v._s_;
}
''',
    );
  }

  void test_slash() {
    testBinaryExpression('/');
  }

  void test_slash_super() {
    testUserDefinableOperatorWithSuper('/');
  }

  void test_star() {
    testBinaryExpression('*');
  }

  void test_star_super() {
    testUserDefinableOperatorWithSuper('*');
  }

  void test_stringInterpolation_unclosed() {
    // https://github.com/dart-lang/sdk/issues/946
    // TODO(brianwilkerson): Try to recover better. Ideally there would be a
    // single error about an unterminated interpolation block.

    // https://github.com/dart-lang/sdk/issues/36101
    // TODO(danrubel): improve recovery so that the scanner/parser associates
    // `${` with a synthetic `}` inside the " " rather than the `}` at the end.

    testRecovery(
      r'''
f() {
  print("${42");
}
''',
      [
        diag.expectedToken,
        diag.expectedToken,
        diag.expectedToken,
        diag.expectedToken,
        diag.unterminatedStringLiteral,
        diag.unterminatedStringLiteral,
      ],
      r'''
f() {
  print("${42}");
}
''',
    );
  }

  void test_tildeSlash() {
    testBinaryExpression('~/');
  }

  void test_tildeSlash_super() {
    testUserDefinableOperatorWithSuper('~/');
  }

  void testBinaryExpression(String operator) {
    testRecovery(
      '''
f() => x $operator
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
f() => x $operator _s_;
''',
    );
  }

  void testUserDefinableOperatorWithSuper(String operator) {
    testRecovery(
      '''
class C {
  int operator $operator(x) => super $operator
}
''',
      [diag.missingIdentifier, diag.expectedToken],
      '''
class C {
  int operator $operator(x) => super $operator _s_;
}
''',
    );
  }
}

/// Test how well the parser recovers when tokens are missing in a parameter
/// list.
@reflectiveTest
class ParameterListTest extends AbstractRecoveryTest {
  @failingTest
  void test_extraComma_named_last() {
    testRecovery(
      '''
f({a, }) {}
''',
      [diag.missingIdentifier],
      '''
f({a, _s_}) {}
''',
    );
  }

  void test_extraComma_named_noLast() {
    testRecovery(
      '''
f({a, , b}) {}
''',
      [diag.missingIdentifier],
      '''
f({a, _s_, b}) {}
''',
    );
  }

  @failingTest
  void test_extraComma_positional_last() {
    testRecovery(
      '''
f([a, ]) {}
''',
      [diag.missingIdentifier],
      '''
f([a, _s_]) {}
''',
    );
  }

  void test_extraComma_positional_noLast() {
    testRecovery(
      '''
f([a, , b]) {}
''',
      [diag.missingIdentifier],
      '''
f([a, _s_, b]) {}
''',
    );
  }

  @failingTest
  void test_extraComma_required_last() {
    testRecovery(
      '''
f(a, ) {}
''',
      [diag.missingIdentifier],
      '''
f(a, _s_) {}
''',
    );
  }

  void test_extraComma_required_noLast() {
    testRecovery(
      '''
f(a, , b) {}
''',
      [diag.missingIdentifier],
      '''
f(a, _s_, b) {}
''',
    );
  }

  void test_fieldFormalParameter_noPeriod_last() {
    testRecovery(
      '''
class C {
  int f;
  C(this);
}
''',
      [diag.expectedIdentifierButGotKeyword],
      '''
class C {
  int f;
  C(_k_);
}
''',
    );
  }

  void test_fieldFormalParameter_noPeriod_notLast() {
    testRecovery(
      '''
class C {
  int f;
  C(this, p);
}
''',
      [diag.expectedIdentifierButGotKeyword],
      '''
class C {
  int f;
  C(_k_, p);
}
''',
    );
  }

  void test_fieldFormalParameter_period_last() {
    testRecovery(
      '''
class C {
  int f;
  C(this.);
}
''',
      [diag.missingIdentifier],
      '''
class C {
  int f;
  C(this._s_);
}
''',
    );
  }

  void test_fieldFormalParameter_period_notLast() {
    testRecovery(
      '''
class C {
  int f;
  C(this., p);
}
''',
      [diag.missingIdentifier],
      '''
class C {
  int f;
  C(this._s_, p);
}
''',
    );
  }

  void test_incorrectlyTerminatedGroup_named_none() {
    testRecovery(
      '''
f({a: 0) {}
''',
      [diag.expectedToken],
      '''
f({a: 0}) {}
''',
    );
  }

  void test_incorrectlyTerminatedGroup_named_positional() {
    testRecovery(
      '''
f({a: 0]) {}
''',
      [diag.expectedToken, diag.expectedToken],
      '''
f({a: 0}) {}
''',
    );
  }

  void test_incorrectlyTerminatedGroup_none_named() {
    testRecovery(
      '''
f(a}) {}
''',
      [diag.expectedToken],
      '''
f(a) {}
''',
    );
  }

  void test_incorrectlyTerminatedGroup_none_positional() {
    testRecovery(
      '''
f(a]) {}
''',
      [diag.expectedToken],
      '''
f(a) {}
''',
    );
  }

  void test_incorrectlyTerminatedGroup_positional_named() {
    testRecovery(
      '''
f([a = 0}) {}
''',
      [diag.expectedToken, diag.expectedToken],
      '''
f([a = 0]) {}
''',
    );
  }

  void test_incorrectlyTerminatedGroup_positional_none() {
    // Maybe put in paired_tokens_test.dart.
    testRecovery(
      '''
f([a = 0) {}
''',
      [diag.expectedToken],
      '''
f([a = 0]) {}
''',
    );
  }

  void test_missingComma() {
    // https://github.com/dart-lang/sdk/issues/22074
    testRecovery(
      '''
g(a, b, c) {}
h(v1, v2, v) {
  g(v1 == v2 || v1 == v 3, true);
}
''',
      [diag.expectedToken],
      '''
g(a, b, c) {}
h(v1, v2, v) {
  g(v1 == v2 || v1 == v, 3, true);
}
''',
    );
  }

  void test_missingDefault_named_last() {
    testRecovery(
      '''
f({a: }) {}
''',
      [diag.missingIdentifier],
      '''
f({a: _s_}) {}
''',
    );
  }

  void test_missingDefault_named_notLast() {
    testRecovery(
      '''
f({a: , b}) {}
''',
      [diag.missingIdentifier],
      '''
f({a: _s_, b}) {}
''',
    );
  }

  void test_missingDefault_positional_last() {
    testRecovery(
      '''
f([a = ]) {}
''',
      [diag.missingIdentifier],
      '''
f([a = _s_]) {}
''',
    );
  }

  void test_missingDefault_positional_notLast() {
    testRecovery(
      '''
f([a = , b]) {}
''',
      [diag.missingIdentifier],
      '''
f([a = _s_, b]) {}
''',
    );
  }

  void test_multipleGroups_mixed() {
    // TODO(brianwilkerson): Figure out the best way to recover from this.
    testRecovery(
      '''
f([a = 0], {b: 1}) {}
''',
      [diag.expectedToken],
      '''
f([a = 0]) {}
''',
    );
  }

  @failingTest
  void test_multipleGroups_mixedAndMultiple() {
    // TODO(brianwilkerson): Figure out the best way to recover from this.
    testRecovery(
      '''
f([a = 0], {b: 1}, [c = 2]) {}
''',
      [diag.mixedParameterGroups],
      '''
f([a = 0, c = 2]) {}
''',
    );
  }

  @failingTest
  void test_multipleGroups_named() {
    testRecovery(
      '''
f({a: 0}, {b: 1}) {}
''',
      [diag.multipleNamedParameterGroups],
      '''
f({a: 0, b: 1}) {}
''',
    );
  }

  @failingTest
  void test_multipleGroups_positional() {
    testRecovery(
      '''
f([a = 0], [b = 1]) {}
''',
      [diag.multiplePositionalParameterGroups],
      '''
f([a = 0, b = 1]) {}
''',
    );
  }

  @failingTest
  void test_namedOutsideGroup() {
    testRecovery(
      '''
f(a: 0) {}
''',
      [diag.namedParameterOutsideGroup],
      '''
f({a: 0}) {}
''',
    );
  }

  @failingTest
  void test_positionalOutsideGroup() {
    testRecovery(
      '''
f(a = 0) {}
''',
      [diag.positionalParameterOutsideGroup],
      '''
f([a = 0]) {}
''',
    );
  }
}

/// Test how well the parser recovers when tokens are missing in a typedef.
@reflectiveTest
class TypedefTest extends AbstractRecoveryTest {
  @failingTest
  void test_missingFunction() {
    testRecovery(
      '''
typedef Predicate = bool <E>(E element);
''',
      [diag.missingIdentifier],
      '''
typedef Predicate = bool Function<E>(E element);
''',
    );
  }
}
