Solving complex whitespace management challenges
There are so many tough problems for web developers to solve. Making applications that don’t have to be downloaded or installed. Adding build tooling without slowing down the instant edit-refresh dev cycle. Changing requirements, framework and library options, programming paradigms.
Managing the whitespace in your source code is not one of these problems.
First a quick summary from my earlier post Why Linting makes me Yawn. Teams should agree on a style guide. It will never be followed without some enforcement. Some of the rules require judgement to apply, for these we have a linter. But for purely mechanical rules like where to put the whitespace, there is no need for a UX where issues are presented to the developer. They should just be applied, always and with zero effort. On the Angular project, and for Google’s internal source code, we simply require that code is formatted by a particular formatter with some config. There’s nothing more to decide or do.
I’ve seen some posts recently about Prettier, which just hit 1.0. See http://jlongster.com/prettier-1.0. Prettier seems to get this right: few configuration options, and totally opinionated: any two files with the same AST will be formatted the same. The drawback, at least right now, is that it doesn’t have TypeScript support. But clang-format, originally for C++, handles TypeScript very nicely, thanks to lots of work from @martin_probst. I recommend you try it!
The one caveat: by adopting a formatter, or changing its configuration, you’re opting into a one-time churn on your code. Getting from here to there requires touching most of the lines, and this shows up in your version control history. Sorry, but the formatting operation inevitably shows up in the blame layer.
There are a few strategies to deal with this:
- Leave the code alone for now, but enforce that a file is formatted whenever it is changed.
- Like the previous, but only enforce that modified ranges in the file are formatted.
- Format everything in a big bang.
I prefer the latter one. It means no stray edits later on in the project, and is the easiest to enforce.
Choose a configuration. Like Prettier, clang-format has few options. They go in a file called
.clang-format in the root of your project, so that editors and tools can share the same config. You can read about the options in the clang-format docs, or try this online config editor: https://clangformat.com/
For Angular, it looks like this:
Install clang-format in your project, or if you use the gulp build tool, install gulp-clang-format. Make sure to pin the version of clang-format. You don’t want to pick up a new version unless you’re ready to re-format files again.
I’ll document the gulp setup below, but you could do something similar in another build tool. This uses Es2015 syntax in the
Now do your big reformatting change, with
format:enforce task will fail if sources are not formatted; configure this to run in your CI. You don’t want changes to sneak in between the enforcement flip and the mass-formatting, so put them in the same pull request or commit.
Ideally, the format error would be a separate status on a pull request from test failures. You really want both bits of information: an early indication that the formatter didn’t run when it should have (maybe you can fix it up and re-push before making coffee) as well as the test results (it sucks to come back from making coffee and the only CI result was that you didn’t format a file). I’d like to find a service that does this for GitHub!
Keeping the code formatted
At first, your coworkers will be annoyed by a red build whenever they change things. You’ll need to convince them that the formatter is worth taking 2 minutes for some local setup. The best setup is the one that requires the least thought: configure your editor to format files on save. For Visual Studio Code, I use this extension. A similar plugin is available in every editor I know of.
Another option is to install the git clang-format command. This is nice because it runs quickly even when you have a large codebase, by only touching files you changed. Or you could just run the same
gulp format command we used above to mass-format everything, as long as it’s not too slow.
In dire situations, there is an escape hatch. You can suppress the formatter with these comments:
Finally, don’t forget to remove any linter rules that serve only to remind you about whitespace. That’s not the linter’s job anymore!