control-freak-ide/server/nodejs/lib/Style.md
plastic-hub-dev-node-saturn 538369cff7 latest
2021-05-12 18:35:18 +02:00

737 lines
17 KiB
Markdown

# 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 = <Something> {
// ...
};
```
1. Usage of `<any>` casts *SHOULD* be documented:
```ts
/* need to coerce to any, because of NodeJS typings */
const req: RootRequire = <any> 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<T>(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;
}
})();
```