Skip to content

Style Guide

Joshua Maxwell edited this page Jan 22, 2022 · 19 revisions

1 Formatting

1.1 Braces

Braces are required for all control structures (i.e. if, else, for, do, while, as well as any others), unless the body contains only a single statement. The first statement of a non-empty block must begin on its own line.

Disallowed:

if (someVeryLongCondition()) { doSomething();
  doSomethingElse();
}

for (let i = 0; i < foo.length; i++) bar(foo[i]);

Exception: A simple if statement that can fit entirely on a single line with no wrapping may be kept on a single line with no braces when it improves readability.

if (shortCondition()) foo();

1.2 Nonempty blocks: K&R style

Braces follow the Kernighan and Ritchie style ("Egyptian brackets") for nonempty blocks and block-like

  • No line break before the opening brace.
  • Line break after the opening brace.
  • Line break before the closing brace.
  • Line break after the closing brace if that brace terminates a statement or the body of a function or class statement, or a class method.

Specifically, there is no line break after the brace if it is followed by else, catch, while, or a comma, semicolon, or right-parenthesis.

Example:

class InnerClass {
  constructor() {}

  /** @param {number} foo */
  method(foo) {
    if (condition(foo)) {
      try {
        // Note: this might fail.
        something();
      } catch (err) {
        recover();
      }
    }
  }
}

1.3 Empty blocks: may be concise

An empty block or block-like construct may be closed immediately after it is opened, with no characters, space, or line break in between (i.e. {}), unless it is a part of a multi-block statement (one that directly contains multiple blocks: if/else or try/catch/finally).

Example:

const doNothing = () => {};

Disallowed:

if (condition) {
  // …
} else if (otherCondition) {} else {
  // …
}

try {
  // …
} catch (e) {}

1.4 Block indentation: +2 spaces

Each time a new block or block-like construct is opened, the indent increases by two spaces. When the block ends, the indent returns to the previous indent level. The indent level applies to both code and comments throughout the block.

1.5 Array literals: optionally "block-like"

Any array literal may optionally be formatted as if it were a “block-like construct.” For example, the following are all valid (not an exhaustive list):

const a = [
  0,
  1,
  2,
];

const b =
    [0, 1, 2];
const c = [0, 1, 2];

someMethod(foo, [
  0, 1, 2,
], bar);

Other combinations are allowed, particularly when emphasizing semantic groupings between elements, but should not be used only to reduce the vertical size of larger arrays.

1.5 Object literals: optionally "block-like"

Any object literal may optionally be formatted as if it were a “block-like construct.”

const a = {
  a: 0,
  b: 1,
};

const b =
    {a: 0, b: 1};
const c = {a: 0, b: 1};

someMethod(foo, {
  a: 0, b: 1,
}, bar);

1.6 Class literals

Class literals (whether declarations or expressions) are indented as blocks. Do not add semicolons after methods, or after the closing brace of a class declaration (statements—such as assignments—that contain class expressions are still terminated with a semicolon). Use the extends keyword, but not the @extends JSDoc annotation unless the class extends a templatized type.

Example:

class Foo {
  constructor() {
    /** @type {number} */
    this.x = 42;
  }

  /** @return {number} */
  method() {
    return this.x;
  }
}
Foo.Empty = class {};
/** @extends {Foo<string>} */
foo.Bar = class extends Foo {
  /** @override */
  method() {
    return super.method() / 2;
  }
};

/** @interface */
class Frobnicator {
  /** @param {string} message */
  frobnicate(message) {}
}

1.7 Function expressions

When declaring an anonymous function in the list of arguments for a function call, the body of the function is indented two spaces more than the preceding indentation depth.

Example:

prefix.something.reallyLongFunctionName('whatever', (a1, a2) => {
  // Indent the function body +2 relative to indentation depth
  // of the 'prefix' statement one line above.
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

1.8 Switch statements

As with any other block, the contents of a switch block are indented +2.

After a switch label, a newline appears, and the indentation level is increased +2, exactly as if a block were being opened. An explicit block may be used if required by lexical scoping. The following switch label returns to the previous indentation level, as if a block had been closed.

A blank line is required between a break and the following case.

Example:

switch (animal) {
  case Animal.BANDERSNATCH:
    handleBandersnatch();
    break;

  case Animal.JABBERWOCK:
    handleJabberwock();
    break;

  default:
    throw new Error('Unknown animal');
}

1.8 Semicolons are required

Every statement must be terminated with a semicolon. Relying on automatic semicolon insertion is forbidden.

1.9 Column limit: 80

JavaScript code has a column limit of 80 characters. Except lines where obeying the column limit is not possible or would hinder discoverability.

1.10 Where to break

The prime directive of line-wrapping is: prefer to break at a higher syntactic level.

Preferred:

currentEstimate =
    calc(currentEstimate + x * currentEstimate) /
        2.0;

Discouraged:

currentEstimate = calc(currentEstimate + x *
    currentEstimate) / 2.0;

In the preceding example, the syntactic levels from highest to lowest are as follows: assignment, division, function call, parameters, number constant.

Operators are wrapped as follows:

  1. When a line is broken at an operator the break comes after the symbol. (Note that this does not apply to the "dot" (.), which is not actually an operator.)
  2. A method or constructor name stays attached to the open parenthesis (() that follows it.
  3. A comma (,) stays attached to the token that precedes it.

1.11 Comments

All comments must have a space between the commented text and the comment operator.

Example:

// this is allowed

/*
 * This is
 * also allowed
 */

Disallowed:

//Don't do this

/*
 *This is
 *not okay
 */

1.12 Control structures

A space must be inserted between a control structure term and its following parenthesis

Example:

if (0 === true)

Disallowed:

if(0 === true)

2 Language Features

2.1 Use const and let

Declare all local variables with either const or let. Use const by default, unless a variable needs to be reassigned. The var keyword must not be used.

2.2 One variable per declaration

Every local variable declaration declares only one variable: declarations such as let a = 1, b = 2; are not used.

2.3 Declared when needed, initialized as soon as possible

Local variables are not habitually declared at the start of their containing block or block-like construct. Instead, local variables are declared close to the point they are first used (within reason), to minimize their scope.

2.4 Functions

All functions, must be declared as arrow functions. The keyword function is strictly forbidden. Example:

const myFunc = (foo) => {
  const bar = 4;
  return foo + bar;
};

const myOtherFunc = (x, y) => x + y; 

Disallowed:

function myFunc(foo) {
  return foo + 'bar';
}

2.5 Template literals

Use template literals (delimited with `) over complex string concatenation, particularly if multiple string literals are involved. Template literals may span multiple lines.

If a template literal spans multiple lines, it does not need to follow the indentation of the enclosing block, though it may if the added whitespace does not matter.

Example:

function arithmetic(a, b) {
  return `Here is a table of arithmetic operations:
${a} + ${b} = ${a + b}
${a} - ${b} = ${a - b}
${a} * ${b} = ${a * b}
${a} / ${b} = ${a / b}`;
}

2.6 Loops

When possible use map or for (const n of arr) over while loops and traditional for loops (for (let i = 0; i < x; ++i))

Clone this wiki locally