Format enhanced enums. (#1096)

* Format enhanced enums.

Fix #1075.

* Include constructor names in enum values.

* Migrate off deprecated analyzer APIs.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a73d161..c661308 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # 2.2.2
 
 * Format named arguments anywhere (#1072).
+* Format enhanced enums (#1075).
 
 # 2.2.1
 
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index b2081bd..30af279 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -802,7 +802,7 @@
       token(node.equals);
       space();
 
-      visit(node.superclass2);
+      visit(node.superclass);
 
       builder.startRule(CombinatorRule());
       visit(node.withClause);
@@ -834,11 +834,12 @@
 
     var needsDouble = true;
     for (var declaration in node.declarations) {
-      var hasClassBody = declaration is ClassDeclaration ||
+      var hasBody = declaration is ClassDeclaration ||
+          declaration is EnumDeclaration ||
           declaration is ExtensionDeclaration;
 
-      // Add a blank line before declarations with class-like bodies.
-      if (hasClassBody) needsDouble = true;
+      // Add a blank line before types with bodies.
+      if (hasBody) needsDouble = true;
 
       if (needsDouble) {
         twoNewlines();
@@ -851,8 +852,8 @@
       visit(declaration);
 
       needsDouble = false;
-      if (hasClassBody) {
-        // Add a blank line after declarations with class-like bodies.
+      if (hasBody) {
+        // Add a blank line after types declarations with bodies.
         needsDouble = true;
       } else if (declaration is FunctionDeclaration) {
         // Add a blank line after non-empty block functions.
@@ -1068,7 +1069,7 @@
 
   @override
   void visitConstructorName(ConstructorName node) {
-    visit(node.type2);
+    visit(node.type);
     token(node.period);
     visit(node.name);
   }
@@ -1165,25 +1166,80 @@
   void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
     visitMetadata(node.metadata);
     visit(node.name);
+
+    var arguments = node.arguments;
+    if (arguments != null) {
+      builder.nestExpression();
+      visit(arguments.typeArguments);
+
+      var constructor = arguments.constructorSelector;
+      if (constructor != null) {
+        token(constructor.period);
+        visit(constructor.name);
+      }
+
+      visitArgumentList(arguments.argumentList, nestExpression: false);
+      builder.unnest();
+    }
   }
 
   @override
   void visitEnumDeclaration(EnumDeclaration node) {
     visitMetadata(node.metadata);
 
+    builder.nestExpression();
     token(node.enumKeyword);
     space();
     visit(node.name);
+    visit(node.typeParameters);
+
+    builder.startRule(CombinatorRule());
+    visit(node.withClause);
+    visit(node.implementsClause);
+    builder.endRule();
     space();
 
+    builder.unnest();
+
     _beginBody(node.leftBracket, space: true);
     visitCommaSeparatedNodes(node.constants, between: splitOrTwoNewlines);
 
     // If there is a trailing comma, always force the constants to split.
-    if (hasCommaAfter(node.constants.last)) {
+    Token? trailingComma = _commaAfter(node.constants.last);
+    if (trailingComma != null) {
       builder.forceRules();
     }
 
+    // The ";" after the constants, which may occur after a trailing comma.
+    Token afterConstants = node.constants.last.endToken.next!;
+    Token? semicolon;
+    if (afterConstants.type == TokenType.SEMICOLON) {
+      semicolon = node.constants.last.endToken.next!;
+    } else if (trailingComma != null &&
+        trailingComma.next!.type == TokenType.SEMICOLON) {
+      semicolon = afterConstants.next!;
+    }
+
+    if (semicolon != null) {
+      // If there is both a trailing comma and a semicolon, move the semicolon
+      // to the next line. This doesn't look great but it's less bad than being
+      // next to the comma.
+      // TODO(rnystrom): If the formatter starts making non-whitespace changes
+      // like adding/removing trailing commas, then it should fix this too.
+      if (trailingComma != null) newline();
+
+      token(semicolon);
+
+      // Always split the constants if they end in ";", even if there aren't
+      // any members.
+      builder.forceRules();
+
+      // Put a blank line between the constants and members.
+      if (node.members.isNotEmpty) twoNewlines();
+    }
+
+    _visitMembers(node.members);
+
     _endBody(node.rightBracket, space: true);
   }
 
@@ -1373,7 +1429,7 @@
     soloSplit();
     token(node.extendsKeyword);
     space();
-    visit(node.superclass2);
+    visit(node.superclass);
   }
 
   @override
@@ -1999,7 +2055,7 @@
 
   @override
   void visitImplementsClause(ImplementsClause node) {
-    _visitCombinator(node.implementsKeyword, node.interfaces2);
+    _visitCombinator(node.implementsKeyword, node.interfaces);
   }
 
   @override
@@ -2264,18 +2320,18 @@
     // If there is only a single superclass constraint, format it like an
     // "extends" in a class.
     var onClause = node.onClause;
-    if (onClause != null && onClause.superclassConstraints2.length == 1) {
+    if (onClause != null && onClause.superclassConstraints.length == 1) {
       soloSplit();
       token(onClause.onKeyword);
       space();
-      visit(onClause.superclassConstraints2.single);
+      visit(onClause.superclassConstraints.single);
     }
 
     builder.startRule(CombinatorRule());
 
     // If there are multiple superclass constraints, format them like the
     // "implements" clause.
-    if (onClause != null && onClause.superclassConstraints2.length > 1) {
+    if (onClause != null && onClause.superclassConstraints.length > 1) {
       visit(onClause);
     }
 
@@ -2326,7 +2382,7 @@
 
   @override
   void visitOnClause(OnClause node) {
-    _visitCombinator(node.onKeyword, node.superclassConstraints2);
+    _visitCombinator(node.onKeyword, node.superclassConstraints);
   }
 
   @override
@@ -2730,7 +2786,7 @@
 
   @override
   void visitWithClause(WithClause node) {
-    _visitCombinator(node.withKeyword, node.mixinTypes2);
+    _visitCombinator(node.withKeyword, node.mixinTypes);
   }
 
   @override
diff --git a/pubspec.lock b/pubspec.lock
index 4fbb2da..edcf821 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,14 +7,14 @@
       name: _fe_analyzer_shared
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "34.0.0"
+    version: "36.0.0"
   analyzer:
     dependency: "direct main"
     description:
       name: analyzer
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.2.0"
+    version: "3.3.1"
   args:
     dependency: "direct main"
     description:
@@ -70,7 +70,7 @@
       name: coverage
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.3"
+    version: "1.1.0"
   crypto:
     dependency: transitive
     description:
@@ -112,7 +112,7 @@
       name: http_multi_server
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.1"
+    version: "3.2.0"
   http_parser:
     dependency: transitive
     description:
@@ -133,7 +133,7 @@
       name: js
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.6.3"
+    version: "0.6.4"
   lints:
     dependency: "direct dev"
     description:
@@ -189,7 +189,7 @@
       name: path
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.0"
+    version: "1.8.1"
   pool:
     dependency: transitive
     description:
@@ -252,7 +252,7 @@
       name: source_span
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.1"
+    version: "1.8.2"
   stack_trace:
     dependency: transitive
     description:
@@ -287,7 +287,7 @@
       name: test
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.20.0"
+    version: "1.20.1"
   test_api:
     dependency: transitive
     description:
@@ -301,7 +301,7 @@
       name: test_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.10"
+    version: "0.4.11"
   test_descriptor:
     dependency: "direct dev"
     description:
@@ -329,7 +329,7 @@
       name: vm_service
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "7.5.0"
+    version: "8.2.0"
   watcher:
     dependency: transitive
     description:
@@ -359,4 +359,4 @@
     source: hosted
     version: "3.1.0"
 sdks:
-  dart: ">=2.14.0 <3.0.0"
+  dart: ">=2.16.0-100.0.dev <3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 6096aff..475dc08 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -9,7 +9,7 @@
   sdk: ">=2.12.0-0 <3.0.0"
 
 dependencies:
-  analyzer: ">=3.2.0 <4.0.0"
+  analyzer: ^3.3.1
   args: ">=1.0.0 <3.0.0"
   path: ^1.0.0
   pub_semver: ">=1.4.4 <3.0.0"
diff --git a/test/comments/enums.unit b/test/comments/enums.unit
index d2ea3ea..59704a9 100644
--- a/test/comments/enums.unit
+++ b/test/comments/enums.unit
@@ -51,4 +51,25 @@
 enum A {
   // comment
   B
+}
+>>> ensure blank line above doc comments
+enum Foo {/// doc
+a,/// doc
+b;/// doc
+var x = 1;
+/// doc
+void y() {}}
+<<<
+enum Foo {
+  /// doc
+  a,
+
+  /// doc
+  b;
+
+  /// doc
+  var x = 1;
+
+  /// doc
+  void y() {}
 }
\ No newline at end of file
diff --git a/test/regression/other/enhanced_enum.unit b/test/regression/other/enhanced_enum.unit
new file mode 100644
index 0000000..590cdba
--- /dev/null
+++ b/test/regression/other/enhanced_enum.unit
@@ -0,0 +1,180 @@
+>>> enhanced enum language test
+// Full syntax, with every possible option.
+@EnumAll.v1
+@EnumAll.sConst
+enum EnumAll<S extends num, T extends num>
+    with GenericEnumMixin<T>, ObjectMixin
+    implements Interface, GenericInterface<S> {
+  @v1
+  @v2
+  v1,
+  @EnumAll.v2
+  v2(y: 2),
+  @sConst
+  v3<int, int>(y: 2),
+  v4.named(1, y: 2),
+  v5<int, int>.renamed(1, y: 2),
+  v6.new(),
+  ;
+
+  /// Static members.
+  ///
+  /// Any kind of static variable.
+  static const sConst = v3;
+  static final sFinal = v3;
+  static late final EnumAll sLateFinal;
+  static late final sLateFinalInit = v3;
+  static late EnumAll sLateVar;
+  static late var sLateVarInit = v3;
+  static EnumAll? sVar;
+  static EnumAll sVarInit = v3;
+  /// Static getters, setters and methods
+  static EnumAll<int, int> get staticGetSet => v3;
+  static set staticGetSet(EnumAll<int, int> _) {}
+  static int staticMethod() => 42;
+
+  // Constructors.
+  // Generative, non-redirecting, unnamed.
+  const EnumAll({T? y})
+      : constructor = "unnamed", this.x = 0 as S, y = y ?? (0 as T);
+  // Generative, non-redirecting, named.
+  const EnumAll.named(this.x, {T? y, String? constructor})
+      : constructor = constructor ?? "named", y = y ?? (0 as T);
+  // Generative, redirecting.
+  const EnumAll.renamed(S x, {T? y})
+      : this.named(x, y: y, constructor: "renamed");
+  // Factory, non-redirecting.
+  factory EnumAll.factory(int index) => values[index] as EnumAll<S, T>;
+  // Factory, redirecting (only to other factory constructor).
+  factory EnumAll.refactory(int index) = EnumAll<S, T>.factory;
+
+  // Cannot have factory constructors redirecting to generative constructors.
+  // (Nothing can refer to generative constructors except redirecting generative
+  // constructors and the implicit element creation expressions.)
+  // Cannot have const factory constructor, because they *must* redirect to
+  // generative constructors.
+  // Cannot have `super`-constuctor invocations in initializer lists.
+
+  // Instance members.
+
+  // Instance variables must be final and non-late because of const constructor.
+  final String constructor;
+  final S x;
+  final num y;
+
+  // Getters, setters, methods and operators.
+  S get instanceGetSet => x;
+  set instanceGetSet(S _) {}
+  S instanceMethod() => x;
+  EnumAll<num, num> operator ^(EnumAll<num, num> other) {
+    var newIndex = index ^ other.index;
+    if (newIndex > 4) newIndex = 4;
+    return values[newIndex]; // Can refer to `values`.
+  }
+
+  // Can access `this` and `super` in an instance method.
+  String thisAndSuper() => "${super.toString()}:${this.toString()}";
+
+  // Can be callable.
+  T call<T>(T value) => value;
+
+  // Can have an `index` setter.
+  set index(int value) {}
+
+  // Instance members shadow extensions.
+  String get notExtension => "not extension";
+
+  String toString() => "this";
+}
+<<<
+// Full syntax, with every possible option.
+@EnumAll.v1
+@EnumAll.sConst
+enum EnumAll<S extends num, T extends num>
+    with GenericEnumMixin<T>, ObjectMixin
+    implements Interface, GenericInterface<S> {
+  @v1
+  @v2
+  v1,
+  @EnumAll.v2
+  v2(y: 2),
+  @sConst
+  v3<int, int>(y: 2),
+  v4.named(1, y: 2),
+  v5<int, int>.renamed(1, y: 2),
+  v6.new(),
+  ;
+
+  /// Static members.
+  ///
+  /// Any kind of static variable.
+  static const sConst = v3;
+  static final sFinal = v3;
+  static late final EnumAll sLateFinal;
+  static late final sLateFinalInit = v3;
+  static late EnumAll sLateVar;
+  static late var sLateVarInit = v3;
+  static EnumAll? sVar;
+  static EnumAll sVarInit = v3;
+
+  /// Static getters, setters and methods
+  static EnumAll<int, int> get staticGetSet => v3;
+  static set staticGetSet(EnumAll<int, int> _) {}
+  static int staticMethod() => 42;
+
+  // Constructors.
+  // Generative, non-redirecting, unnamed.
+  const EnumAll({T? y})
+      : constructor = "unnamed",
+        this.x = 0 as S,
+        y = y ?? (0 as T);
+  // Generative, non-redirecting, named.
+  const EnumAll.named(this.x, {T? y, String? constructor})
+      : constructor = constructor ?? "named",
+        y = y ?? (0 as T);
+  // Generative, redirecting.
+  const EnumAll.renamed(S x, {T? y})
+      : this.named(x, y: y, constructor: "renamed");
+  // Factory, non-redirecting.
+  factory EnumAll.factory(int index) => values[index] as EnumAll<S, T>;
+  // Factory, redirecting (only to other factory constructor).
+  factory EnumAll.refactory(int index) = EnumAll<S, T>.factory;
+
+  // Cannot have factory constructors redirecting to generative constructors.
+  // (Nothing can refer to generative constructors except redirecting generative
+  // constructors and the implicit element creation expressions.)
+  // Cannot have const factory constructor, because they *must* redirect to
+  // generative constructors.
+  // Cannot have `super`-constuctor invocations in initializer lists.
+
+  // Instance members.
+
+  // Instance variables must be final and non-late because of const constructor.
+  final String constructor;
+  final S x;
+  final num y;
+
+  // Getters, setters, methods and operators.
+  S get instanceGetSet => x;
+  set instanceGetSet(S _) {}
+  S instanceMethod() => x;
+  EnumAll<num, num> operator ^(EnumAll<num, num> other) {
+    var newIndex = index ^ other.index;
+    if (newIndex > 4) newIndex = 4;
+    return values[newIndex]; // Can refer to `values`.
+  }
+
+  // Can access `this` and `super` in an instance method.
+  String thisAndSuper() => "${super.toString()}:${this.toString()}";
+
+  // Can be callable.
+  T call<T>(T value) => value;
+
+  // Can have an `index` setter.
+  set index(int value) {}
+
+  // Instance members shadow extensions.
+  String get notExtension => "not extension";
+
+  String toString() => "this";
+}
\ No newline at end of file
diff --git a/test/splitting/enums.unit b/test/splitting/enums.unit
index d326e40..2568ad8 100644
--- a/test/splitting/enums.unit
+++ b/test/splitting/enums.unit
@@ -16,4 +16,30 @@
   CHIMP,
   GORILLA,
   ORANGUTAN,
+}
+>>> wrapped argument lists in values
+enum Args {
+firstEnumValue(longArgument,anotherArgument),
+secondEnumValue(longArgument,anotherArgument,aThirdArgument,theLastOne),
+thirdGenericOne<FirstTypeArgument,
+SecondTypeArgument<NestedTypeArgument,AnotherNestedTypeArgument>,
+LastTypeArgument>(namedArgument: firstValue,anotherNamed:argumentValue)
+}
+<<<
+enum Args {
+  firstEnumValue(
+      longArgument, anotherArgument),
+  secondEnumValue(
+      longArgument,
+      anotherArgument,
+      aThirdArgument,
+      theLastOne),
+  thirdGenericOne<
+          FirstTypeArgument,
+          SecondTypeArgument<
+              NestedTypeArgument,
+              AnotherNestedTypeArgument>,
+          LastTypeArgument>(
+      namedArgument: firstValue,
+      anotherNamed: argumentValue)
 }
\ No newline at end of file
diff --git a/test/whitespace/enums.unit b/test/whitespace/enums.unit
index 26c2271..dc76c6b 100644
--- a/test/whitespace/enums.unit
+++ b/test/whitespace/enums.unit
@@ -50,4 +50,233 @@
   gorilla
 }
 <<<
-enum Primate { bonobo, chimp, gorilla }
\ No newline at end of file
+enum Primate { bonobo, chimp, gorilla }
+>>> one blank line between values and members
+enum E { a, b, c;
+
+
+
+int x; }
+<<<
+enum E {
+  a,
+  b,
+  c;
+
+  int x;
+}
+>>> always go multiline if there are members
+enum E { a, b, c; int x; }
+<<<
+enum E {
+  a,
+  b,
+  c;
+
+  int x;
+}
+>>> indentation
+enum A { a;
+var z;
+inc(int x) => ++x;
+foo(int x) {
+if (x == 0) {
+return true;
+}}}
+<<<
+enum A {
+  a;
+
+  var z;
+  inc(int x) => ++x;
+  foo(int x) {
+    if (x == 0) {
+      return true;
+    }
+  }
+}
+>>> trailing space inside body
+enum A { a, b
+  }
+<<<
+enum A { a, b }
+>>> leading space before "enum"
+  enum A { a
+}
+<<<
+enum A { a }
+>>>
+enum A  { a;int meaningOfLife() => 42; }
+<<<
+enum A {
+  a;
+
+  int meaningOfLife() => 42;
+}
+>>>
+enum A{a;var z;inc(int x) => ++x;}
+<<<
+enum A {
+  a;
+
+  var z;
+  inc(int x) => ++x;
+}
+>>> insert blank line after non-empty block-bodied members
+enum Foo {
+  x;
+var a = 1; b() {;} c() => null; get d {;} get e => null; set f(value) {;
+} set g(value) => null; var h = 1;}
+<<<
+enum Foo {
+  x;
+
+  var a = 1;
+  b() {
+    ;
+  }
+
+  c() => null;
+  get d {
+    ;
+  }
+
+  get e => null;
+  set f(value) {
+    ;
+  }
+
+  set g(value) => null;
+  var h = 1;
+}
+>>> no required blank line after empty block-bodied members
+enum Foo {x;
+var a = 1; b() {} c() => null; get d {} get e => null; set f(value) {
+} set g(value) => null; var h = 1;}
+<<<
+enum Foo {
+  x;
+
+  var a = 1;
+  b() {}
+  c() => null;
+  get d {}
+  get e => null;
+  set f(value) {}
+  set g(value) => null;
+  var h = 1;
+}
+>>> blank line before and after enum
+var x = 1;
+enum A { a }
+var y = 2;
+<<<
+var x = 1;
+
+enum A { a }
+
+var y = 2;
+>>> semicolon after values but no members
+enum   E { a, b; }
+<<<
+enum E {
+  a,
+  b;
+}
+>>> enhanced with clauses and members
+enum E with M<R, S>, F implements C<T>, D {
+value;
+late final String field;
+static var staticField = initializer;
+int method() { body; }
+static String staticMethod(int x) => body;
+List<int> get getter => 3;
+int operator +(other) => 3;
+const E([String parameter]) : field = parameter;
+const E.named({String parameter});
+}
+<<<
+enum E
+    with M<R, S>, F
+    implements C<T>, D {
+  value;
+
+  late final String field;
+  static var staticField = initializer;
+  int method() {
+    body;
+  }
+
+  static String staticMethod(int x) =>
+      body;
+  List<int> get getter => 3;
+  int operator +(other) => 3;
+  const E([String parameter])
+      : field = parameter;
+  const E.named({String parameter});
+}
+>>> argument lists in values
+enum Args {
+first(),second(a,b,c),
+third(named:1,2,another:3)
+}
+<<<
+enum Args {
+  first(),
+  second(a, b, c),
+  third(named: 1, 2, another: 3)
+}
+>>> generic enum
+enum MagicNumbers< T    extends num   ,   S> {
+  one(1), two(2),pi<double,String>(3.14159)
+}
+<<<
+enum MagicNumbers<T extends num, S> {
+  one(1),
+  two(2),
+  pi<double, String>(3.14159)
+}
+>>> trailing commas in value arguments
+enum Numbers {
+  one(1,),
+  two(1,2,),
+}
+<<<
+enum Numbers {
+  one(
+    1,
+  ),
+  two(
+    1,
+    2,
+  ),
+}
+>>> trailing comma and semicolon after constants
+enum E {a,b,c,;}
+<<<
+enum E {
+  a,
+  b,
+  c,
+  ;
+}
+>>> trailing comma and semicolon after constants with member
+enum E {a,b,c,;var x;}
+<<<
+enum E {
+  a,
+  b,
+  c,
+  ;
+
+  var x;
+}
+>>> trailing comma and semicolon after constant with argument list
+enum E {a,b,c(123),;}
+<<<
+enum E {
+  a,
+  b,
+  c(123),
+  ;
+}