Add visitor support for record literals

Change-Id: I8f5aff11786a75ce02a8f3483585dc0513ae2c86
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255142
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart
index 5038cbd..652148b 100644
--- a/pkg/analyzer/lib/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -551,6 +551,8 @@
 
   R? visitPropertyAccess(PropertyAccess node);
 
+  R? visitRecordLiteral(RecordLiteral node);
+
   R? visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node);
 
diff --git a/pkg/analyzer/lib/dart/ast/visitor.dart b/pkg/analyzer/lib/dart/ast/visitor.dart
index a82ad8e..c57c9a6 100644
--- a/pkg/analyzer/lib/dart/ast/visitor.dart
+++ b/pkg/analyzer/lib/dart/ast/visitor.dart
@@ -518,6 +518,9 @@
   R? visitPropertyAccess(PropertyAccess node) => visitExpression(node);
 
   @override
+  R? visitRecordLiteral(RecordLiteral node) => visitLiteral(node);
+
+  @override
   R? visitRedirectingConstructorInvocation(
           RedirectingConstructorInvocation node) =>
       visitConstructorInitializer(node);
@@ -1263,6 +1266,12 @@
   }
 
   @override
+  R? visitRecordLiteral(RecordLiteral node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  @override
   R? visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     node.visitChildren(this);
@@ -1784,6 +1793,9 @@
   R? visitPropertyAccess(PropertyAccess node) => null;
 
   @override
+  R? visitRecordLiteral(RecordLiteral node) => null;
+
+  @override
   R? visitRedirectingConstructorInvocation(
           RedirectingConstructorInvocation node) =>
       null;
@@ -2212,6 +2224,9 @@
   R? visitPropertyAccess(PropertyAccess node) => _throw(node);
 
   @override
+  R? visitRecordLiteral(RecordLiteral node) => _throw(node);
+
+  @override
   R? visitRedirectingConstructorInvocation(
           RedirectingConstructorInvocation node) =>
       _throw(node);
@@ -3139,6 +3154,14 @@
   }
 
   @override
+  T? visitRecordLiteral(RecordLiteral node) {
+    stopwatch.start();
+    T? result = _baseVisitor.visitRecordLiteral(node);
+    stopwatch.stop();
+    return result;
+  }
+
+  @override
   T? visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     stopwatch.start();
@@ -3751,6 +3774,9 @@
   R? visitPropertyAccess(PropertyAccess node) => visitNode(node);
 
   @override
+  R? visitRecordLiteral(RecordLiteral node) => visitNode(node);
+
+  @override
   R? visitRedirectingConstructorInvocation(
           RedirectingConstructorInvocation node) =>
       visitNode(node);
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index 1345044..fbf9fe1 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -9679,17 +9679,11 @@
     ..addToken('rightParenthesis', rightParenthesis);
 
   @override
-  E? accept<E>(AstVisitor<E> visitor) {
-    // TODO: implement accept
-    throw UnimplementedError();
-    // visitor.visitRecordLiteral(this);
-  }
+  E? accept<E>(AstVisitor<E> visitor) => visitor.visitRecordLiteral(this);
 
   @override
   void resolveExpression(ResolverVisitor resolver, DartType? contextType) {
-    // TODO: implement resolveExpression
-    throw UnimplementedError();
-    // resolver.visitRecordLiteral(this, contextType: contextType);
+    resolver.visitRecordLiteral(this, contextType: contextType);
   }
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
index 963f028..f0ed96b 100644
--- a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
@@ -895,6 +895,13 @@
   }
 
   @override
+  void visitRecordLiteral(RecordLiteral node) {
+    _visitToken(node.leftParenthesis);
+    _visitNodeList(node.fields, separator: ', ');
+    _visitToken(node.rightParenthesis);
+  }
+
+  @override
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     sink.write('this');
diff --git a/pkg/analyzer/lib/src/dart/ast/utilities.dart b/pkg/analyzer/lib/src/dart/ast/utilities.dart
index fe970af..86a005e 100644
--- a/pkg/analyzer/lib/src/dart/ast/utilities.dart
+++ b/pkg/analyzer/lib/src/dart/ast/utilities.dart
@@ -1030,6 +1030,14 @@
   }
 
   @override
+  bool visitRecordLiteral(RecordLiteral node) {
+    var other = _other as RecordLiteral;
+    return isEqualTokens(node.leftParenthesis, other.leftParenthesis) &&
+        _isEqualNodeLists(node.fields, other.fields) &&
+        isEqualTokens(node.rightParenthesis, other.rightParenthesis);
+  }
+
+  @override
   bool visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     RedirectingConstructorInvocation other =
@@ -2753,6 +2761,14 @@
   }
 
   @override
+  bool visitRecordLiteral(covariant RecordLiteralImpl node) {
+    if (_replaceInList(node.fields)) {
+      return true;
+    }
+    return visitNode(node);
+  }
+
+  @override
   bool visitRedirectingConstructorInvocation(
       covariant RedirectingConstructorInvocationImpl node) {
     if (identical(node.constructorName, _oldNode)) {
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index ad15347..c633a68 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -2176,6 +2176,12 @@
   }
 
   @override
+  void visitRecordLiteral(RecordLiteral node, {DartType? contextType}) {
+    // TODO(brianwilkerson) Implement resolution for record literals.
+    super.visitRecordLiteral(node);
+  }
+
+  @override
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     //
diff --git a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
index e564a7a..6a9deb2 100644
--- a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
@@ -980,6 +980,14 @@
       astFactory.propertyAccess(target, TokenFactory.tokenFromType(operator),
           identifier3(propertyName));
 
+  static RecordLiteral recordLiteral(List<Expression> fields) {
+    return RecordLiteralImpl(
+      leftParenthesis: TokenFactory.tokenFromType(TokenType.OPEN_PAREN),
+      fields: fields,
+      rightParenthesis: TokenFactory.tokenFromType(TokenType.CLOSE_PAREN),
+    );
+  }
+
   static RedirectingConstructorInvocationImpl redirectingConstructorInvocation(
           [List<Expression> arguments = const []]) =>
       redirectingConstructorInvocation2(null, arguments);
diff --git a/pkg/analyzer/lib/src/lint/linter_visitor.dart b/pkg/analyzer/lib/src/lint/linter_visitor.dart
index 5935e7e..c0c0223 100644
--- a/pkg/analyzer/lib/src/lint/linter_visitor.dart
+++ b/pkg/analyzer/lib/src/lint/linter_visitor.dart
@@ -624,6 +624,12 @@
   }
 
   @override
+  void visitRecordLiteral(RecordLiteral node) {
+    _runSubscriptions(node, registry._forRecordLiterals);
+    node.visitChildren(this);
+  }
+
+  @override
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     _runSubscriptions(node, registry._forRedirectingConstructorInvocation);
@@ -970,6 +976,7 @@
   final List<_Subscription<PrefixedIdentifier>> _forPrefixedIdentifier = [];
   final List<_Subscription<PrefixExpression>> _forPrefixExpression = [];
   final List<_Subscription<PropertyAccess>> _forPropertyAccess = [];
+  final List<_Subscription<RecordLiteral>> _forRecordLiterals = [];
   final List<_Subscription<RedirectingConstructorInvocation>>
       _forRedirectingConstructorInvocation = [];
   final List<_Subscription<RethrowExpression>> _forRethrowExpression = [];
@@ -1459,6 +1466,10 @@
     _forPropertyAccess.add(_Subscription(linter, visitor, _getTimer(linter)));
   }
 
+  void addRecordLiteral(LintRule linter, AstVisitor visitor) {
+    _forRecordLiterals.add(_Subscription(linter, visitor, _getTimer(linter)));
+  }
+
   void addRedirectingConstructorInvocation(
       LintRule linter, AstVisitor visitor) {
     _forRedirectingConstructorInvocation
diff --git a/pkg/analyzer/lib/src/test_utilities/find_node.dart b/pkg/analyzer/lib/src/test_utilities/find_node.dart
index 942afd0..1a287bc 100644
--- a/pkg/analyzer/lib/src/test_utilities/find_node.dart
+++ b/pkg/analyzer/lib/src/test_utilities/find_node.dart
@@ -428,6 +428,10 @@
     return _node(search, (n) => n is PropertyAccess);
   }
 
+  RecordLiteral recordLiteral(String search) {
+    return _node(search, (n) => n is RecordLiteral);
+  }
+
   RedirectingConstructorInvocation redirectingConstructorInvocation(
       String search) {
     return _node(search, (n) => n is RedirectingConstructorInvocation);
diff --git a/pkg/analyzer/test/generated/utilities_test.dart b/pkg/analyzer/test/generated/utilities_test.dart
index 13d6e07..331ed22 100644
--- a/pkg/analyzer/test/generated/utilities_test.dart
+++ b/pkg/analyzer/test/generated/utilities_test.dart
@@ -1525,6 +1525,22 @@
     );
   }
 
+  @failingTest
+  void test_recordLiteral() {
+    // Failing because record literals can't be parsed yet.
+    var findNode = _parseStringToFindNode(r'''
+void f() {
+  (1, 2);
+}
+''');
+    var node = findNode.recordLiteral('(1');
+    _assertReplaceInList(
+      destination: node,
+      child: node.fields[0],
+      replacement: node.fields[1],
+    );
+  }
+
   void test_redirectingConstructorInvocation() {
     var findNode = _parseStringToFindNode(r'''
 class A {
diff --git a/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart b/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
index 2be9ce0..a227a68 100644
--- a/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
+++ b/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
@@ -2786,6 +2786,24 @@
             AstTestFactory.identifier3("a"), "b", TokenType.QUESTION_PERIOD));
   }
 
+  void test_visitRecordLiteral_named() {
+    _assertSource(
+        "(a: 1, b: 2)",
+        AstTestFactory.recordLiteral([
+          AstTestFactory.namedExpression2('a', AstTestFactory.integer(1)),
+          AstTestFactory.namedExpression2('b', AstTestFactory.integer(2)),
+        ]));
+  }
+
+  void test_visitRecordLiteral_positional() {
+    _assertSource(
+        "(1, 2)",
+        AstTestFactory.recordLiteral([
+          AstTestFactory.integer(1),
+          AstTestFactory.integer(2),
+        ]));
+  }
+
   void test_visitRedirectingConstructorInvocation_named() {
     _assertSource(
         "this.c()", AstTestFactory.redirectingConstructorInvocation2("c"));