Skip to content

Instantly share code, notes, and snippets.

Last active December 9, 2019 13:36
Show Gist options
  • Save jsnajdr/abe94d9ce0af0c0ac9f1ee6ebd8763af to your computer and use it in GitHub Desktop.
Save jsnajdr/abe94d9ce0af0c0ac9f1ee6ebd8763af to your computer and use it in GitHub Desktop.

Aligning the JavaScript Coding Standards With Prettier

Across the JavaScript community, the Prettier code formatter has become immensely popular over that last three years since it was originally released. It automatically performs high-quality formatting of your JavaScript code: when you press Save, your code is instantly formatted. This removes a distraction for contributors who write or review code, and allows them to focus on the more valuable aspects of their work without having to discuss the JavaScript WordPress Coding Standards so often. That's why we'd like to adopt it in the WordPress JavaScript code bases, too.

The official Prettier formatter is very opinionated and has very few options. The reasons are both technical and cultural. The complexity of the formatting algorithm would explode exponentially with too many options and their combinations, and a part of the project vision is to establish an unified JavaScript formatting standard.

The WordPress formatting standard has one major incompatibility with the Prettier convention: it requires spaces inside all kinds of parens -- (), {}, [], inside template strings, JSX attributes and expressions, everywhere:

function Label( { text, icon } ) {
  if ( ! [ 'left', 'right' ].includes( icon ) ) {
    return null;
  return (
    <label className={ `icon-${ icon }` }>
      { text }

To teach this convention to Prettier, we had to create a fork that adds an extra option and modified the paren-formatting code, and publish it on NPM under the name wp-prettier (@wordpress/prettier would arguably be even better name!). At this moment, we've been using that fork for 2.5 years in the Calypso and Jetpack projects, have maintained and updated it over countless upstream releases, and are confident that we can recommend it to anyone who wants to format their JavaScript code the WordPress way.

In a Gutenberg pull request, we proposing adopting the WordPress Prettier tool in the project.

After adding support for the WordPress style paren spacing, there remain several very minor cases where Prettier formats JavaScript code slightly differently from what the current WordPress JavaScript Coding Standards recommend. They don't diverge from the essence and spirit of the WordPress coding standards. Further patching of Prettier would be, on our opinion, not worth the coding and maintenance effort. And in some cases is outright impossible, because the recommendation asks for human judgment that an algorithm cannot implement.

In this post, we propose several small changes of the coding standards that align them fully with the Prettier convention.

Formatting ternaries and binary ops

The standard says that when breaking a long statement, the "operator" should be at the end of line, and doesn't distinguish between binary and ternary operators. But Prettier does. When breaking a binary operator, it indeed puts the operator at the end of line:

const result =
  partOne +

But the parts of a ternary operator are put on the start of the new line (after the indentation):

const result = isRtl
  ? 'right'
  : 'left';

Also, the standard recommends that long "lines should be broken into logical groups if it improves readability". That doesn't happen with Prettier -- it wraps the lines if and only if the line would be longer than maximum line length otherwise. We propose to remove that ambiguous and subjective formulation from the standard.

Wrapping chained n-ary operators

Another difference related to binary operators and the Multi-Line Statements section is that Prettier puts each operand on separate line, even the left side of an assignment. It doesn't do the "fluid text wrap" style like this:

const result = a + b +
  c + d;

but this:

const result =
  a +
  b +
  c +

To address all these differences, we propose to reformulate the Multi-Line Statements section of the standards document as follows (the last paragraph about conditionals is unchanged):

When a statement with operators is too long to fit on one line and is broken into multiple lines, line breaks must occur after a binary operator. Each operand with the operator that follows it must be on a separate line.

// Bad
const html = '<p>The sum of ' + a + ' and ' + b + " plus " + c
  + ' is ' + ( a + b + c ) + '</p>';
// Good
const html =
  '<p>The sum of ' +
  a +
  ' and ' +
  b +
  ' plus ' +
  c +
  ' is ' +
  ( a + b + c ) +

On the other hand, when a long statement with a ternary operator is broken into multiple lines, the parts of the ternary operator should be after the line break:

// Bad
const baz = true === conditionalStatement() ?
  'thing 1' : 
  'thing 2';

// Good
const baz =
  true === conditionalStatement()
    ? 'thing 1'
    : 'thing 2';

When a conditional is too long to fit on one line, each operand of a logical operator in the boolean expression must appear on its own line, indented one extra level from the opening and closing parentheses.

if (
  firstCondition() &&
  secondCondition() &&
) {

Chained method calls and context

The standard recommends that when one method in a chain "changes the context", it should add an extra indentation:

  .addClass( 'foo' )
    .html( 'hello' )
  .appendTo( 'body' );

But Prettier doesn't know what the context is -- it's an ambiguous concept even for a human. So it indents everything equally:

  .addClass( 'foo' )
  .html( 'hello' )
  .appendTo( 'body' );

It also adds one extra touch: if the initial identifier is just one or two characters long, it will keep the first method call on the same line:

el.addClass( 'foo' )
  .html( 'hello' )
  .appendTo( 'body' );

To address these differences, we propose to reformulate the Chained Method Calls section of the standards document as follows:

When a chain of method calls is too long to fit on one line, it must be broken into multiple lines where each line contains one call from the chain. All lines after the first must be indented by one tab.

findFocusable( context )
  .filter( isTabbableIndex )
  .map( mapElementToObjectTabbable )
  .sort( compareObjectTabbables )
  .map( mapObjectTabbableToElement )
  .reduce( createStatefulCollapseRadioGroup(), [] );

The new formulation removes the requirements that can't be reasonably implemented, focuses on the main points, i.e., breaking into multiple lines and consistent indentation, and modernizes the jQuery-based example to show more contemporary, functional JavaScript code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment