diff --git a/CHANGELOG.md b/CHANGELOG.md index a73d1619..c661308e 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 b2081bd6..30af2799 100644 --- a/lib/src/source_visitor.dart +++ b/lib/src/source_visitor.dart @@ -802,7 +802,7 @@ class SourceVisitor extends ThrowingAstVisitor { token(node.equals); space(); - visit(node.superclass2); + visit(node.superclass); builder.startRule(CombinatorRule()); visit(node.withClause); @@ -834,11 +834,12 @@ class SourceVisitor extends ThrowingAstVisitor { 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 @@ class SourceVisitor extends ThrowingAstVisitor { 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 @@ class SourceVisitor extends ThrowingAstVisitor { @override void visitConstructorName(ConstructorName node) { - visit(node.type2); + visit(node.type); token(node.period); visit(node.name); } @@ -1165,25 +1166,80 @@ class SourceVisitor extends ThrowingAstVisitor { 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 @@ class SourceVisitor extends ThrowingAstVisitor { soloSplit(); token(node.extendsKeyword); space(); - visit(node.superclass2); + visit(node.superclass); } @override @@ -1999,7 +2055,7 @@ class SourceVisitor extends ThrowingAstVisitor { @override void visitImplementsClause(ImplementsClause node) { - _visitCombinator(node.implementsKeyword, node.interfaces2); + _visitCombinator(node.implementsKeyword, node.interfaces); } @override @@ -2264,18 +2320,18 @@ class SourceVisitor extends ThrowingAstVisitor { // 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 @@ class SourceVisitor extends ThrowingAstVisitor { @override void visitOnClause(OnClause node) { - _visitCombinator(node.onKeyword, node.superclassConstraints2); + _visitCombinator(node.onKeyword, node.superclassConstraints); } @override @@ -2730,7 +2786,7 @@ class SourceVisitor extends ThrowingAstVisitor { @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 4fbb2daf..edcf821b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 @@ packages: 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 6096aff5..475dc08e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,7 @@ environment: 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 d2ea3eac..59704a9f 100644 --- a/test/comments/enums.unit +++ b/test/comments/enums.unit @@ -51,4 +51,25 @@ enum A { 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 00000000..590cdba0 --- /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 + with GenericEnumMixin, ObjectMixin + implements Interface, GenericInterface { + @v1 + @v2 + v1, + @EnumAll.v2 + v2(y: 2), + @sConst + v3(y: 2), + v4.named(1, y: 2), + v5.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 get staticGetSet => v3; + static set staticGetSet(EnumAll _) {} + 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; + // Factory, redirecting (only to other factory constructor). + factory EnumAll.refactory(int index) = EnumAll.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 operator ^(EnumAll 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 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 + with GenericEnumMixin, ObjectMixin + implements Interface, GenericInterface { + @v1 + @v2 + v1, + @EnumAll.v2 + v2(y: 2), + @sConst + v3(y: 2), + v4.named(1, y: 2), + v5.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 get staticGetSet => v3; + static set staticGetSet(EnumAll _) {} + 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; + // Factory, redirecting (only to other factory constructor). + factory EnumAll.refactory(int index) = EnumAll.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 operator ^(EnumAll 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 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 d326e40a..2568ad8f 100644 --- a/test/splitting/enums.unit +++ b/test/splitting/enums.unit @@ -16,4 +16,30 @@ enum Primate { CHIMP, GORILLA, ORANGUTAN, +} +>>> wrapped argument lists in values +enum Args { +firstEnumValue(longArgument,anotherArgument), +secondEnumValue(longArgument,anotherArgument,aThirdArgument,theLastOne), +thirdGenericOne, +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 26c2271c..dc76c6b1 100644 --- a/test/whitespace/enums.unit +++ b/test/whitespace/enums.unit @@ -50,4 +50,233 @@ enum Primate { 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, F implements C, D { +value; +late final String field; +static var staticField = initializer; +int method() { body; } +static String staticMethod(int x) => body; +List get getter => 3; +int operator +(other) => 3; +const E([String parameter]) : field = parameter; +const E.named({String parameter}); +} +<<< +enum E + with M, F + implements C, D { + value; + + late final String field; + static var staticField = initializer; + int method() { + body; + } + + static String staticMethod(int x) => + body; + List 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(3.14159) +} +<<< +enum MagicNumbers { + one(1), + two(2), + pi(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), + ; +}