Skip to content

iOffice/tslint-config-ioffice

Repository files navigation

iOffice TypeScript Style Guide

NPM Version License Build Status

Disclaimer: This guide is inspired by the Airbnb JavaScript Style Guide. Most sections we see here will be taken straight from their guide and slowly adapted to the typescript language.

Table of Contents

  1. Types
    1. Primitives
    2. Complex
    3. Type Assertion
  2. References
    1. Prefer Const
    2. Disallow Var
  3. Functions
    1. Unused Parameters
    2. Signature/Invocation
  4. Classes
  5. Arrow Functions
    1. Use Them
  6. Blocks
    1. Braces
    2. Cuddled Elses
    3. No Else Return
  7. Whitespace
    1. Spaces
    2. Single Space
    3. In Braces
    4. Consecutive Blank Lines
  8. Commas
    1. Leading Commas
    2. Trailing Commas
  9. Semicolons
    1. Required
  10. Modules
    1. Use Them
    2. Single Export
    3. Import Order
    4. Multiline Imports

Types

  • 1.1 Primitives: When you access a primitive type you work directly on its value.

    • number
    • string
    • boolean
    • null
    • undefined

    These types can be inferred by the typescript compiler and should not explicitly typed.

    Why? Explicit types where they can be easily inferred by the compiler make code more verbose.

    const foo = 1;
    let bar = foo;
    
    bar = 9;
    
    console.log(foo, bar);  // => 1, 9
    // bad
    const foo: number = 1;
    
    // good
    let bar: number = foo;
    
    bar = 9;
    
    console.log(foo, bar);  // => 1, 9

  • 1.2 Complex: When you access a complex type you work on a reference to its value.

    • object
    • array
    • function
    const foo: number[] = [1, 2];
    const bar: number[] = foo;
    
    bar[0] = 9;
    
    console.log(foo[0], bar[0]); // => 9, 9

  • 1.3 Type Assertion: Avoid using the angle bracket type assertion.

    Although both formats have the same effect our goal is to have a consistent type assertion style across our codebase. Using the as Type syntax can also avoid confusion with generic methods and classes that use angle brackets.

    // bad
    processProperty(<Person>user.property);
    
    // good
    processProperty((user as Person).property);
    // bad
    someGenericMethod<Person>(<Person>user);
    
    // good
    someGenericMethod<Person>(user as Person);

⬆ back to top

References

  • 2.1 Prefer Const: 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;
    // bad
    function printPI() {
      let pi = 3.14;
      console.log(pi);
    }
    
    // good
    function printPI() {
      const pi = 3.14;
      console.log(pi);
    }

  • 2.2 Disallow Var: 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;
    }

⬆ back to top

Functions

  • 3.1 Unused Parameters: Remove them. To prevent them make sure to use noUnusedParameters in your tsconfig.json file.

    We may end up with parameters that are not used when we refactor. If we keep them we risk having incorrect documentation and all sort of confusions.

    In some cases, when creating listeners a function may require a certain signature which will undoubtedly bring us unused parameters. When this is the case simply name the placeholder variables with a leading underscore.

    // bad
    function foo(a, b, c) {
      return a + b;
    }
    
    // good
    function foo(a, b) {
      return a + b;
    }

  • 3.2 Signature/Invocation: 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
    function foo(bar,
                 baz,
                 quux) {
      // ...
    }
    
    // good
    function foo(
      bar,
      baz,
      quux,
    ) {
      // ...
    }
    // bad
    console.log(foo,
      bar,
      baz);
    
    // good
    console.log(
      foo,
      bar,
      baz,
    );

⬆ back to top

Classes

⬆ back to top

Arrow Functions

  • 5.1 Use Them: 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.

    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) {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });
    // good
    [0, null, 1, null, 2].filter(x => x !== null);

⬆ back to top

Blocks

  • 6.1 Braces: Use braces with all multi-line blocks.

    // bad
    if (test)
      return false;
    
    // good
    if (test) return false;
    
    // good
    if (test) {
      return false;
    }
    // bad
    function foo() { return false; }
    
    // good
    function bar() {
      return false;
    }

  • 6.2 Cuddled Elses: 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();
    }

  • 6.3 No Else Return: If an if block always executes a return statement, the subsequent else block is unnecessary. A return in an else if block following an if block that contains a return can be separated into multiple if blocks.

    // bad
    function foo() {
      if (x) {
        return x;
      } else {
        return y;
      }
    }
    
    // good
    function foo() {
      if (x) {
        return x;
      }
    
      return y;
    }
    // bad
    function cats() {
      if (x) {
        return x;
      } else if (y) {
        return y;
      }
    }
    
    // good
    function cats() {
      if (x) {
        return x;
      }
    
      if (y) {
        return y;
      }
    }
    // bad
    function dogs() {
      if (x) {
        return x;
      } else {
        if (y) {
          return y;
        }
      }
    }
    
    // good
    function dogs() {
      if (x) {
        if (z) {
          return y;
        }
      } else {
        return z;
      }
    }

⬆ back to top

Whitespace

  • 7.1 Spaces: Use soft tabs set to 2 spaces.

    // bad
    function foo() {
        const name;
    }
    
    // bad
    function bar() {
     const name;
    }
    
    // good
    function baz() {
      const name;
    }

  • 7.2 Single Space: Use no more than a single space. Multiple spaces in a row that are not used for indentation are typically mistakes. No other rule should allow multiple spaces.

    Adding unnecessary spaces for the sake of aligning code typically leads to chaotic git differences.

    // bad
    import { mod          } from 'mod';
    import { someOtherMod } from 'some-other-mod';
    
    // good
    import { mod } from 'mod';
    import { someOtherMod } from 'some-other-mod';
    // bad
    const someVar      = 'foo';
    const someOtherVar = 'barBaz';
    
    // good
    const someVar = 'foo';
    const someOtherVar = 'barBaz';
    // bad
    const obj = {
      first:      'first',
      secondLine: 'second',
    };
    
    // good
    const obj = {
      first: 'first',
      secondLine: 'second',
    };

  • 7.3 In Braces: Add spaces inside curly braces.

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

  • 7.4 Consecutive Blank Lines: Avoid multiple empty lines and only allow one line at the end of the file.

    // bad
    const x = 1;
    
    
    
    const y = 2;
    
    // good
    const x = 1;
    
    const y = 2;

⬆ back to top

Commas

  • 8.1 Leading Commas: Nope.

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

  • 8.2 Trailing Commas: Additional trailing comma: Yup.

    Why? This leads to cleaner git diffs. Also, the Typescript transpiler 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
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully'
    };
    
    const heroes = [
      'Batman',
      'Superman'
    ];
    
    // good
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully',
    };
    
    const heroes = [
      'Batman',
      'Superman',
    ];
    // bad
    function createHero(
      firstName,
      lastName,
      inventorOf
    ) {
      // does nothing
    }
    
    // good
    function createHero(
      firstName,
      lastName,
      inventorOf,
    ) {
      // does nothing
    }
    
    // good (note that a comma must not appear after a "rest" element)
    function createHero(
      firstName,
      lastName,
      inventorOf,
      ...heroArgs
    ) {
      // does nothing
    }
    // bad
    createHero(
      firstName,
      lastName,
      inventorOf
    );
    
    // good
    createHero(
      firstName,
      lastName,
      inventorOf,
    );
    
    // good (note that a comma must not appear after a "rest" element)
    createHero(
      firstName,
      lastName,
      inventorOf,
      // TODO: Remove next tslint disable once the rule is fixed.
      // tslint:disable-next-line
      ...heroArgs
    );
    // good
    createHero(firstName, lastName, inventorOf);
    
    // bad
    createHero(firstName, lastName, inventorOf, );

⬆ back to top

Semicolons

  • 9.1 Required: Yup.

    Why? When JavaScript encounters a line break without a semicolon, it uses a set of rules called Automatic Semicolon Insertion to determine whether or not it should regard that line break as the end of a statement, and (as the name implies) place a semicolon into your code before the line break if it thinks so. ASI contains a few eccentric behaviors, though, and your code will break if JavaScript misinterprets your line break. These rules will become more complicated as new features become a part of JavaScript. Explicitly terminating your statements and configuring your linter to catch missing semicolons will help prevent you from encountering issues.

    // bad - raises exception
    const luke = {}
    const leia = {}
    [luke, leia].forEach(jedi => jedi.father = 'vader')
    
    // bad - raises exception
    const reaction = "No! That's impossible!"
    (async function meanwhileOnTheFalcon() {
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }())
    
    // bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI!
    function foo() {
      return
        'search your feelings, you know it to be foo'
    }
    
    // good
    const luke = {};
    const leia = {};
    [luke, leia].forEach((jedi) => {
      jedi.father = 'vader';
    });
    
    // good
    const reaction = "No! That's impossible!";
    (async function meanwhileOnTheFalcon() {
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }());
    
    // good
    function foo() {
      return 'search your feelings, you know it to be foo';
    }

⬆ back to top

Modules

  • 10.1 Use Them: Always use modules (import/export) over a non-standard module system. You can always transpile to your preferred module system.

    Why? Modules are the future

    // bad
    const iOfficeStyleGuide = require('./iOfficeStyleGuide');
    module.exports = iOfficeStyleGuide.ts;
    
    // ok
    import * as iOfficeStyleGuide from './iOfficeStyleGuide';
    const ts = iOfficeStyleGuide.ts;
    export {
      ts,
    };
    
    // best
    import { ts } from './iOfficeStyleGuide';
    export {
      ts,
    };

  • 10.2 Single Export: Do not use default exports. Use a single named export which declares all the classes, functions, objects and interfaces that the module is exporting.

    Why? Named imports/exports promote clarity. In addition, current tooling differs on the correct way to handle default imports/exports. Avoiding them all together can help avoid tooling bugs and conflicts.

    Using a single named export allows us to see in one place all the objects that we are exporting.

    // bad
    export class A {}
    export class B {}
    export default A;
    
    // good
    class C {}
    class D {}
    export {
      C,
      D,
    };
    // bad
    export default function() {
    }
    
    // good
    function A() {
    }
    export { A };
    // good
    function A() {
    }
    export { A };

  • 10.3 Import Order: Import statements should be alphabetized and sorted. Sources of different groups must be sorted by 3rd party libraries, libraries provided by iOFFICE and finally local modules.

    Currently this is specified by the io-import-style since the ordered-imports rule has not merged the change that will allow us to create custom groups.

    Why? It enforces a consistent ordering.

    // bad
    import { B, A } from 'xyz';
    
    // good
    import { A, B } from 'xyz';
    // bad
    import { a } from './local/path';
    import { B, A, D, C } from 'xyz';
    import { b } from '../parent/directory';
    // good
    import { A, B, C, D } from 'xyz';
    
    import { b } from '../parent/directory';
    import { a } from './local/path';

  • 10.4 Multiline Imports: 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

License

MIT License

Copyright (c) 2018 iOFFICE

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Amendments

Following Airbnb's advice, we also encourage you to fork this guide and change the rules to fit your team's style guide.

The code provided should make it easy to make adjustments to the examples since they are linted with the tslint configuration. If you do not agree with part of the configuration simply change it, test the guide and make the appropiate changes to it.