Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New tall style: Automated trailing commas and other formatting changes #1253

Closed
munificent opened this issue Aug 22, 2023 · 227 comments
Closed

Comments

@munificent
Copy link
Member

munificent commented Aug 22, 2023

TL;DR: We're proposing a set of style changes to dart format that would affect about 10% of all Dart code. We want to know what you think.

The main change is that you no longer need to manually add or remove trailing commas. The formatter adds them to any argument or parameter list that splits across multiple lines and removes them from ones that don't. When an argument list or parameter list splits, it is formatted like:

longFunction(
  longArgument,
  anotherLongArgument,
);

This change means less work writing and refactoring code, and no more reminding people to add or remove trailing commas in code reviews. We have a prototype implementation that you can try out. There is a detailed feedback form below, but you can give us your overall impression by reacting to the issue:

  • 👍 Yes, I like the proposal. Ship it!
  • 👎 No, I don't like the proposal. Don't do it.

We have always been very cautious about changing the formatter's style rules to minimize churn in already-formatted code. Most of these style rules were chosen before Flutter existed and before we knew what idiomatic Flutter code looked like.

We've learned a lot about what idiomatic Dart looks like since then. In particular, large deeply nested function calls of "data-like" code are common. To accommodate them, the formatter lets you partially opt into a different formatting style by explicitly authoring trailing commas in argument and parameter lists. I think we can do better, but doing so would be the largest change we've ever made to the formatter.

I'm starting this change process to determine whether or not it's a change the community is in favor of. Details are below, but in short, I propose:

  • The formatter treats trailing commas in argument lists and parameter lists as "whitespace". The formatter adds or removes them as it sees fit just as it does with other whitespace.

  • When an argument or parameter list is split, it always uses a "tall" style where the elements are indented two spaces, and the closing ) is moved to the next line:

    longFunction(
      longArgument,
      anotherLongArgument,
    );
  • The formatting rules for other language constructs are adjusted to mesh well with argument and parameter lists formatted in that style. For example, with that style, I think it looks better to prefer splitting in an argument list instead of after = in an assignment or variable declaration:

    // Before:
    var something =
        function(argument);
    
    // After:
    var something = function(
      argument,
    );

The overall goal is to produce output that is as beautiful and readable as possible for the kind of code most Dart users are writing today, and to give you that without any additional effort on your part.

Background

The formatter currently treats a trailing comma in an argument or parameter list as an explicit hand-authored signal to select one of two formatting styles:

// Page width:               |

// No trailing comma = "short" style:
noTrailingComma(
    longArgument,
    anotherLongArgument);

// Trailing comma = "tall" style:
withTrailingComma(
  longArgument,
  anotherLongArgument,
);

A trailing comma also forces the surrounding construct to split even when it would otherwise fit within a single line:

force(
  arg,
);

The pros of this approach are:

  • Before Dart allowed trailing commas in argument and parameter lists, they were formatted in the short style. Having to opt in to the tall style by writing an explicit trailing comma, avoids churn in already-formatted code.

  • Users who prefer either the short or tall style can each have it their way.

  • If a user wants to force an argument or parameter list to split that would otherwise fit (and they want a tall style), they can control this explicitly.

The cons are:

  • The short style is inconsistent with how list and map literals are formatted, so nested code containing a mixture of function calls and collections—very common in Flutter code—looks inconsistent.

  • If a user wants the tall style but doesn't want to force every argument or parameter list to split, they have no way of getting that. There's no way to say "split or unsplit this as needed, but if you do split, use a tall style".

    Instead, users who want a tall style must remember to manually remove any trailing comma on an argument or parameter list if they want to allow it fit on one line. This is particularly painful with large scale automated refactorings where it's infeasible to massage the potentially thousands of modified lists to remove their trailing commas.

    Worse, there's no way to tell if a trailing comma was authored to mean "I want to force this to split" versus "I want this to have tall style", so anyone maintaining the code afterwards doesn't know whether a now-unnecessary trailing comma should remain or not.

  • Users must be diligent in maintaining trailing commas in order to get the same style across an entire codebase.

    The goal of automated formatting is to get users out of the business of making style choices. But the current implementation requires them to hand-author formatting choices on every argument and parameter list.

    For users who prefer a tall style, they must be careful to add a trailing comma and then reformat whenever an existing argument or parameter list that used to fit on one line no longer does and gets split. By default, as soon as the list overflows, it will get the short style.

    Again, this is most painful with large scale refactorings. A rename that makes an identifier longer could lead to thousands of argument lists splitting. If those are in code that otherwise prefers a tall style, the result is a mixture of short and tall styles.

  • The rest of the style rules aren't designed to harmonize with tall style argument and parameter lists since they don't know whether nearby code will use a short or tall style. There's no way to opt in to a holistic set of style rules designed to make all Dart code look great in that style.

    This leads to a long tail of bug reports where some other construct clearly doesn't look good when composed with tall-style argument and parameter lists, but there's not much we can do because the construct does look fine when composed with short style code.

I believe the cons outweigh the pros, which leads to this proposal.

Proposal

There are basically three interelated pieces to the proposal:

Trailing commas as whitespace

Trailing commas in argument and parameter lists are treated as "whitespace" characters and under the formatter's purview to add and remove. (Arguably, they really are whitespace characters, since they have zero effect on program semantics.)

The rule for adding and removing them is simple:

  • If a comma-separated construct is split across multiple lines, then add a trailing comma after the last element. This applies to argument lists, parameter lists, list literals, map literals, set literals, records, record types, enum values, switch expression cases, and assert arguments.

    (There are a couple of comma-separated constructs that don't allow trailing commas that are excluded from this like type parameter and type argument lists.)

  • Conversely, if the formatter decides to collapse a comma-separated construct onto a single line, then do so and remove the trailing comma.

The last part means that users can no longer hand-author a trailing comma to mean, "I know it fits on one line but force it to split anyway because I want it to." I think it's worth losing that capability in order to preserve reversibility. If the formatter treated trailing commas as user-authored intent, but also inserted them itself, then once a piece of code has been formatted once, it can no longer tell which commas came from a human and which came from itself.

Always use tall style argument and parameter lists

If a trailing comma no longer lets a user choose between a short or tall style, then the formatter has to choose. I think it should always choose a tall style.

Both inside Google and externally, the clear trend is users adding trailing commas to opt into the tall style. An analysis of the 2,000 most recently published Pub packages shows:

-- Arg list (2672877 total) --
1522870 ( 56.975%): Single-line  ===========================
 455266 ( 17.033%): Empty        =========
 347996 ( 13.020%): Tall         =======
 208585 (  7.804%): Block-like   ====
 137682 (  5.151%): Short        ===
    478 (  0.018%): Other        =

The two lines that are relevant here are:

-- Arg list (2672877 total) --
 347996 ( 13.020%): Tall         =======
 137682 (  5.151%): Short        ===

Of the argument lists where either a short or tall style preference can be distinguished, users prefer the tall style ~71% of the time, even though they have to remember to hand-author a trailing comma on every argument list to get it.

Block-like argument lists

I've been talking about "short" and "tall" argument lists, but there are actually three ways that argument lists get formatted:

// Page width:               |

// Short style:
longFunctionName(
    veryLongArgument,
    anotherLongArgument);

// Tall style:
longFunctionName(
  veryLongArgument,
  anotherLongArgument,
);

// Block style:
longFunctionName(() {
  closure();
});

The third style is used when some of the arguments are function expressions or collection literals and formatting the argument list almost as if it were a built-in statement in the language looks better. The most common example is in tests:

main() {
  group('Some group', () {
    test('A test', () {
      expect(1 + 1, 2);
    });
  });
}

This proposal still supports block-style argument lists. It does somewhat tweak the heuristics the formatter uses to decide when an argument list should be block-like or not. The current heuristics are very complex, subtle, and still don't always look right.

Format the rest of the language holistically to match

The set of formatting rules for the different language features were not designed to make each feature look nice in isolation. Instead, the goal was a coherent set of style rules that hang together and make an entire source file clear and readable.

Those rules currently assume that argument and parameter lists have short formatting. With this proposal, we also adjust many of those other rules and heuristics now that we know that when an argument or parameter list splits, it will split in a tall style way.

There are many of these mostly small-scale tweaks. Some examples:

  • Prefer to split in the initializer instead of after "=":

    // Page width:               |
    
    // Before:
    var something =
        function(argument);
    
    // After:
    var something = function(
      argument,
    );
  • Less indentation for collection literal => member bodies:

    // Page width:               |
    
    // Before:
    class Foo {
      List<String> get things => [
            'a long string literal',
            'another long string literal',
          ];
    }
    
    // After:
    class Foo {
      List<String> get things => [
        'a long string literal',
        'another long string literal',
      ];
    }
  • Don't split after => if a parameter list splits:

    // Page width:               |
    
    // Before:
    function(String parameter,
            int another) =>
        otherFunction(
            parameter, another);
    
    // After:
    function(
      String parameter,
      int another,
    ) => otherFunction(
      parameter,
      another,
    );

I used the Flutter repository as a reference, which uses tall style and has been very carefully hand-formatted for maximum readability, and tweaked the rules to follow that wherever I could. Many of the changes are subtle in ways that are hard to describe here. The best way to see them in action is to try out the prototype implementation, described below.

Risks

While I believe the proposed style looks better for most Dart code and makes users' lives simpler by not having to worry about maintaining trailing commas, this is a large change with some downsides:

Churn

Formatting a previously formatted codebase with this new style will cause about 10% of the lines of code to change. That's a large diff and can be disruptive to codebases where there are many in-progress changes.

Migrating to the new style may be painful for users, though of course it is totally automated.

Users may dislike the style

Obviously, if a large enough fraction of users don't want this change, we won't do it, which is what the change process aims to discover. But even if 90% of the users prefer the new style, that still leaves 10% who now feel the tool is worse than it was before.

Realistically, no change of this scale will please everyone. One of the main challenges in maintaining an opinionated formatter that only supports one consistent style is that some users are always unhappy with it. At least by rarely changing the style, we avoid drawing user attention to the style when they don't like it. This large, disruptive change will make all users at least briefly very aware of automated formatting.

Two configurable styles

An obvious solution to user dislike is to make the style configurable: We could let you specify whether you want the old formatting rules or the new ones. The formatter could support two separate holistic styles, without going all the way towards full configurability (which is an anti-goal for the tool).

There are engineering challenges with this. The internal representation the formatter uses to determine where to split lines has grown piecemeal over the formatter's history. The result is hard to extend and limited in the kind of style rules it can represent. When new language features are added it's often difficult to express the style rules we want in terms that the internal representation supports. Many long-standing bugs can't be fixed because the rule a user wants can't be modeled in the current representation.

This became clear when working on a prototype of the proposal. Getting it working was difficult and there are edge cases where I can't get it to model the rules I want.

If this proposal is accepted and we make large-scale changes to the formatting rules, we intend to take the opportunity to also implement a better internal representation. That's a large piece of work for a tool that generally doesn't have a lot of engineering resources allocated to it.

We don't have the bandwidth during this change to write a new IR, a new set of style rules and migrate the old style rules to the new IR. There may be old rules the new IR can't represent well.

We could keep the old IR around for the old style and use the new IR only for the new style. But going forward, we don't have the resources to maintain two sets of style rules and two separate internal representations, one for each. Every time a new language feature ships, the formatter needs support for it. Bugs would have to get fixed twice (or, more likely, only fixed for one style).

We might be able to temporarily support both styles in order to offer a migration period. But we don't have the resources to support both styles in perpetuity. We would rather spend those resources supporting one style really well instead of two styles poorly.

Evaluation

This is a large style change. Because the formatter is deliberately opinionated and non-configurable, it will affect all users of dart format. That means it's important to make sure that it's a good change in the eyes of as many users as possible. We can't please everyone, but we should certainly please a clear majority.

To that end, we need your feedback. That feedback is most useful when it's grounded in concrete experience.

Prototype implementation

To help with that, we have a prototype implementation of the new style rules. The prototype has known performance regressions and doesn't get the style exactly right in some cases. But, for the most part, it should show you the proposed behavior.

Diff corpus

We ran this prototype on a randomly selected corpus of code, which yields the resulting diff. This shows you how the new formatting compares to the behavior of the current formatter. Keep in mind that a diff focuses your attention on places where the style is different. It doesn't highlight the majority of lines of code that are formatted exactly the same under this proposal as they are today.

Running it yourself

To get a better sense of what it feels like to use this proposed behavior, you can install the prototype branch of the formatter from Git:

$ dart pub global activate \
    --source=git https://github.com/dart-lang/dart_style \
    --git-ref=flutter-style-experiment

You can run this using the same command-line options as dart format. For example, to reformat the current directly and its subdirectories, you would run:

$ dart pub global run dart_style:format .

To format just a single file, example.dart, you would run:

$ dart pub global run dart_style:format example.dart

Give us your feedback

Once you've tried it out, let us know what you think by taking this survey. You can also reply directly on this issue, but the survey will help us aggregate the responses more easily. We'll take the survey responses and any comments here into account and try our best to do what's right for the community.

After a week or, to give people time to reply, I'll update with what we've learned.

This is an uncomfortably large change to propose (and a hard one to implement!), so I appreciate your patience and understanding while we work through the process.

Thank you!

– Bob

@suragch
Copy link

suragch commented Aug 23, 2023

I like it. I manually add trailing commas a lot in just the style that this proposal would handle automatically. This would save me time and effort.

Below are a few comments after trying out the prototype implementation.

Tall style readability

I often have a series of )))) end parentheses (or brackets or braces) in my code like so:

@override
Widget build(BuildContext context) {
  return const MaterialApp(
    home: Scaffold(
        body: Padding(
            padding: EdgeInsets.all(8.0),
            child: Center(
                child: Column(children: [
              Text('Hello'),
              Text('Dart'),
              Text('World')
            ])))),
  );
}

I usually add in trailing commas like this:

@override
Widget build(BuildContext context) {
  return const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Scaffold(
      body: Padding(
        padding: EdgeInsets.all(8.0),
        child: Center(
          child: Column(
            children: [
              Text('Hello'),
              Text('Dart'),
              Text('World'),
            ],
          ),
        ),
      ),
    ),
  );
}

However, I noticed the proposed formatter takes my carefully crafted formatting and does the following with it:

@override
Widget build(BuildContext context) {
  return const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Scaffold(
      body: Padding(
        padding: EdgeInsets.all(8.0),
        child: Center(
          child:
              Column(children: [Text('Hello'), Text('Dart'), Text('World')]),
        ),
      ),
    ),
  );
}

That's not terrible, but putting all of the Column elements on one line just because they fit isn't the most readable way of displaying them. It's possible to use a comment hack (see the next section) to force tall style, but I don't know if that's what you intend to become common practice.

Forcing tall style

When I make enums, sometimes I like the tall style even if they would fit on one line:

enum Colors {
  red,
  green,
  blue,
}

The proposed formatter wouldn't allow that anymore and would reformat the code above to the following:

enum Colors { red, green, blue }

That's no problem with me. I can get used to that.

And if I really want to keep the tall style, I can add a comment after the last item like so:

enum Colors {
  red,
  green,
  blue, //
}

Now the proposed formatter leaves my code style alone.

The following is a bug, I think, but if I add a comment after the second item like so:

enum Colors {
  red,
  green, //
  blue,
}

The proposed formatter breaks the code (and its promise not change more than the style):

enum Colors { red, green, // blue }

@munificent
Copy link
Member Author

munificent commented Aug 23, 2023

Thank you for the feedback!

Re:

          child:
              Column(children: [Text('Hello'), Text('Dart'), Text('World')]),

Yeah, that looks like a bug in the prototype. I wouldn't expect it to split after a named parameter like this. It should produce output closer to what you have with the current formatter and explicit trailing commas.

The proposed formatter breaks the code (and its promise not change more than the style):

enum Colors { red, green, // blue }

That's definitely a bug. The prototype is, uh, pretty prototype-y. :)

@rakudrama
Copy link
Member

I find the proposed style quite a bit less consistent.

This example comes from https://github.com/dart-lang/sdk/blob/main/pkg/js_ast/lib/src/nodes.dart

Old - there are basically two formats for the visitor methods, one-line and two-line.
The two-line version keeps the expression intact, and it is easy to see these methods all do the same thing.
(If I was hand-formatting I might make the middle one break after => just like the others.)

  T visitVariableDeclarationList(VariableDeclarationList node) =>
      visitExpression(node);

  T visitAssignment(Assignment node) => visitExpression(node);

  T visitVariableInitialization(VariableInitialization node) =>
      visitExpression(node);

New - there are now three formats, depending on where the line splits are.

  T visitVariableDeclarationList(
    VariableDeclarationList node,
  ) => visitExpression(node);

  T visitAssignment(Assignment node) => visitExpression(node);

  T visitVariableInitialization(VariableInitialization node) => visitExpression(
    node,
  );

The formatting does not help me see these methods all do the same thing.

The line-breaks here fight against a cognitive principle. Programs, like sentences, have structure. The big things are composed of little things, and it is helpful to ingest the small concepts completely. Line breaks in the middle of small things hinder that. Breaks higher up the semantic structure are less disruptive:

The nervous chicken
quickly crosses
the dangerous road.

is clearer than

The nervous
chicken quickly
crosses the dangerous road.

The proposed formatting is too much like the latter.

Here is an example from the same package where the main point gets lost

  Instantiator visitLiteralExpression(LiteralExpression node) =>
      TODO('visitLiteralExpression');

  Instantiator visitLiteralStatement(LiteralStatement node) => TODO(
    'visitLiteralStatement',
  );

I believe there is a middle ground that is a little bit of the old style for smaller statements, expressions and definitions, and a little bit of the proposed style for larger constructs.

@bsutton
Copy link

bsutton commented Aug 23, 2023

Generally in agreement.

I do generally prefer my commas a the start of the line as it's easier to see that it's a continuation but I suspect I'm in the minority.

           callSomething(a
               , b
               , c);

@BirjuVachhani
Copy link

I agree. Manually adding trailing commas has been such a pain! Having to not type them manually for better formatting would be great!

@Levi-Lesches
Copy link

Love it! The formatter using "short style" is the main reason why I actually don't use it, and tell my team to disable it when working on Flutter projects. Tall style is much more my style and I'd be way more likely to use the formatter consistently if it switched to that.

Regarding

  T visitVariableInitialization(VariableInitialization node) =>
      visitExpression(node);

vs

  T visitVariableInitialization(VariableInitialization node) => visitExpression(
    node,
  );

I would personally aim for the first version for shorter constructs, but in general, the second version does scale better for longer expressions. So I'd understand if the formatter picked the "wrong" one because humans disagree on this too.

@filiph
Copy link

filiph commented Aug 23, 2023

I really like the proposal!

One nit I have is this:

// Before:
var something =
    function(argument);

// After:
var something = function(
  argument,
);

I like Before better. To me, in this particular case, it's more readable, because we're keeping the verb(noun) together as long as possible, and the var something = line is very readable/skimmable to me (it's clear that it continues).

I feel quite strongly about this when it comes to functions. I'm not so sure when it comes to constructors (Widget(...)) but my preference still holds.

I agree with @Levi-Lesches's comment above (#1253 (comment)).

@bshlomo
Copy link

bshlomo commented Aug 23, 2023

A good idea but to know if the implementation is indeed good will take time
less coding is always better.
We will check and use it.
10x

@mateusfccp
Copy link

There are a few cases where I don't use trailing comma.

  1. Single arguments, when the argument is a single token:
// Single token single argument
foo(10); // <--- No trailing comma

// Multi token single argument
foo(
  numberFromText('ten'),
);
  1. When arguments are many but short (usually number literals)
foo(1, 2, 3); // <--- No trailing comma

// Instead of
foo( // <--- Unnecessarily long
  1,
  2,
  3,
)

Other than that, I always use trailing comma.

Overall, I think this is a good change. I think I would only avoid the case (1), as it's really unnecessary, but it would obviously make things more complicated.

@modulovalue
Copy link

My observation is that adding optional trailing commas almost always improves code readability, because it forces the formatter to put everything on a separate line, which implicitly enforces a "one concept per line"-rule, and I have found that to be very helpful when reading code.

The main change is that you no longer need to manually add or remove trailing commas

I think that automatically adding optional trailing commas could be helpful and I wouldn't be against that, but I think I wouldn't want the formatter to remove anything from my code except empty newlines. I want things to be on separate lines more often than not, and an explicit optional trailing comma has worked well as a way to tell the formatter that that's what I want.


If this moves forward, I think it would be great if this could be part of a bigger effort to promote more optional trailing commas as the preferred style (in, e.g., the form of recommended guidelines) + new syntax to support optional trailing commas in more places (e.g. dart-lang/language#2430)

@lesnitsky
Copy link

lesnitsky commented Aug 23, 2023

Could this also account for pattern-matching formatting?

Current

final child = switch (a) {
  B() => BWrapper(
      child: const Text('Hello B'),
    ), // two spaces that feel redundant
  C() => CWrapper(
      child: const Text('Hello C'),
    ),  // two spaces that feel redundant
};

Desired

final child = switch (a) {
  B() => BWrapper(
    child: const Text('Hello B'),
  ),
  C() => CWrapper(
    child: const Text('Hello C'),
  ),
};

UPD: I've used dart pub global run dart_style:format and it doesn't add trailing comma:

final child = switch (a) {
  B() =>
    BWrapper(child: const Text('Hello B'), prop: 'Some long text goes here'),
  C() =>
    CWrapper(child: const Text('Hello C'), prop: 'Some long text goes here'),
};

@munificent
Copy link
Member Author

Could this also account for pattern-matching formatting?

Yeah, there's probably some tweaks needed there to harmonize with the proposal. Thanks for bringing that example up. :)

@lucavenir
Copy link

lucavenir commented Aug 23, 2023

var something =
   function(argument);

This hurts my eyes so bad 😆 Getting rid of this would lift a lot of OCD pain when writing code

@greglittlefield-wf
Copy link

greglittlefield-wf commented Aug 23, 2023

Thanks for getting this prototype together and soliciting feedback!

After trying it on some of our code, the main piece of feedback that wasn't already mentioned already is that being able to force the "tall" style with trailing commas is unfortunate.

It doesn't happen often, but when using longer line lengths where function calls don't "need" to wrap as often, you can get code that's a bit harder to read (especially when it's parentheses-heavy 😅).

For example, at line length 120:

      // Current
      (Dom.div()..addProps(getModalBodyScrollerProps()))(
        renderCalloutIcons(),
        props.calloutHeader,
        props.children,
      ),
      // Prototype
      (Dom.div()..addProps(getModalBodyScrollerProps()))(renderCalloutIcons(), props.calloutHeader, props.children),

This problem also exists for map literals, which, unlike the example above, seem to be affected more often at smaller line lengths.

  • Example 1 (line length 80)
          // Current
          ..sx = {
            'width': '100%',
            'display': 'flex',
            'p': 0,
            ...?props.sx,
          }
          // Prototype
          ..sx = {'width': '100%', 'display': 'flex', 'p': 0, ...?props.sx}
  • Example 2 (line length 100)
      // Current
      ..style = {
        'margin': 0,
        'padding': 0,
        'boxSizing': 'border-box',
        'display': 'block',
      }
      // Prototype
      ..style = {'margin': 0, 'padding': 0, 'boxSizing': 'border-box', 'display': 'block'}
  • Example 3 (line length 120)
        // Current
        matchState.addAll(<dynamic, dynamic>{
          if (offset != null) 'offset': offset,
          if (length != null) 'length': length,
        });
        // Prototype
        matchState.addAll(<dynamic, dynamic>{if (offset != null) 'offset': offset, if (length != null) 'length': length});

We'd probably end up decreasing our line lengths to get better wrapping, which isn't ideal for some of our packages with longer class and variable names. I realize, though, that 120 is quite a bit larger that dart_style's preferred line length of 80, so I'd understand if we'd have to move closer to that to get good formatting.

But for map literals, perhaps the rules could be tweaked to prefer "tall" style more often? For example, force “tall” style if a map has more than 2 or 3 elements, or if it contains more than 1 if or for element.

@nex3
Copy link
Member

nex3 commented Aug 24, 2023

I prefer the old version generally, but I'm willing to accept that I'm in the minority on that one and global consistency is more important than my preferences.

I'll echo that I don't like the splitting behavior for formerly-2-line => functions. It also seems very strange that, for example,

ModifiableCssSupportsRule copyWithoutChildren() =>
    ModifiableCssSupportsRule(condition, span);

becomes

ModifiableCssSupportsRule copyWithoutChildren() => ModifiableCssSupportsRule(
  condition,
  span,
);

which is actually taller than

ModifiableCssSupportsRule copyWithoutChildren() {
  return ModifiableCssSupportsRule(condition, span);
}

...which goes against the grain of => notionally being the "shorthand" method syntax.

@mernen
Copy link

mernen commented Aug 24, 2023

Nice! Overall, I'm in favor, though I'm not sure which heuristics would work well for all cases, particularly involving collections. For example, in a layout container, I'd never want it to smash all children into a single line:

return Center(
  child: Column(
    children: [Text('Loading... please wait'), CircularProgressIndicator()],
  ),
);

Also, since this extra indentation of 2 levels goes completely against the grain, I'm guessing it wasn't intentional?

 placeholder: (context, url) => Center(
-  child: CircularProgressIndicator(
-    backgroundColor: Colors.white,
-  ),
-),
+      child: CircularProgressIndicator(
+        backgroundColor: Colors.white,
+      ),
+    ),

@satvikpendem
Copy link

satvikpendem commented Aug 24, 2023

I think enums should always be in the tall style just because reading each enumeration one line at a time makes it much easier to grasp what it's doing. I'd even support the tall style for enums that have just one member.

For functions, perhaps we should switch short vs tall based on the number of arguments, where one or two is short but 3 or more becomes tall (which is usually what I already do manually by adding commas), as well as basing it on column widths. We can even combine short and tall, perhaps:

ModifiableCssSupportsRule copyWithoutChildren() =>
  ModifiableCssSupportsRule(
    condition,
    span,
    anotherArgument,
  );

This is because I generally don't want to scroll all the way right just to see what is being returned in the arrow syntax. It is in the same vein as:

return Center(
  child: Column(
    children: ...
  ),
);

// instead of
return Center(child:
  Column(children: 
    ...
  ),
);

That is to say, the first (and current) example keeps things as left aligned as possible which makes scanning files much easier. Tthe combination of both short and tall as above feels to me to be a good compromise and is even more readable left to right, top to bottom, than either short or tall alone.

With regards to column widths, the formatter should check that first, then the number of arguments in a function or elements in an array or map, ie even if the following only has two elements, it should still force the tall version:

return Center(
  child: Column(
    children: [
      Text('Loading... please wait'),
      CircularProgressIndicator(),
    ],
  ),
);

Beyond that, I like the proposal as it saves a lot of time manually adding commas. I generally prefer the tall style for consistency.

@shovelmn12
Copy link

Why not adding a formater config so users can choose their style....

dartformat.yaml

@julemand101
Copy link

Why not adding a formater config so users can choose their style....

dartformat.yaml

https://github.com/dart-lang/dart_style/wiki/FAQ#why-cant-i-configure-it

@munificent
Copy link
Member Author

Also, since this extra indentation of 2 levels goes completely against the grain, I'm guessing it wasn't intentional?

 placeholder: (context, url) => Center(
-  child: CircularProgressIndicator(
-    backgroundColor: Colors.white,
-  ),
-),
+      child: CircularProgressIndicator(
+        backgroundColor: Colors.white,
+      ),
+    ),

This one's a bug in the prototype. If a named argument's value is a closure, then it shouldn't add an extra +4 indentation like that. I'll fix that in the real implementation.

@DanTup
Copy link
Contributor

DanTup commented Aug 24, 2023

Gets my 👍! I tend to use trailing commas a lot (even when not controlling the formatter, I like to not have extra modified lines in my diff if I'm appending new arguments to a "tall" list!).

I do have some questions though:

  1. Do I understand correctly that the formatter will actually add/remove commas (and not just format as if they were there)?
    I ask because in the analysis_server there's some code to compute a minimal set of edits from the formatter output (because replacing the entire file will mess up breakpoints/recent navigation/etc.) and the current implementation is very simple because it takes advantage of there only being whitespace changes. It would need some tweaks (or to become a real diff) if there could be non-whitespace changes.

  2. Will this functionality be opt-in (either permanently, or temporarily)?
    Many users have format-on-save enabled in VS Code and it could be surprising if in some future update (if you don't keep up with release notes etc.) you save a file you'd been working on and the formatting all changes (this exists a little today when there are minor formatting changes, but those tend to have less impact on the resulting diff). Hopefully you'd notice and could undo this before the undo buffer is lost and then migrate, but it's not guaranteed.
    Perhaps it'd be nice if the IDE could help with this - notifying that the formatting has changed and might produce significant edits and encouraging committing and reformatting the project/repo in one go?

@munificent
Copy link
Member Author

  1. Do I understand correctly that the formatter will actually add/remove commas (and not just format as if they were there)?

Yes.

Will this functionality be opt-in (either permanently, or temporarily)?

There won't be any permanent opt-in or opt-out because we don't have the resources to maintain two formatting styles in perpetuity. There will be an experimental period where the new style is hidden behind a flag, mostly so that I can land in-progress work on master, but I expect few users to see that.

Once it's fully working and ready to ship, there may be a temporary opt-in (or out, not sure about the default) in order to ease the migration for users. I'm waiting for feedback to come in to get a sense of how important it is. So far, based on survey results, it doesn't seem to be a high priority for most users.

I'll try to get a better feel for what will help the community when a real implementation is farther along. I definitely want to minimize pain.

@satvikpendem
Copy link

I don't think it's necessarily high priority in terms of timeline (as I believe many will wait and adopt it when they wish to migrate) but it confers quite a large benefit to mental overhead as other languages don't really have this notion of changing commas to fix formatting, I've really only seen that in Dart. Most languages have their own fixed formatter, ie prettier, black, rustfmt, etc where most people don't really think about the formatting manually, but in Dart it seems we need to.

@saierd
Copy link

saierd commented Aug 25, 2023

The last part means that users can no longer hand-author a trailing comma to mean, "I know it fits on one line but force it to split anyway because I want it to."

I think it is quite important to have this ability for Flutter code. Take for example this snippet:

Row(children: [
  Left(),
  Right(),
]);

This is not just a bunch of functions. It's a widget tree and formatting it in tall style reflects that. For this reason the Flutter documentation explicitly recommends to always add trailing commas in widgets to take advantage of this formatting behavior.

For users who prefer a tall style, they must be careful to add a trailing comma and then reformat whenever an existing argument or parameter list that used to fit on one line no longer does and gets split. By default, as soon as the list overflows, it will get the short style.

I find that enforcing trailing commas in places where wrapping happened anyway is not a problem in practice, since the require_trailing_commas rule exists and can automatically fix this.

@hpoul
Copy link

hpoul commented Aug 26, 2023

I always thought controlling the formatter by just appending a , felt really natural.. sometimes i want short lines to break and use tall formatting, either to stay consistent with other lines which happen to be a few characters longer, or to minimize diffs when more items are added to a list or enum, etc..

The workaround to use a // comment (#1253 (comment)) to force wrapping would definitely not be an improvement 🙈

@munificent
Copy link
Member Author

But for map literals, perhaps the rules could be tweaked to prefer "tall" style more often? For example, force “tall” style if a map has more than 2 or 3 elements, or if it contains more than 1 if or for element.

Yeah, that's not a bad idea. I'm interested in exploring this too. There is something roughly in the same direction in the current formatter in that it will split an outer collection literal if it contains another collection literal, even if the outer one otherwise doesn't need to split.

I've considered other rules around splitting when not strictly necessary to fit the line length, but I haven't really tried to implement anything because it's not clear what those rules should be and how they should best adapt to different line lengths.

One thing I've talked with @Hixie about is having the line length restriction work more like Prettier:

In code styleguides, maximum line length rules are often set to 100 or 120. However, when humans write code, they don’t strive to reach the maximum number of columns on every line. Developers often use whitespace to break up long lines for readability. In practice, the average line length often ends up well below the maximum.

Prettier’s printWidth option does not work the same way. It is not the hard upper allowed line length limit. It is a way to say to Prettier roughly how long you’d like lines to be. Prettier will make both shorter and longer lines, but generally strive to meet the specified printWidth.

So instead of treating it like a hard limit (which it currently does), it would be a softer boundary where lines are somewhat punished for going over when calculating the cost, but not heavily. I'm interested in exploring this approach, but I'm not planning to do that for this proposed set of changes. There are enough changes already queued up with this as it is. :)

@alestiago
Copy link

alestiago commented Aug 29, 2023

Thanks @munificent for this proposal. Overall, I'm quite happy with the change. I was wondering how would this affect (or if it has been considered) matrix representation, since currently it is not very convenient to read matrices.

I would like:

  Matrix4 m =
      Matrix4(11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44);

To be something as:

  Matrix4 m = Matrix4(
      11, 12, 13, 14,
      21, 22, 23, 24,
      31, 32, 33, 34,
      41, 42, 43, 44);

Definitely not something as:

  Matrix4 m = Matrix4(
    11,
    12,
    13,
    14,
    21,
    22,
    23,
    24,
    31,
    32,
    33,
    34,
    41,
    42,
    43,
    44,
  );

The overall idea is simply to read the matrix as it is usually written in mathematics.

This comment is somewhat related to what was mentioned here.

@julemand101
Copy link

julemand101 commented Aug 29, 2023

@alestiago
I think it is going to be hard to know for sure how people wants to split their lists/arguments for best formatting. The current practice is to add comments to force the line breaks:

  Matrix4 m = Matrix4(
    11, 12, 13, 14, //
    21, 22, 23, 24, //
    31, 32, 33, 34, //
    41, 42, 43, 44, //
  );

I hope this is still going to be a valid solution after the suggested change in this proposal.

@alestiago
Copy link

alestiago commented Aug 29, 2023

@julemand101 , thanks for hopping in! I also use the comments to avoid the new line, and also hope that this behaviour is improved or kept the same after the proposed changed.

@bambinoua
Copy link

bambinoua commented Feb 19, 2025

Is it possible to disable this feature without lowering the SDK version?

@ykmnkmi
Copy link

ykmnkmi commented Feb 19, 2025

@bambinoua add // @dart=3.6 file header:

// @dart=3.6
library;

// Dart code...

@Guru-Zachw
Copy link

Guru-Zachw commented Feb 19, 2025

You can use hack I have been using for awhile when I want to force a new line

Expanded(
  child: Center(
    child: DisplayWidget(display: 1), //
  ),
),
Expanded(child: Center(child: DisplayWidget(display: _counter))),

Adding a comment in some places can improve readability.
I use this in a bunch of places, Especially ternaries, which I nearly always prefer to be on 3 lines, despite it being common for them to be on 1 line.

It doesn't work so well reorganizing '.' chains or if statement '||'/'&&' chains

@wliumelb
Copy link

Clearly some miscommunication has happened from the beginning.

We voted for the proposal assuming the new formatter would automatically add the commas for us, so that we don't have to manually add them every time (which is slightly annoying). The result we got was that commas are removed without the possibility of adding it back (which deal-breakingly annoying for many of us).

@p4-k4
Copy link

p4-k4 commented Feb 20, 2025

Abandon macros, now this... What's going on with dart?...

@Nyaacinth
Copy link

Clearly some miscommunication has happened from the beginning.

We voted for the proposal assuming the new formatter would automatically add the commas for us, so that we don't have to manually add them every time (which is slightly annoying). The result we got was that commas are removed without the possibility of adding it back (which deal-breakingly annoying for many of us).

This proposal gave me this kind of first impression as well. I thought it will automatically add commas and turn the code to tall style when short style calling/construction/etc. become multi-line. But clearly we're wrong: for the most of the time it's doing the opposite.

@hpoul
Copy link

hpoul commented Feb 20, 2025

We voted for the proposal assuming the new formatter would automatically add the commas for us, so that we don't have to manually add them every time

Thanks for confirming my suspicions #1253 (comment) 😂
But as soon as somebody proposed using // as workaround it is clear that this proposal failed imho #1253 (comment)

If this is really the way forward maybe we could have another setting similar to page_width but which only configures when to use tall style, but won't wrap anything else.. then we could set tall_style_width to 0 and use it everywhere.. I'd rather use it for all named argument lists, rather than being forced into using single line for everything that fits..

@mit-mit
Copy link
Member

mit-mit commented Feb 20, 2025

Wanted to clear up a bit of confusion about how the new style is enabled:

Dart 3.7 and dart_style 3.0.0 introduced unexpected breaking changes, which is uncommon for built-in language tools.

...

Just got stung by this all of a sudden, how can I revert this change? Forcing formatting is not a good idea. We need an editor option to disable it ASAP.

The new formatter style is gated on the language version of the project being formatted. The language version is set in the pubspec.yaml as the lower constraint of the SDK. If that lower constraint is 3.7 or above, you get the new style. So this will get new style:

environment:
  sdk: '>=3.7.0 <4.0.0'

But this will get old style:

environment:
  sdk: '>=3.6.0 <4.0.0'

The formatter style is not gated on what version of the Dart SDK you are using. You can use new Dart SDKs like Dart 3.7, and still get the old style. Thus, we do not consider this a formal breaking change. That being said, you may at some point want to upgrade to a new language version (e.g. to pick up support for a new language feature) so I understand the strong opinions over the style.

@GiacomoPignoni
Copy link

I understand why this feature has been made like this, add a comma to make the code tall when the line is too long.

Ok but it's very frustrating that it also takes the initiative to delete a comma when the row is not too long.
I like the fact that can add a comma, but I don't like when it removes a comma I've put.
So I think that to improve this feature, it's needed that the formatter can only add commas and NOT remove them.

@stan-at-work
Copy link

Wanted to clear up a bit of confusion about how the new style is enabled:

Dart 3.7 and dart_style 3.0.0 introduced unexpected breaking changes, which is uncommon for built-in language tools.

...

Just got stung by this all of a sudden, how can I revert this change? Forcing formatting is not a good idea. We need an editor option to disable it ASAP.

The new formatter style is gated on the language version of the project being formatted. The language version is set in the pubspec.yaml as the lower constraint of the SDK. If that lower constraint is 3.7 or above, you get the new style. So this will get new style:

environment:
  sdk: '>=3.7.0 <4.0.0'

But this will get old style:

environment:
  sdk: '>=3.6.0 <4.0.0'

The formatter style is not gated on what version of the Dart SDK you are using. You can use new Dart SDKs like Dart 3.7, and still get the old style. Thus, we do not consider this a formal breaking change. That being said, you may at some point want to upgrade to a new language version (e.g. to pick up support for a new language feature) so I understand the strong opinions over the style.

Maybe it would be good if we could specify a dart_style: "<3.0.0" constraint in the pubspec.yaml config.

Then we can set the dart sdk to ">=3.7.0" and still have the good old styling.
If you want the new, change your dart_style version.

What do you think of that?

@alterproxi
Copy link

Maybe it would be good if we could specify a dart_style: "<3.0.0" constraint in the pubspec.yaml config.

Then we can set the dart sdk to ">=3.7.0" and still have the good old styling.
If you want the new, change your dart_style version.

What do you think of that?

All fine and well but this is only temporary. In the future it is planned that the old formatter will be gone.

@julemand101
Copy link

@stan-at-work While perhaps a bit easy to do now, it is not going to work that great in the future since you then need to implement any syntax changes in the language in both formatters.

A point of locking this to the Dart language version is to make sure the old formatter are not going the handle new stuff.

@stan-at-work
Copy link

@stan-at-work While perhaps a bit easy to do now, it is not going to work that great in the future since you then need to implement any syntax changes in the language in both formatters.

A point of locking this to the Dart language version is to make sure the old formatter are not going the handle new stuff.

Valid point, had not thought of that

@mateusfccp
Copy link

mateusfccp commented Feb 20, 2025

Yesterday, we used , to force line breaks. Now we can use // (which I already used all the time because I was often unsatisfied with the way the formatter handled line breaks for non -, cases). Why so much hate to // when , served the same purpose?

(I foresee many downvotes)

@jamesderlin
Copy link
Contributor

jamesderlin commented Feb 20, 2025

Yesterday, we used , to force line breaks. Now we can use // (which I already used all the time because I was often unsatisfied with the way the formatter handled line breaks for non -, cases). Why so much hate to // when , served the same purpose?

(I foresee many downvotes)

I mentioned already in my previous comment, empty // comments are obtrusive and look worse. They stick out like a sore thumb, whereas trailing commas looked consistent and natural in lists that already had commas after elements.

Aesthetics aside, from a more practical standpoint, I have hundreds (if not thousands) of sites that would need // comments explicitly added.

@Guru-Zachw
Copy link

Guru-Zachw commented Feb 20, 2025

I certainly wouldn't say that // should be the permanent solution. It is simply helpful for now. If I had it my way I wouldn't need to use // for anything except ternaries (which I can see a legitimate case for wanting it to be on 1 line by default if it can be).

I think there are 2 problems.

  1. This feature should never remove commas, only adding them in obvious situations. such as the ))) situation at the end of a decent chunk of code. Removing code is the problem here. It removes the power dev's have over their code, which is a bad thing.
  2. This feature is very opinionated and depending largely on it's implementation, I might not want it to be a part of my workflow. Allowing disabling it seems prudent.

I personally thing the optional trailing comma thing is the best solution, it's simple. It provides control and makes things consistent. its SUPER easy to add commas after formatting in pretty much every situation. This feature is probably overcomplicating and opinionating something that already has a perfectly acceptable simple solution. Optomising a task that will end up causing people to fight and waste more time than they spend adding a few trailing commas (which is very easy is 95% of situations.)

Finally, I think there are always exceptions to generally good rules and it makes sense to have a tool that allows one to format something the most readable way. AKA be ignored by the formatter. I run into this from time to time in an if statement or something that the formatter handles in a less than ideal way and I end up rewriting my code to help the formatter format the code better. When I was able to make it look perfectly readable when I formatted it myself. It's a form of meta programming that I don't think is beneficial to the tool-chain.

I know the generally feeling with dart formatting is, "This is what dart code looks like", but I don't think this statement is meant to (but sometimes does) mean "This is what dart code looks like, sometimes dart code just looks bad" or "This is what dart code looks like, babysit the formatter so that your code doesn't look bad"

@KristianRPM
Copy link

I agree with the sentiment others have shared here about the removal of commas being a major negative, and wliumelb's previous comment is spot-on.

The addition of trailing commas to lines that are too long is a time-saver and a positive improvement, but the suggestion of removing trailing commas that I've added is quite frustrating. There are frequently cases where my team has added a trailing comma to a "short" line for readability purposes and adding a // as a workaround is not a good solution to this.

@jamesderlin
Copy link
Contributor

Clearly some miscommunication has happened from the beginning.

We voted for the proposal assuming the new formatter would automatically add the commas for us, so that we don't have to manually add them every time (which is slightly annoying). The result we got was that commas are removed without the possibility of adding it back (which deal-breakingly annoying for many of us).

The first comment in this issue did explain (emphasis mine):

The formatter treats trailing commas in argument lists and parameter lists as "whitespace". The formatter adds or removes them as it sees fit just as it does with other whitespace.

Bob wrote a whole page about reversibility which explains why trailing commas are both added and removed.

I do agree that reversibility is a nice property, which is why I dislike both adding and removing trailing commas and which is why I think that making any non-whitespace changes is a dangerous, slippery slope. (Although I definitely dislike automatic removal way, way more, so I'd be fine with giving up reversibility if it meant getting rid of the automatic removal of trailing commas.)

@azimuthdeveloper
Copy link

I think I'm yelling into the wind at this point in time but:

  • Bob wrote the page on reversibility, and a large component of the new formatter is the capability to be reversible. That's fine. Sometimes in software engineering we pursue certain things to avoid common technical pitfalls like O(n) complexity. However, it's seems strange to me that someone would write the book on something like reversibility, and then code a formatter against it. Like a middle step of asking "is this important to the language?". If it is, it should be added to something like "Dart Design Goals" (like there is for typescript: https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals). If someone made a document up like "all variables should be names for cats" and then strictly kept to that without first checking if this was something that was important, then that'd be a problem. So, did anyone else in the Dart design team or wider community consider the topic of reversibility and deem it as very important, so important that it should be more important than possibly messier code? If so, that's fine. If not, that's a problem.
  • The new formatter has been out for days, these comments have been getting heated in some parts, and yet, it seems like no updates from Bob or the core team. That's also fine, they're not at our beck and call. However it's unusual to ship something, have people be unhappy about it and ask good questions, only to have no response from the original contributor. What would happen if this was a bigger problem causing a crash or something, or a styling change that people objected to? It seems strange to drop something like this, have this issue blow up, and then have nobody come and answer about what has actually happened.
  • Not enough communication/consultation also contributed to this.

I'm not trying to troll anyone or come off as aggressive. We're all just entitled developers' who are happy to use other people's hard work after all, so we're entitled to nothing. Maybe there's a good explanation to the above, and if there's not, there's probably some things to learn. Just my 2c.

@alanfortlink
Copy link

The removal of the commas shouldn't be there, or I should be able to turn off that specifically in my pubspec. If I am explicitly adding a comma, it means I'm telling it to do something. I am all for things that bring value, but is this enforcement really bringing any value? It was great before. You want it, you add it, you don't want it, you don't add it. Please, let the developer decide.

@monoblaine
Copy link

If I am explicitly adding a comma, it means I'm telling it to do something.

THIS. Please make this feature configurable, I sincerely hated it.

@HideakiSago
Copy link

HideakiSago commented Feb 24, 2025

Opinions based on subjectivity (preference) will only lead to war, so I would like to express my opinion based on rational judgment.
Please let me know if there are any parts that lack rationality.

Introduction

When personal preferences and judgments interfere with code style, the code style becomes inconsistent.
Inconsistent code often becomes difficult to read.
That's why functions such as formatters were created and automated.
Considering this history, I think that code will be easier to read if exceptions to manual code style are eliminated as much as possible.

As munificent said, automating trailing commas does not always result in the optimal code style.
I imagine that this is why there were so many troublesome problems.
I respect them for taking on this difficult challenge.

However, new challenges always come with problems.
I wanted to face the problems in a positive manner.

Problem

Automation should make code easier to read, but there are many cases where it doesn't work as suggested here.

Here are some examples. (Line length = 120)

✅️ This is one line and is clear and desirable.

  Widget build(BuildContext context) => Text('data');

❌️ I don't think it should be like this. If I had to give a reason, it would be pointless redundancy.

  Widget build(BuildContext context) => Text(
        'data',
      );

❓️ What about this?

  Widget build(BuildContext context) => Text('data', textAlign: TextAlign.center);
  Widget build(BuildContext context) => Text(
        'data',
        textAlign: TextAlign.center,
      );

And this one?
🤔 This is no longer on one line, but with the new formatter, it looks like this.
As you can see, it is on two lines, which means it is already broken up.

  Widget build(BuildContext context) =>
      Text('data', textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 1);

✅️ This is more consistent in that it already includes the "breaks".

  Widget build(BuildContext context) => Text(
        'data',
        textAlign: TextAlign.center,
        overflow: TextOverflow.ellipsis,
        maxLines: 1,
      );

🤔 But what if there is only one argument?

  Widget build(BuildContext context) =>
      Text('long long long long long long long long long long long long long long data');

If we follow the line length rule, I think the following is more consistent, but
how about adding a comma even though there is only one argument?
It might be better to make the rule "If the argument is 1 or less, do not break the line."

  Widget build(BuildContext context) => Text(
        'long long long long long long long long long long long long long long data',
      );

This is likely to be a controversial issue, but the single line is more consistent.

  Widget build(BuildContext context) => Scaffold(body: Center(child: Text('data', textAlign: TextAlign.center)));

In this case, the line has already been broken, so I think a trailing comma should be added to all widgets.

  Widget build(BuildContext context) =>
      Scaffold(body: Center(child: Text('data', textAlign: TextAlign.center, overflow: TextOverflow.ellipsis)));

For example, how about this?
The parent Scaffold and Center have trailing commas, but the Text is on a single line.
I feel that there is no consistency here.
This rule seems reasonable at first glance when viewed from the perspective of the Widget alone.
However, if formatting a single Widget does not achieve overall consistency, there is no point in formatting it.

  Widget build(BuildContext context) => Scaffold(
    body: Center(
      child: Text('data', textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false),
    ),
  );

The following would probably be more consistent overall.

  Widget build(BuildContext context) => Scaffold(
    body: Center(
      child: Text(
        'data',
        textAlign: TextAlign.center,
        overflow: TextOverflow.ellipsis,
        maxLines: 1,
        softWrap: false,
      ),
    ),
  );

🤷🏻 This is very annoying as reported by dmoses-xh.
I think the priority of line breaks is completely wrong.

Text(
    'Some Heading',
    style: Theme.of(
      context,
    ).textTheme.titleMedium?.copyWith(color: Theme.of(context).colorScheme.surfaceContainerLowest),
),

✅️ This should prioritize the line break ., which has less binding than the argument.

Text(
  'data',
  style: Theme.of(context)
      .textTheme
      .titleMedium
      ?.copyWith(color: Theme.of(context).colorScheme.surfaceContainerLowest),
),

Or this.

Text(
  'data',
  style: Theme.of(context)
      .textTheme
      .titleMedium
      ?.copyWith(
        color: Theme.of(context)
            .colorScheme
            .surfaceContainerLowest,
      ),
),

Proposal

  • I think the rules regarding the priority of line breaks and adding trailing commas need to be reviewed.
  • I think it would be more consistent to apply line break rules to expressions and statements as a whole, rather than to widgets alone.
  • I think it's too early to fully automate this at this stage, when the discussion is still immature. I think a workaround should be provided and more discussion should be held on the format rules.

@bar4488
Copy link

bar4488 commented Feb 24, 2025

@munificent may I suggest a proposal that will let both solutions coexist:

  • Tall style will be automatically used on long line lengths like the new implementation, but will not add commas. Basically this means the formatter will never insert or delete commas.
  • in case the user added a comma - the formatter will always choose tall style and break the line.

This means that in case the user does not add any commas - the formatter will choose itself exactly like the current proposal - with the difference that it will not add any commas.

For the edge cases of short lines where the user wants to break - they will now be able to.

Personally - I like being able to add a comma to hint the formatter how I want it to behave, just like I can add a blank line between 2 statements for easier readability and the formatter won't delete it (even though you could make the same case that the formatter should decide for itself and users choice should be eliminated)

This solution respects the users choice and will also let us highlight the places the automatic formatter is still immature in (the places the user decided to insert a comma), and it respects reversibility (lines without comma will switch between one line and multiline, while lines with comma will always be multiline.

@HyperJames
Copy link

HyperJames commented Feb 24, 2025

So I am late to the party on this one but I've recently just tried the new Dart Tall Style with the recent release of Dart 3.7.0 and I will admit this is causing us a headache - mostly in the way we all read and write Dart/Flutter.

Previously forcing a trailing comma to cause a multi-line wrap was a way for us to create code that can be easily skimmed read vertically (rather than horizontally) - however this update changes all of this for shorter lines of code. I'm sure we'll get used to it but I would rather we could opt out. I do understand the issue of maintaining two formatters, however imposing this change on everyone may have been misguided. We're now having to retrain our brains on how we skim read Dart code.

@larssn
Copy link

larssn commented Feb 26, 2025

I admit that I upvoted this, but the auto-removal of trailing commas is killing me.

@munificent
Copy link
Member Author

  • The new formatter has been out for days, these comments have been getting heated in some parts, and yet, it seems like no updates from Bob or the core team.

I was out last week on vacation and the week before I was mostly out dealing with some complications from a severely broken ankle. There's no lack of caring about users here, but sometimes real life gets in the way.

@Levi-Lesches
Copy link

@munificent your tireless work and dedication are much appreciated, and we're very glad you take the time to rest when you need it. There will always be more work, but our strength, happiness, and time spent with loved ones sadly aren't as infinite.

@munificent
Copy link
Member Author

The new formatter has shipped (for better or worse! 😅) so it's time to close this issue.

However, that doesn't mean all work on the new formatter is done. If anything, now that we're getting more feedback, it means some of the work is just beginning. In order to track that feedback and future changes better, I'd like to move discussion to more specific, concrete issues.

There are a lot of comments on this issue (which I've read all of multiple times) but I think most of complaints are one of:

  • The formatter sometimes packs an argument list into one line when it would be easier to read split. I've filed Split argument lists that would otherwise fit #1660 to track that problem and various ways we could solve it. If you have feedback on that, please comment there. In particular, concrete examples of code where you feel the formatter should have split and didn't is very helpful.

  • "I want to manually add trailing commas to force things to split". That issue is Preserve trailing commas #1652. I hear you, but it's not clear to me if that's because you just really enjoy spending time deciding for each argument list whether it should split or not, or because the new formatter's rule for doing so isn't good enough and you feel the need to wrestle control away from it.

Personally, I believe that with a few fairly straightforward heuristics, we can get automated argument splitting to a good enough place where most users don't wish they could still do it by hand. After all, the whole point of the formatter is to spare you the effort of making manual formatting decisions.

If there are other problems with the new style, please do file issues. It's much easier to track multiple separate issues (which I can deduplicate as needed) than to have a single issue with hundreds of comments talking about different problems.

I'm sorry for the delay in responding. It's been a hectic couple of weeks. But don't panic. No one is going to cram a new formatter down your throat and then disappear. Yes, there will be some churn in the new formatter as we tweak style rules here and there. I'm optimistic that we can get it to a state where most of you are happy with its output.

We have a natural tendency to notice the problems, but there is a whole lot of code that the new formatter is doing a better job at. The new formatter is also easier to work on than the old one was. That's why many of those issues had been open for years and not fixed. So I think we're in a good place to keep moving forward. It will just take some time to fix bugs and tweak style rules.

In the meantime, the most helpful thing you all can do is file issues or add comments to existing issues showing real code in your app and how you wish it had been formatted. Thanks for all the comments so far and I appreciate your patience. We haven't significantly changed the formatter in nearly a decade (when the Dart ecosystem was about three orders of magnitude smaller), so it's been a learning experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests