# The xblox Style Guide ## Code Conventions All names and comments *MUST* be written in English. ### Naming The following naming conventions *MUST* be used: |Construct|Convention| |---------|----------| |package|`lower-dash-case`| |module exporting default class|`UpperCamelCase`| |all other modules|`lowerCamelCase`| |classes, interfaces, and type aliases|`UpperCamelCase`| |enums and enum value names|`UpperCamelCase`| |constants|`UPPER_CASE_WITH_UNDERSCORES`| |variables|`lowerCamelCase` or `_lowerCamelCase`*| |parameters|`lowerCamelCase` or `_lowerCamelCase`*| |public properties|`lowerCamelCase`| |protected/private properties|`_lowerCamelCase`| *: Variables and parameter names generally *SHOULD NOT* be prefixed with underscores, but this may be warranted in rare cases where two variables in nested scopes make sense to have the same name, while avoiding shadowing the outer variable. The following naming conventions *SHOULD* be used: |Variable type|Convention| |-------------|----------| |Deferred|`dfd`| |Promise|`promise`| |Identifier|`id`| |Numeric iterator|`i`, `j`, `k`, `l`| |String iterator (for-in)|`key`, `k`| |Event|`event`| |Destroyable handle|`handle`| |Error object|`error`| |Options arguments|`options`| |Origin, source, from|`source`| |Destination, target, to|`target`| |Coordinates|`x`, `y`, `z`, `width`, `height`, `depth`| |All others|Do not abbreviate| 1. All names *SHOULD* be as clear as necessary, *SHOULD NOT* be contracted just for the sake of less typing, and *MUST* avoid unclear shortenings and contractions (e.g. `MouseEventHandler`, not `MseEvtHdlr` or `hdl` or `h`). 1. Names *SHOULD* use American English (`en-us`) spelling. 1. Names representing an interface *MUST NOT* use "I" as a prefix (e.g. `Event` not `IEvent`). 1. Abbreviations and acronyms *SHOULD NOT* be uppercase when used as a name (e.g. `getXml` not `getXML`). 1. Collections *SHOULD* be named using a plural form. 1. Names representing boolean states *SHOULD* start with `is`, `has`, `can`, or `should`. 1. Names representing boolean states *SHOULD NOT* be negative (e.g. `isNotFoo` is unacceptable). 1. Names representing a count of a number of objects *SHOULD* start with `num`. 1. Names representing methods *SHOULD* be verbs or verb phrases (e.g. `getValue()`, not `value()`). 1. Factories or non-constructor methods that generate new objects *SHOULD* use the verb "create". 1. Magic numbers *MUST* either be represented using a constant or enum, or be prefixed with a comment representing the literal value of the number (e.g. `if (event.keyCode === Keys.KEY_A)` or `if (event.keyCode === /* "a" */ 97)`). ### Variables 1. All variables which are not reassigned in the block *SHOULD* be declared with `const`: ```ts // correct const a = 1; // incorrect var a = 1; let a = 1; ``` 1. All variables which are reassigned in the block *SHOULD* be declared with `let`: ```ts // correct for (let i = 0; i < items.length; i++) { } // incorrect for (var i = 0; i < items.length; i++) { } ``` 1. All variable declarations *SHOULD* use one `const` or `let` declaration per variable. The exception to this rule is the initialization expression of a `for` statement, and object/array destructuring (e.g. for imports). This prevents variable declarations being lost inside long lists that may also include immediate assignments: ```ts // correct const items = getItems(); const length = items.length; let i = 0; let item; // also right const items = getItems(); for (let i = 0, item; (item = items[i]); i++) { } // incorrect const items = getItems(), length = items.length; let i = 0, item; ``` 1. Variable declarations *SHOULD* be grouped by declaration type; `const` first, then `let`: ```ts // correct const items = getItems(); const length = items.length; let i = 0; let item; // incorrect const items = getItems(); let item; const length = items.length; let i = 0; ``` 1. Variables *SHOULD* be declared where they are first assigned: ```ts // correct render(): void { const items = this.getItems(); if (!items.length) { return; } for (let i = 0, item; (item = items[i]); i++) { this.renderItem(item); } } // incorrect render(): void { const items = this.getItems(); let i; let item; if (!items.length) { return; } for (i = 0; (item = items[i]); i++) { this.renderItem(item); } } ``` 1. The most appropriate data types *SHOULD* be used in all cases (e.g. boolean for booleans, not number). ### Coding Conventions #### General 1. Strings *MUST* use single quotes. 1. Equality comparisons *MUST* use strict comparison operators, with the exception that comparisons matching null or undefined *MAY* use `== null`. 1. When type coersion is necessary, it *SHOULD* be performed using the appropriate global function (*not* constructor): ```ts // correct let myBoolean = Boolean(something); let myNumber = Number(something); let myString = String(something); // incorrect let myBoolean = !!something; let myNumber = +something; let myString = '' + something; // also incorrect (and doesn't produce primitives, so don't do it!) let myBoolean = new Boolean(something); let myNumber = new Number(something); let myString = new String(something); ``` 1. When using a *literal function*, an anonymous function that is being passed as an argument, arrow functions *SHOULD* be used. Implicit returns from arrow functions are allowed if they increase the code readability and the return value is not ignored: ```ts // correct [ 1, 2, 3 ].forEach((value) => { console.log(value); }); const arr = [ 1, 2, 3 ].map((value) => value * 2); // incorrect [ 1, 2, 3 ].forEach((value) => console.log(value)); ``` 1. When functions are not *literal functions*, arrow functions *SHOULD NOT* be used: ```ts // correct const fn = function (): string { return 'foo'; } // incorrect const fn = () => 'foo'; ``` 1. `this` typing *MUST* be used if accessing `this` and *SHOULD NOT* be typed as `any`. Use of `const self = this;` *SHOULD NOT* be used to scope arrow functions and *MAY* only be used when there is a need to preseve context across a normal `function` or IIFC. ```ts // correct function foo(this: SomeType) { this.arr.forEach((item) => { if (this.doSomething(item)) { console.log(item); } }); } // incorrect function foo(this: any) { this.dangerous(); } function foo() { const self = this; self.arr.forEach((item) => { if (self.doSomething(item)) { console.log(item); } }); } ``` 1. All code *SHOULD* assume it will run in *strict mode*. ES Modules are always parsed in strict mode and TypeScript will automatically emit modules with the `'use strict';` prolog to help ensure that compatability. 1. `arguments.callee` *MUST NOT* be used. 1. The `debugger` statement *MUST NOT* be used. 1. The `eval` function *MUST NOT* be used. 1. The `radix` parameter of `parseInt` *MUST* be used. 1. There *MUST NOT* be unreachable code after `break`, `catch`, `throw`, and `return` statements. 1. All imports, variables, functions, and private class members *MUST* be used. #### TypeScript 1. Avoid casts when possible. If a type declaration can solve the problem instead, prefer that over a cast: ```ts // correct const something: Something = { // ... }; // incorrect const something = { // ... }; ``` 1. Usage of `` casts *SHOULD* be documented: ```ts /* need to coerce to any, because of NodeJS typings */ const req: RootRequire = require; ``` 1. Variable declarations *SHOULD NOT* include an explicit type declaration if it can be easily and safely inferred on the same line. ```ts // correct const count = 0; const message = ''; // incorrect const count: number = 0; const message: string = ''; ``` 1. Declarations for exported functions and class methods *SHOULD* include an explicit return type declaration for clarity. ```ts // correct export function foo(arg1: number, arg2: string): boolean { // ... return true; } // incorrect export function foo(arg1: number, arg2: string) { // ... return true; } ``` 1. Constructor parameters *MUST NOT* use `public` and `private` modifiers. 1. Parameter flags `noImplicitAny`, `noImplicitThis` and `strictNullChecks` must be enabled. ### Ordering 1. Class properties *SHOULD* be ordered alphabetically, case-insensitively, ignoring leading underscores, in the following order: * static properties * static methods * instance index signature * instance properties (including getters and setters; private, protected, then public) * constructor * instance methods (private, protected, then public) 1. Interface properties *SHOULD* be ordered alphabetically, case-insensitively, ignoring leading underscores, in the following order: * constructor * function call * index signature * properties (private, protected, then public) * methods (private, protected, then public) 1. Module exports *SHOULD* be ordered alphabetically, case-insensitively, by identifier. 1. Module imports *SHOULD* be ordered alphabetically *by module ID*, starting with the package name. Module imports from the current package *SHOULD* come last. Module imports *SHOULD NOT* be ordered by variable name, due to potential confusion when destructuring is involved. 1. Functions *SHOULD* be declared before their use. The exceptions to this rule are functions exported from a module and methods of a class: ```ts // correct function getItems(): Item[] { // ... } const items = getItems(); // also correct export function one(): void { two(); } export function two(): void { // ... } // incorrect const items = getItems(); function getItems(): Item[] { // ... } ``` ### Inline Documentation 1. Comments documenting code entities *SHOULD* be written in JSDoc format. Type information *SHOULD NOT* be included, since it *should* be possible to pick up from function signatures. Example: ```ts /** * Produces something useful and/or interesting. * * @param foo Indicates how useful something *should* be. * @param bar Indicates how interesting something *should* be. * @template T Some sort * @return Something useful and/or interesting. */ export function doSomethingInteresting(foo: string, bar: T): SomethingInteresting { } ``` *Note:* That typescript services used to have a formatting issue when there was not a clear line break between the code block and additional parameters list. This has been resolved, though it is preferred to have a break between the comment block and the first `@param`. 1. All exports and members of exported interfaces and classes *SHOULD* have a JSDoc code block documenting the function, class, method, type, variable, constant, or property. Internal methods *MAY* also include JSDoc code blocks. 1. JSDoc blocks *SHOULD* use markdown syntax for providing formatting: ```ts /** * Blocks *SHOULD* use additional `markdown` syntax to make intellisense more expressive. */ ``` 1. The following block tags *SHOULD* be used as appropriate: |Block Tag|Notes| |---------|-----| |`@param`|Denotes an argument for a method or function.| |`@return`|A description of the return value.| |`@template`|A description of a generic slot.| ### Comments 1. Comments *SHOULD* be used to explain *why* something needs to be written, *not* what it does. If a "what" comment seems necessary, consider rewriting the code to be clearer instead. Comments summarizing entire blocks of code are permissible. 1. Comments indicating areas to revisit *SHOULD* start with `TODO` or `FIXME` to make them easy to find. Ideally, such comments *should* only ever appear in personal/feature branches, but may be merged at maintainers' discretion. 1. Single line comments *MUST* begin with a space, i.e., `// comment` and not `//comment`. ### Whitespace and Formatting #### Files 1. Files *MUST* use hard tabs for indentation. Spaces MAY be used for alignment (under the assumption that hard tabs align to 4 spaces). 1. Files *MUST* end with a single newline character. 1. Files *MUST NOT* have more than one consecutive blank line. 1. Lines *SHOULD NOT* exceed 120 characters in length. 1. Lines *MUST NOT* contain trailing whitespace. #### Semicolons 1. Semicolons *MUST* be used. 1. Semicolons *SHOULD NOT* be preceded by a space. 1. Semicolons in `for` statements *MUST* be followed by a space. #### Commas 1. Commas *SHOULD* be followed by a space or newline, and *MUST NOT* be preceded by a space. 1. Commas *SHOULD NOT* appear at the beginning of lines (i.e. no leading commas). 1. All other binary/ternary operators *SHOULD* be surrounded by a space on both sides, unless immediately followed by a newline, in which case the operator *SHOULD* *precede* the newline (except for `.`). #### Colons 1. Colons in type definitions *MUST* be followed by a space or newline, and *MUST NOT* be preceded by a space. 1. Colons in object definitions *SHOULD* be followed by a space, and *MUST NOT* be preceded by a space. 1. Colons in `case` clauses *SHOULD* be followed by a newline, and *MUST NOT* be preceded by a space. The body of each `case` clause *SHOULD* be indented one level deeper than the `case` clause, and *SHOULD* conclude with the `break` keyword or a `// fall through` comment. #### Braces, Brackets, and Parentheses 1. Parentheses immediately preceding a block expression (e.g. `if`, `for`, `catch`) *MUST* be surrounded by a space on each side: ```ts // correct if (foo) { } // incorrect if(foo){ } ``` 1. The opening brace of a code block *MUST* be written on the same line as its statement, preceded by a space: ```ts // correct if (foo) { } // incorrect if (foo) { } if(foo){ } ``` 1. All `if`, `for`, `do`, `while` keywords *MUST* have opening and closing brackets. 1. The opening and closing brackets on objects and arrays *SHOULD* be surrounded by whitespace on the inside of the object literal: ```ts // correct let object = { foo: 'foo' }; let array = [ obj, 'foo' ]; let arrayOfObjects = [ { foo: 'foo' } ]; // incorrect let object = {foo: 'foo'}; let array = [obj, 'foo']; let arrayOfObjects = [{foo: 'foo'}]; ``` 1. Parentheses *SHOULD NOT* have space on the inside: ```ts // correct if (foo) { doSomething(foo); } // incorrect if ( foo ) { doSomething( foo ); } ``` 1. Anonymous functions *SHOULD* have a space between the `function` keyword and the opening parenthesis; named functions *SHOULD NOT* have a space between the function name and the opening parenthesis: ```ts // correct function myFunction() { return function () { }; } // incorrect function myFunction () { return function() { } } ``` 1. Blocks with a single statement *SHOULD NOT* be written on the same line as the opening brace: ```ts // correct if (foo) { bar; } // incorrect if (foo) { bar; } ``` 1. Chained methods, when cannot be expressed on a single line, *SHOULD* line break after the first function call and before each subsequent function call in the chain, indented further than the original block: ```ts // correct const promise = new Promise.resolve(() => { // return some value }) .then((result) => { // do something with result }) .catch((error) => { // do something with error }); const body = fetchResponse.text() .then((text) => { // do something with text }); // incorrect const promise = new Promise.resolve(() => { // return some value }).then((result) => { // do something with result }).catch((error) => { // do something with error }); const promise = new Promise .resolve(() => { // return some value }) .then((result) => { // do something with result }); const body = fetchResponse .text().then((text) => { // do something with text }); ``` 1. `else` and `while` keywords *SHOULD* be on their own line, not cuddled with the closing brace of the previous `if`/`do` block. This is consistent with the use of all other block statements and allows comments to be placed consistently before conditional statements, rather than sometimes-before, sometimes-inside: ```ts // correct if (foo) { } else if (bar) { } else { } do { } while (baz) // incorrect if (foo) { } else if (bar) { } else { } do { } while(baz) ``` #### Labels 1. Labels *MUST* only be used on `do`, `for`, `while` and `switch` statements. ```ts // correct loop: for (let i = 0; i < 10; i++) { break loop; } // incorrect console: console.log('1, 2, 3`); ``` 1. Labels *MUST* be defined before usage. ```ts // correct loop: for (let i = 0; i < 10; i++) { break loop; } // incorrect loop: for (let i = 0; i < 10; i++) { break loop; } (function() { for (let i = 0; i < 10; i++) { // label out of scope break loop; } })(); ```