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

dartfmt should recognize off/on delimiter comment pairs to temporarily disable reformatting #361

Closed
tlarsen4google opened this issue Jun 17, 2015 · 29 comments · Fixed by #1522

Comments

@tlarsen4google
Copy link

Since no formatter is perfect (e.g., some const/final nested collection constructs try to represent actual readable structure meaning in the code via their indentation that the formatter cannot be expected to understand and preserve), formatter off/on comment pairs should be recognized that turn off reformatting for the specified lines in between these delimiters.

// dartfmt off
Foo barBaz() => new Quux({
    'alpha.beta.gamma': [new Zuul({
      'yarrr': Abracadabra.OPEN_SESAME,
    }, {
    'toil': {
      'trouble': {
        'fireBurn': {
            'cauldronBubble': EYE_OF_NEWT,
        }
      }
    }
  })]
});
// dartfmt on

Yes, use of this construct should be discouraged and limited. Perhaps emit a warning as dartfmt is run indicating that it encountered such blocks of unformatted code?

Also, it is only reasonable to require complete constructs be present between the delimiters, and not expect the formatter to do anything reasonable when the delimiters arbitrarily break up the middle of some block level construct. For example, the formatter could produce an error, or just not be expected to do anything sane, for misuses like:

  if (someTest) {
    someThenCode();
// dartfmt off
               unformattedThenCode();
} else {
          unformattedElseCode();
// dartfmt on
    moreElseCode();
  }

At worst, such unformatted segments that span block boundaries should produced undefined results for the rest of the source file. At best, an error should be printed and formatting should stop.

@munificent
Copy link
Member

I understand the desire here, but it goes against the fundamental goals of the formatter. Its goals, in order of descending priority, are:

  1. Produce consistently formatted code.
  2. End debates about style issues in code reviews.
  3. Free users from having to think about formatting.
  4. Produce beautiful, readable output that helps users understand the intent and structure of the code.

Point number 4 is critical because if the formatter isn't good enough, people simply won't use it. In some cases, though, it can't do as good a job as a human can, because a human has semantic knowledge, like "split here to keep these two arguments together since they form a logical pair".

A "let the human do this" annotation would give an escape hatch for those cases. Unfortunately, it does so at the expect of the first three goals. As soon as you have these annotations, you're right back to quibbling about how the code inside them should be formatted. Worse, you now also open the metargument about where in the code you should be allowed to use the annotations. I certainly don't want to start more style debates!

The set of places where a human could better than the formatter shrinks with every release. Note that I use "could" deliberately here. While a human can in theory do a better job hand-formatting sometimes, we're also error-prone.

Your carefully hand-formattted example has two erroneous spaces before 'cauldronBubble'. Today, the formatter fixes that error and formats your example to:

Foo barBaz() => new Quux({
      'alpha.beta.gamma': [
        new Zuul({
          'yarrr': Abracadabra.OPEN_SESAME,
        }, {
          'toil': {'trouble': {'fireBurn': {'cauldronBubble': EYE_OF_NEWT,}}}
        })
      ]
    });

Personally, I prefer that over your hand formatting. If we take your suggestion on #223 (which I like but I need to see how it plays out in practice), you get:

Foo barBaz() => new Quux({
      'alpha.beta.gamma': [
        new Zuul({
          'yarrr': Abracadabra.OPEN_SESAME,
        }, {
          'toil': {
            'trouble': {
              'fireBurn': {'cauldronBubble': EYE_OF_NEWT,}
            }
          }
        })
      ]
    });

I think that's the best formatting of all.

Finally—and this is the least important motivation—not having an escape hatch keeps me honest. Since the formatter will touch all of your code, it forces me to work very hard to get it to do so in a way you like. If you could just turn off formatting, it would be easy for me to close bugs with just, "Eh, just don't format that bit."

@tlarsen4google
Copy link
Author

Sorry, but what is actually happening is that our team is simply not running dartfmt on entire files because they have special huge (512 entries) byte tables in Lists that are getting expanded to ridiculous single item per line reformats.

So, lack of this feature is causing entire files, with lots of other code that could benefit from the standardization of Dart formatting, to completely be hand-formatted because of regions in the file that dartfmt will never be able to handle properly.

@tlarsen4google
Copy link
Author

Perhaps there at least need to be ways to control the aggressiveness of dartfmt?
E.g. something like this (though I'm just making up the format as an example and don't care what it actually ends up looking like):

// dartfmt: compact structured literals
...
// dartfmt: reset to defaults

// dartfmt: expanded structured literals
...
// dartfmt: reset to defaults

My point is that there are places where dartfmt actually harms the readability of the resulting code, and both possibilities are still legal Dart style results.

@tlarsen4google
Copy link
Author

Real world example:

  /**
   * Basic one-byte 608 CC char set, mostly ASCII.
   * Indexed by (char-0x20).
   */
  static const List<int> ccUtfTable0 = const <int> [
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,     //   ! " # $ % & '
    0x28, 0x29,                                         // ( )
    0xE1,       // 2A: 225 'á' "Latin small letter A with acute"
    0x2b, 0x2c, 0x2d, 0x2e, 0x2f,     //       + , - . /
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,     // 0 1 2 3 4 5 6 7
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,     // 8 9 : ; < = > ?
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,     // @ A B C D E F G
    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,     // H I J K L M N O
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,     // P Q R S T U V W
    0x58, 0x59, 0x5a, 0x5b,                             // X Y Z [
    0xE9,       // 5C: 233 'é' "Latin small letter E with acute"
    0x5d,                                               //           ]
    0xED,       // 5E: 237 'í' "Latin small letter I with acute"
    0xF3,       // 5F: 243 'ó' "Latin small letter O with acute"
    0xFA,       // 60: 250 'ú' "Latin small letter U with acute"
    0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,     //   a b c d e f g
    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,     // h i j k l m n o
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,     // p q r s t u v w
    0x78, 0x79, 0x7a,                                   // x y z
    0xE7,       // 7B: 231 'ç' "Latin small letter C with cedilla"
    0xF7,       // 7C: 247 '÷' "Division sign"
    0xD1,       // 7D: 209 'Ñ' "Latin capital letter N with tilde"
    0xF1,       // 7E: 241 'ñ' "Latin small letter N with tilde"
    0x25A0      // 7F:         "Black Square" (NB: 2588 = Full Block)
  ];

@lyceel
Copy link

lyceel commented Jun 17, 2015

Regarding objective 2 above, I can tell you that on our team, the introduction of dartfmt has created a great deal of debate over style issues in code reviews. There have been improvements, and we can live with some of the idiosyncrasies. The consistency and automatic nature are going to be worth most of the minor niggles going forward.

However, we really do need a way to preserve format on specific constructs like Todd's example above. All of the humans on the team agree that the "correct" format is the way that it is formatted above. The only disagreement comes from dartfmt. It's for cases like this that we need a way to overrule dartfmt.

@EmmanuelOga
Copy link

This would be a useful addition IMO.

I had a similar need when working with a different language that also included a formatter. The code had static Strings for specifying SQL queries. In Dart, that code would look something like this:

class Something {
  static String TableName = "SomeTable";

  // dartfmt off
  static String query1 = "SELECT * FROM ${TableName}"
                        " WHERE Blah Blah"
                        " HAVING Blah Blah";

  static String query2 = "SELECT * FROM ${TableName}"
                        " WHERE Blah Blah";
  // dartfmt on
}

While I was happy with the standard auto-formatting all across the app, I used some special alignment for the SQL queries that helped a big deal to make the embedded SQL queries readable.

If I hadn't had a way to turn off the formatter on sections of the file, my custom formatting would had been impossible.

@munificent
Copy link
Member

So, lack of this feature is causing entire files, with lots of other code that could benefit from the standardization of Dart formatting, to completely be hand-formatted because of regions in the file that dartfmt will never be able to handle properly.

It sounds to me like the poor formatting of list literals is causing you to not use dartfmt, not the ability to prevent certain areas from being formatted.

In this example, I believe #139 would solve your problem.

I used some special alignment for the SQL queries that helped a big deal to make the embedded SQL queries readable.

Here, I think putting the space on the previous string literal and then lining up the adjacent strings would be enough to get what you want, right?

@tlarsen4google
Copy link
Author

No, #139 is not sufficient. Please see the closed captioning 608 character set table I added. dartfmt would have to simply leave that section alone for information to not be lost.

@EmmanuelOga
Copy link

@munificent my SQL code was just an example. The point is that sometimes you can't make the formatter work for your content.

Sometimes your want to embed content that, even when expressed as Dart code, can be made more readable when formatted using rules other than the default dartfmt ones.

@lyceel
Copy link

lyceel commented Jun 17, 2015

The List literal above is just one example. What if this particular case didn't have comments after each
line? What if we had a Map literal, or an entire JSON-like structure that was formatted in a non-standard way for enhanced readability? Is it really possible to have a special case for every contingency?

So far, the argument against this idea has been that it makes it too easy for someone to turn off the formatter at the top of the file and avoid formatting. In this case, why would they bother to run the formatter at all? In an engineering team setting, either the team decides to use the formatter or they don't. If a member of a team that has adopted the formatter decides to turn off formatting for a file, they'd have to justify this to the rest of the team (via a code review or something similar). If the team agrees that it's the right thing to do, why should dartfmt not give them that choice?

The argument to not support such a control seems arbitrary to me. A good tool leaves decisions like this up to the developers that use it. The clang-format tool is one example that provides this option: http://goo.gl/mipb9i

@munificent
Copy link
Member

Please see the closed captioning 608 character set table I added.

Your example was what I was referring to when I said #139 would probably fix it. I have a local implementation of that and it outputs:

class Foo {
  /**
   * Basic one-byte 608 CC char set, mostly ASCII.
   * Indexed by (char-0x20).
   */
  static const List<int> ccUtfTable0 =
      const <int>[
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, //   ! " # $ % & '
    0x28, 0x29, // ( )
    0xE1, // 2A: 225 'á' "Latin small letter A with acute"
    0x2b, 0x2c, 0x2d, 0x2e, 0x2f, //       + , - . /
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0 1 2 3 4 5 6 7
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, // 8 9 : ; < = > ?
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // @ A B C D E F G
    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // H I J K L M N O
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // P Q R S T U V W
    0x58, 0x59, 0x5a, 0x5b, // X Y Z [
    0xE9, // 5C: 233 'é' "Latin small letter E with acute"
    0x5d, //           ]
    0xED, // 5E: 237 'í' "Latin small letter I with acute"
    0xF3, // 5F: 243 'ó' "Latin small letter O with acute"
    0xFA, // 60: 250 'ú' "Latin small letter U with acute"
    0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, //   a b c d e f g
    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // h i j k l m n o
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // p q r s t u v w
    0x78, 0x79, 0x7a, // x y z
    0xE7, // 7B: 231 'ç' "Latin small letter C with cedilla"
    0xF7, // 7C: 247 '÷' "Division sign"
    0xD1, // 7D: 209 'Ñ' "Latin capital letter N with tilde"
    0xF1, // 7E: 241 'ñ' "Latin small letter N with tilde"
    0x25A0 // 7F:         "Black Square" (NB: 2588 = Full Block)
  ];
}

It could be better if it lined up the line comments (by implementing #48), but it would also be easy to just tweak the spacing after the //:

class Foo {
  /**
   * Basic one-byte 608 CC char set, mostly ASCII.
   * Indexed by (char-0x20).
   */
  static const List<int> ccUtfTable0 =
      const <int>[
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, //       ! " # $ % & '
    0x28, 0x29, //                                         ( )
    0xE1, //    2A: 225 'á' "Latin small letter A with acute"
    0x2b, 0x2c, 0x2d, 0x2e, 0x2f, //                             + , - . /
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, //     0 1 2 3 4 5 6 7
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, //     8 9 : ; < = > ?
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, //     @ A B C D E F G
    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, //     H I J K L M N O
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, //     P Q R S T U V W
    0x58, 0x59, 0x5a, 0x5b, //                             X Y Z [
    0xE9, //    5C: 233 'é' "Latin small letter E with acute"
    0x5d, //                                                         ]
    0xED, //    5E: 237 'í' "Latin small letter I with acute"
    0xF3, //    5F: 243 'ó' "Latin small letter O with acute"
    0xFA, //    60: 250 'ú' "Latin small letter U with acute"
    0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, //           a b c d e f g
    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, //     h i j k l m n o
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, //     p q r s t u v w
    0x78, 0x79, 0x7a, //                                   x y z
    0xE7, //    7B: 231 'ç' "Latin small letter C with cedilla"
    0xF7, //    7C: 247 '÷' "Division sign"
    0xD1, //    7D: 209 'Ñ' "Latin capital letter N with tilde"
    0xF1, //    7E: 241 'ñ' "Latin small letter N with tilde"
    0x25A0 //   7F:         "Black Square" (NB: 2588 = Full Block)
  ];
}

I don't want to sound cheeky here, but I will also point out that your original carefully hand-formatted example has two formatting errors in it.

What if this particular case didn't have comments after each
line?

Well, you could add them. If you want your code to be consistently formatted and tuned to your liking, the easiest way is to let the formatter do the dirty work and work with it by tweaking your code a bit so that its output looks how you want.

What if we had a Map literal, or an entire JSON-like structure that was formatted in a non-standard way for enhanced readability? Is it really possible to have a special case for every contingency?

No, I try to avoid special case rules. What I like are general rules that solve a large number of cases. Here, the general rule seems to be that large collection literals often have some internal structure that the author wants to highlight. Letting the formatter preserve that seems reasonable to me.

There aren't an infinite number of contingencies. All we have to do is look at the world's actual Dart code and see how the formatter handles it. I'm sure it will produce weird output it, I don't know, you make all of your identifiers two letters and give every class twenty type parameters. But, in practice, people don't do that, so the formatter doesn't have to optimize for that kind of code.

So far, the argument against this idea has been that it makes it too easy for someone to turn off the formatter at the top of the file and avoid formatting.

No, the argument is that disabling formatting makes code less consistent, which is the primary goal of the formatter.

If the team agrees that it's the right thing to do, why should dartfmt not give them that choice?

It does: just don't pass that file to the list of files you want it to format.

A good tool leaves decisions like this up to the developers that use it. The clang-format tool is one example that provides this option.

clang-format has different goals and—more importantly—different constraints than the Dart formatter. When you're trying to be a useful tool for the millions of C/C++ developers maintaining billions of lines of code written under a variety of style guides over the past 50 years, yeah, you have to be configurable.

But, if you're a new language that is trying to improve the consistency of a relatively small corpus of code, configuration hinders your goals. gofmt has no configurability nor any ability to disable it on sections of code.

@tlarsen4google
Copy link
Author

Ah, but #golang takes a very hard line by:

  • having no compiler warnings at all (if it is worth output, it is an error)
    (compared with Dart having problems, warnings, and hints)
  • enforcing certain style decisions at the language level (e.g. placement of { and })
    (versus if (foo) { bar = baz; } being perfectly valid in Dart, from a compiler perspective)

I don't see Dart ever heading in that strictness direction, so I don't think the comparison to gofmt is completely apt. Is the strictness of #golang an actual goal for Dart?

@munificent
Copy link
Member

I don't see Dart ever heading in that strictness direction, so I don't think the comparison to gofmt is completely apt. Is the strictness of #golang an actual goal for Dart?

I can't speak for the entire language, but when it comes to style, yes, it is. It's not that we want to be strict it's that we want to reduce the set of things you have to do manually to zero.

@dart-lang dart-lang deleted a comment from phlebotinum Mar 20, 2020
@AldoMX
Copy link

AldoMX commented May 11, 2021

I reached here trying to copy the examples from ColorFilter:
https://api.flutter.dev/flutter/dart-ui/ColorFilter/ColorFilter.matrix.html

My workaround was to add a comment at the end of the matrices, although I would had liked to preserve the padding between elements:

  static const _invertMatrix = <double>[
    -1, 0, 0, 0, 255,
    0, -1, 0, 0, 255,
    0, 0, -1, 0, 255,
    0, 0, 0, 1, 0,
    //
  ];
  static const _sepiaMatrix = <double>[
    0.393, 0.769, 0.189, 0, 0,
    0.349, 0.686, 0.168, 0, 0,
    0.272, 0.534, 0.131, 0, 0,
    0, 0, 0, 1, 0,
    //
  ];
  static const _grayscaleMatrix = <double>[
    0.2126, 0.7152, 0.0722, 0, 0,
    0.2126, 0.7152, 0.0722, 0, 0,
    0.2126, 0.7152, 0.0722, 0, 0,
    0, 0, 0, 1, 0,
    //
  ];

@aktxyz
Copy link

aktxyz commented Apr 24, 2022

soooo much better to add random blank comments to poke the formatter into doing something less annoying ... than to be able to turn on/off formatting

@munificent munificent reopened this Mar 20, 2024
@munificent
Copy link
Member

Casting a resurrection spell!

After many many discussions over the years, and recent aspirations to get the Flutter repositories using dart format and the upcoming style (#1253), I'm persuaded that it's an overall net win to support opting out regions of code from formatting.

I still believe that it's a feature that should be rarely used. And, certainly, if you find yourself arguing with someone in a code review about whether or not a given region should be hand- or automatically formatted, there are more valuable things you could be doing with your life.

But for some kinds of code with complex structure, it may make sense to be able to, um, artisanally format it. And, in order to ease the migration to the new style (or other potential future style changes), it might be worth being able to control which parts of a program get reformatted and when.

It's yet to be determined what the markers to opt in or out would look like, or how opted out code interacts with the surrounding indentation.

@Hixie
Copy link

Hixie commented Jun 6, 2024

At the risk of starting a bike shed argument... The current convention is to put trailing // on a line to trigger as much of a disabling of the formatter as it currently allows (largely this is used to control where line breaks happen in lists, if I understand correctly). This seems both intuitive (as demonstrated by people already doing it) and very low-profile (certainly much less disruptive than the // clang-format off style used by LLVM). For example (taking examples from one of the issues linked above):

static const List<int> RAYS = [
     17,  0,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0,  0, 15, 0, //
      0, 17,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0, 15,  0, 0, //
      0,  0, 17,  0,  0,  0,  0, 16,  0,  0,  0,  0, 15,  0,  0, 0, //
      0,  0,  0, 17,  0,  0,  0, 16,  0,  0,  0, 15,  0,  0,  0, 0, //
      0,  0,  0,  0, 17,  0,  0, 16,  0,  0, 15,  0,  0,  0,  0, 0, //
      0,  0,  0,  0,  0, 17,  0, 16,  0, 15,  0,  0,  0,  0,  0, 0, //
      0,  0,  0,  0,  0,  0, 17, 16, 15,  0,  0,  0,  0,  0,  0, 0, //
      1,  1,  1,  1,  1,  1,  1,  0, -1, -1,  -1,-1, -1, -1, -1, 0, //
      0,  0,  0,  0,  0,  0,-15,-16,-17,  0,  0,  0,  0,  0,  0, 0, //
      0,  0,  0,  0,  0,-15,  0,-16,  0,-17,  0,  0,  0,  0,  0, 0, //
      0,  0,  0,  0,-15,  0,  0,-16,  0,  0,-17,  0,  0,  0,  0, 0, //
      0,  0,  0,-15,  0,  0,  0,-16,  0,  0,  0,-17,  0,  0,  0, 0, //
      0,  0,-15,  0,  0,  0,  0,-16,  0,  0,  0,  0,-17,  0,  0, 0, //
      0,-15,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,-17,  0, 0, //
    -15,  0,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,  0,-17, 0, //
  ];

const [4580,4470, 630,520, 630,520, 580,1670, 630,520, 580,1670, 630,520, 580,1670, 630,520, 630,1620, 630,520, 580,570, 580,520, 630,1670, 580,520, 630,1670, 580,520, 680,1620, 580,1670, 630,520, 580,520, 630,520, 630,520, 580,570, 580,520, 630,520, 580,570, 580,570, 580,1670, 580,570, 580,1670, 580,570, 580,1670, 580]; //

@munificent
Copy link
Member

The current convention is to put trailing // on a line to trigger as much of a disabling of the formatter as it currently allows

Actually, any line comment will trigger this behavior, not just an empty one.

(largely this is used to control where line breaks happen in lists, if I understand correctly).

It works inside all collection literals—list, map, set, record—but not anywhere else.

(certainly much less disruptive than the // clang-format off style used by LLVM).

I know we've discussed this before but I think it's good to have it on the issue comments. Personally, I do prefer something more explicit like // clang-format off's style. My reasoning is:

I hope this feature will be relatively rarely used. (We likely disagree on this. :) ) Assuming that's true, then users may infrequently encounter these markers and be confused by what they are for. If I saw an empty // and didn't know better, I'd just delete it. Then probably be confused when the formatting changed. A more explicit marker comment will help a reader understand what the comment is for.

Some comparisons with other popular formatters:

clang-format

Use marker comments to disable and resume formatting. Everything between the pair of comments retains its original format:

// clang-format off
code();
// clang-format on

prettier

Use a leading marker comment to disable formatting "the next node in the abstract syntax tree":

// prettier-ignore
code();

black

Use a pair of comments to disable formatting between them:

# fmt: off
code()
# fmt: on

Use a single trailing comment to disable formatting just that line:

code() # fmt: skip

spotless

Use a pair of comments:

// spotless:off
code();
// spotless:on

Users can globally configure the comments used to turn formatting off and on.

Google's Java formatter

Deliberately doesn't support disabling formatting on regions of code.

gofmt

Deliberately doesn't support disabling formatting on regions of code.

@Hixie
Copy link

Hixie commented Jul 27, 2024

any line comment will trigger this behavior, not just an empty one.

Sure, but the convention people use is to just have an empty comment. (I'm describing people's behaviour, not the tool's behaviour.)

I hope this feature will be relatively rarely used. (We likely disagree on this. :) )

It certainly will be more rarely used if enabling it is more disruptive than just having the formatter do a bad job!

What I'm trying to optimize for most of all here is code readability, with a secondary goal of letting people use an autoformatter so that by default they don't have to think about how to format the code.

Having a disruptive opt-out fails the code readability goal, which then prevents the secondary goal (since we can't use a formatter if it's not achieving the primary goal).

Assuming that's true, then users may infrequently encounter these markers and be confused by what they are for.

I would agree with this in the abstract, except for the point that people are already using // for this. That's why I brought up the existing convention. People aren't going to be any more confused.

Also I'm not really that worried about them being confused to be honest. As you say:

If I saw an empty // and didn't know better, I'd just delete it. Then probably be confused when the formatting changed.

But you'd then quickly understand oh, the formatting changed. That must be why the comment was there.

A more explicit marker comment will help a reader understand what the comment is for.

The goal is for the reader to not think about this at all, though.

Some comparisons with other popular formatters:

Other formatters suck. I'm hoping we do a lot better. :-)

@benthillerkus
Copy link

To be honest I know enough people who would clean up empty comments and then don't run the formatter.
Or - I do this too - format files just before closing them. Some may even have set up a git commit hook to format for them.
In all those cases you wouldn't have that 'aha' moment of 'this broke deliberate formatting'.

Therefore I strongly prefer the clang style format on / format off comments.

@Hixie
Copy link

Hixie commented Jul 28, 2024

To be clear, I have no objection to there also being an explicit on-off syntax.

@munificent
Copy link
Member

any line comment will trigger this behavior, not just an empty one.

Sure, but the convention people use is to just have an empty comment. (I'm describing people's behaviour, not the tool's behaviour.)

No, it's definitely not the convention to use an empty comment. It may be inside the Flutter repo, but if so, that convention has not escaped into the wild. I scraped a large corpus of open source Flutter widgets and pub packages:

-- Comments (8564 total) --
   8242 ( 96.240%): Non-empty  ================================================
    168 (  1.962%): Empty      =
    154 (  1.798%): Both       =

Of the 8564 collection literals I found that contained line comments, almost all of them only contained non-empty ones. (The few that contain both // and other non-empty // comments are because the // is acting as a paragraph separator for other non-empty comments.)

The reason I added this feature is that I found that most collection literals where bespoke newlines were helpful were ones that already happened to contain meaningful comments. So what I did was make those comments be the signal. I suspect that many users benefit from this feature without even realizing they have explicitly opted into it.

But the feature proposed here is a different because it is about opting entire regions of code out of formatting altogether. The current feature only works within collection literals and only retains existing newlines. It otherwise still contains to format the code. (In fact, I intend to have both features. An explicit way to opt out an arbitrary region of code from all formatting and the existing feature where a line comment inside a collection literal preserves the original newlines.)

I hope this feature will be relatively rarely used. (We likely disagree on this. :) )

It certainly will be more rarely used if enabling it is more disruptive than just having the formatter do a bad job!

What I'm trying to optimize for most of all here is code readability, with a secondary goal of letting people use an autoformatter so that by default they don't have to think about how to format the code.

Having a disruptive opt-out fails the code readability goal, which then prevents the secondary goal (since we can't use a formatter if it's not achieving the primary goal).

An opt-out comment that clearly states why it exists does not seem very disruptive to me. Aren't you usually the one arguing that being more explicit at the cost of brevity is usually the right thing?

Assuming that's true, then users may infrequently encounter these markers and be confused by what they are for.

I would agree with this in the abstract, except for the point that people are already using // for this. That's why I brought up the existing convention. People aren't going to be any more confused.

I don't see any evidence that this is true.

Some comparisons with other popular formatters:

Other formatters suck. I'm hoping we do a lot better. :-)

I do too, but innovating in the text placed after // to opt out of formatting is probably not the most impactful place to spend our innovation budget.

I would make the opt out explicit and clear and then focus on making the formatting so good that users rarely want to opt out.

@Hixie
Copy link

Hixie commented Aug 2, 2024

I don't think trailing // is a Flutter invention. After all, Flutter doesn't generally use autoformatting at all currently, so it wouldn't be useful for that purpose there. If I'm wrong about it being a widely used convention then fair enough.

I'm not really sure I understand what you're proposing though. Maybe it would be useful to look at concrete examples of places where we'd disable the formatter currently, and see what it would look like with your proposed syntaxes?

Here are comments in this issue where people have examples they'd like to hand-format, if that helps:
#361 (comment)
#361 (comment)
#361 (comment)
#361 (comment)

@munificent
Copy link
Member

Maybe it would be useful to look at concrete examples of places where we'd disable the formatter currently, and see what it would look like with your proposed syntaxes?

For the comments you linked, here's my thoughts:

This is a good one to just explicitly disable the formatting:

static const List<int> RAYS = [
  // dart format off
   17,  0,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0,  0, 15, 0,
    0, 17,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0, 15,  0, 0,
    0,  0, 17,  0,  0,  0,  0, 16,  0,  0,  0,  0, 15,  0,  0, 0,
    0,  0,  0, 17,  0,  0,  0, 16,  0,  0,  0, 15,  0,  0,  0, 0,
    0,  0,  0,  0, 17,  0,  0, 16,  0,  0, 15,  0,  0,  0,  0, 0,
    0,  0,  0,  0,  0, 17,  0, 16,  0, 15,  0,  0,  0,  0,  0, 0,
    0,  0,  0,  0,  0,  0, 17, 16, 15,  0,  0,  0,  0,  0,  0, 0,
    1,  1,  1,  1,  1,  1,  1,  0, -1, -1,  -1,-1, -1, -1, -1, 0,
    0,  0,  0,  0,  0,  0,-15,-16,-17,  0,  0,  0,  0,  0,  0, 0,
    0,  0,  0,  0,  0,-15,  0,-16,  0,-17,  0,  0,  0,  0,  0, 0,
    0,  0,  0,  0,-15,  0,  0,-16,  0,  0,-17,  0,  0,  0,  0, 0,
    0,  0,  0,-15,  0,  0,  0,-16,  0,  0,  0,-17,  0,  0,  0, 0,
    0,  0,-15,  0,  0,  0,  0,-16,  0,  0,  0,  0,-17,  0,  0, 0,
    0,-15,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,-17,  0, 0,
  -15,  0,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,  0,-17, 0,
  // dart format on
];

Likewise with:

  static const _invertMatrix = <double>[
    // dart format off
    -1,  0,  0, 0, 255,
     0, -1,  0, 0, 255,
     0,  0, -1, 0, 255,
     0,  0,  0, 1,   0,
    // dart format on
  ];
  static const _sepiaMatrix = <double>[
    // dart format off
    0.393, 0.769, 0.189, 0, 0,
    0.349, 0.686, 0.168, 0, 0,
    0.272, 0.534, 0.131, 0, 0,
    0,     0,     0,     1, 0,
    // dart format on
  ];
  static const _grayscaleMatrix = <double>[
    // dart format off
    0.2126, 0.7152, 0.0722, 0, 0,
    0.2126, 0.7152, 0.0722, 0, 0,
    0.2126, 0.7152, 0.0722, 0, 0,
    0,      0,      0,      1, 0,
    // dart format on
  ];

(I am interested in exploring automatically aligning columns in collection literals if all elements are number literals, but at least until then, manual formatting makes sense for these examples.)

I don't think any manual formatting is worth it for the next example:

class Something {
  static String TableName = "SomeTable";

  static String query1 =
      "SELECT * FROM ${TableName}"
      " WHERE Blah Blah"
      " HAVING Blah Blah";

  static String query2 =
      "SELECT * FROM ${TableName}"
      " WHERE Blah Blah";
}

The new formatter does a better job of aligning adjacent strings.

  /// Basic one-byte 608 CC char set, mostly ASCII.
  /// Indexed by (char-0x20).
  static const List<int> ccUtfTable0 = const <int> [
    // dart format off
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,     //   ! " # $ % & '
    0x28, 0x29,                                         // ( )
    0xE1,       // 2A: 225 'á' "Latin small letter A with acute"
    0x2b, 0x2c, 0x2d, 0x2e, 0x2f,                       //       + , - . /
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,     // 0 1 2 3 4 5 6 7
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,     // 8 9 : ; < = > ?
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,     // @ A B C D E F G
    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,     // H I J K L M N O
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,     // P Q R S T U V W
    0x58, 0x59, 0x5a, 0x5b,                             // X Y Z [
    0xE9,       // 5C: 233 'é' "Latin small letter E with acute"
    0x5d,                                               //           ]
    0xED,       // 5E: 237 'í' "Latin small letter I with acute"
    0xF3,       // 5F: 243 'ó' "Latin small letter O with acute"
    0xFA,       // 60: 250 'ú' "Latin small letter U with acute"
    0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,           //   a b c d e f g
    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,     // h i j k l m n o
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,     // p q r s t u v w
    0x78, 0x79, 0x7a,                                   // x y z
    0xE7,       // 7B: 231 'ç' "Latin small letter C with cedilla"
    0xF7,       // 7C: 247 '÷' "Division sign"
    0xD1,       // 7D: 209 'Ñ' "Latin capital letter N with tilde"
    0xF1,       // 7E: 241 'ñ' "Latin small letter N with tilde"
    0x25A0      // 7F:         "Black Square" (NB: 2588 = Full Block)
    // dart format on
  ];

And this is one where manually formatting might be worth it. Though, if it were me, I'd probably do:

  /// Basic one-byte 608 CC char set, mostly ASCII.
  /// Indexed by (char-0x20).
  static const List<int> ccUtfTable0 = const <int> [
    for (var i = 0x20; i <= 0x27; i++) i, // '!' '"' '#' '$' '%' '&' "'"
    0x28, 0x29, // '(' ')'
    0xE1, // 2A: 225 'á' "Latin small letter A with acute"
    for (var i = 0x2b; i <= 0x2f; i++) i, // '+' ',' '-' '.' '/'
    for (var i = 0x30; i <= 0x3a; i++) i, // '0' - '9'
    for (var i = 0x3b; i <= 0x40; i++) i, // ':' ';' '<' '=' '>' '?' '@'
    for (var i = 0x41; i <= 0x5a; i++) i, // 'A' - 'Z'
    0x5b, // '['
    0xE9, // 5C: 233 'é' "Latin small letter E with acute"
    0x5d, // ']'
    0xED, // 5E: 237 'í' "Latin small letter I with acute"
    0xF3, // 5F: 243 'ó' "Latin small letter O with acute"
    0xFA, // 60: 250 'ú' "Latin small letter U with acute"
    for (var i = 0x61; i <= 0x7a; i++) i, // 'a' - 'z'
    0xE7, // 7B: 231 'ç' "Latin small letter C with cedilla"
    0xF7, // 7C: 247 '÷' "Division sign"
    0xD1, // 7D: 209 'Ñ' "Latin capital letter N with tilde"
    0xF1, // 7E: 241 'ñ' "Latin small letter N with tilde"
    0x25A0 // 7F:         "Black Square" (NB: 2588 = Full Block)
  ];

Much less chance of a typo this way.

@Hixie
Copy link

Hixie commented Aug 9, 2024

for the last one I might agree, except you can't do that with const literals.

Most of the above are pretty reasonable.

The _invertMatrix/_sepiaMatrix/_grayscaleMatrix one is borderline for me, that's a lot of verbosity telling the formatter to go away (25% of the lines are formatter directives). A lot of the examples I've run into in Flutter code where I want to temporarily disable formatting are even shorter (e.g. just if statement expressions that run across two lines), where the ratio would be even worse.

Consider e.g.:

  @override
  double x(double time) {
    // dart format off
    return _c1 * math.pow(math.e, _r1 * time) +
           _c2 * math.pow(math.e, _r2 * time);
    // dart format on
  }

  @override
  double dx(double time) {
    // dart format off
    return _c1 * _r1 * math.pow(math.e, _r1 * time) +
           _c2 * _r2 * math.pow(math.e, _r2 * time);
    // dart format on
  }

It starts getting really disruptive IMHO.

Contrast:

@override
double x(double time) {
  return _c1 * math.pow(math.e, _r1 * time) +  	        //
     	 _c2 * math.pow(math.e, _r2 * time);            //
}
 
@override
double dx(double time) {
  return _c1 * _r1 * math.pow(math.e, _r1 * time) +     //
  	 _c2 * _r2 * math.pow(math.e, _r2 * time);      //
}

@munificent
Copy link
Member

A lot of the examples I've run into in Flutter code where I want to temporarily disable formatting are even shorter (e.g. just if statement expressions that run across two lines), where the ratio would be even worse.
Consider e.g.:

  @override
  double x(double time) {
    // dart format off
    return _c1 * math.pow(math.e, _r1 * time) +
           _c2 * math.pow(math.e, _r2 * time);
    // dart format on
  }

  @override
  double dx(double time) {
    // dart format off
    return _c1 * _r1 * math.pow(math.e, _r1 * time) +
           _c2 * _r2 * math.pow(math.e, _r2 * time);
    // dart format on
  }

When the code in question is shorter, the value of manual formatting is also relatively smaller. In this case, I'd just let the formatter do its thing:

@override
double dx(double time) {
  return _c1 * _r1 * math.pow(math.e, _r1 * time) +
      _c2 * _r2 * math.pow(math.e, _r2 * time);
}

Sure the two parallel clauses don't line up but... I think a reader can still figure out what's going on without much effort.

@munificent
Copy link
Member

This is now implemented using // dart format off and // dart format on comments. It's only supported under the new tall style (and I have no intention to implement it under the old style), so it's not really accessible until the "tall-style" experiment ships.

@Hixie
Copy link

Hixie commented Aug 14, 2024

Sure the two parallel clauses don't line up but... I think a reader can still figure out what's going on without much effort.

I've literally had bugs that I hadn't noticed until lining up this kind of expression. The value of my code working is infinitely higher than anything the formatter can give me. :-)

@munificent
Copy link
Member

The value of my code working is infinitely higher than anything the formatter can give me. :-)

Heh, fair. :)

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

Successfully merging a pull request may close this issue.

8 participants