Require analyzer 10.0.0, stop using deprecated nodes and properties. (#1795)

* Require analyzer 10.0.0, stop using deprecated nodes and properties.

* Prepare for publishing 3.1.4, remove -wip.

* Fixes for review comments.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3dc28ca..64a83db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 3.1.4-wip
+## 3.1.4
 
 * Remove dependencies on analyzer internal implementation.
+* Require `analyzer: '^10.0.0'`.
 
 ## 3.1.3
 
diff --git a/lib/src/front_end/ast_node_visitor.dart b/lib/src/front_end/ast_node_visitor.dart
index 4193f13..eb9729e 100644
--- a/lib/src/front_end/ast_node_visitor.dart
+++ b/lib/src/front_end/ast_node_visitor.dart
@@ -293,15 +293,17 @@
         node.mixinKeyword,
         node.classKeyword,
       ],
-      node.name,
-      typeParameters: node.typeParameters,
+      name: node.namePart.typeName,
+      typeParameters: node.namePart.typeParameters,
       extendsClause: node.extendsClause,
       withClause: node.withClause,
       implementsClause: node.implementsClause,
       nativeClause: node.nativeClause,
       body: () {
+        // TODO(scheglov): support for EmptyBody
+        var body = node.body as BlockClassBody;
         return pieces.build(() {
-          writeBody(node.leftBracket, node.members, node.rightBracket);
+          writeBody(body.leftBracket, body.members, body.rightBracket);
         });
       },
     );
@@ -320,10 +322,10 @@
         node.mixinKeyword,
         node.typedefKeyword,
       ],
-      node.name,
+      name: node.name,
+      typeParameters: node.typeParameters,
       equals: node.equals,
       superclass: node.superclass,
-      typeParameters: node.typeParameters,
       withClause: node.withClause,
       implementsClause: node.implementsClause,
       bodyType: TypeBodyType.semicolon,
@@ -455,8 +457,9 @@
       var header = pieces.build(() {
         pieces.modifier(node.externalKeyword);
         pieces.modifier(node.constKeyword);
+        pieces.modifier(node.newKeyword);
         pieces.modifier(node.factoryKeyword);
-        pieces.visit(node.returnType);
+        pieces.visit(node.typeName);
         pieces.token(node.period);
         pieces.token(node.name);
       });
@@ -634,13 +637,15 @@
     writeType(
       node.metadata,
       [node.enumKeyword],
-      node.name,
-      typeParameters: node.typeParameters,
+      name: node.namePart.typeName,
+      typeParameters: node.namePart.typeParameters,
       withClause: node.withClause,
       implementsClause: node.implementsClause,
-      bodyType: node.members.isEmpty ? TypeBodyType.list : TypeBodyType.block,
+      bodyType: node.body.members.isEmpty
+          ? TypeBodyType.list
+          : TypeBodyType.block,
       body: () {
-        if (node.members.isEmpty) {
+        if (node.body.members.isEmpty) {
           // If there are no members, format the constants like a list. This
           // keeps the enum declaration on one line if it fits.
           var builder = DelimitedListBuilder(
@@ -648,19 +653,22 @@
             const ListStyle(spaceWhenUnsplit: true),
           );
 
-          builder.leftBracket(node.leftBracket);
-          node.constants.forEach(builder.visit);
-          builder.rightBracket(semicolon: node.semicolon, node.rightBracket);
+          builder.leftBracket(node.body.leftBracket);
+          node.body.constants.forEach(builder.visit);
+          builder.rightBracket(
+            semicolon: node.body.semicolon,
+            node.body.rightBracket,
+          );
           return builder.build(
             forceSplit: style.preserveTrailingCommaBefore(
-              node.semicolon ?? node.rightBracket,
+              node.body.semicolon ?? node.body.rightBracket,
             ),
           );
         } else {
           // If there are members, format it like a block where each constant
           // and member is on its own line.
           var builder = SequenceBuilder(this);
-          builder.leftBracket(node.leftBracket);
+          builder.leftBracket(node.body.leftBracket);
 
           // In 3.10 and later, preserved trailing commas will also preserve a
           // trailing comma in an enum with members. That in turn forces the
@@ -668,15 +676,15 @@
           // behavior is the same as when preserved trailing commas is off
           // where the last constant's comma is removed and the `;` is placed
           // there instead.
-          for (var constant in node.constants) {
-            var isLast = constant == node.constants.last;
+          for (var constant in node.body.constants) {
+            var isLast = constant == node.body.constants.last;
             builder.addCommentsBefore(constant.firstNonCommentToken);
             builder.add(
               createEnumConstant(
                 constant,
                 commaAfter:
                     !isLast || style.preserveTrailingCommaAfterEnumValues,
-                semicolon: isLast ? node.semicolon : null,
+                semicolon: isLast ? node.body.semicolon : null,
               ),
             );
           }
@@ -684,13 +692,13 @@
           // If we are preserving the trailing comma, then put the `;` on its
           // own line after the last constant.
           if (style.preserveTrailingCommaAfterEnumValues) {
-            builder.add(tokenPiece(node.semicolon!));
+            builder.add(tokenPiece(node.body.semicolon!));
           }
 
           // Insert a blank line between the constants and members.
           builder.addBlank();
 
-          for (var node in node.members) {
+          for (var node in node.body.members) {
             builder.visit(node);
 
             // If the node has a non-empty braced body, then require a blank
@@ -698,7 +706,7 @@
             if (node.hasNonEmptyBody) builder.addBlank();
           }
 
-          builder.rightBracket(node.rightBracket);
+          builder.rightBracket(node.body.rightBracket);
           return builder.build();
         }
       },
@@ -785,12 +793,16 @@
     writeType(
       node.metadata,
       [node.extensionKeyword],
-      node.name,
+      name: node.name,
       typeParameters: node.typeParameters,
       onType: onType,
       body: () {
         return pieces.build(() {
-          writeBody(node.leftBracket, node.members, node.rightBracket);
+          writeBody(
+            node.body.leftBracket,
+            node.body.members,
+            node.body.rightBracket,
+          );
         });
       },
     );
@@ -800,18 +812,14 @@
   void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
     writeType(
       node.metadata,
-      [
-        node.extensionKeyword,
-        node.typeKeyword,
-        if (node.constKeyword case var keyword?) keyword,
-      ],
-      node.name,
-      typeParameters: node.typeParameters,
-      representation: node.representation,
+      [node.extensionKeyword, node.typeKeyword],
+      primaryConstructor: node.primaryConstructor,
       implementsClause: node.implementsClause,
       body: () {
         return pieces.build(() {
-          writeBody(node.leftBracket, node.members, node.rightBracket);
+          // TODO(scheglov): support for EmptyBody
+          var body = node.body as BlockClassBody;
+          writeBody(body.leftBracket, body.members, body.rightBracket);
         });
       },
     );
@@ -1532,13 +1540,17 @@
     writeType(
       node.metadata,
       [node.baseKeyword, node.mixinKeyword],
-      node.name,
+      name: node.name,
       typeParameters: node.typeParameters,
       onClause: node.onClause,
       implementsClause: node.implementsClause,
       body: () {
         return pieces.build(() {
-          writeBody(node.leftBracket, node.members, node.rightBracket);
+          writeBody(
+            node.body.leftBracket,
+            node.body.members,
+            node.body.rightBracket,
+          );
         });
       },
     );
@@ -1747,6 +1759,62 @@
   }
 
   @override
+  void visitPrimaryConstructorBody(PrimaryConstructorBody node) {
+    pieces.withMetadata(node.metadata, () {
+      pieces.token(node.thisKeyword);
+
+      if (node.initializers.isNotEmpty) {
+        pieces.space();
+        pieces.token(node.colon);
+        pieces.space();
+        pieces.add(createCommaSeparated(node.initializers));
+      }
+
+      pieces.visit(node.body);
+    });
+  }
+
+  @override
+  void visitPrimaryConstructorDeclaration(PrimaryConstructorDeclaration node) {
+    pieces.modifier(node.constKeyword);
+    pieces.token(node.typeName);
+    pieces.visit(node.typeParameters);
+    pieces.visit(node.constructorName);
+
+    if (node.parent is ExtensionTypeDeclaration) {
+      var formalParameters = node.formalParameters;
+      var builder = DelimitedListBuilder(
+        this,
+        const ListStyle(commas: Commas.nonTrailing),
+      );
+      builder.leftBracket(formalParameters.leftParenthesis);
+      for (var formalParameter in formalParameters.parameters) {
+        // TODO(scheglov): support for optional formal parameters
+        formalParameter as SimpleFormalParameter;
+        builder.add(
+          pieces.build(() {
+            writeParameter(
+              metadata: formalParameter.metadata,
+              formalParameter.type,
+              formalParameter.name,
+            );
+          }),
+        );
+      }
+      builder.rightBracket(formalParameters.rightParenthesis);
+      pieces.add(builder.build());
+    } else {
+      pieces.visit(node.formalParameters);
+    }
+  }
+
+  @override
+  void visitPrimaryConstructorName(PrimaryConstructorName node) {
+    pieces.token(node.period);
+    pieces.token(node.name);
+  }
+
+  @override
   void visitPropertyAccess(PropertyAccess node) {
     // If there's no target, this is a section in a cascade.
     if (node.target == null) {
@@ -1861,34 +1929,6 @@
   }
 
   @override
-  void visitRepresentationConstructorName(RepresentationConstructorName node) {
-    pieces.token(node.period);
-    pieces.token(node.name);
-  }
-
-  @override
-  void visitRepresentationDeclaration(RepresentationDeclaration node) {
-    pieces.visit(node.constructorName);
-
-    var builder = DelimitedListBuilder(
-      this,
-      const ListStyle(commas: Commas.nonTrailing),
-    );
-    builder.leftBracket(node.leftParenthesis);
-    builder.add(
-      pieces.build(() {
-        writeParameter(
-          metadata: node.fieldMetadata,
-          node.fieldType,
-          node.fieldName,
-        );
-      }),
-    );
-    builder.rightBracket(node.rightParenthesis);
-    pieces.add(builder.build());
-  }
-
-  @override
   void visitRethrowExpression(RethrowExpression node) {
     pieces.token(node.rethrowKeyword);
   }
diff --git a/lib/src/front_end/piece_factory.dart b/lib/src/front_end/piece_factory.dart
index 5202d59..1bc8aba 100644
--- a/lib/src/front_end/piece_factory.dart
+++ b/lib/src/front_end/piece_factory.dart
@@ -1437,16 +1437,16 @@
   /// If the type is an extension, then [onType] is a record containing the
   /// `on` keyword and the on type.
   ///
-  /// If the type is an extension type, then [representation] is the primary
-  /// constructor for it.
+  /// If the type has a primary constructor, e.g. an extension type, then
+  /// [primaryConstructor] is not `null`.
   void writeType(
     NodeList<Annotation> metadata,
-    List<Token?> keywords,
-    Token? name, {
+    List<Token?> keywords, {
+    Token? name,
     TypeParameterList? typeParameters,
     Token? equals,
     NamedType? superclass,
-    RepresentationDeclaration? representation,
+    PrimaryConstructorDeclaration? primaryConstructor,
     ExtendsClause? extendsClause,
     MixinOnClause? onClause,
     WithClause? withClause,
@@ -1481,9 +1481,8 @@
           pieces.visit(superclass!);
         }
 
-        // Extension types have a representation type.
-        if (representation != null) {
-          pieces.visit(representation);
+        if (primaryConstructor != null) {
+          pieces.visit(primaryConstructor, spaceBefore: true);
         }
       });
 
diff --git a/lib/src/short/source_visitor.dart b/lib/src/short/source_visitor.dart
index 660347d..63973c5 100644
--- a/lib/src/short/source_visitor.dart
+++ b/lib/src/short/source_visitor.dart
@@ -599,15 +599,17 @@
     modifier(node.mixinKeyword);
     token(node.classKeyword);
     space();
-    token(node.name);
-    visit(node.typeParameters);
+    token(node.namePart.typeName);
+    visit(node.namePart.typeParameters);
     visit(node.extendsClause);
     _visitClauses(node.withClause, node.implementsClause);
     visit(node.nativeClause, before: space);
     space();
 
     builder.unnest();
-    _visitBody(node.leftBracket, node.members, node.rightBracket);
+    // TODO(scheglov): support for EmptyBody
+    var body = node.body as BlockClassBody;
+    _visitBody(body.leftBracket, body.members, body.rightBracket);
   }
 
   @override
@@ -774,8 +776,9 @@
 
     modifier(node.externalKeyword);
     modifier(node.constKeyword);
+    modifier(node.newKeyword);
     modifier(node.factoryKeyword);
-    visit(node.returnType);
+    visit(node.typeName);
     token(node.period);
     token(node.name);
 
@@ -1017,28 +1020,28 @@
     builder.nestExpression();
     token(node.enumKeyword);
     space();
-    token(node.name);
-    visit(node.typeParameters);
+    token(node.namePart.typeName);
+    visit(node.namePart.typeParameters);
     _visitClauses(node.withClause, node.implementsClause);
     space();
 
     builder.unnest();
 
-    _beginBody(node.leftBracket, space: true);
+    _beginBody(node.body.leftBracket, space: true);
 
-    visitCommaSeparatedNodes(node.constants, between: splitOrTwoNewlines);
+    visitCommaSeparatedNodes(node.body.constants, between: splitOrTwoNewlines);
 
     // If there is a trailing comma, always force the constants to split.
-    var trailingComma = node.constants.last.commaAfter;
+    var trailingComma = node.body.constants.last.commaAfter;
     if (trailingComma != null) {
       builder.forceRules();
     }
 
     // The ";" after the constants, which may occur after a trailing comma.
-    var afterConstants = node.constants.last.endToken.next!;
+    var afterConstants = node.body.constants.last.endToken.next!;
     Token? semicolon;
     if (afterConstants.type == TokenType.SEMICOLON) {
-      semicolon = node.constants.last.endToken.next!;
+      semicolon = node.body.constants.last.endToken.next!;
     } else if (trailingComma != null &&
         trailingComma.next!.type == TokenType.SEMICOLON) {
       semicolon = afterConstants.next!;
@@ -1055,23 +1058,23 @@
       token(semicolon);
 
       // Put a blank line between the constants and members.
-      if (node.members.isNotEmpty) twoNewlines();
+      if (node.body.members.isNotEmpty) twoNewlines();
     }
 
-    _visitBodyContents(node.members);
+    _visitBodyContents(node.body.members);
 
     _endBody(
-      node.rightBracket,
+      node.body.rightBracket,
       forceSplit:
           semicolon != null ||
           trailingComma != null ||
-          node.members.isNotEmpty ||
+          node.body.members.isNotEmpty ||
           // If there is a line comment after an enum constant, it won't
           // automatically force the enum body to split since the rule for
           // the constants is the hard rule used by the entire block and its
           // hardening state doesn't actually change. Instead, look
           // explicitly for a line comment here.
-          node.constants.containsLineComments(),
+          node.body.constants.containsLineComments(),
     );
   }
 
@@ -1168,7 +1171,11 @@
     }
     space();
     builder.unnest();
-    _visitBody(node.leftBracket, node.members, node.rightBracket);
+    _visitBody(
+      node.body.leftBracket,
+      node.body.members,
+      node.body.rightBracket,
+    );
   }
 
   @override
@@ -1179,14 +1186,8 @@
     token(node.extensionKeyword);
     space();
     token(node.typeKeyword);
-    token(node.constKeyword, before: space);
-    space();
-    token(node.name);
 
-    builder.nestExpression();
-    visit(node.typeParameters);
-    visit(node.representation);
-    builder.unnest();
+    visit(node.primaryConstructor, before: space);
 
     builder.startRule(CombinatorRule());
     visit(node.implementsClause);
@@ -1194,7 +1195,9 @@
 
     space();
     builder.unnest();
-    _visitBody(node.leftBracket, node.members, node.rightBracket);
+    // TODO(scheglov): support for EmptyBody
+    var body = node.body as BlockClassBody;
+    _visitBody(body.leftBracket, body.members, body.rightBracket);
   }
 
   @override
@@ -2152,7 +2155,11 @@
     space();
 
     builder.unnest();
-    _visitBody(node.leftBracket, node.members, node.rightBracket);
+    _visitBody(
+      node.body.leftBracket,
+      node.body.members,
+      node.body.rightBracket,
+    );
   }
 
   @override
@@ -2366,6 +2373,41 @@
   }
 
   @override
+  void visitPrimaryConstructorDeclaration(PrimaryConstructorDeclaration node) {
+    modifier(node.constKeyword);
+    token(node.typeName);
+    visit(node.typeParameters);
+    visit(node.constructorName);
+    visit(node.formalParameters);
+  }
+
+  @override
+  void visitPrimaryConstructorBody(PrimaryConstructorBody node) {
+    visitMetadata(node.metadata);
+    token(node.thisKeyword);
+
+    // TODO(scheglov): unify with `visitConstructorDeclaration`.
+    if (node.initializers.isNotEmpty) {
+      builder.startRule();
+      builder.indent(Indent.constructorInitializer);
+      split();
+      token(node.colon);
+      space();
+      visitCommaSeparatedNodes(node.initializers);
+      builder.unindent();
+      builder.endRule();
+    }
+
+    visit(node.body);
+  }
+
+  @override
+  void visitPrimaryConstructorName(PrimaryConstructorName node) {
+    token(node.period);
+    token(node.name);
+  }
+
+  @override
   void visitPropertyAccess(PropertyAccess node) {
     if (node.isCascaded) {
       token(node.operator);
@@ -2518,46 +2560,6 @@
   }
 
   @override
-  void visitRepresentationConstructorName(RepresentationConstructorName node) {
-    token(node.period);
-    token(node.name);
-  }
-
-  @override
-  void visitRepresentationDeclaration(RepresentationDeclaration node) {
-    visit(node.constructorName);
-
-    token(node.leftParenthesis);
-
-    var rule = PositionalRule(null, argumentCount: 1);
-
-    builder.startRule(rule);
-    rule.beforeArgument(zeroSplit());
-
-    // Make sure record and function type parameter lists are indented.
-    builder.startBlockArgumentNesting();
-    builder.startSpan();
-
-    visitParameterMetadata(node.fieldMetadata, () {
-      builder.startLazyRule(Rule(Cost.parameterType));
-      builder.nestExpression();
-
-      visit(node.fieldType);
-      _separatorBetweenTypeAndVariable(node.fieldType);
-      token(node.fieldName);
-
-      builder.unnest();
-      builder.endRule();
-    });
-
-    builder.endBlockArgumentNesting();
-    builder.endSpan();
-    builder.endRule();
-
-    token(node.rightParenthesis);
-  }
-
-  @override
   void visitRethrowExpression(RethrowExpression node) {
     token(node.rethrowKeyword);
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index 933209e..ee195f6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: dart_style
 # Note: See tool/grind.dart for how to bump the version.
-version: 3.1.4-wip
+version: 3.1.4
 description: >-
   Opinionated, automatic Dart source code formatter.
   Provides an API and a CLI tool.
@@ -9,7 +9,7 @@
   sdk: ^3.9.0
 
 dependencies:
-  analyzer: '>=8.2.0 <10.0.0'
+  analyzer: ^10.0.0
   args: ^2.5.0
   collection: ^1.19.0
   package_config: ^2.1.0
diff --git a/test/short/whitespace/extension_types.unit b/test/short/whitespace/extension_types.unit
index c597597..6011c95 100644
--- a/test/short/whitespace/extension_types.unit
+++ b/test/short/whitespace/extension_types.unit
@@ -95,11 +95,11 @@
     const // d
     A // e
     < // f
-            T // g
-            > // h
-        . // i
-        name // j
-        (
+        T // g
+        > // h
+    . // i
+    name // j
+    (
         // k
         @ // l
         required // m