5.0.1 • Published 9 months ago

tsjs v5.0.1

Weekly downloads
236
License
MIT
Repository
github
Last release
9 months ago

TSJS

TypeScript (and JavaScript) Standards for the Coveo Cloud Platform

Other Standards

The current repository aggregates all code standards that must be respected when writing and reviewing TypeScript code related to the Coveo Cloud Platform. This document should therefore be read and applied by anyone having to write TypeScript code for the Coveo Cloud Platform.

The current repository is greatly inspired from Airbnb's JavaScript Style Guide, but most code examples were rewritten in TypeScript.

If you are somewhat new to JavaScript with ES6, we recommend you read the full Airbnb's JavaScript Style Guide. Feel free to use additional resources, there are tons of it out there.

If you are somewhat new to TypeScript, we recommend you follow this 5 minutes tutorial, and read the official TypeScript Handbook in full.

Adopting code standards means ranking the importance of code clarity and team conventions higher than personal style. In a more pragmatical perspective, we also believe that code standards have very positive and tangible effects on a team workflow:

  1. Team members get productive time back by avoiding subjective code style debates. If the code doesn't respect team conventions, simply point the author to the commonly accepted code standard that must be respected.
  2. Code reviews' focus are redirected towards what is most critical:
    1. Code architecture (Is there a better/more intelligent way to handle the use case at hand?)
    2. Code fidelity (Will the code crash in real life situations? Are all possible cases handled?)
    3. Code quality (Is the code well tested, meaningful, and DRY?)
  3. The code base gets easier to read and navigate for team members.
  4. Written standards (as opposed to implicit, word-to-mouth standards) allow newcomers to get up to speed faster by knowing how to write proper code from day one.

In summary, code standards make developers happier. Embrace them.

  1. Types
  2. References
  3. Objects
  4. Arrays
  5. Destructuring
  6. Strings
  7. Functions
  8. Arrow Functions
  9. Classes & Constructors
  10. Modules
  11. Iterators and Generators
  12. Properties
  13. Variables
  14. Hoisting
  15. Comparison Operators & Equality
  16. Blocks
  17. Control Statements
  18. Comments
  19. Whitespace
  20. Commas
  21. Semicolons
  22. Type Casting & Coercion
  23. Naming Conventions
  24. Accessors
  25. Events
  26. jQuery
  27. ECMAScript 6+ (ES 2015+) Styles
  28. Testing
  29. Reviewing
  30. Being Reviewed
  31. TypeScript
  32. Libraries and Frameworks
  33. Notes on Legacy Code
  34. Remaining Sections

Types

Since this part appeared to have a more educational purpose, you can refer to the original Airbnb's JavaScript Style Guide for more information.

⬆ back to top

References

  • 1.1 Use const for all of your references; avoid using var.

    Why? This ensures that you can't reassign your references, which can lead to bugs and difficult to comprehend code.

    // bad
    var a = 1;
    var b = 2;
    
    // good
    const a = 1;
    const b = 2;

  • 1.2 If you must reassign references, use let instead of var.

    Why? let is block-scoped rather than function-scoped like var.

    // bad
    var count = 1;
    if (true) {
        count += 1;
    }
    
    // good, use the let.
    let count = 1;
    if (true) {
        count += 1;
    }

  • 1.3 Note that both let and const are block-scoped.

    // const and let only exist in the blocks they are defined in.
    {
        let a = 1;
        const b = 1;
    }
    console.log(a); // ReferenceError
    console.log(b); // ReferenceError

⬆ back to top

Objects

  • 2.1 Use the literal syntax for object creation.

    // bad
    const item: Interface = new Object();
    
    // good
    const item: Interface = {value: 1};

  • 2.2 Methods defined on objects should use arrow functions.

    // bad
    const atom: Interface = {
        value: 1,
        addValue: function (value: number): number {
            return atom.value + value;
        },
    };
    
    // good
    const atom: Interface = {
        value: 1,
        addValue: (value: number): number => value + 1,
    };

  • 2.3 Use property value shorthand.

    Why? It is shorter to write and descriptive.

    const lukeSkywalker = 'Luke Skywalker';
    
    // bad
    const obj: Interface = {
        lukeSkywalker: lukeSkywalker,
    };
    
    // good
    const obj: Interface = {
        lukeSkywalker,
    };

  • 2.4 Group your shorthand properties at the beginning of your object declaration.

    Why? It's easier to tell which properties are using the shorthand.

    const anakinSkywalker = 'Anakin Skywalker';
    const lukeSkywalker = 'Luke Skywalker';
    
    // bad
    const obj: Interface = {
        episodeOne: 1,
        twoJediWalkIntoACantina: 2,
        lukeSkywalker,
        episodeThree: 3,
        mayTheFourth: 4,
        anakinSkywalker,
    };
    
    // good
    const obj: Interface = {
        lukeSkywalker,
        anakinSkywalker,
        episodeOne: 1,
        twoJediWalkIntoACantina: 2,
        episodeThree: 3,
        mayTheFourth: 4,
    };

  • 2.5 Only quote properties that are invalid identifiers.

    Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.

    // bad
    const bad: Interface = {
        foo: 3,
        bar: 4,
        'data-blah': 5,
    };
    
    // good
    const good: Interface = {
        foo: 3,
        bar: 4,
        'data-blah': 5,
    };

  • 2.6 Use Underscore's extend and omit functions to shallow-copy objects, and make sure not to mutate the original object...

    // very bad
    const original: Interface = {a: 1, b: 2};
    const copy: Interface = _.extend(original, {c: 3}); // this mutates `original` ಠ_ಠ
    delete copy.a; // so does this
    
    // good
    const original: Interface = {a: 1, b: 2};
    const copy: Interface = _.extend({}, original, {c: 3}); // copy => { a: 1, b: 2, c: 3 }
    
    // good
    const original: Interface = {a: 1, b: 2};
    const copy: Interface = _.omit(original, 'a'); // copy => { b: 2 }, _.omit does not mutate `original`

⬆ back to top

Arrays

  • 3.1 Use the literal syntax for array creation.

    // bad
    const items: Interface[] = new Array();
    
    // good
    const items: Interface[] = [];

  • 3.2 Use return statements in array method callbacks. It's ok to omit the return if the function body consists of a single statement. We also encourage the use of the ternary operator in simple if/else cases.

    // bad
    [1, 2, 3].map((x: number): number => {
        return x + 1;
    });
    
    // good
    [1, 2, 3].map((x: number): number => x + 1);
    
    // good
    [1, 2, 3].map((x: number): number => {
        const y = x + 1;
        return x * y;
    });
    
    // good
    inbox.filter((msg: string): boolean => {
        if (msg.subject === 'Mockingbird') {
            return msg.author === 'Harper Lee';
        } else if (msg.subject === 'AnotherSubject') {
            return msg.author === 'The Author';
        }
    
        return false;
    });

    / Simple if/else cases /

    // bad inbox.filter((msg: string): boolean => { if (msg.subject === 'Mockingbird') { return msg.author === 'Harper Lee'; } else { return false; } });

    // best inbox.filter((msg: string): boolean => msg.subject === 'Mockingbird' ? msg.author === 'Harper Lee' : false );

⬆ back to top

  • 3.3 Use line breaks after open and before close array brackets if an array has multiple lines
// bad
const arr: number[][] = [
    [0, 1],
    [2, 3],
    [4, 5],
];

const objectInArray: Interface[] = [
    {
        id: 1,
    },
    {
        id: 2,
    },
];

const numberInArray: number[] = [1, 2];

// good
const arr: number[][] = [
    [0, 1],
    [2, 3],
    [4, 5],
];

const objectInArray: Interface[] = [
    {
        id: 1,
    },
    {
        id: 2,
    },
];

const numberInArray: number[] = [1, 2];

⬆ back to top

Destructuring

  • 4.1 Use object destructuring when accessing and using multiple properties of an object.

    Why? Destructuring saves you from creating temporary references for those properties.

    // bad
    const getFullName = (user: User): string => {
        const firstName = user.personalInformation.firstName;
        const lastName = user.personalInformation.lastName;
    
        return `${firstName} ${lastName}`;
    };
    
    // good
    const getFullName = (user: User): string => {
        const {firstName, lastName} = user.personalInformation;
        return `${firstName} ${lastName}`;
    };

  • 4.2 Use array destructuring.

    const arr: number[] = [1, 2, 3, 4];
    
    // bad
    const first: number = arr[0];
    const second: number = arr[1];
    
    // good
    const [first, second] = arr;

  • 4.3 Use object destructuring for multiple return values, not array destructuring.

    Why? You can add new properties over time or change the order of things without breaking call sites.

    // bad
    const processInput = (input: Input): ProcessedInput => {
        // then a miracle occurs
        return [left, right, top, bottom];
    };
    
    // the caller needs to think about the order of return data
    const [left, __, top] = processInput(input);
    
    // good
    const processInput = (input: Input): ProcessedInput => {
        // then a miracle occurs
        return {left, right, top, bottom};
    };
    
    // the caller selects only the data they need
    const {left, top} = processInput(input);

⬆ back to top

Strings

  • 5.1 Use single quotes '' for strings.

    // bad
    const name: string = 'Capt. Janeway';
    
    // bad - template literals should contain interpolation or newlines
    const name: string = `Capt. Janeway`;
    
    // good
    const name: string = 'Capt. Janeway';

  • 5.2 Strings that cause the line to go over 140 characters should not be written across multiple lines using string concatenation.

    Why? Broken strings are painful to work with and make code less searchable.

    // bad
    const errorMessage: string =
        'This is a super long error that was thrown because \
    of Batman. When you stop to think about how Batman had anything to do \
    with this, you would get nowhere \
    fast.';
    
    // bad
    const errorMessage: string =
        'This is a super long error that was thrown because ' +
        'of Batman. When you stop to think about how Batman had anything to do ' +
        'with this, you would get nowhere fast.';
    
    // good
    const errorMessage: string =
        'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';

  • 5.3 When programmatically building up strings, use template strings instead of concatenation.

    Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.

    // bad
    const sayHi = (name: string): string => 'How are you, ' + name + '?';
    
    // bad
    const sayHi = (name: string): string => ['How are you, ', name, '?'].join();
    
    // bad
    const sayHi = (name: string): string => `How are you, ${name}?`;
    
    // good
    const sayHi = (name: string): string => `How are you, ${name}?`;

  • 5.4 Do not unnecessarily escape characters in strings.

    Why? Backslashes harm readability, thus they should only be present when necessary.

    // bad
    const foo: string = '\'this\' is "quoted"';
    
    // good
    const foo: string = `'this' is "quoted"`;
    const foo: string = `my name is '${name}'`;

  • 5.5 Never hardcode a string that will appear in the UI in the code base. Localize the string in a dedicated json file.

    Why? Coveo develops international products, strings appearing in the UI can be translated in multiple languages, thus we localize them.

⬆ back to top

Functions

  • 6.1 Use named function expressions instead of function declarations. Prefer arrow functions if you do not absolutely need function to retrieve the proper this context. Most importantly, define functions as a method inside a class whenever possible.

    Why? Function declarations are hoisted, which means that it’s easy - too easy - to reference the function before it is defined in the file. This harms readability and maintainability. If you find that a function’s definition is large or complex enough that it is interfering with understanding the rest of the file, then perhaps it’s time to extract it to its own module!

    // bad
    const foo = function (bar: Interface): ReturnedInterface {
        // ...
    };
    
    // bad
    const foo = function bar(bar: Interface): ReturnedInterface {
        // ...
    };
    
    // good
    const foo = (bar: Interface): ReturnedInterface => {
        // ...
    };
    
    // best (declare functions as methods inside classes)
    class MyClass {
        foo(bar: Interface): ReturnedInterface {
            // ...
        }
    }

  • 6.2 Never name a parameter arguments. This will take precedence over the arguments object that is given to every function scope.

    // bad
    const foo = (name: string, options: Options, arguments: Arguments): ReturnedInterface => {
        // ...
    };
    
    // good
    const foo = (name: string, options: Options, args: Arguments): ReturnedInterface => {
        // ...
    };

  • 6.3 Use default parameter syntax rather than mutating function arguments.

    // really bad
    const handleThings = (options?: Options): ReturnedInterface {
      // No! We shouldn't mutate function arguments.
      // Double bad: if opts is falsy it'll be set to an object which may
      // be what you want but it can introduce subtle bugs.
      options = options || {};
      // ...
    };
    
    // still bad
    const handleThings = (options?: Options): ReturnedInterface => {
      if (options === void 0) {
        options = {};
      }
      // ...
    };
    
    // good
    const handleThings = (options: Options = {}): ReturnedInterface => {
      // ...
    };

  • 6.4 Never reassign parameters.

    Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the arguments object. It can also cause optimization issues, especially in V8.

    // bad
    const f1 = (a: number) => {
        a = 1;
        // ...
    };
    
    const f2 = (a?: number) => {
        if (!a) {
            a = 1;
        }
        // ...
    };
    
    // good
    const f3 = (a?: number) => {
        const b: number = a || 1;
        // ...
    };
    
    const f4 = (a: number = 1) => {
        // ...
    };

  • 6.5 Prefer the use of the spread operator ... to call variadic functions.

    Why? It's cleaner, you don't need to supply a context, and you can not easily compose new with apply.

    // bad
    const x: number[] = [1, 2, 3, 4, 5];
    console.log.apply(console, x);
    
    // good
    const x: number[] = [1, 2, 3, 4, 5];
    console.log(...x);
    
    // bad
    new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]))();
    
    // good
    new Date(...[2016, 8, 5]);

  • 6.6 Functions with multiline signatures, or invocations, should be indented just like every other multiline list in this guide: with each item on a line by itself, with a trailing comma on the last item.

    // bad
    const foo = (bar: string, baz: number, quux: number) => {
        // ...
    };
    
    // good
    const foo = (bar: string, baz: number, quux: number) => {
        // ...
    };
    
    // bad
    console.log(foo, bar, baz);
    
    // good
    console.log(foo, bar, baz);

⬆ back to top

Arrow Functions

  • 7.1 When you must use function expressions (as when passing an anonymous function), use arrow function notation.

    Why? It creates a version of the function that executes in the context of this, which is usually what you want, and is a more concise syntax. Only use function if really needed.

    Why not? If you have a fairly complicated function, you might move that logic out into its own function declaration.

    // bad
    [1, 2, 3].map(function (x: number): number {
        const y: number = x + 1;
        return x * y;
    });
    
    // good
    [1, 2, 3].map((x: number): number => {
        const y: number = x + 1;
        return x * y;
    });

  • 7.2 If the function body consists of a single expression, omit the braces and use the implicit return. Otherwise, keep the braces and use a return statement.

    Why? Syntactic sugar. It reads well when multiple functions are chained together.

    // bad
    [1, 2, 3].map((x: number): string => {
        const nextValue: number = x + 1;
        `A string containing the ${nextValue}.`;
    });
    
    // good
    [1, 2, 3].map((x: number): string => `A string containing the ${x}.`);
    
    // good
    [1, 2, 3].map((x: number): string => {
        const nextValue: number = x + 1;
        return `A string containing the ${nextValue}.`;
    });
    
    // good
    [1, 2, 3].map((x: number, index: number): {[key: number]: number} => ({
        [index]: x,
    }));

  • 7.3 In case the expression spans over multiple lines, wrap it in parentheses for better readability.

    Why? It shows clearly where the function starts and ends.

    // bad
    ['get', 'post', 'put'].map(
        (httpMethod: string): ReturnedInterface =>
            Object.prototype.hasOwnProperty.call(httpMagicObjectWithAVeryLongName, httpMethod),
    );
    
    // good
    ['get', 'post', 'put'].map((httpMethod: string): boolean =>
        Object.prototype.hasOwnProperty.call(httpMagicObjectWithAVeryLongName, httpMethod),
    );

  • 7.4 Always include parentheses around arguments for clarity and consistency.

    Why? Less visual clutter. Scopes the parameter with its type.

    // bad (not even possible in TypeScript with typed parameters)
    [1, 2, 3].map(x: number => (
      `A long string with the ${x}. It’s so long that we don’t want it to take up space on the .map line!`
    ));
    
    // good
    [1, 2, 3].map((x: number): string => (
      `A long string with the ${x}. It’s so long that we don’t want it to take up space on the .map line!`
    ));

  • 7.5 Avoid confusing arrow function syntax (=>) with comparison operators (<=, >=).

    // bad
    const itemHeight = (item: Item): string => (item.height > 256 ? item.largeSize : item.smallSize);
    
    // good
    const itemHeight = (item: Item): string => (item.height > 256 ? item.largeSize : item.smallSize);
    
    // good
    const itemHeight = (item: Item): string => {
        const {height, largeSize, smallSize} = item;
        return height > 256 ? largeSize : smallSize;
    };

⬆ back to top

Classes & Constructors

  • 8.1 Always use class. Avoid manipulating prototype directly.

    Why? class syntax is more concise and easier to reason about.

    // bad
    function Queue(contents: any[] = []) {
        this.queue = [...contents];
    }
    Queue.prototype.pop = function () {
        const value: any = this.queue[0];
        this.queue.splice(0, 1);
        return value;
    };
    
    // good
    class Queue {
        constructor(contents: any[] = []) {
            this.queue = [...contents];
        }
        pop() {
            const value: any = this.queue[0];
            this.queue.splice(0, 1);
            return value;
        }
    }

  • 8.2 Use extends for inheritance.

    Why? It is a built-in way to inherit prototype functionality without breaking instanceof.

    // bad
    const inherits = require('inherits');
    function PeekableQueue(contents) {
        Queue.apply(this, contents);
    }
    inherits(PeekableQueue, Queue);
    PeekableQueue.prototype.peek = () => this.queue[0];
    
    // good
    class PeekableQueue extends Queue {
        peek() {
            return this.queue[0];
        }
    }

  • 8.3 Methods can return this to help with method chaining.

    // bad
    Jedi.prototype.jump = function (): boolean {
        this.jumping = true;
        return true;
    };
    
    Jedi.prototype.setHeight = function (height: number) {
        this.height = height;
    };
    
    const luke: Jedi = new Jedi();
    luke.jump(); // => true
    luke.setHeight(20); // => undefined
    
    // good
    class Jedi {
        private jumping: boolean;
        private height: number;
    
        jump(): Jedi {
            this.jumping = true;
            return this;
        }
    
        setHeight(height: number): Jedi {
            this.height = height;
            return this;
        }
    }
    
    const luke: Jedi = new Jedi();
    
    luke.jump().setHeight(20);

  • 8.4 It's okay to write a custom toString() method, just make sure it works successfully and causes no side effects.

    class Jedi {
        private name: string;
    
        constructor(options: Options = {}) {
            this.name = options.name || 'no name';
        }
    
        getName(): string {
            return this.name;
        }
    
        toString(): string {
            return `Jedi - ${this.getName()}`;
        }
    }

  • 8.5 Classes have a default constructor if one is not specified. An empty constructor function or one that just delegates to a parent class is unnecessary.

    // bad
    class Jedi {
      constructor() {}
    
      // ...
    }
    
    // bad
    class Rey extends Jedi {
      constructor(options: Options) {
        super(options);
      }
    
      // ...
    }
    
    // good
    class Rey extends Jedi {
      private name: string;
    
      constructor(options: Options) {
        super(options: Options);
        this.name = 'Rey';
      }
    }

⬆ back to top

Modules

  • 9.1 Always use modules (import/export) over a non-standard module system.

    Why? Modules are the future, let's start using the future now.

    // bad
    const AirbnbStyleGuide = require('./AirbnbStyleGuide');
    module.exports = AirbnbStyleGuide.es6;
    
    // best
    import { es6 } from './AirbnbStyleGuide';
    export es6;

  • 9.2 Do not use wildcard (unless you're forced to) or default imports/exports.

    Why? This makes sure you have a single default export.

    // bad
    import * as AirbnbStyleGuide from './AirbnbStyleGuide';
    
    // bad
    export default AirbnbStyleGuide; // inside one file
    
    import AirbnbStyleGuide from './AirbnbStyleGuide'; inside another file
    
    // good
    export AirbnbStyleGuide; // inside one file
    
    import { AirbnbStyleGuide } from './AirbnbStyleGuide'; // inside another file

  • 9.3 And do not export directly from an import.

    Why? Although the one-liner is concise, having one clear way to import and one clear way to export makes things consistent.

    // bad
    // filename es6.ts
    export { es6 } from './AirbnbStyleGuide';
    
    // good
    // filename es6.ts
    import { es6 } from './AirbnbStyleGuide';
    export es6;

  • 9.4 Only import from a path in one place, and each import should be on its own line.

    Why? Having multiple lines that import from the same path can make code harder to maintain.

    // bad
    import {named1} from 'foo';
    // … some other imports … //
    import {named2} from 'foo';
    
    // bad
    import {named1, named2} from 'foo';
    
    //
    import foo, {named1, named2} from 'foo';

  • 9.5 Do not export mutable bindings.

    Why? Mutation should be avoided in general, but in particular when exporting mutable bindings. While this technique may be needed for some special cases, in general, only constant references should be exported.

    // bad
    export let foo: number = 3;
    
    // good
    export const foo: number = 3;

  • 9.6 Put all imports above non-import statements.

    Why? Since imports are hoisted, keeping them all at the top prevents surprising behavior.

    // bad
    import {foo} from 'foo';
    foo.init();
    
    import {bar} from 'bar';
    
    // good
    import {foo} from 'foo';
    import {bar} from 'bar';
    
    foo.init();

  • 9.7 Multiline imports should be indented just like multiline array and object literals.

    Why? The curly braces follow the same indentation rules as every other curly brace block in the style guide, as do the trailing commas.

    // bad
    import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
    
    // good
    import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

⬆ back to top

Iterators and Generators

  • 10.1 Don't use iterators. Prefer JavaScript's higher-order functions instead of loops like for-in or for-of.

    Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.

    Use map() / every() / filter() / find() / findIndex() / reduce() / some() / ... to iterate over arrays, and Object.keys() / Object.values() / Object.entries() to produce arrays so you can iterate over objects.

    const numbers: number[] = [1, 2, 3, 4, 5];
    
    // bad
    let sum: number = 0;
    for (let num: number of numbers) {
        sum += num;
    }
    sum === 15;
    
    // best (use the functional force)
    const sum: number = numbers.reduce((total: number, num: number): number => total + num, 0);
    sum === 15;
    
    // bad
    const increasedByOne: number[] = [];
    for (let i: number = 0; i < numbers.length; i++) {
        increasedByOne.push(numbers[i] + 1);
    }
    
    // best (keeping it functional)
    const increasedByOne: number[] = numbers.map((num: number): number => num + 1);

  • 10.2 Don't use generators for now.

    Why? They don't transpile well to ES5.

⬆ back to top

Properties

  • 11.1 Use dot notation when accessing properties.

    const luke: Interface = {
        jedi: true,
        age: 28,
    };
    
    // bad
    const isJedi: boolean = luke['jedi'];
    
    // good
    const isJedi: boolean = luke.jedi;

  • 11.2 Use bracket notation [] when accessing properties with a variable.

    const luke: Interface = {
        jedi: true,
        age: 28,
    };
    
    const getProp = (prop: string) => luke[prop];
    
    const isJedi: boolean = getProp('jedi');

⬆ back to top

Variables

  • 12.1 Always use const or let (not var) to declare variables. Not doing so will result in global variables. We want to avoid polluting the global namespace. Captain Planet warned us of that.

    // bad
    superPower: SuperPower = new SuperPower();
    
    // good
    const superPower: SuperPower = new SuperPower();

  • 12.2 Use one const or let declaration per variable.

    Why? It's easier to add new variable declarations this way, and you never have to worry about swapping out a ; for a , or introducing punctuation-only diffs. You can also step through each declaration with the debugger, instead of jumping through all of them at once.

    // bad
    const items: Item[] = getItems(),
        goSportsTeam: boolean = true,
        dragonball: string = 'z';
    
    // bad
    // (compare to above, and try to spot the mistake)
    const items: Item[] = getItems(),
        goSportsTeam: boolean = true;
    dragonball: string = 'z';
    
    // good
    const items: Item[] = getItems();
    const goSportsTeam: boolean = true;
    const dragonball: string = 'z';

  • 12.3 Group all your consts and then group all your lets.

    Why? This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables.

    // bad
    const items: Item[] = getItems();
    let dragonball: string;
    const goSportsTeam: boolean = true;
    let len: number;
    
    // good
    const goSportsTeam = true;
    const items: Item[] = getItems();
    let dragonball: string;
    let len: number;

  • 12.4 Assign variables where you need them, but place them in a reasonable place.

    Why? let and const are block scoped and not function scoped.

    // bad - unnecessary function call
    const checkName = (hasName: string): string | boolean => {
        const name: string = getName();
    
        if (hasName === 'test') {
            return false;
        }
    
        if (name === 'test') {
            this.setName('');
            return false;
        }
    
        return name;
    };
    
    // good
    const checkName = (hasName: string): string | boolean => {
        if (hasName === 'test') {
            return false;
        }
    
        const name: string = getName();
    
        if (name === 'test') {
            this.setName('');
            return false;
        }
    
        return name;
    };

  • 12.5 Avoid using unary increments and decrements (++, --).

    Why? Disallowing unary increment and decrement statements prevents you from pre-incrementing/pre-decrementing values unintentionally which can also cause unexpected behavior in your programs.

    // bad
    
    const array: number[] = [1, 2, 3];
    let num: number = 1;
    num++;
    --num;
    
    let sum: number = 0;
    let truthyCount: number = 0;
    for (let i: number = 0; i < array.length; i++) {
        let value: number = array[i];
        sum += value;
        if (value) {
            truthyCount++;
        }
    }
    
    // good
    
    const array: number[] = [1, 2, 3];
    let num: number = 1;
    num += 1;
    num -= 1;
    
    const sum: number = array.reduce((a: number, b: number): number => a + b, 0);
    const truthyCount: number = array.filter(Boolean).length;

⬆ back to top

Hoisting

Since this part appears to be more educational than anything else, you can refer to the original Airbnb Style Guide for more information about hoisting. In short, use const and let, and always avoid using var.

⬆ back to top

Comparison Operators & Equality

  • 13.1 Use === and !== over == and !=.

  • 13.2 Conditional statements such as the if statement evaluate their expression using coercion with the ToBoolean abstract method and always follow these simple rules:

    • Objects evaluate to true
    • Undefined evaluates to false
    • Null evaluates to false
    • Booleans evaluate to the value of the boolean
    • Numbers evaluate to false if +0, -0, or NaN, otherwise true
    • Strings evaluate to false if an empty string '', otherwise true
    if ([0] && []) {
        // true
        // an array (even an empty one) is an object, objects will evaluate to true
    }

  • 13.3 Use shortcuts for conditionals as often as possible.

    // bad
    if (isValid === true) {
        // ...
    }
    
    // good
    if (isValid) {
        // ...
    }
    
    // bad (unless you are testing for empty string only and not all falsy values)
    if (name !== '') {
        // ...
    }
    
    // good
    if (name) {
        // ...
    }
    
    // bad
    if (collection.length > 0) {
        // ...
    }
    
    // good
    if (collection.length) {
        // ...
    }

  • 13.5 Use braces to create blocks in case and default clauses that contain lexical declarations (e.g. let, const, function, and class).

    Why? Lexical declarations are visible in the entire switch block but only get initialized when assigned, which only happens when its case is reached. This causes problems when multiple case clauses attempt to define the same thing.

    // bad
    switch (foo) {
        case 1:
            let x: number = 1;
            break;
        case 2:
            const y: number = 2;
            break;
        case 3:
            const f = () => {
                // ...
            };
            break;
        default:
            class C {}
    }
    
    // good
    switch (foo) {
        case 1: {
            let x: number = 1;
            break;
        }
        case 2: {
            const y: number = 2;
            break;
        }
        case 3: {
            const f = () => {
                // ...
            };
            break;
        }
        case 4:
            bar();
            break;
        default: {
            class C {}
        }
    }

  • 13.6 Ternaries should not be nested and generally be single line expressions.

    // bad
    const foo: string | null = maybe1 > maybe2 ? 'bar' : value1 > value2 ? 'baz' : null;
    
    // good
    const maybeNull: string | null = value1 > value2 ? 'baz' : null;
    
    const foo: string | null = maybe1 > maybe2 ? 'bar' : maybeNull;

  • 13.7 Avoid unneeded ternary statements.

    // bad
    const foo: Interface = a ? a : b;
    const bar: boolean = c ? true : false;
    const baz: boolean = c ? false : true;
    
    // good
    const foo: Interface = a || b;
    const bar: boolean = !!c;
    const baz: boolean = !c;

⬆ back to top

Blocks

  • 14.1 Always use braces for if/else blocks or functions with multiple statemets, and place statements on their own lines.

    // bad
    if (test) return false;
    
    if (test) return false;
    
    // good
    if (test) {
        return false;
    }
    
    // bad
    const foo = (): boolean => {
        const isTrue: boolean = true;
        return isTrue;
    };
    
    // good
    const bar = (): boolean => {
        const isTrue: boolean = true;
        return isTrue;
    };
    
    // good
    const foo = (bar: boolean): boolean => bar;

  • 14.2 If you're using multi-line blocks with if and else, put else on the same line as your if block's closing brace.

    // bad
    if (test) {
        thing1();
        thing2();
    } else {
        thing3();
    }
    
    // good
    if (test) {
        thing1();
        thing2();
    } else {
        thing3();
    }

⬆ back to top

Control Statements

  • 15.1 In case your control statement (if, while etc.) gets too long or exceeds the maximum line length, each (grouped) condition could be put into a new line. The logical operator should be placed at the beginning of the line.

    // bad
    if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
        thing1();
    }
    
    // bad
    if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
        thing1();
    }
    
    // good
    if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
        thing1();
    }

⬆ back to top

Comments

  • 16.1 Use /** ... */ for multi-line comments.

    // bad
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param {String} tag
    // @return {Element} element
    const make = (tag: string): Element => {
    
      // ...
    
      return element;
    }
    
    // good
    /**
     * make() returns a new element
     * based on the passed-in tag name
     */
    const make = (tag: string): Element {
    
      // ...
    
      return element;
    }

  • 16.2 Use // for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment unless it's on the first line of a block.

    // bad
    const active: boolean = true; // is current tab
    
    // good
    // is current tab
    const active: boolean = true;
    
    // bad
    const getType = (): string => {
        console.log('fetching type...');
        // set the default type to 'no type'
        const type: string = this.type || 'no type';
    
        return type;
    };
    
    // good
    const getType = (): string => {
        console.log('fetching type...');
    
        // set the default type to 'no type'
        const type: string = this.type || 'no type';
    
        return type;
    };
    
    // also good
    const getType = (): string => {
        // set the default type to 'no type'
        const type: string = this.type || 'no type';
    
        return type;
    };

  • 16.3 Start all comments with a space to make it easier to read.

    // bad
    //is current tab
    const active: boolean = true;
    
    // good
    // is current tab
    const active: boolean = true;
    
    // bad
    /**
     *make() returns a new element
     *based on the passed-in tag name
     */
    const make = (tag: string): Element {
    
      // ...
    
      return element;
    };
    
    // good
    /**
     * make() returns a new element
     * based on the passed-in tag name
     */
    const make = (tag: string): Element {
    
      // ...
    
      return element;
    };

  • 16.4 Prefixing your comments with TODO helps other developers quickly understand if you're pointing out a problem that needs to be solved. Whenever possible, these comments should be supplemented with more context, like a linked story that can be specified by, for example, a JIRA issue number.

    class Calculator extends Abacus {
        private total: number;
    
        constructor() {
            super();
    
            // TODO: total should be configurable by an options param - JIRA issue: UI-4312
            this.total = 0;
        }
    }

⬆ back to top

Whitespace

  • 17.1 Place 1 space before the opening parenthesis in control statements (if, while etc.). Place no space between the argument list and the function name in function calls and declarations.

    // bad
    if(isJedi) {
      fight ();
    }
    
    // good
    if (isJedi) {
      fight();
    }
    
    // bad
    fight () {
      console.log ('Swooosh!');
    }
    
    // good
    fight() {
      console.log('Swooosh!');
    }

  • 17.2 Set off operators with spaces.

    // bad
    const x: number = y + 5;
    
    // good
    const x: number = y + 5;

  • 17.3 End files with a single newline character.

    // bad
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export es6;
    // bad
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export es6;↵
    ↵
    // good
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export es6;↵

  • 17.4 Use indentation when making long method chains (more than 2 method chains). Use a leading dot, which emphasizes that the line is a method call, not a new statement.

    // bad
    $('#items').find('.selected').highlight().end().find('.open').updateCount();
    
    // bad
    $('#items').find('.selected').focus().click();
    
    // good
    $('#items').find('.selected').focus().click();
    
    // good
    const leds: string = stage.selectAll('.led').data(data);

  • 17.5 Leave a blank line after blocks and before the next statement.

    // bad
    if (foo) {
        return bar;
    }
    return baz;
    
    // good
    if (foo) {
        return bar;
    }
    
    return baz;
    
    // bad
    const obj: Interface = {
        foo: () => {},
        bar: () => {},
    };
    return obj;
    
    // good
    const obj: Interface = {
        foo: () => {},
        bar: () => {},
    };
    
    return obj;

  • 17.6 Do not pad your blocks with blank lines.

    // bad
    bar() {
    
      console.log(foo);
    
    }
    
    // also bad
    if (baz) {
    
      console.log(qux);
    } else {
      console.log(foo);
    
    }
    
    // good
    bar() {
      console.log(foo);
    }
    
    // good
    if (baz) {
      console.log(qux);
    } else {
      console.log(foo);
    }

  • 17.7 Do not add spaces inside parentheses.

    // bad
    bar( foo: Foo ): Foo {
      return foo;
    }
    
    // good
    bar(foo: Foo): Foo {
      return foo;
    }
    
    // bad
    if ( foo ) {
      console.log(foo);
    }
    
    // good
    if (foo) {
      console.log(foo);
    }

  • 17.8 Do not add spaces inside brackets.

    // bad
    const foo: number[] = [1, 2, 3];
    console.log(foo[0]);
    
    // good
    const foo: number[] = [1, 2, 3];
    console.log(foo[0]);

  • 17.9 Add spaces inside curly braces.

    // bad
    const foo: Interface = {clark: 'kent'};
    
    // good
    const foo: Interface = {clark: 'kent'};

  • 17.10 Avoid having lines of code that are longer than 140 characters (including whitespace). If a line is to be broken, it should be broken at a meaningful position. If it barely exceeds 140 characters, you can leave it as is. Note: per above, long strings are exempt from this rule, and should not be broken up.

    Why? This ensures readability and maintainability.

    // bad
    const foo: Interface =
        jsonData &&
        jsonData.foo &&
        jsonData.foo.bar &&
        jsonData.foo.bar.baz &&
        jsonData.foo.bar.baz.quux &&
        jsonData.foo.bar.baz.quux.xyzzy;
    
    // good
    const foo: Interface =
        jsonData &&
        jsonData.foo &&
        jsonData.foo.bar &&
        jsonData.foo.bar.baz &&
        jsonData.foo.bar.baz.quux &&
        jsonData.foo.bar.baz.quux.xyzzy;
    
    // bad
    $.ajax({method: 'POST', url: 'https://airbnb.com/', data: {name: 'John'}})
        .done(() => console.log('Congratulations!'))
        .fail(() => console.log('You have failed this city.'));
    
    // good
    $.ajax({
        method: 'POST',
        url: 'https://airbnb.com/',
        data: {name: 'John'},
    })
        .done(() => console.log('Congratulations!'))
        .fail(() => console.log('You have failed this city.'));

⬆ back to top

Commas

  • 18.1 Do not use leading commas, use trailing commas.

    // bad
    const story: Word[] = [once, upon, aTime];
    
    // good
    const story: Word[] = [once, upon, aTime];
    
    // bad
    const hero: Hero = {
        firstName: 'Ada',
        lastName: 'Lovelace',
        birthYear: 1815,
        superPower: 'computers',
    };
    
    // good
    const hero: Hero = {
        firstName: 'Ada',
        lastName: 'Lovelace',
        birthYear: 1815,
        superPower: 'computers',
    };

  • 18.2 Use the additional trailing comma.

    Why? This leads to cleaner git diffs. Also, transpilers like Babel will remove the additional trailing comma in the transpiled code which means you don't have to worry about the trailing comma problem in legacy browsers.

    // bad - git diff without trailing comma
    const hero: Hero = {
         firstName: 'Florence',
    -    lastName: 'Nightingale'
    +    lastName: 'Nightingale',
    +    inventorOf: ['coxcomb chart', 'modern nursing']
    };
    
    // good - git diff with trailing comma
    const hero: Hero = {
         firstName: 'Florence',
         lastName: 'Nightingale',
    +    inventorOf: ['coxcomb chart', 'modern nursing'],
    };
    // bad
    const hero: Hero = {
      firstName: 'Dana',
      lastName: 'Scully'
    };
    
    const heroes: string[] = [
      'Batman',
      'Superman'
    ];
    
    // good
    const hero: Hero = {
      firstName: 'Dana',
      lastName: 'Scully',
    };
    
    const heroes: string[] = [
      'Batman',
      'Superman',
    ];
    
    // bad
    createHero(
      firstName: string,
      lastName: string,
      inventorOf: string[]
    ) {
      // does nothing
    }
    
    // good
    createHero(
      firstName: string,
      lastName: string,
      inventorOf: string[],
    ) {
      // does nothing
    }
    
    // bad
    createHero(
      firstName: string,
      lastName: string,
      inventorOf: string[]
    );
    
    // good
    createHero(
      firstName: string,
      lastName: string,
      inventorOf: string[],
    );
    
    // good (note that a comma must not appear after a "rest" element)
    createHero(
      firstName: string,
      lastName: string,
      inventorOf: string[],
      ...heroArgs
    );

⬆ back to top

Semicolons

  • 19.1 Use semicolons to end your code statements.

    ```typescript
    // bad
    ((): string {
      const name: string = 'Skywalker'
      return name
    })()
    
    // good
    ((): string {
      const name: string = 'Skywalker';
      return name;
    }());
    ```

    ⬆ back to top

Type Casting & Coercion

  • 20.1 Perform type coercion at the beginning of the statement.

  • 20.2 Strings:

    // => this.reviewScore = 9;
    
    // bad
    const totalScore: string = this.reviewScore + ''; // invokes this.reviewScore.valueOf()
    
    // bad
    const totalScore: string = this.reviewScore.toString(); // isn't guaranteed to return a string
    
    // good
    const totalScore: string = String(this.reviewScore);

  • 20.3 Numbers: Use Number for type casting and parseInt always with a radix for parsing strings.

    const inputValue: string = '4';
    
    // bad
    const val: number = new Number(inputValue);
    
    // bad
    const val: number = +inputValue;
    
    // bad
    const val: number = inputValue >> 0;
    
    // bad
    const val: number = parseInt(inputValue);
    
    // good
    const val: number = Number(inputValue);
    
    // good
    const val: number = parseInt(inputValue, 10);

  • 20.4 If for whatever reason you are doing something wild and parseInt is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you're doing.

    // good
    /**
     * parseInt was the reason my code was slow.
     * Bitshifting the String to coerce it to a
     * Number made it a lot faster.
     */
    const val: number = inputValue >> 0;

  • 20.5 Note: Be careful when using bitshift operations. Numbers are represented as 64-bit values, but bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:

    2147483647 >> 0; // => 2147483647
    2147483648 >> 0; // => -2147483648
    2147483649 >> 0; // => -2147483647

  • 20.6 Booleans:

    const age: number = 0;
    
    // bad
    const hasAge: boolean = new Boolean(age);
    const hasAge: boolean = Boolean(age);
    
    // best
    const hasAge: boolean = !!age;

⬆ back to top

Naming Conventions

  • 21.1 Be descriptive with your naming.

    // bad
    const q = () => {
        // ...
    };
    
    // good
    const query = () => {
        // ...
    };

  • 21.2 Use camelCase when naming objects, functions, and instances.

    // bad
    const OBJEcttsssss: Interface = {};
    const this_is_my_object: Interface = {};
    const c = () => {};
    
    // good
    const thisIsMyObject: Interface = {};
    const thisIsMyFunction = () => {};

  • 21.3 Use PascalCase only when naming constructors or classes.

    // bad
    class user {
        private name: string;
    
        constructor(options: Options) {
            this.name = options.name;
        }
    }
    
    const bad: user = new user({
        name: 'nope',
    });
    
    // good
    class User {
        private name: string;
    
        constructor(options: Options) {
            this.name = options.name;
        }
    }
    
    const good: User = new User({
        name: 'yup',
    });

  • 21.4 Do not use trailing or leading underscores.

⬆ back to top

Accessors

  • 22.1 Accessor functions for properties are not required.

  • 22.2 Do use TypeScript getters/setters.

    ```typescript
    // good
    class Dragon {
      get age(): number {
        // ...
      }
    
      set age(value: number): number {
        // ...
      }
    }
    ```

    ⬆ back to top

Events

  • 23.1 When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event. For example, instead of:

    // bad
    $(this).trigger('listingUpdated', listing.id);
    
    // ...
    
    $(this).on('listingUpdated', (e: JQueryEvent, listingId: string) => {
        // do something with listingId
    });

    prefer:

    // good
    $(this).trigger('listingUpdated', {listingId: listing.id});
    
    // ...
    
    $(this).on('listingUpdated', (e: JQueryEvent, data: {listingId: string}) => {
        // do something with data.listingId
    });

    ⬆ back to top

jQuery

  • 24.1 Prefix jQuery object variables with a $ if they are outside ui elements bind to a Marionette View.

    // bad
    const sidebar: JQueryElement = $('.sidebar');
    
    // good
    const $sidebar: JQueryElement = $('.sidebar');
    
    // good
    const $sidebarBtn: JQueryElement = $('.sidebar-btn');

  • 24.2 Cache jQuery lookups whenever possible.

    // bad
    function setSidebar() {
        $('.sidebar').hide();
    
        // ...
    
        $('.sidebar').css({
            'background-color': 'pink',
        });
    }
    
    // good
    function setSidebar() {
        const $sidebar: JQueryElement = $('.sidebar');
        $sidebar.hide();
    
        // ...
    
        $sidebar.css({
            'background-color': 'pink',
        });
    }

  • 24.3 For DOM queries use Cascading $('.sidebar ul') or parent > child $('.sidebar > ul'). jsPerf

  • 24.4 Use find with scoped jQuery object queries.

    ```typescript
    // bad
    $('ul', '.sidebar').hide();
    
    // bad
    $('.sidebar').find('ul').hide();
    
    // good
    $('.sidebar ul').hide();
    
    // good
    $('.sidebar > ul').hide();
    
    // good
    $sidebar.find('ul').hide();
    ```

    ⬆ back to top

ECMAScript 6+ (ES 2015+) Styles

  • 25.1 Do not use TC39 proposals that have not reached stage 3.

    Why? They are not finalized, and they are subject to change or to be withdrawn entirely. We want to use JavaScript, and proposals are not JavaScript yet.

⬆ back to top

Testing

  • 26.1 We use Jasmine at Coveo, and combine it with Enzyme when testing React components.

  • 26.2 You should be writing tests for all new code you write. 100% test coverage is a good goal to strive for, even if it's not always practical to reach it.

    Why? Testing aggressively gives you solid proofs that your system/application will work the way you want. Plus, if new code breaks your application, it will be much easier to find out why it happened if the code base is well tested.

  • 26.3 Whenever you fix a bug, write a regression test. In other words, add additional unit tests proving that the bug is really fixed and unlikely to break again in the future.

    Why? A bug fixed without a regression test is almost certainly going to break again in the future.

  • 26.4 Use mocks to mock objects, and create them in their own files.

    Why? Having your mocks outside your test files makes your tests more readable.

  • 26.5 Name your spies with the name of the entity you want to spy on (be it a method or property) following with the Spy suffix. For example, if you want to spy on a method called renderChildren, you should name your spy renderChildrenSpy.

    Why? Naming your spies makes your tests easier to understand and shorter to write overall. Adding the Spy suffix makes the person who reads your code aware that it is a "spied upon" entity.

  • 26.6 Prefer built-in jasmine matchers (toBeDefined, toEqual, toBe, toContain, etc) before custom matchers, except for toBeTruthy and toBeFalsy.

    Why? Jasmine's matchers are robust and give clear information in the logs if your test breaks, which make things easier to debug.

  • 26.7 Do not use variables in your tests description.

    Why? If the test fails, and in most other circumstances, it will make it unsearchable.

  • 26.8 Do not break your test descriptions on two lines, always put them on one line even if it's long.

    Why? If the test fails, and in most other circumstances, it will make the test unsearchable.

⬆ back to top

Reviewing

  • 27.1 Review code of your peers daily as long as there are pull requests to review. No, this won't affect your productivity negatively, it will speed it up.

    Why? Receiving or giving code reviews at least daily speeds up the feedback rate for each team member and consequently speeds up the rate at which your team merges its pull requests without losing in quality. Reviewing and merging pull requests at a standard and predictible pace gives momentum to the team. Adopting this habit can also free your brain from thinking about tasks that have remained pending in a "review" state for multiple days without having received any feedback.

  • 27.2 Consider a complete code review as having read (and hopefully understood) each line of code contained in the p
5.0.1

9 months ago

5.0.0

9 months ago

4.2.1

2 years ago

4.2.0

2 years ago

4.1.1

2 years ago

4.1.0

2 years ago

4.0.0

3 years ago

3.0.2

4 years ago

3.0.1

4 years ago

3.0.0

4 years ago

2.0.0

4 years ago

1.0.3

5 years ago

1.0.1

5 years ago

1.0.1-beta

5 years ago

1.0.0

5 years ago

0.9.0

5 years ago

0.8.2

5 years ago

0.8.1

5 years ago

0.8.0

5 years ago

0.7.2

5 years ago

0.7.1

5 years ago

0.6.7

6 years ago

0.6.6

6 years ago

0.6.5

6 years ago

0.6.4

6 years ago

0.6.3

6 years ago

0.6.2

6 years ago

0.6.1

6 years ago

0.6.0

6 years ago

0.5.3

6 years ago

0.5.2

6 years ago

0.5.1

6 years ago

0.5.0

6 years ago

0.4.1

6 years ago

0.4.0

6 years ago

0.3.3

6 years ago

0.3.2

6 years ago

0.3.1

6 years ago

0.3.0

6 years ago

0.2.1

6 years ago

0.2.0

6 years ago

0.1.6

6 years ago

0.1.5

6 years ago

0.1.4

6 years ago

0.1.3

6 years ago

0.1.2

6 years ago

0.1.1

6 years ago

0.1.0

6 years ago