Why is Prettier rock solid?
mrmr.io
mrmr.io
https://github.com/prettier/prettier/issues/15942
My only bad experience with prettier, besides the incredible slowness (orders of magnitude slower than ruff)
Which comment are you referring to? The release notes sound practical
I sort of misspoke, the comment itself is not silly, just the fact that it was that point that got a resolution.
1: Suggested package: sillier.
This is such an insane position to take for a maintainer. Don't be idealistic and silly. If you break compatibility, it is on you. No one cares that you are right.
The Linus Torvalds "we do not break the user-space, ever" mantra should be a best practice for every open source programmer, and user-space in this case is "what the rest of the world is doing".
I think that has to forever live in tension with its opposite. I mean, every opensource project also lives or dies based on the volunteered time of its maintainers. If "being be idealistic and silly" is needed to keep maintainers around, then its not silly.
https://github.com/prettier/prettier/issues/15956
and to be honest, it didn't look like silly to me :) It was an interesting read for me, who as a maintainer, I tend to give more importance to the official statements such as in this case written recommendations of the source company that defined the new format.
This person is not just a good engineer but a solid politician.
Ruff is based on the same foundations that Biome (https://biomejs.dev/). Although Biome doesn't support all languages that Prettier supports, you should give a try, it is fast.
The tsconfig.json files tsc itself generates and parse are definitionally JSONC. TypeScript config files are JSONC. TypeScript itself via `tsc --init` creates a file absolutely loaded with comments.
It’s clearly the other tools that can’t handle JSONC that are the broken part. They can’t handle configs that typescript itself generates.
The contract for “breaking” here isn’t between the tool and every single poorly written tool in existence. It’s between Prettier and the config file, and it’s correctly encoding the config file.
It's not a "breaking change" if a library continues to do exactly what it's advertised as doing in a completely compatible way and already broken code downstream at no fault of the authors suddenly doesn't like it.
Say a JPEG library added an additional and completely harmless metadata field - and then some JPEG parser explodes because it had hardcoded metadata fields. That's not a breaking change on the JPEG libraries part. That's just a bug on the parsers part.
Also your analogy is wrong and not what is happening here. Suppose that a JPEG encoder encoded files as png when the flag is `--filetype=unknown`. Then in a patch release the behavior changes so that "unknown" means jpeg. That's closer to what happened here.
> Dig into past research.
> If you're excited about an idea, it's super tempting to sit down an immediately get going. But you shouldn't do that until you've done some cursory research about how people have solved it before. Spending a few days researching the topic always completely changes how I am going to solve it.
https://archive.jlongster.com/How-I-Became-Better-Programmer
My workflow is:
* Learn the concept
* Implement it
* Research prior art
* Throw away my first implementation and rewrite it properly
(Edit: formatting)
- Sometimes you hack, and you end up with a mess. And then the lesson you learn is to study the prior art first.
- Sometimes you spin your wheels reading stuff that isn't actually applicable to the problem you have. Then the lesson learned is to "do the simplest thing that could work", "solve 80% of the problem with 20% of the effort", etc.
So to me, programming is really a cycle of learning, and you can't really generalize the order of doing things ... It's iterative stumbling and learning, in both orders :)
It’s very well tested and easy to add cases too - just add an input and an output so that may be why it seems so solid, just whenever there is an issue people fix it.
I think the main thing is that Meta maintains it which probably is why it’s so solid.
https://github.com/prettier/prettier/issues/15476
Prettier moves ts-ignore comments which can cause TypeScript errors:
https://github.com/prettier/prettier/issues/15876
Interpreting nested CSS functions' "-" as minus and inserting a space:
[1]: https://www.npmjs.com/package/prettier
In my experience, the number of open issues not only correlates with popularity, but how crappy the language is. Javascript projects, with its myriad of dependencies and attracting junior, inexperienced devs, tend to accumulate a great number of bugs.
For reference, curl has 24 open issues (4k closed), it is a couple orders of magnitude more complex AND more used than prettier.
I see this way of thinking around CVEs too. I think it’s a mistake of making data-driven decisions based on noise rather than signals. It sounds good when you have a comparative number to go on.
"Gofmt's style is no one's favorite, yet gofmt is everyone's favorite." I guess.
Some are years old! I think the more popular languages tend to be better supported so you probably don’t know about it but prettier is a massive monolith with support for many languages and use cases. Sometimes there are bugs that mangle code
That issue has been open for 7 years.
That’s possibly why React and Go and Prettier continue to flourish. While the maintainers have had a lot of hard technical problems to solve, they didn’t need to worry about making rent or burning their weekends triaging issues instead of spending time with family. Many of the original authors are no longer working on the projects, but they’ve been replaced by other Google and Meta employees. This had a bigger impact than basing Go on a paper written by a Haskeller.
All this to say - if you’re depending on open source you need to fund it directly. Dont expect that FAANG will forever, or there will be a magic wand like a research paper that makes it automatically easier to maintain.
There are a few linting rules that can help identify semantic errors or dead code, but only a small number of rules are needed to get all of the benefits.
Autoformatting (prettier, gofmt, etc.) is the way to go.
Take a look at the “eslint.rules.customizations” key in this file:
https://github.com/antfu/eslint-config/blob/main/.vscode/set...
Python can't do that. It's one of a few things about the language which is "nice" when using it for simple tasks, but which makes more complex programming pointlessly error-prone.
This also happens rarely enough that it's never been an issue for me.
Of course this is mostly a tooling issue, since there is a shortcut for adjusting indentation but not for adjusting parenthesis. Or maybe there is and I just haven't discovered it yet.
Copying, moving, reordering, newlines, pretty much anything will keep correct pairing of brackets.
Regardless, to me, this is such a minor issue that I don't consider it at all when choosing a language.
As someone who appreciates `{ "semi": false }` in my prettierrc, I take a lot of advantage of JS' ASI and find prettier's behavior interesting and conservative, but useful.
I do solid touch typing 60WPM writing plain text. With code completion and linting it easily goes twice as fast writing code.
I think other tools (like the ones people are mentioning) do stuff like this for people: autocomplete, copilot, formatters, etc. I also use formatters and linters, and my autocomplete is "be strict about conventions, look up the actual meanings of words", which makes me touchy about conventions haha.
I code in an entirely different way now, I would of thought it impossible to change my ways (about 20 years of coding)
I’m similarly surprised to see such an ingrained skill undergo such rapid change. And it’s funny because for me prettier was originally just meant to fix the chaos that is every dev having their own style/editor preferences.
Always using `<tag></tag>` or `<tag/>` is so simple and easy.
Ocamlformat is instant in the meanwhile.
Just don't enable the experimental or potentially breaking format options on a large existing code base. They aren't enabled by default.
Deno's in the box trolling is pretty nice as well. YMMV of course.
Missed opportunity I think to have a prettier with the prettiest defaults. Sadly the name is taken [0]
But it doesn't appear that almost every pretty printer is based on the Wadler algorithm. It seems like MOST of them are not?
e.g. clang-format is one of the biggest and best, and it has a model that includes "unwrapped lines", a "layouter", a line break cost function, exhaustive search with memoization, and Dijikstra's algorithm:
https://llvm.org/devmtg/2013-04/jasper-slides.pdf
The YAPF Python formatter is based on this same algorithm - https://github.com/google/yapf
The Dart formatter used a model of "chunks, rules, and spans" - https://journal.stuffwithstuff.com/2015/09/08/the-hardest-pr...
It almost seems like there are 2 camps -- the functional algorithms for functional/expression-based languages, and other algorithms for more statement-based languages.
Though I guess Prettier/JavaScript falls on the functional side.
I just ran across this paper that includes a nice survey (on lobste.rs) and it seems to cover the functional pretty printing languages influenced by Wadler, in the functional style (e.g. for and in Racket or Haskell), but not the other kind of formatter ("Google" formatters perhaps)
interesting because for me it's hard to read changelogs of js/ts based repos that are using prettier compared to go repos, I wonder if the indentation is a reason for that. I'm honestly not sure what the author means by that either, gofmt seems to fix indentation for me.
For example
git blame a1b2c4 -- foo.js
Where a1b2c4 is the commit hash. Could even tag the commit to make it more convenient: git tag beforeRefmt a1b2c4
git blame beforeRefmt —- foo.js
and push the tag up for everyone to have access to it git push —-tagsGuess I'll need to find that link myself ;)
Here's a surprise:
const a = 1
const b = 2
(a+b).toString()
Calling the convention "standard" was irresponsible, and it led many people (especially beginners) to assume that it was some sort of a preferred convention. Glad I'm seeing it less these days; thanks to TypeScript, and people following Microsoft's conventions.It's a silly sounding rule, but that makes it easy to remember.
The rest of Automatic Semicolon Insertion footguns happen to everyone whether they use semicolons or not, such as: do not add a newline after return, that makes it a return undefined. Semicolons won't help. (Typescript return type annotations will help.)
Related to the topic at hand, prettier configured with `{ "semi": false }` (which I prefer to standard these days because I think prettier autoformatting is, uh, prettier) in my experience catches most winky frown violations for you and auto-fixes them.
Prettier is rock solid but compared to rust based tools like ruff, it is dog shit slow.
<!-- prettier-ignore -->
above a <div> would cause the <style> block at the bottom to become duplicated on save.
Weird as, and I couldn’t be bothered troubleshooting, I just added the file to .prettierignore. But still.
(I maintain said plugin, so, well, it's my fault)
But I really wish gofmt gets inspired by prettier and automatically breaks long (80+ columns) lines into smaller lines (For example, move parameters of a long function each into their row).
Huh? I'm pretty sure it indents with tabs.
I wrote that article and I'm actually in the middle of rewriting dartfmt right now to have an entirely new internal representation.
What do you think of the functional "pretty printing languages" like Wadler's (cited in the blog post)?
It is kinda interesting that the author of prettier credits the algorithm, but other people say that it's more about labor and testing of specific style rules.
Having never written a pretty printer, it does seem like there is a lot of labor in encoding the "rules people like", and flags for different styles. And then there's the actual algorithm to find the line breaks.
Or maybe they are not really separate things. I think the more academic work is trying to separate the 2 things, but they can be fairly intertwined? (e.g. for performance reasons)
The primary driver is that we're moving to a fairly different formatting style: https://github.com/dart-lang/dart_style/issues/1253
The formatter works sort of like a compiler in that it parses the code, translates it to an internal representation, does optimization on that IR, and then outputs final code. The main difference is that the "final code" is also source code, and the "optimization" is line splitting.
The old IR grew organically over time and got increasingly difficult to work with. It baked certain formatting choices directly into the IR (mainly indentation) which line splitting then had no control over. For example, given a function call like:
someLongFunctionName(some + long + argument + expression, [firstElement, anotherElement, aThirdElement]);
We might want to format it like this if the function name and first argument fits on one line: someLongFunctionName(some + long + argument + expression, [
firstElement,
anotherElement,
aThirdElement
]);
But if the first argument doesn't fit, then we probably want: someLongFunctionName(
some + long + argument + expression,
[
firstElement,
anotherElement,
aThirdElement
]);
Note how the indentation of the list elements depends on how we choose to line split the argument list. The old formatter's IR just couldn't model that at all.For years, I've wanted a better IR that could express formatting like this. And since we were making sweeping changes to the formatting style (including some that would be very hard to implement with the old IR), it seemed like the right time to move to a new internal representation too.
> What do you think of the functional "pretty printing languages" like Wadler's (cited in the blog post)?
I have to confess that I worked on dartfmt for a few years before I stumbled onto that paper. I'm somewhat familiar with it, but I've never taken the time to really dig into it.
I could be wrong, but I strongly suspect that the formatting rules we want for Dart are too complex to model using Wadler's formalism directly, and I'm not sure if extending it to support the formatting rules we want would sacrifice its simplicity or performance.
Given that, I sort of stuck with the devil I already knew—dartfmt's current architecture—and built off of that.
> it does seem like there is a lot of labor in encoding the "rules people like", and flags for different styles. And then there's the actual algorithm to find the line breaks.
Yes, and the two are deeply intertwined. The majority of dartfmt's code by line count is just implementing the style rules for every part of the language grammar. The most difficult code to write and maintain in dartfmt is the line splitting algorithm, largely because it's combinatorial when done naïvely.
It does seem like there are at least two schools of thought on the pretty printers -- those influenced by the Wadler pretty printing language (functional style), and those more influenced by go fmt (which does no line wrapping) and clang-format (which does).
There's a very recent paper linked here which has a good survey of the functional style. Table 1 is a nice summary of the different algorithms and IRs, a bunch of them appearing only in the last 10 years.
https://lobste.rs/s/aevptj/why_is_prettier_rock_solid
A Pretty Expressive Printer (with Appendices) - https://arxiv.org/pdf/2310.01530.pdf
I'm probably biased toward the "non-PPL" style of clang-format because I think it's the best formatter I've used. It's fast and does what I want. I would probably attribute that to all the elbow grease and testing put in over the years, and being written in C++, not necessarily the wrapping algorithm.
But now that I see the examples, my suspicion is that some of the newer IRs and algorithms do address some of the Dart problems (though I didn't try anything!). Dart seems to have long expressions very much in common with functional languages, as opposed to Go, which apparently doesn't need wrapping at all!
I found this post linked from the paper, and it has 3 examples of formatting s-expressions that seem pretty similar to your examples.
And they discuss what IR is needed to express that. They talk about greedy algorithms vs. "non-local" dependencies.
https://jyp.github.io/posts/towards-the-prettiest-printer.ht...
It seems like the IR of chunks, rules, and spans has a lot of similarity to what they call "groups" and so forth. Several years ago, long before I looked at any of this, I actually implemented a data structure printer with the same 2 rules as on page 2 of this paper - https://lindig.github.io/papers/strictly-pretty-2000.pdf
i.e. try to fit all the parts on a single line, and if that fails, put each part on its own line. So the basic intuition is natural and obvious.
---
But I always want to see how these algorithms are tested with real languages, and how they perform. I'm still not sure how easy it is to encode all the language specific rules in these "cost functions", but I may give it a stab (I have more than one formatter to write!).
The paper says it's tested on Racket S-expressions, and that there is a large amount of non-trivial convention around that syntax. But I think that's still a far cry from C++ or Python, in both the amount of syntax and how many users / how much code, etc.
I do wonder about the problem of computing layouts from scratch vs. fixing existing layouts. And the problem of formatter upgrades causing churn. My impression is that go fmt tends to fix things, erring on the side of leaving code alone, and people seem to like that? I guess the fear is with the "global optimization" algorithms, the formatter will make a bunch of choices and it's hard to figure out why.
At $PREVIOUS_WORKPLACE, it totally removed the burden of formatting issues in PRs.
It’s far from perfect but at least everyone in the team was producing the same style and we could focus on logic errors rather than formatting (which is the first thing you spot and can’t ignore when doing a code review).
That said, I do believe in the value of consistent formatting tools, I just think Prettier is a somewhat lazy choice that attempts to skirt responsibility for deciding on things or cultivating agreement. It's a disfunctional team who's members care too much about their opinions on such trite topics, and/or who aren't capable of coming to agreement on a common set of them.
For PRs, I've always just asked someone to run their formatter if it's clear they forgot, and usually there's a common config used by some formatting tool, it's not worth much beyond a a one-liner to make these decisions, and you should be able to do this periodically as things change.
Prettier, to me, is about as average as these tools come. It's not particularly offensive, though I don't understand why it's not way faster than it is.
This is the criterion for "rock solid"? I don't think a formatter has ever broken my code (in any language) and I'd be absolutely livid if one did.
If it's not even fast, then nothing of note was even achieved. Anybody can write a slow formatter that conforms to a test suite.
I entirely disagree. Juniors have to learn so many more important things - how to logically structure code, testing, working on teams, git...
Why take time away from learning those (and similar) concepts to teach them something a tool does well and, if necessary, they can learn later?
I like it to deciding to teach teens how to drive with a manual transmission instead of an automatic one- they need to be able to drive and not hit things, and the more we can simplify that, the happier we all are. If needed, some kids can go back and learn how to use a stick shift.
You did not interpret what I said correctly. Not that you had much to work with, but that small wall of text is based on a major extrapolation.
I said completely relying on these tools is doing them disservice. How do you train your muscle memory when the tools are doing everything for you. I, for one, believe proper formatting is a sign of care and it's really easy to spot when someone is not paying attention.
Go on, mod me down harder.