Add quick fix for null_check_on_nullable_type_parameter

Change-Id: Idb4f617880ceac0972edb8c4f04feb23fd97145b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/234560
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_null_check_with_cast.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_null_check_with_cast.dart
new file mode 100644
index 0000000..8f2faf8
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_null_check_with_cast.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, 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:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class ReplaceNullCheckWithCast extends CorrectionProducer {
+  @override
+  bool get canBeAppliedInBulk => true;
+
+  @override
+  bool get canBeAppliedToFile => true;
+
+  @override
+  FixKind get fixKind => DartFixKind.REPLACE_NULL_CHECK_WITH_CAST;
+
+  @override
+  FixKind get multiFixKind => DartFixKind.REPLACE_NULL_CHECK_WITH_CAST_MULTI;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    final node = this.node;
+    if (node is! PostfixExpression) {
+      return;
+    }
+    var operand = node.operand;
+    var operator = node.operator;
+    var operandType = operand.staticType;
+    if (operandType is! TypeParameterType) {
+      return;
+    }
+    // It is possible that there are cases of precedence and syntax which would
+    // require additional parentheses, for example converting `p!.hashCode` to
+    // `(p as T).hashCode`. However no such cases are known to trigger the lint
+    // rule.
+    // TODO(srawlins): Follow up on
+    // https://github.com/dart-lang/linter/issues/3256.
+    await builder.addDartFileEdit(file, (builder) {
+      builder.addSimpleReplacement(range.token(operator),
+          ' as ${operandType.getDisplayString(withNullability: false)}');
+    });
+  }
+
+  /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+  static ReplaceNullCheckWithCast newInstance() => ReplaceNullCheckWithCast();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 819a2a7..724920c 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -1258,6 +1258,16 @@
     DartFixKindPriority.IN_FILE,
     "Replace 'new' with 'const' where possible in file",
   );
+  static const REPLACE_NULL_CHECK_WITH_CAST = FixKind(
+    'dart.fix.replace.nullCheckWithCast',
+    DartFixKindPriority.DEFAULT,
+    'Replace null check with a cast',
+  );
+  static const REPLACE_NULL_CHECK_WITH_CAST_MULTI = FixKind(
+    'dart.fix.replace.nullCheckWithCast.multi',
+    DartFixKindPriority.IN_FILE,
+    'Replace null checks with casts in file',
+  );
   static const REPLACE_NULL_WITH_CLOSURE = FixKind(
     'dart.fix.replace.nullWithClosure',
     DartFixKindPriority.DEFAULT,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 901695f..bb89d71 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -152,6 +152,7 @@
 import 'package:analysis_server/src/services/correction/dart/replace_final_with_const.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_final_with_var.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_new_with_const.dart';
+import 'package:analysis_server/src/services/correction/dart/replace_null_check_with_cast.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_null_with_closure.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_return_type.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_return_type_future.dart';
@@ -459,6 +460,9 @@
     LintNames.non_constant_identifier_names: [
       RenameToCamelCase.newInstance,
     ],
+    LintNames.null_check_on_nullable_type_parameter: [
+      ReplaceNullCheckWithCast.newInstance,
+    ],
     LintNames.null_closures: [
       ReplaceNullWithClosure.newInstance,
     ],
diff --git a/pkg/analysis_server/lib/src/services/linter/lint_names.dart b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
index 986a5a4..d94fd94 100644
--- a/pkg/analysis_server/lib/src/services/linter/lint_names.dart
+++ b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
@@ -63,6 +63,8 @@
       'no_leading_underscores_for_local_identifiers';
   static const String non_constant_identifier_names =
       'non_constant_identifier_names';
+  static const String null_check_on_nullable_type_parameter =
+      'null_check_on_nullable_type_parameter';
   static const String null_closures = 'null_closures';
   static const String omit_local_variable_types = 'omit_local_variable_types';
   static const String prefer_adjacent_string_concatenation =
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_null_check_with_cast_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_null_check_with_cast_test.dart
new file mode 100644
index 0000000..d99e73f
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_null_check_with_cast_test.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2022, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ReplaceNullCheckWithCastBulkTest);
+    defineReflectiveTests(ReplaceNullCheckWithCastTest);
+  });
+}
+
+@reflectiveTest
+class ReplaceNullCheckWithCastBulkTest extends BulkFixProcessorTest {
+  @override
+  String get lintCode => LintNames.null_check_on_nullable_type_parameter;
+
+  Future<void> test_singleFile() async {
+    await resolveTestCode('''
+T f<T>(T? result) {
+  if (1==1) {
+    return result!;
+  } else {
+    return result!;
+  }
+}
+''');
+    await assertHasFix('''
+T f<T>(T? result) {
+  if (1==1) {
+    return result as T;
+  } else {
+    return result as T;
+  }
+}
+''');
+  }
+}
+
+@reflectiveTest
+class ReplaceNullCheckWithCastTest extends FixProcessorLintTest {
+  @override
+  FixKind get kind => DartFixKind.REPLACE_NULL_CHECK_WITH_CAST;
+
+  @override
+  String get lintCode => LintNames.null_check_on_nullable_type_parameter;
+
+  Future<void> test_simpleIdentifier() async {
+    await resolveTestCode('''
+T run<T>(T? result) {
+  return result!;
+}
+''');
+    await assertHasFix('''
+T run<T>(T? result) {
+  return result as T;
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index c1d46b5..fc4bb66 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -187,6 +187,7 @@
 import 'replace_final_with_const_test.dart' as replace_final_with_const;
 import 'replace_final_with_var_test.dart' as replace_final_with_var;
 import 'replace_new_with_const_test.dart' as replace_new_with_const;
+import 'replace_null_check_with_cast_test.dart' as replace_null_check_with_cast;
 import 'replace_null_with_closure_test.dart' as replace_null_with_closure;
 import 'replace_return_type_future_test.dart' as replace_return_type_future;
 import 'replace_return_type_iterable_test.dart' as replace_return_type_iterable;
@@ -382,6 +383,7 @@
     replace_final_with_const.main();
     replace_final_with_var.main();
     replace_new_with_const.main();
+    replace_null_check_with_cast.main();
     replace_null_with_closure.main();
     replace_null_with_void.main();
     replace_return_type.main();