// 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/analysis/features.dart';
import 'package:analyzer/dart/ast/token.dart' show TokenType;
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../util/ast_type_matchers.dart';
import '../util/feature_sets.dart';
import 'parser_test_base.dart';

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

@reflectiveTest
class NNBDParserTest extends FastaParserTestCase {
  @override
  CompilationUnitImpl parseCompilationUnit(
    String content, {
    List<DiagnosticCode>? codes,
    List<ExpectedDiagnostic>? diagnostics,
    FeatureSet? featureSet,
  }) => super.parseCompilationUnit(
    content,
    codes: codes,
    diagnostics: diagnostics,
    featureSet: featureSet ?? FeatureSet.latestLanguageVersion(),
  );

  void test_assignment_complex() {
    parseCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x + bar(7); }');
  }

  void test_assignment_complex2() {
    parseCompilationUnit(r'''
main() {
  A? a;
  String? s = '';
  a?..foo().length..x27 = s!..toString().length;
}
''');
  }

  void test_assignment_simple() {
    parseCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x; }');
  }

  void test_bangBeforeFunctionCall1() {
    // https://github.com/dart-lang/sdk/issues/39776
    var unit = parseCompilationUnit('f() { Function? f1; f1!(42); }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement1 = body.block.statements[0] as VariableDeclarationStatement;
    expect(statement1.toSource(), "Function? f1;");
    var statement2 = body.block.statements[1] as ExpressionStatement;

    // expression is "f1!(42)"
    var expression = statement2.expression as FunctionExpressionInvocation;
    expect(expression.toSource(), "f1!(42)");

    var functionExpression = expression.function as PostfixExpression;
    var identifier = functionExpression.operand as SimpleIdentifier;
    expect(identifier.name, 'f1');
    expect(functionExpression.operator.lexeme, '!');

    expect(expression.typeArguments, null);

    expect(expression.argumentList.arguments.length, 1);
    var argument = expression.argumentList.arguments.single as IntegerLiteral;
    expect(argument.value, 42);
  }

  void test_bangBeforeFunctionCall2() {
    // https://github.com/dart-lang/sdk/issues/39776
    var unit = parseCompilationUnit('f() { Function f2; f2!<int>(42); }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement1 = body.block.statements[0] as VariableDeclarationStatement;
    expect(statement1.toSource(), "Function f2;");
    var statement2 = body.block.statements[1] as ExpressionStatement;

    // expression is "f2!<int>(42)"
    var expression = statement2.expression as FunctionExpressionInvocation;
    expect(expression.toSource(), "f2!<int>(42)");

    var functionExpression = expression.function as PostfixExpression;
    var identifier = functionExpression.operand as SimpleIdentifier;
    expect(identifier.name, 'f2');
    expect(functionExpression.operator.lexeme, '!');

    expect(expression.typeArguments!.arguments.length, 1);
    var typeArgument = expression.typeArguments!.arguments.single as NamedType;
    expect(typeArgument.name.lexeme, "int");

    expect(expression.argumentList.arguments.length, 1);
    var argument = expression.argumentList.arguments.single as IntegerLiteral;
    expect(argument.value, 42);
  }

  void test_bangQuestionIndex() {
    // http://dartbug.com/41177
    CompilationUnit unit = parseCompilationUnit('f(dynamic a) { a!?[0]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;

    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;

    var index = expression.index as IntegerLiteral;
    expect(index.value, 0);

    var question = expression.question!;
    expect(question, isNotNull);
    expect(question.lexeme, "?");

    var target = expression.target as PostfixExpression;
    var identifier = target.operand as SimpleIdentifier;
    expect(identifier.name, 'a');
    expect(target.operator.lexeme, '!');
  }

  void test_binary_expression_statement() {
    var unit = parseCompilationUnit('D? foo(X? x) { X ?? x2; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as BinaryExpression;
    var lhs = expression.leftOperand as SimpleIdentifier;
    expect(lhs.name, 'X');
    expect(expression.operator.lexeme, '??');
    var rhs = expression.rightOperand as SimpleIdentifier;
    expect(rhs.name, 'x2');
  }

  void test_cascade_withNullCheck_indexExpression() {
    var unit = parseCompilationUnit('main() { a?..[27]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var cascade = statement.expression as CascadeExpression;
    var indexExpression = cascade.cascadeSections[0] as IndexExpression;
    expect(indexExpression.period!.lexeme, '?..');
    expect(indexExpression.toSource(), '?..[27]');
  }

  void test_cascade_withNullCheck_invalid() {
    parseCompilationUnit(
      'main() { a..[27]?..x; }',
      diagnostics: [
        expectedError(ParserErrorCode.nullAwareCascadeOutOfOrder, 16, 3),
      ],
    );
  }

  void test_cascade_withNullCheck_methodInvocation() {
    var unit = parseCompilationUnit('main() { a?..foo(); }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var cascade = statement.expression as CascadeExpression;
    var invocation = cascade.cascadeSections[0] as MethodInvocation;
    expect(invocation.operator!.lexeme, '?..');
    expect(invocation.toSource(), '?..foo()');
  }

  void test_cascade_withNullCheck_propertyAccess() {
    var unit = parseCompilationUnit('main() { a?..x27; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var cascade = statement.expression as CascadeExpression;
    var propertyAccess = cascade.cascadeSections[0] as PropertyAccess;
    expect(propertyAccess.operator.lexeme, '?..');
    expect(propertyAccess.toSource(), '?..x27');
  }

  void test_conditional() {
    parseCompilationUnit('D? foo(X? x) { X ? 7 : y; }');
  }

  void test_conditional_complex() {
    parseCompilationUnit('D? foo(X? x) { X ? x2 = x + bar(7) : y; }');
  }

  void test_conditional_error() {
    parseCompilationUnit(
      'D? foo(X? x) { X ? ? x2 = x + bar(7) : y; }',
      diagnostics: [
        expectedError(ParserErrorCode.missingIdentifier, 19, 1),
        expectedError(ParserErrorCode.expectedToken, 40, 1),
        expectedError(ParserErrorCode.missingIdentifier, 40, 1),
      ],
    );
  }

  void test_conditional_simple() {
    parseCompilationUnit('D? foo(X? x) { X ? x2 = x : y; }');
  }

  void test_for() {
    parseCompilationUnit('main() { for(int x = 0; x < 7; ++x) { } }');
  }

  void test_for_conditional() {
    parseCompilationUnit('main() { for(x ? y = 7 : y = 8; y < 10; ++y) { } }');
  }

  void test_for_nullable() {
    parseCompilationUnit('main() { for(int? x = 0; x < 7; ++x) { } }');
  }

  void test_foreach() {
    parseCompilationUnit('main() { for(int x in [7]) { } }');
  }

  void test_foreach_nullable() {
    parseCompilationUnit('main() { for(int? x in [7, null]) { } }');
  }

  test_fuzz_38113() async {
    // https://github.com/dart-lang/sdk/issues/38113
    parseCompilationUnit(
      r'+t{{r?this}}',
      diagnostics: [
        expectedError(ParserErrorCode.expectedExecutable, 0, 1),
        expectedError(ParserErrorCode.missingFunctionParameters, 1, 1),
        expectedError(ParserErrorCode.expectedToken, 6, 4),
        expectedError(ParserErrorCode.expectedToken, 10, 1),
        expectedError(ParserErrorCode.missingIdentifier, 10, 1),
      ],
    );
  }

  void test_gft_nullable() {
    parseCompilationUnit('main() { C? Function() x = 7; }');
  }

  void test_gft_nullable_1() {
    parseCompilationUnit('main() { C Function()? x = 7; }');
  }

  void test_gft_nullable_2() {
    parseCompilationUnit('main() { C? Function()? x = 7; }');
  }

  void test_gft_nullable_3() {
    parseCompilationUnit('main() { C? Function()? Function()? x = 7; }');
  }

  void test_gft_nullable_prefixed() {
    parseCompilationUnit('main() { C.a? Function()? x = 7; }');
  }

  void test_indexed() {
    CompilationUnit unit = parseCompilationUnit('main() { a[7]; }');
    var method = unit.declarations[0] as FunctionDeclaration;
    var body = method.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    expect(expression.leftBracket.lexeme, '[');
  }

  void test_indexed_nullAware() {
    CompilationUnit unit = parseCompilationUnit('main() { a?[7]; }');
    var method = unit.declarations[0] as FunctionDeclaration;
    var body = method.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    expect(expression.question, isNotNull);
    expect(expression.leftBracket.lexeme, '[');
    expect(expression.rightBracket.lexeme, ']');
    expect(expression.leftBracket.endGroup, expression.rightBracket);
  }

  void test_is_nullable() {
    CompilationUnit unit = parseCompilationUnit(
      'main() { x is String? ? (x + y) : z; }',
    );
    var function = unit.declarations[0] as FunctionDeclaration;
    var body = function.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as ConditionalExpression;

    var condition = expression.condition as IsExpression;
    expect((condition.type as NamedType).question, isNotNull);
    Expression thenExpression = expression.thenExpression;
    expect(thenExpression, isParenthesizedExpression);
    Expression elseExpression = expression.elseExpression;
    expect(elseExpression, isSimpleIdentifier);
  }

  void test_is_nullable_parenthesis() {
    CompilationUnit unit = parseCompilationUnit(
      'main() { (x is String?) ? (x + y) : z; }',
    );
    var function = unit.declarations[0] as FunctionDeclaration;
    var body = function.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as ConditionalExpression;

    var condition = expression.condition as ParenthesizedExpression;
    var isExpression = condition.expression as IsExpression;
    expect((isExpression.type as NamedType).question, isNotNull);
    Expression thenExpression = expression.thenExpression;
    expect(thenExpression, isParenthesizedExpression);
    Expression elseExpression = expression.elseExpression;
    expect(elseExpression, isSimpleIdentifier);
  }

  void test_late_as_identifier() {
    parseCompilationUnit('''
class C {
  int late;
}

void f(C c) {
  print(c.late);
}

main() {
  f(new C());
}
''', featureSet: FeatureSets.language_2_9);
  }

  void test_late_as_identifier_optOut() {
    parseCompilationUnit('''
// @dart = 2.2
class C {
  int late;
}

void f(C c) {
  print(c.late);
}

main() {
  f(new C());
}
''');
  }

  void test_nullableTypeInInitializerList_01() {
    // http://dartbug.com/40834
    var unit = parseCompilationUnit(r'''
class Foo {
  String? x;
  int y;

  Foo(Object? o) : x = o as String?, y = 0;
}
''');
    var classDeclaration = unit.declarations.first as ClassDeclaration;
    var constructor = classDeclaration.members
        .whereType<ConstructorDeclaration>()
        .single;

    // Object? o
    var parameter =
        constructor.parameters.parameters.single as SimpleFormalParameter;
    expect(parameter.name!.lexeme, 'o');
    var type = parameter.type as NamedType;
    expect(type.question!.lexeme, '?');
    expect(type.name.lexeme, 'Object');

    expect(constructor.initializers.length, 2);

    // o as String?
    {
      var initializer =
          constructor.initializers[0] as ConstructorFieldInitializer;
      expect(initializer.fieldName.name, 'x');
      var expression = initializer.expression as AsExpression;
      var identifier = expression.expression as SimpleIdentifier;
      expect(identifier.name, 'o');
      var expressionType = expression.type as NamedType;
      expect(expressionType.question!.lexeme, '?');
      expect(expressionType.name.lexeme, 'String');
    }

    // y = 0
    {
      var initializer =
          constructor.initializers[1] as ConstructorFieldInitializer;
      expect(initializer.fieldName.name, 'y');
      var expression = initializer.expression as IntegerLiteral;
      expect(expression.value, 0);
    }
  }

  void test_nullableTypeInInitializerList_02() {
    var unit = parseCompilationUnit(r'''
class Foo {
  String? x;
  int y;

  Foo(Object? o) : y = o is String? ? o.length : null, x = null;
}
''');
    var classDeclaration = unit.declarations.first as ClassDeclaration;
    var constructor = classDeclaration.members
        .whereType<ConstructorDeclaration>()
        .single;

    // Object? o
    var parameter =
        constructor.parameters.parameters.single as SimpleFormalParameter;
    expect(parameter.name!.lexeme, 'o');
    var type = parameter.type as NamedType;
    expect(type.question!.lexeme, '?');
    expect(type.name.lexeme, 'Object');

    expect(constructor.initializers.length, 2);

    // y = o is String? ? o.length : null
    {
      var initializer =
          constructor.initializers[0] as ConstructorFieldInitializer;
      expect(initializer.fieldName.name, 'y');
      var expression = initializer.expression as ConditionalExpression;
      var condition = expression.condition as IsExpression;
      var identifier = condition.expression as SimpleIdentifier;
      expect(identifier.name, 'o');
      var expressionType = condition.type as NamedType;
      expect(expressionType.question!.lexeme, '?');
      expect(expressionType.name.lexeme, 'String');
      var thenExpression = expression.thenExpression as PrefixedIdentifier;
      expect(thenExpression.identifier.name, 'length');
      expect(thenExpression.prefix.name, 'o');
      var elseExpression = expression.elseExpression as NullLiteral;
      expect(elseExpression, isNotNull);
    }

    // x = null
    {
      var initializer =
          constructor.initializers[1] as ConstructorFieldInitializer;
      expect(initializer.fieldName.name, 'x');
      var expression = initializer.expression as NullLiteral;
      expect(expression, isNotNull);
    }
  }

  void test_nullableTypeInInitializerList_03() {
    // As test_nullableTypeInInitializerList_02 but without ? on String in is.
    var unit = parseCompilationUnit(r'''
class Foo {
  String? x;
  int y;

  Foo(Object? o) : y = o is String ? o.length : null, x = null;
}
''');
    var classDeclaration = unit.declarations.first as ClassDeclaration;
    var constructor = classDeclaration.members
        .whereType<ConstructorDeclaration>()
        .single;

    // Object? o
    var parameter =
        constructor.parameters.parameters.single as SimpleFormalParameter;
    expect(parameter.name!.lexeme, 'o');
    var type = parameter.type as NamedType;
    expect(type.question!.lexeme, '?');
    expect(type.name.lexeme, 'Object');

    expect(constructor.initializers.length, 2);

    // y = o is String ? o.length : null
    {
      var initializer =
          constructor.initializers[0] as ConstructorFieldInitializer;
      expect(initializer.fieldName.name, 'y');
      var expression = initializer.expression as ConditionalExpression;
      var condition = expression.condition as IsExpression;
      var identifier = condition.expression as SimpleIdentifier;
      expect(identifier.name, 'o');
      var expressionType = condition.type as NamedType;
      expect(expressionType.question, isNull);
      expect(expressionType.name.lexeme, 'String');
      var thenExpression = expression.thenExpression as PrefixedIdentifier;
      expect(thenExpression.identifier.name, 'length');
      expect(thenExpression.prefix.name, 'o');
      var elseExpression = expression.elseExpression as NullLiteral;
      expect(elseExpression, isNotNull);
    }

    // x = null
    {
      var initializer =
          constructor.initializers[1] as ConstructorFieldInitializer;
      expect(initializer.fieldName.name, 'x');
      var expression = initializer.expression as NullLiteral;
      expect(expression, isNotNull);
    }
  }

  void test_nullCheck() {
    var unit = parseCompilationUnit('f(int? y) { var x = y!; }');
    var function = unit.declarations[0] as FunctionDeclaration;
    var body = function.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as VariableDeclarationStatement;
    var expression =
        statement.variables.variables[0].initializer as PostfixExpression;
    var identifier = expression.operand as SimpleIdentifier;
    expect(identifier.name, 'y');
    expect(expression.operator.lexeme, '!');
  }

  void test_nullCheckAfterGetterAccess() {
    parseCompilationUnit('f() { var x = g.x!.y + 7; }');
  }

  void test_nullCheckAfterMethodCall() {
    parseCompilationUnit('f() { var x = g.m()!.y + 7; }');
  }

  void test_nullCheckBeforeGetterAccess() {
    parseCompilationUnit('f() { var x = g!.x + 7; }');
  }

  void test_nullCheckBeforeIndex() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { foo.bar!.baz[arg]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    expect(expression.index.toSource(), 'arg');
    var propertyAccess = expression.target as PropertyAccess;
    expect(propertyAccess.propertyName.toSource(), 'baz');
    var target = propertyAccess.target as PostfixExpression;
    expect(target.operand.toSource(), 'foo.bar');
    expect(target.operator.lexeme, '!');
  }

  void test_nullCheckBeforeMethodCall() {
    parseCompilationUnit('f() { var x = g!.m() + 7; }');
  }

  void test_nullCheckFunctionResult() {
    parseCompilationUnit('f() { var x = g()! + 7; }');
  }

  void test_nullCheckIndexedValue() {
    parseCompilationUnit('f(int? y) { var x = y[0]! + 7; }');
  }

  void test_nullCheckIndexedValue2() {
    parseCompilationUnit('f(int? y) { var x = super.y[0]! + 7; }');
  }

  void test_nullCheckInExpression() {
    parseCompilationUnit('f(int? y) { var x = y! + 7; }');
  }

  void test_nullCheckMethodResult() {
    parseCompilationUnit('f() { var x = g.m()! + 7; }');
  }

  void test_nullCheckMethodResult2() {
    parseCompilationUnit('f() { var x = g?.m()! + 7; }');
  }

  void test_nullCheckMethodResult3() {
    parseCompilationUnit('f() { var x = super.m()! + 7; }');
  }

  void test_nullCheckOnConstConstructor() {
    parseCompilationUnit('f() { var x = const Foo()!; }');
  }

  void test_nullCheckOnConstructor() {
    parseCompilationUnit('f() { var x = new Foo()!; }');
  }

  void test_nullCheckOnIndex() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { obj![arg]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    var target = expression.target as PostfixExpression;
    expect(target.operand.toSource(), 'obj');
    expect(target.operator.lexeme, '!');
  }

  void test_nullCheckOnIndex2() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { obj![arg]![arg2]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    expect(expression.index.toSource(), 'arg2');
    var target = expression.target as PostfixExpression;
    expect(target.operator.lexeme, '!');
    expression = target.operand as IndexExpression;
    expect(expression.index.toSource(), 'arg');
    target = expression.target as PostfixExpression;
    expect(target.operator.lexeme, '!');
    expect(target.operand.toSource(), 'obj');
  }

  void test_nullCheckOnIndex3() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { foo.bar![arg]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    expect(expression.index.toSource(), 'arg');
    var target = expression.target as PostfixExpression;
    expect(target.operand.toSource(), 'foo.bar');
    expect(target.operator.lexeme, '!');
  }

  void test_nullCheckOnIndex4() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { foo!.bar![arg]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    var fooBarTarget = expression.target as PostfixExpression;
    expect(fooBarTarget.toSource(), "foo!.bar!");
    var propertyAccess = fooBarTarget.operand as PropertyAccess;
    var targetFoo = propertyAccess.target as PostfixExpression;
    expect(targetFoo.operand.toSource(), "foo");
    expect(targetFoo.operator.lexeme, "!");
    expect(propertyAccess.propertyName.toSource(), "bar");
    expect(fooBarTarget.operator.lexeme, '!');
    expect(expression.index.toSource(), 'arg');
  }

  void test_nullCheckOnIndex5() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { foo.bar![arg]![arg2]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as IndexExpression;
    expect(expression.index.toSource(), 'arg2');
    var target = expression.target as PostfixExpression;
    expect(target.operator.lexeme, '!');
    expression = target.operand as IndexExpression;
    expect(expression.index.toSource(), 'arg');
    target = expression.target as PostfixExpression;
    expect(target.operator.lexeme, '!');
    expect(target.operand.toSource(), 'foo.bar');
  }

  void test_nullCheckOnIndex6() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { foo!.bar![arg]![arg2]; }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;

    // expression is "foo!.bar![arg]![arg2]"
    var expression = statement.expression as IndexExpression;
    expect(expression.index.toSource(), 'arg2');

    // target is "foo!.bar![arg]!"
    var target = expression.target as PostfixExpression;
    expect(target.operator.lexeme, '!');

    // expression is "foo!.bar![arg]"
    expression = target.operand as IndexExpression;
    expect(expression.index.toSource(), 'arg');

    // target is "foo!.bar!"
    target = expression.target as PostfixExpression;
    expect(target.operator.lexeme, '!');

    // propertyAccess is "foo!.bar"
    PropertyAccess propertyAccess = target.operand as PropertyAccess;
    expect(propertyAccess.propertyName.toSource(), "bar");

    // target is "foo!"
    target = propertyAccess.target as PostfixExpression;
    expect(target.operator.lexeme, '!');

    expect(target.operand.toSource(), "foo");
  }

  void test_nullCheckOnLiteralDouble() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = 1.2!; }');
  }

  void test_nullCheckOnLiteralInt() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = 0!; }');
  }

  void test_nullCheckOnLiteralList() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = [1,2]!; }');
  }

  void test_nullCheckOnLiteralMap() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = {1:2}!; }');
  }

  void test_nullCheckOnLiteralSet() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = {1,2}!; }');
  }

  void test_nullCheckOnLiteralString() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = "seven"!; }');
  }

  void test_nullCheckOnNull() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = null!; }');
  }

  void test_nullCheckOnSend() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { obj!(arg); }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as FunctionExpressionInvocation;
    var target = expression.function as PostfixExpression;
    expect(target.operand.toSource(), 'obj');
    expect(target.operator.lexeme, '!');
  }

  void test_nullCheckOnSend2() {
    // https://github.com/dart-lang/sdk/issues/37708
    var unit = parseCompilationUnit('f() { obj!(arg)!(arg2); }');
    var funct = unit.declarations[0] as FunctionDeclaration;
    var body = funct.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var expression = statement.expression as FunctionExpressionInvocation;
    expect(expression.argumentList.toSource(), '(arg2)');
    var target = expression.function as PostfixExpression;
    expect(target.operator.lexeme, '!');
    expression = target.operand as FunctionExpressionInvocation;
    expect(expression.argumentList.toSource(), '(arg)');
    target = expression.function as PostfixExpression;
    expect(target.operator.lexeme, '!');
    expect(target.operand.toSource(), 'obj');
  }

  void test_nullCheckOnSymbol() {
    // Issues like this should be caught during later analysis
    parseCompilationUnit('f() { var x = #seven!; }');
  }

  void test_nullCheckOnValue() {
    parseCompilationUnit('f(Point p) { var x = p.y! + 7; }');
  }

  void test_nullCheckParenthesizedExpression() {
    parseCompilationUnit('f(int? y) { var x = (y)! + 7; }');
  }

  void test_nullCheckPropertyAccess() {
    parseCompilationUnit('f() { var x = g.p! + 7; }');
  }

  void test_nullCheckPropertyAccess2() {
    parseCompilationUnit('f() { var x = g?.p! + 7; }');
  }

  void test_nullCheckPropertyAccess3() {
    parseCompilationUnit('f() { var x = super.p! + 7; }');
  }

  void test_postfix_null_assertion_and_unary_prefix_operator_precedence() {
    // -x! is parsed as -(x!).
    var unit = parseCompilationUnit('void main() { -x!; }');
    var function = unit.declarations[0] as FunctionDeclaration;
    var body = function.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var outerExpression = statement.expression as PrefixExpression;
    expect(outerExpression.operator.type, TokenType.MINUS);
    var innerExpression = outerExpression.operand as PostfixExpression;
    expect(innerExpression.operator.type, TokenType.BANG);
  }

  void test_postfix_null_assertion_of_postfix_expression() {
    // x++! is parsed as (x++)!.
    var unit = parseCompilationUnit('void main() { x++!; }');
    var function = unit.declarations[0] as FunctionDeclaration;
    var body = function.functionExpression.body as BlockFunctionBody;
    var statement = body.block.statements[0] as ExpressionStatement;
    var outerExpression = statement.expression as PostfixExpression;
    expect(outerExpression.operator.type, TokenType.BANG);
    var innerExpression = outerExpression.operand as PostfixExpression;
    expect(innerExpression.operator.type, TokenType.PLUS_PLUS);
  }
}
