Prettier 1.3
This post provides an update to Facebook's adoption or Prettier, outlines our progress on TypeScript, and details some of the improvements and fixes included in the 1.3.0 release of Prettier.
Facebook Adoption Update
The reason why I (@vjeux) embarked on this journey working on prettier has always been to get the entire Facebook codebase converted over. I would like to give an update on how it is going and what is the process to get there.
The first projects to adopt prettier were Jest, React and immutable-js. Those are small codebases in the order of hundreds of files that have their own infrastructure. There are a handful of people working on them full time.
Then, Oculus and Nuclide converted their codebase over. The scale is bigger with a few thousands of files and tens of full time contributors but looks pretty similar to the first projects. The conversions went in one big codemod and that's it.
Now, the entire Facebook codebase is way bigger than this and it's not feasible to just convert everything in one go and to convince everyone that their entire codebase is going to be reformatted under their feet. So we need to find a more incremental approach.
Scaling adoption
Running prettier on a piece of code is a pretty expensive operation, it makes your pull request look bad because of a lot of unrelated changes and it causes merge conflicts for all the outstanding pull requests. So once a file has been formatted, you should do everything to make sure it remains formatted.
- When pretty-printing a file, add
@format
to the first block comment like@flow
. - Have a lint rule with autofix that checks if the file is correctly pretty printed when
@format
is present.- When running Nuclide, it's going to show as an inline warning and have a fix button.
- When sending a pull request, it's going to show the lint failing with a [Yn] prompt that you can just press enter.
- Update the default code templates to add
@format
to the header. - When you run code formatting via cmd-shift-c inside of Nuclide, automatically insert the
@format
header. - Disable all the stylistic rules like max-len when
@format
is in the header. - Have script to run prettier through an entire folder with everything configured as a one line operation.
- Have a good guide to help people that want to convert their codebase over with instructions and best practices.
- When pushing a new release of prettier, also run it through all the files with
@format
in order to avoid getting warnings afterwards. - Add tracking for the number of files with
@format
over time.
We finally got all those things wired up 1.5 weeks ago and the reception has been insane. Many people from various teams converted their codebase to prettier on their own. As of today, 15% of Facebook codebase has been converted over!
When I started working on prettier, I had a hunch that people were hungry for tools to solve formatting. But I had no idea that once the tooling was in place, people would rush to convert their codebase over! This is great confirmation that this project is useful to people and not just a gimmicky tool.
TypeScript Support Progress
@despairblue, @azz and @JamesHenry have been hard at work around getting TypeScript supported by prettier as it's the top requested feature. 2000 out of 11000 files in the TypeScript test suite are not yet properly printed. You can follow progress on https://github.com/prettier/prettier/issues/1480 and do not hesitate to help out!
Flow
#1381)
Add trailing commas on flow generics (The --trailing-comma=all
option is supposed to add trailing commas everywhere possible, but as an oversight we forgot to do it for flow generics.
// Before
type Errors = Immutable.Map<
Ahohohhohohohohohohohohohohooh,
Fbt | Immutable.Map<ErrorIndex, Fbt>
>;
// After
type Errors = Immutable.Map<
Ahohohhohohohohohohohohohohooh,
Fbt | Immutable.Map<ErrorIndex, Fbt>,
>;
#1426)
Inline nullable in flow generics (The phase after printing things correctly is to tweak the output to make it closer to the way people write code in practice. Inlining optional flow types is a small thing that makes a difference.
// Before
type Cursor = Promise<
?{
newCursor?: number,
formatted: string,
}
>;
// After
type Cursor = Promise<?{
newCursor?: number,
formatted: string,
}>;
#1437)
Allow flow declarations to break on StringLiteralTypeAnnotations (We can always find more places to add breaks when things don't fit 80 columns. This time it's around declaring a type as a constant string.
// Before
export type AdamPlacementValidationSingleErrorKey = 'SOME_FANCY_TARGETS.GLOBAL_TARGET';
// After
export type AdamPlacementValidationSingleErrorKey =
'SOME_FANCY_TARGETS.GLOBAL_TARGET';
=
for flow generics default arguments (#1476)
Add space around Another example of small thing where we can improve the display of flow code. For function default arguments we put a space around =
but didn't around flow generics.
// Before
class PolyDefault<T=string> {}
// After
class PolyDefault<T = string> {}
#1452)
Don't break for unparenthesised single argument flow function (I'm trying to figure out something to write here, but ... it just looks weird!
// Before
const selectorByPath:
Path
=> SomethingSelector<
SomethingUEditorContextType,
SomethingUEditorContextType,
SomethingBulkValue<string>
> = memoizeWithArgs(/* ... */)
// After
const selectorByPath: Path => SomethingSelector<
SomethingUEditorContextType,
SomethingUEditorContextType,
SomethingBulkValue<string>
> = memoizeWithArgs(/* ... */);
#1357)
Fix optional flow parenthesis (We were a bit too lenient around parenthesis for optional flow types. In one case in the entire Facebook codebase, it generated code with different semantics. As part of this fix, we hardened the list of types that can be written without parenthesis.
// Before
type X = ?(number, number) => number => void;
// After
type X = (?(number, number) => number) => void;
#1364)
Skip trailing commas with FlowShorthandWithOneArg (It is a parse error to add a trailing comma without parenthesis for arguments of arrow function types. We found one case in Facebook codebase when this happened, it's a very rare occurrence.
// Before
type IdeConnectionFactory =
child_process$ChildProcess,
=> FlowIDEConnection = defaultIDEConnectionFactory;
// After
type IdeConnectionFactory =
child_process$ChildProcess
=> FlowIDEConnection = defaultIDEConnectionFactory;
#1451)
Reorder flow object props (This one is an example where the way the AST is structured is not our favor. Instead of having a list of elements inside of a type, the AST is structured in a way where normal keys and array keys each have their own group. In order to restore the initial order, we're now reading from the original source :(
// Before
type Foo = {
[key: string]: void,
alpha: "hello",
beta: 10
};
// After
type Foo = {
alpha: 'hello',
[key: string]: void,
beta: 10
}
Template Literal
#1385)
Proper indentation for template literals (A long standing issue with template literals and prettier is around the indentation of code inside of ${}
. It used to be the indentation of the backtick but turned out to give poor results. Instead people tend to use the indent of the ${
. We changed this behavior and it magically made GraphQL queries look pretty!
// Before
Relay.createContainer({
nodes: ({ solution_type, time_frame }) => Relay.QL`
fragment {
__typename
${OptimalSolutionsSection.getFragment("node", {
solution_type,
time_frame
})}
}
`
})
// After
Relay.createContainer({
nodes: ({ solution_type, time_frame }) => Relay.QL`
fragment {
__typename
${OptimalSolutionsSection.getFragment("node", {
solution_type,
time_frame
})}
}
`
})
#873)
Do not indent calls with a single template literal argument (Template literals are very hard to deal with for a pretty printer because the spaces inside are meaningful so you can't re-indent them. We didn't know what to do for a call with a single template literal so we didn't do anything, but we kept receiving reports of people saying that prettier indented it the wrong way, so we are now inlining them. Fingers crossed it is going to cover most use cases.
// Before
insertRule(
`*, *:before, *:after {
box-sizing: inherit;
}`,
);
// After
insertRule(`*, *:before, *:after {
box-sizing: inherit;
}`);
#1439)
Fix windows line ending on template literals (We manipulate line endings in a lot of places in prettier and took great care of handling both \n
and \r\n
except for template literals where we forgot. Now this is fixed!
// Before
const aLongString = `
Line 1
Line 2
Line 3
`;
// After
const aLongString = `
Line 1
Line 2
Line 3
`;
#1485)
Inline template literals as arrow body (We already inline template literals that are tagged (eg graphql`query`
) but didn't for plain template literals. For the anecdote, it turns out the code was supposed to support it but it was using TemplateElement
instead of TemplateLiteral
:(
// Before
const inlineStore = preloadedState =>
`
<script>
window.preloadedState = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
`
// After
const inlineStore = preloadedState => `
<script>
window.preloadedState = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
`
Ternaries
#1386)
Add parenthesis for unusual nested ternaries (While working on printing nested ternaries, everyone focused on the ones with the shape of an if then else cond1 ? elem1_if : cond2 ? elem2_if : elem_else
which is the most common. But, if you move some ?
and :
around you can have another pattern. It looks almost the same but has a different meaning. In order to reduce confusion, we're adding parenthesis around the uncommon form.
// Before
cond1 ? cond2 ? elem2_if : elem2_else : elem1_else
// After
cond1 ? (cond2 ? elem2_if : elem2_else) : elem1_else
#1450)
Only add parenthesis on ternaries inside of arrow functions if doesn't break (There's an eslint rule no-confusing-arrows
which suggests adding parenthesis for ternaries in arrow functions without brackets. The following two pieces of code are confusing:
var x = a => 1 ? 2 : 3;
var x = a <= 1 ? 2 : 3;
It makes sense when code is in one line, but when it is split into multiple lines, the parenthesis are unnecessary given the indentation, so we now only put them when they serve their disambiguation purpose.
// Before
var x = a => (1 ? 2 : 3);
var x = a =>
(1
? 2
: 3);
// After
var x = a => (1 ? 2 : 3);
var x = a =>
1
? 2
: 3;
General JavaScript Improvements
#1173)
Inline function declaration with single arg as object (This one was often requested for React Stateless Functional Components (SFC). If you make use of a lot of them, it's likely going to be a big change for you.
// Before
const X = (
props: {
a: boolean,
},
) => <div />;
// After
const X = (props: {
a: boolean,
}) => <div />;
#1453)
Break inline object first in function arguments (One thing we discovered early on is that people usually break the arguments of the function before breaking the return type. Unfortunately, the code responsible to inline single destructuring argument broke that assumption and it introduced bad looking code like this example. The good news is that it enables us to turn on inlining for single arguments that are typed with an object.
// Before
class X {
async onDidInsertSuggestion({editor, triggerPosition, suggestion}): Promise<
void
> {
}
}
// After
class X {
async onDidInsertSuggestion({
editor,
triggerPosition,
suggestion
}): Promise<void> {
}
}
#1440)
Don't break on empty arrays and objects (This one has been a long standing issue and is an easy fix, but was an invaluable tool: whenever someone reported that []
or {}
would break, we were able to fix the example by fixing something else. So it was a great way to surface edge cases. Fortunately, this vein has now ran out and all the recent examples just look bad with no other reason than the fact that they are breaking. So it's time to finally do it!
// Before
const a = someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeLong.Expression || [
];
// After
const a = someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeLong.Expression || [];
[0]
(#1441)
Do not break on We have a lot of issues where code breaks in array access when it doesn't look good. We don't yet have a good generic solution for it, but we can add a specific fix a common situation: [0]
.
// Before
queryThenMutateDOM(() => {
title = SomeThing.call(root, "someLongStringThatPushesThisTextReall")[
0
];
});
// After
queryThenMutateDOM(() => {
title = SomeThing.call(
root,
"someLongStringThatPushesThisTextReall",
)[0];
});
#1373)
Indent do while condition (We were not using the correct indentation logic for do-while condition but someone noticed, now we do!
// Before
do {}
while (someVeryLongStringA && someVeryLongStringB && someVeryLongStringC && someVeryLongStringD);
// After
do {}
while (
someVeryLongStringA &&
someVeryLongStringB &&
someVeryLongStringC &&
someVeryLongStringD
);
#1390)
Preserve inline comment as last argument (We forgot to add one case in the comment detection code when they appear last for JSX attributes and function arguments which made them go after the closing. In the case of JSX, it generated code that had a different meaning. Fortunately, since we don't usually commit commented out code it didn't affect production code, but it is not a good experience while coding.
// Before
const x = (
<div
attr1={1}>
// attr3={3}
{children}
</div>
);
// After
const x = (
<div
attr1={1}
// attr3={3}
>
{children}
</div>
);
#1464)
Break class expression returned by arrow call (In 1.0, we made class be inline inside of arrow functions. It turns out that it doesn't work great when the class is non trivial, so we are reverting this change. We're trying really hard to avoid making trashy decisions like this where the style changes back and forth, but we allow ourselves to do it sometimes to fix mistakes!
// Before
export default (ViewComponent: Function, ContainerComponent: Function) =>
class extends React.Component {
static propTypes = {};
};
// After
export default (ViewComponent: Function, ContainerComponent: Function) =>
class extends React.Component {
static propTypes = {};
};
#1375)
Fix empty line in block with EmptyStatement (This one was found by fuzzing. You're unlikely going to hit this in real code but it's good to know it is fixed!
// Input
if (a) {
b;
;
}
// Before
if (a) {
b;
}
// After
if (a) {
b;
}