Update parser to allow unnamed constructor reference in comment reference

Code like `/// See also [List.new].` will be accepted as a valid comment
reference.

Test in front_end, analyzer, analysis_server.

Remove TODOs in analyzer for reporting a reference to an element whose
library is not the library in which the comment is found.

Bug: https://github.com/dart-lang/sdk/issues/47446
Change-Id: Ifa19278f92ac003082b62b121c66f0559c0152e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/216583
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
index 2d4f96e..896bede 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
@@ -8257,6 +8257,16 @@
     if (token.isIdentifier && optional('.', token.next!)) {
       prefix = token;
       period = token.next!;
+      Token identifier = period.next!;
+      if (identifier.kind == KEYWORD_TOKEN && optional('new', identifier)) {
+        // Treat `new` after `.` is as an identifier so that it can represent an
+        // unnamed constructor. This support is separate from the
+        // constructor-tearoffs feature.
+        rewriter.replaceTokenFollowing(
+            period,
+            new StringToken(TokenType.IDENTIFIER, identifier.lexeme,
+                identifier.charOffset));
+      }
       token = period.next!;
     }
     if (token.isEof) {
diff --git a/pkg/analysis_server/test/search/element_references_test.dart b/pkg/analysis_server/test/search/element_references_test.dart
index 55ab879..326a8eb 100644
--- a/pkg/analysis_server/test/search/element_references_test.dart
+++ b/pkg/analysis_server/test/search/element_references_test.dart
@@ -95,30 +95,32 @@
   Future<void> test_constructor_unnamed() async {
     addTestFile('''
 /// [new A] 1
+/// [A.new] 2
 class A {
   A() {}
-  A.other() : this(); // 2
+  A.other() : this(); // 3
 }
 
 class B extends A {
-  B() : super(); // 3
-  factory B.other() = A; // 4
+  B() : super(); // 4
+  factory B.other() = A; // 5
 }
 
 void f() {
-  A(); // 5
-  A.new; // 6
+  A(); // 6
+  A.new; // 7
 }
 ''');
     await findElementReferences('A() {}', false);
     expect(searchElement!.kind, ElementKind.CONSTRUCTOR);
-    expect(results, hasLength(6));
+    expect(results, hasLength(7));
     assertHasResult(SearchResultKind.REFERENCE, '] 1', 0);
-    assertHasResult(SearchResultKind.INVOCATION, '(); // 2', 0);
+    assertHasResult(SearchResultKind.REFERENCE, '.new] 2', 4);
     assertHasResult(SearchResultKind.INVOCATION, '(); // 3', 0);
-    assertHasResult(SearchResultKind.REFERENCE, '; // 4', 0);
-    assertHasResult(SearchResultKind.INVOCATION, '(); // 5', 0);
-    assertHasResult(SearchResultKind.REFERENCE, '.new; // 6', 4);
+    assertHasResult(SearchResultKind.INVOCATION, '(); // 4', 0);
+    assertHasResult(SearchResultKind.REFERENCE, '; // 5', 0);
+    assertHasResult(SearchResultKind.INVOCATION, '(); // 6', 0);
+    assertHasResult(SearchResultKind.REFERENCE, '.new; // 7', 4);
   }
 
   Future<void> test_constructor_unnamed_potential() async {
diff --git a/pkg/analyzer/lib/src/generated/element_resolver.dart b/pkg/analyzer/lib/src/generated/element_resolver.dart
index 7f2a709..daa682e 100644
--- a/pkg/analyzer/lib/src/generated/element_resolver.dart
+++ b/pkg/analyzer/lib/src/generated/element_resolver.dart
@@ -129,27 +129,19 @@
     if (identifier is SimpleIdentifierImpl) {
       var element = _resolveSimpleIdentifier(identifier);
       if (element == null) {
-        // TODO(brianwilkerson) Report this error?
-        //        resolver.reportError(
-        //            CompileTimeErrorCode.UNDEFINED_IDENTIFIER,
-        //            simpleIdentifier,
-        //            simpleIdentifier.getName());
-      } else {
-        if (element.library == null || element.library != _definingLibrary) {
-          // TODO(brianwilkerson) Report this error?
-        }
-        identifier.staticElement = element;
-        if (node.newKeyword != null) {
-          if (element is ClassElement) {
-            var constructor = element.unnamedConstructor;
-            if (constructor == null) {
-              // TODO(brianwilkerson) Report this error.
-            } else {
-              identifier.staticElement = constructor;
-            }
-          } else {
+        return;
+      }
+      identifier.staticElement = element;
+      if (node.newKeyword != null) {
+        if (element is ClassElement) {
+          var constructor = element.unnamedConstructor;
+          if (constructor == null) {
             // TODO(brianwilkerson) Report this error.
+          } else {
+            identifier.staticElement = constructor;
           }
+        } else {
+          // TODO(brianwilkerson) Report this error.
         }
       }
     } else if (identifier is PrefixedIdentifierImpl) {
@@ -160,7 +152,6 @@
       var name = identifier.identifier;
 
       if (prefixElement == null) {
-//        resolver.reportError(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, prefix, prefix.getName());
         return;
       }
 
@@ -173,11 +164,6 @@
         return;
       }
 
-      var library = prefixElement.library;
-      if (library != _definingLibrary) {
-        // TODO(brianwilkerson) Report this error.
-      }
-
       if (node.newKeyword == null) {
         if (prefixElement is ClassElement) {
           name.staticElement = prefixElement.getMethod(name.name) ??
diff --git a/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart b/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart
index 3f6571e..7fbe061 100644
--- a/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart
+++ b/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart
@@ -256,6 +256,14 @@
     expect(methodInvocation.argumentList, isNotNull);
   }
 
+  void test_constructor_tearoff_in_comment_reference() {
+    createParser('');
+    var commentReference = parseCommentReference('C.new', 5)!;
+    var identifier = commentReference.identifier as PrefixedIdentifier;
+    expect(identifier.prefix.name, 'C');
+    expect(identifier.identifier.name, 'new');
+  }
+
   void test_constructor_tearoff_method_invocation() {
     var methodInvocation =
         parseExpression('C.new.toString()', featureSet: constructorTearoffs)
diff --git a/pkg/front_end/parser_testcases/general/new_as_identifier.dart b/pkg/front_end/parser_testcases/general/new_as_identifier.dart
index 32a868f..30b926d 100644
--- a/pkg/front_end/parser_testcases/general/new_as_identifier.dart
+++ b/pkg/front_end/parser_testcases/general/new_as_identifier.dart
@@ -7,6 +7,7 @@
 //
 // Unless otherwise noted, these tests cases should not result in a parse error.
 
+/// See [C.new].
 class C {
   C.new();
 
diff --git a/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect b/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect
index e3d208d..89b9611 100644
--- a/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect
+++ b/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect
@@ -1,42 +1,42 @@
 Problems reported:
 
-parser/general/new_as_identifier:15:44: Expected an identifier, but got 'new'.
+parser/general/new_as_identifier:16:44: Expected an identifier, but got 'new'.
   C.constructor_field_initializer() : this.new = null;
                                            ^^^
 
-parser/general/new_as_identifier:15:39: Expected an assignment after the field name.
+parser/general/new_as_identifier:16:39: Expected an assignment after the field name.
   C.constructor_field_initializer() : this.new = null;
                                       ^^^^
 
-parser/general/new_as_identifier:15:44: Expected a function body, but got 'new'.
+parser/general/new_as_identifier:16:44: Expected a function body, but got 'new'.
   C.constructor_field_initializer() : this.new = null;
                                            ^^^
 
-parser/general/new_as_identifier:15:44: Expected a class member, but got 'new'.
+parser/general/new_as_identifier:16:44: Expected a class member, but got 'new'.
   C.constructor_field_initializer() : this.new = null;
                                            ^^^
 
-parser/general/new_as_identifier:15:48: Operator declarations must be preceded by the keyword 'operator'.
+parser/general/new_as_identifier:16:48: Operator declarations must be preceded by the keyword 'operator'.
   C.constructor_field_initializer() : this.new = null;
                                                ^
 
-parser/general/new_as_identifier:15:48: The string '=' isn't a user-definable operator.
+parser/general/new_as_identifier:16:48: The string '=' isn't a user-definable operator.
   C.constructor_field_initializer() : this.new = null;
                                                ^
 
-parser/general/new_as_identifier:15:48: A method declaration needs an explicit list of parameters.
+parser/general/new_as_identifier:16:48: A method declaration needs an explicit list of parameters.
   C.constructor_field_initializer() : this.new = null;
                                                ^
 
-parser/general/new_as_identifier:15:50: Expected a function body, but got 'null'.
+parser/general/new_as_identifier:16:50: Expected a function body, but got 'null'.
   C.constructor_field_initializer() : this.new = null;
                                                  ^^^^
 
-parser/general/new_as_identifier:15:50: Expected a class member, but got 'null'.
+parser/general/new_as_identifier:16:50: Expected a class member, but got 'null'.
   C.constructor_field_initializer() : this.new = null;
                                                  ^^^^
 
-parser/general/new_as_identifier:15:54: Expected a class member, but got ';'.
+parser/general/new_as_identifier:16:54: Expected a class member, but got ';'.
   C.constructor_field_initializer() : this.new = null;
                                                      ^