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),
+ ;
+}