Coding Standard - Typescript

As of April 2018, Vanilla will follow the following coding standard for all frontend scripts. Existing code should not (and cannot) be mass-updated, but all new code must follow this standard.

We adopted this standard for all the usual reasons: our team is growing, and we want to make sure things stay consistent between our various projects, and it’s annoying and hard to read when everything is styled differently. Key words in this document (“MUST”, “SHOULD”, etc.) are used as described in RFC 2119 which is as boring and obvious as you expect but mercifully brief.

Language Features

For code in release/2.6 or below, no transpilation step or polyfills may be assumed.

For code in master or release/2.7 the vanilla-cli build tool MUST be used.

All typescript features up to typescript version 2.8 is supported. This includes anything found in the Typescript documentation. This includes but is not limited to the following features:

Browser Support

Whether the final javascript that runs in the browser has been transpiled or not, all javascript must support the following browsers:

  • IE11
  • The last 2 versions of the following browsers
    • Edge
    • Firefox
    • Chrome
    • Chrome for Android
    • Safari
    • iOS Safari

Validating your Typescript

The simplest way to validate your Typescript code is to use the vanilla-cli build tool. If the a build succeeds with the no warnings or errors, then it should be valid.

Vanilla uses Prettier and TSLint to enforce coding standards on Typescript. Prettier will automatically format your code to conform to style standards, while TSLint will perform static analysis to check code for readability, maintainability, and functionality errors.

There are multiple ways to use these tools.

vanilla-cli

Running the build command from the vanilla-cli build tool will automatically format all compiled code with Prettier. Additionally it will attempt to validate the code with TSLint. Some warnings can and will be automatically fixed by TSLint. Others will require manual attention.

IDE Integration

Prettier and TSLint have integrations with many popular IDEs and editors.

Prettier

Prettier’s IDE integrations generally allow automatic formatting on save, on input, or manually.

Vanilla’s prettier config can be found at vanilla/prettier-config or on npm @vanillaforums/prettier-config.

All repositories in the vanilla organization are meant to be developed in the context of a vanilla/vanilla installation. master and release/2.7 and above should have these presets already installed listed in their package.json along with their peer-dependencies. Additionally vanilla-cli contains its own copies.

TSLint

Vanilla’s TSLint config can be found at vanilla/prettier-config or on npm @vanillaforums/tslint-config.

All repositories in the vanilla organization are meant to be developed in the context of a vanilla/vanilla installation. master and release/2.7 and above should have these presets already installed listed in their package.json along with their peer-dependencies. Additionally vanilla-cli contains its own copies.

1. Overview

  • All new files in the vanilla/vanilla repo MUST be in Typescript.

  • New code MUST NOT use JQuery. Instead native browser API’s and utility functions from @core/dom may be used.

  • All files MUST be formatted with Prettier.

  • Files SHOULD NOT declare more than 1 class in a single file.

  • Files with a default export MUST be named equivalently to the symbol (class, function, interface, constant) that they export.

  • Interfaces for code defined inside of Vanilla Forums code MUST be named beginning with the character I (Eg. IThing, IButtonOptions). This rule does not apply to type definitions for dependencies.

  • Method names SHOULD be declared in camelCase.

  • Static class properties MUST be declared in all upper case with underscore separators.

  • Class names MUST be declared in PascalCase.

  • const MUST be used where possible. Otherwise let MUST be used. var MUST NOT be used.

  • === MUST be used instead of ==. An exception is made for null checks specifically someVar == null.

  • A file MUST NOT contain unused imports.

  • Test files MUST be located in a directory __tests__ and end with the extension .test.ts or .test.js.

  • console.log and other built in logging functions MUST NOT be used. Instead logging functions from @core/utility may be used.

1.1. Styling rules

All files MUST be formatted with Prettier. This is to ensure consistent formatting, and to prevent overly large diffs if someone when someone else formats a file. This encompasses all spacing and formatting rules. The following rules among others will all be automatically enforced by formatting with Prettier.

  • Code MUST use 4 spaces for indenting, not tabs.

  • Opening braces for classes and functions MUST be on the same line.

  • Control structure keywords MUST have one space after them; method and function calls MUST NOT.

  • Opening braces for control structures MUST go on the same line, and closing braces MUST go on the next line after the body.

  • Opening parentheses for control structures MUST NOT have a space after them, and closing parentheses for control structures MUST NOT have a space before.

  • Lines MUST be less than 120 characters or less.

  • Semicolons are REQUIRED;

  • Strings SHOULD use double quotes " or Backtick quotes \`.

  • Colons in object and interface declarations MUST NOT be preceeded by a space and MUST be followed by a space.

  • Object and array declarations MUST contain a trailing comma, if it is declared on multiple lines.

1.2. Example

This example encompasses some of the rules below as a quick overview:

/**
 * @copyright 2009-2018 Vanilla Forums Inc.
 * @license http://www.opensource.org/licenses/gpl-2.0.php GPLv2
 */

/**
 * The is the foo class that does foo.
 *
 * This is a longer description that spans multiple
 * lines.
 */
export default class SomeClass extends ParentClass implements ISome {

    /**
     * The is a method that does a thing.
     *
     * This is a longer description that spans multiple
     * lines.
     *
     * @param a Must be a full sentence if provided.
     * @param b Must be a full sentence if provided.
     *
     * @returns Must be a full sentence if provided.
     */
    public function sampleFunction(a: string, b?:  = string): boolean{
        if (a === b) {
            return bar();
        } else if (a > b) {
            return foo->bar(a);
        } else {
            return BazClass.bar(a, b);
        }
    }
}

2. Isolating legacy code

New code MUST NOT use JQuery. Instead native browser API’s and utility functions from @core/dom may be used.

An exception is made to this rule for code being gradually ported into the new code base, but does not have a long term future. This code MUST be contained in a directory call legacy. At some point in the future legacy code will be completely removed so anything important enough to save SHOULD be migrated into the either @core/application, @core/utility, or @core/dom, without a dependency on JQuery.

New code MUST NOT access methods or properties in the global Vanilla or gdn objects. Instead functions from @core/utility and @core/application may be used.

3. General

3.1. Files

All files MUST use the Unix LF (linefeed) line ending.

All files MUST end with a single blank line.

A file with a default export MUST be named the same as the export.

3.2. Character Encoding

Code MUST use only UTF-8 without BOM.

3.3. Lines

There MUST NOT be a hard limit on line length.

The soft limit on line length MUST be 120 characters; automated style checkers MUST warn but MUST NOT error at the soft limit.

Lines MUST NOT be longer than 120 characters; lines longer than that MUST be split into multiple subsequent lines of no more than 120 characters each.

There MUST NOT be trailing whitespace of any kind.

Blank lines MAY be added to improve readability and to indicate related blocks of code.

There MUST NOT be more than one statement per line.

3.4. Indenting

Code MUST use an indent of 4 spaces, and MUST NOT use tabs for indenting.

3.5. Single and Double Quotes

All strings will automatically have their quotes adjusted by Prettier.

Strings SHOULD use double quotes " or Backtick quotes \`.

Strings MAY use double quotes if there are double quotes that would have to otherwise be escaped.

// Good
"Something"
"OMG she's using double quotes!"
`This one uses backtick quotes and has ${numberOfVars} variables.`
'"Single quotes can work sometimes too!", he excaimed.'

// Bad
'Single quotes with no escaped characters'
'Definitely not single quotes if there\'s single quotes that need to be escaped.'

4. Namespaces, Types, and Interfaces

Namespaces

Typescript namespaces MUST NOT be used. ES Modules MUST be used instead.

4.1. Types and Interfaces

  • Interfaces names MUST be prefixed with the an uppercase I eg. IProps, IState.

  • Interfaces MUST be used instead of type literals. Eg.

// Good
interface IThing {
    foo: number;
}

// Bad
type IOtherThing = {
    foo: number;
}

4.2. Type casting

  • When casting a type the variable as IType syntax MUST be used.

  • When casting a type the <IType>variable syntax MUST NOT be used.

interface IFoo {
    foo: string;
}

// Good
(getFooLikeStructure() as IFoo).foo;

// Bad
(<IFoo>getFooLikeStructure()).foo;

4.3. Forbidden Types

The following types MUST not be used. Instead their alternatives should be used.

Forbidden Type Reason Alternative
Boolean This type refers to non-primitive boxed object that are almost never used appropriately in JavaScript code. Use boolean
Number This type refers to non-primitive boxed object that are almost never used appropriately in JavaScript code. Use number
String This type refers to non-primitive boxed object that are almost never used appropriately in JavaScript code. Use string
Symbol This type refers to non-primitive boxed object that are almost never used appropriately in JavaScript code. Use symbol
Object This type refers to non-primitive boxed object that are almost never used appropriately in JavaScript code. An interface is the preferred method method of annotating an object. If you do not know the contents, use the any type. If you actually are trying to refer to the primitive object type use object.
Function Function is overly generic descriptor. If you are accepting a function you should be more specific about the properties and return types. If you are to pass the function arguments, those should be in the type declaration. Use a explicit function declaration () => void or (arg1: string) => boolean.

5. Class Constants, Properties, and Methods

5.1. Static class properties

Static class properties MUST be declared in all upper case with underscore separators.


class Foo {
    public static VERSION = '1.0';
    public static DATE_APPROVED = '2012-06-01';
}

5.2. Extends and Implements

The extends and implements keywords MUST be declared on the same line as the class name.

The opening brace for the class MUST go on the same line as the class name; the closing brace for the class MUST go on the next line after the body.

class ClassName extends ParentClass implements ArrayAccess, Countable {
    // constants, properties, methods
}

Lists of implements MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line.

export default class ClassName extends ParentClass implements
    ArrayAccess,
    Countable,
    Serializable
{
    // constants, properties, methods
}

5.3. Properties

Visibility MUST be declared on all properties.

A type declaration SHOULD be declared on all properties.

There MUST NOT be more than one property declared per statement.

Private properties SHOULD use the the private or protected visibility instead of prefix with a single underscore. A single underscore MAY be used to denote a internal property that must still be exported, but should not be used elsewhere.

A property declaration looks like the following.

export default class ClassName {
    public foo = null;
}

5.4. Methods

Method names MUST be declared in camelCase().

Visibility MUST be declared on all methods.

Private methods SHOULD use the the private or protected visibility instead of prefix with a single underscore. A single underscore MAY be used to denote a internal method that must still be exported, but should not be used elsewhere.

Method names MUST NOT be declared with a space after the method name. The opening brace MUST go on the same line as the method name, and the closing brace MUST go on the next line following the body. There MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis.

A method declaration looks like the following. Note the placement of parentheses, commas, spaces, and braces:

export default class ClassName {
    public static function fooBarBaz(arg1: string, arg2: number , arg3?: IOptions[] = []) {
        // method body
    }
}

5.5 Method and property order

Properties and methods MUST be implemented and in the following order.

  • public-static-field
  • public-static-method
  • protected-static-field
  • protected-static-method
  • private-static-field
  • private-static-method
  • public-instance-field
  • protected-instance-field
  • private-instance-field
  • constructor
  • public-instance-method
  • protected-instance-method
  • private-instance-method

5.6 Methods with a bound this context

When passing a method as callback or as an event handler, it often necessary to bind the this context.

The context SHOULD NOT be bound in the constructor or at the call site.

The context SHOULD be bound by declaring the method as a class property with an arrow function like so:

export default class ClassName {

    // This method will automatically have it's context bound as the class instance.
    public static fooBarBaz = (arg1: string, arg2: number , arg3?: IOptions[] = []) => {
        // method body
    }
}

5.7. abstract and static

When present, the abstract declaration MUST precede the visibility declaration.

When present, the static declaration MUST come after the visibility declaration.

<?php

abstract class ClassName {
    protected static $foo;

    abstract protected function zim();

    final public static function bar() {
        // method body
    }
}

6. Variables, Objects & Functions

6.1. Variable declarations

  • const MUST be used where possible. Otherwise let MUST be used. var MUST NOT be used.

  • Multiple variables MUST NOT be declared at once.

// Good
const foo = "foo";
const bar = "bar";

// Bad
const foo = "foo",
    bar = "bar";

let thing1, thing2, thing3;
  • Variables MUST be named in either lowerCamelCased or UPPER_CASED formatting.

6.2. Objects

  • Objects keys MUST NOT use quotes unless necessary.
const object = {
    lookMa: "noQuotes",
    "quote-are-necessary-here",
}
  • Object literal shorthand MUST be used where possible.
const foo = "foo";
const bar = "bar";

// Good
const good = {
    foo,
    bar,
    other: "other",
};

// Bad
const bad = {
    foo: foo,
    bar: bar,
    other: "other",
};
  • The “spread” operator MUST be used instead of Object.assign.
const thing1 = {
    foo: "foo",
};

const thing2 = {
    bar: "bar",
};

// Good
const good = {
    other: "other",
    ...thing1,
    ...thing2,
};

// Bad
const bad = Object.assign(
    {},
    thing1,
    thing2
);

6.3. Declaring functions

Functions MUST be declared as - An arrow function. - A named function. - A function declaration.

Anonymous functions that are not an arrow function MUST NOT be used.

// Good
function foo(event: ClickEvent) {}

const foo = function foo(event: ClickEvent) {};

const foo = (event: ClickEvent) => {};

document.addEventListener("click", (event: ClickEvent) => {})

document.addEventListener("click", foo);

// Bad
const foo = function() {};

document.addEventListener("click", function(event: ClickEvent) {})

6.4. Calling Functions

When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis, there MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis. In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.

bar();
foo.bar($arg1);
Foo.baz($arg2, $arg3);

Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.

foo.bar(
    $longArgument,
    $longerArgument,
    $muchLongerArgument
);

7. Control Structures

The general style rules for control structures are as follows. Many are automatically enforced by Prettier.

  • There MUST be one space after the control structure keyword
  • There MUST NOT be a space after the opening parenthesis
  • There MUST NOT be a space before the closing parenthesis
  • There MUST be one space between the closing parenthesis and the opening brace
  • The structure body MUST be indented once
  • The closing brace MUST be on the next line after the body

The body of each structure MUST be enclosed by braces. This standardizes how the structures look, and reduces the likelihood of introducing errors as new lines get added to the body.

7.1. if, elseif, else

An if structure looks like the following. Note the placement of parentheses, spaces, and braces; and that else and elseif are on the same line as the closing brace from the earlier body.

if (expr1) {
    // if body
} else if (expr2) {
    // else if body
} else {
    // else body;
}

The keyword elseif SHOULD be used instead of else if so that all control keywords look like single words.

If statements MUST have opening and closing brackets and be split onto multiple lines. Single line if statements are prohibited.

7.2. switch, case

A switch structure looks like the following. Note the placement of parentheses, spaces, and braces. The case statement MUST be indented once from switch, and the break keyword (or other terminating keyword) MUST be indented at the same level as the case body. There MUST be a comment such as // no break when fall-through is intentional in a non-empty case body.

switch (expr) {
    case 0:
        doThing('First case, with a break');
        break;
    case 1:
        doThing('Second case, which falls through');
        // no break
    case 2:
    case 3:
    case 4:
        doThing('Third case, return instead of break');
        return;
    default:
        doThing('Default case');
        break;
}

7.3. for of, forEach, and for in

for of and foreach are preferred over for in.

const arrayVals = [1, 2, 3, 4];
const objectVals = {
    key: "value",
};

arrayVals.forEach(val => {
    // Do something
});

// Iterate over an object
for (const [key, value] of Object.entries(objectVals)) {
    // do something
}

A for in loop MUST contain a hasOwnProperty() check.

for (const key in objectVals) {
    if (objectVals.hasOwnProperty(key)) {
        // Do something
    }
}

8. Doc Blocks

  • Classes MUST contain a multi-line description comment.

  • Class methods and properties MUST contain a visibility declaration.

  • All files MUST contain an opening multi-line comment containing @copyright 2009-2018 Vanilla Forums Inc. where 2018 shall be replaced with the current year. Scripts in open source projects MUST contain an @license parameter with name and link to license of the project it is contained in. For example a file in the vanilla/vanilla repo, which is licensed under GPLv2 MUST contain @license http://www.opensource.org/licenses/gpl-2.0.php GPLv2.

  • All functions, except for anonymous functions, and all class methods, MUST contain a multi-line JSDoc style comment. This comment:

    • MUST contain a short description.
    • MAY contain an extended description.
    • MAY contain @param annotations.
    • MUST NOT contain type hints in its @param or @returns annoations. Type hints should be declared directly as part of the function signature.
    • MUST NOT align its @param descriptions by using additional spaces.
    • MAY contain a single @returns annotation.

Cloud Hosting

We believe that online communities should be intuitive, engaging and true to your brand. Vanilla allows you to create a customized community that rewards positive participation, automatically curates content and lets members drive moderation.

Learn More …