// Copyright (c) 2020, 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/dart/scanner/scanner.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'parser_test_base.dart';

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

@reflectiveTest
class ExtensionMethodsParserTest extends FastaParserTestCase {
  void test_complex_extends() {
    var unit = parseCompilationUnit(
        'extension E extends A with B, C implements D { }',
        errors: [
          expectedError(ParserErrorCode.EXPECTED_INSTEAD, 12, 7),
          expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 22, 4),
          expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 28, 1),
          expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 32, 10),
        ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'extends');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'A');
    expect(extension.members, hasLength(0));
  }

  void test_complex_implements() {
    var unit = parseCompilationUnit('extension E implements C, D { }', errors: [
      expectedError(ParserErrorCode.EXPECTED_INSTEAD, 12, 10),
      expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 24, 1),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'implements');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    expect(extension.members, hasLength(0));
  }

  void test_complex_type() {
    var unit = parseCompilationUnit('extension E on C<T> { }');
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    var namedType = extension.onClause!.extendedType as NamedType;
    expect(namedType.name2.lexeme, 'C');
    expect(namedType.typeArguments!.arguments, hasLength(1));
    expect(extension.members, hasLength(0));
  }

  void test_complex_type2() {
    var unit = parseCompilationUnit('extension E<T> on C<T> { }');
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    var namedType = extension.onClause!.extendedType as NamedType;
    expect(namedType.name2.lexeme, 'C');
    expect(namedType.typeArguments!.arguments, hasLength(1));
    expect(extension.members, hasLength(0));
  }

  void test_complex_type2_no_name() {
    var unit = parseCompilationUnit('extension<T> on C<T> { }');
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name, isNull);
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    var namedType = extension.onClause!.extendedType as NamedType;
    expect(namedType.name2.lexeme, 'C');
    expect(namedType.typeArguments!.arguments, hasLength(1));
    expect(extension.members, hasLength(0));
  }

  void test_constructor_named() {
    var unit = parseCompilationUnit('''
extension E on C {
  E.named();
}
class C {}
''', errors: [
      expectedError(ParserErrorCode.EXTENSION_DECLARES_CONSTRUCTOR, 21, 1),
    ]);
    expect(unit.declarations, hasLength(2));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.members, hasLength(0));
  }

  void test_constructor_unnamed() {
    var unit = parseCompilationUnit('''
extension E on C {
  E();
}
class C {}
''', errors: [
      expectedError(ParserErrorCode.EXTENSION_DECLARES_CONSTRUCTOR, 21, 1),
    ]);
    expect(unit.declarations, hasLength(2));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.members, hasLength(0));
  }

  void test_missing_on() {
    var unit = parseCompilationUnit('extension E', errors: [
      expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1),
      expectedError(ParserErrorCode.EXPECTED_TYPE_NAME, 11, 0),
      expectedError(ParserErrorCode.EXPECTED_EXTENSION_BODY, 11, 0),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, '');
    expect(extension.members, hasLength(0));
  }

  void test_missing_on_withBlock() {
    var unit = parseCompilationUnit('extension E {}', errors: [
      expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1),
      expectedError(ParserErrorCode.EXPECTED_TYPE_NAME, 12, 1),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, '');
    expect(extension.members, hasLength(0));
  }

  void test_missing_on_withClassAndBlock() {
    var unit = parseCompilationUnit('extension E C {}', errors: [
      expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    expect(extension.members, hasLength(0));
  }

  void test_parse_toplevel_member_called_late_calling_self() {
    var unit = parseCompilationUnit('void late() { late(); }');
    var method = unit.declarations[0] as FunctionDeclaration;

    expect(method.documentationComment, isNull);
    expect(method.externalKeyword, isNull);
    expect(method.propertyKeyword, isNull);
    expect(method.returnType, isNotNull);
    expect(method.name.lexeme, 'late');
    expect(method.functionExpression, isNotNull);

    var body = method.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var invocation = statement.expression as MethodInvocation;
    expect(invocation.operator, isNull);
    expect(invocation.toSource(), 'late()');
  }

  void test_simple() {
    var unit = parseCompilationUnit('extension E on C { }');
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    var namedType = extension.onClause!.extendedType as NamedType;
    expect(namedType.name2.lexeme, 'C');
    expect(namedType.typeArguments, isNull);
    expect(extension.members, hasLength(0));
  }

  void test_simple_extends() {
    var unit = parseCompilationUnit('extension E extends C { }', errors: [
      expectedError(ParserErrorCode.EXPECTED_INSTEAD, 12, 7),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'extends');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    expect(extension.members, hasLength(0));
  }

  void test_simple_implements() {
    var unit = parseCompilationUnit('extension E implements C { }', errors: [
      expectedError(ParserErrorCode.EXPECTED_INSTEAD, 12, 10),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'implements');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    expect(extension.members, hasLength(0));
  }

  void test_simple_no_name() {
    var unit = parseCompilationUnit('extension on C { }');
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name, isNull);
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    var namedType = extension.onClause!.extendedType as NamedType;
    expect(namedType.name2.lexeme, 'C');
    expect(namedType.typeArguments, isNull);
    expect(extension.members, hasLength(0));
  }

  void test_simple_with() {
    var unit = parseCompilationUnit('extension E with C { }', errors: [
      expectedError(ParserErrorCode.EXPECTED_INSTEAD, 12, 4),
    ]);
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'with');
    expect((extension.onClause!.extendedType as NamedType).name2.lexeme, 'C');
    expect(extension.members, hasLength(0));
  }

  void test_void_type() {
    var unit = parseCompilationUnit('extension E on void { }');
    expect(unit.declarations, hasLength(1));
    var extension = unit.declarations[0] as ExtensionDeclaration;
    expect(extension.name!.lexeme, 'E');
    expect(extension.onClause!.onKeyword.lexeme, 'on');
    expect(
        (extension.onClause!.extendedType as NamedType).name2.lexeme, 'void');
    expect(extension.members, hasLength(0));
  }
}
