Migration: recognize /*!*/ as expressing non-null intent.

Change-Id: Ic9f0b3f560df00adf5e7cbab71b3380f6c43c0aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104801
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/node_builder.dart b/pkg/analysis_server/lib/src/nullability/node_builder.dart
index b85765b..e1c6939 100644
--- a/pkg/analysis_server/lib/src/nullability/node_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/node_builder.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/generated/resolver.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:front_end/src/scanner/token.dart';
 
 /// Visitor that builds nullability nodes based on visiting code to be migrated.
 ///
@@ -149,6 +150,9 @@
         node.end,
         typeArguments: typeArguments);
     _variables.recordDecoratedTypeAnnotation(_source, node, decoratedType);
+    if (_isBangComment(node.endToken.next.precedingComments)) {
+      _graph.connect(decoratedType.node, NullabilityNode.never, hard: true);
+    }
     return decoratedType;
   }
 
@@ -188,6 +192,13 @@
     }
     _variables.recordDecoratedElementType(declaredElement, functionType);
   }
+
+  bool _isBangComment(Token token) {
+    if (token is CommentToken) {
+      if (token.lexeme == '/*!*/') return true;
+    }
+    return false;
+  }
 }
 
 /// Repository of constraint variables and decorated types corresponding to the
diff --git a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
index 357b500..115dc45 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -1013,6 +1013,13 @@
     expect(decoratedType.node, isNot(NullabilityNode.never));
   }
 
+  test_type_comment_bang() async {
+    await analyze('''
+void f(int/*!*/ i) {}
+''');
+    assertEdge(decoratedTypeAnnotation('int').node, never, hard: true);
+  }
+
   test_type_parameter_explicit_bound() async {
     await analyze('''
 class C<T extends Object> {}
diff --git a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
index cc27ad0..ba01ca0 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -76,6 +76,28 @@
 
 /// Mixin containing test cases for the provisional API.
 mixin _ProvisionalApiTestCases on _ProvisionalApiTestBase {
+  test_comment_bang_implies_non_null_intent() async {
+    var content = '''
+void f(int/*!*/ i) {}
+void g(bool b, int i) {
+  if (b) f(i);
+}
+main() {
+  g(false, null);
+}
+''';
+    var expected = '''
+void f(int/*!*/ i) {}
+void g(bool b, int? i) {
+  if (b) f(i!);
+}
+main() {
+  g(false, null);
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   test_conditional_assert_statement_does_not_imply_non_null_intent() async {
     var content = '''
 void f(bool b, int i) {