Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
During the past few weeks, I've been working on <a href="https://github.com/jlongster/prettier">prettier</a>, which is a JavaScript pretty printer. We are approaching the phase where we can actually use it so this is a good time to explain how it works.
We're going to go through an example
<pre lang="javascript">
if (!pretty) { makePretty() }
</pre>
<h3>String -> AST</h3>
The first step is to take this string that represents some JavaScript and to parse it in order to get an AST out of it. An AST is a tree that represents the program. Using either <a href="https://github.com/babel/babylon/">Babylon</a> or <a href="https://www.npmjs.com/package/flow-parser">Flow</a> we can parse this example and we get the following tree.
<pre lang="javascript">
Program
IfStatement
UnaryExpression(!)
Identifier(pretty)
BlockStatement({})
ExpressionStatement
CallExpression
Identifier(makePretty)
</pre>
You can explore the full AST using <a href="http://astexplorer.net/#/rINLNBzlAh">astexplorer.net</a>.
<h3>AST -> IR</h3>
Now that we have this tree, we want to print it. For each type of node like IfStatement, UnaryExpression... we're going to output something. In the case of prettier, this something is an intermediate representation called a document as described by the paper <a href="http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf">a prettier printer</a> by Philip Wadler.
<pre lang="javascript">
[
group([
"if (",
group([ indent(2, [ softline, "!", "pretty" ]), softline ]),
")",
" ",
"{",
indent(2, [ hardline, "makePretty", "()", ";" ]),
hardline,
"}"
]),
hardline
];
</pre>
You can play around with this representation on <a href="https://jlongster.github.io/prettier/#%7B%22content%22%3A%22if%20(!pretty)%20%7B%20makePretty()%20%7D%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3Afalse%2C%22bracketSpacing%22%3Atrue%2C%22doc%22%3Atrue%7D%7D">the prettier explorer</a>.
<h3>IR -> String</h3>
The interesting thing about this representation is that it is the same no matter what the line-length is. The basic idea is that the primitives such as <code>group</code>, <code>indent</code>, <code>softline</code> encode the way they should look if they fit in the line or if they don't.
For example, a <code>softline</code> does not print anything if the group it is contained on fits and a line otherwise. <code>indent</code> adds a level of indentation if it doesn't fit. If you are curious, you can look at the <a href="https://github.com/jlongster/prettier/blob/master/commands.md">list of available commands</a>.
So, we just need to take this IR, send it through a <a href="https://github.com/jlongster/prettier/blob/d20a5806558a438490b264732bdcbba7ef3d4380/src/doc-printer.js">solver</a> along with the desired line width and we get the result!
<img src="http://blog.vjeux.com/wp-content/uploads/2017/01/HN6WzI9wFW.gif" alt="HN6WzI9wFW" width="150" height="91" class="aligncenter size-full wp-image-5107" />
<h3>Conclusion</h3>
Hopefully this gives you a better idea of how a pretty printer that takes into account the desired width work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment