TypeScript Coding Standards
1. Code Style
1.1 Indentation
Use 2 spaces for indentation. Do not use 4 spaces or tabs:
// bad
function foo() {
let name;
}
// good
function foo() {
let name;
}1.2 Semicolons
Always terminate statements with semicolons. This avoids quirks with JavaScript's Automatic Semicolon Insertion mechanism and makes intent more explicit.
// bad - causes Uncaught ReferenceError
const luke = {}
const leia = {}
[luke, leia].forEach((jedi) => {
jedi.father = 'vader'
})
// good
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
jedi.father = 'vader';
});
// bad - function returns undefined instead of the value on the next line
function foo() {
return
'Result want to be returned'
}
// good
function foo() {
return 'Result want to be returned';
}1.3 Commas
For comma-separated multi-line structures, do not use leading commas; always include a trailing comma. This makes adding and removing lines easier and produces cleaner git diffs.
// bad
const story = [
once
, upon
, aTime
];
// good
const story = [
once,
upon,
aTime,
];
// bad
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
// good
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
const heroes = [
'Batman',
'Superman',
];
function createHero(
firstName,
lastName,
inventorOf,
) {
// ...
}
// Note: no trailing comma after spread elements
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
// ...
}1.4 Blocks
Always use braces to wrap code blocks:
// bad
if (foo)
bar();
baz(); // this line is not inside the if statement
// good
if (foo) {
bar();
baz();
}When a block contains only a single statement, braces may be omitted and the statement placed on the same line as the control statement. However, for consistency and readability, this is not recommended:
// bad
if (foo)
return false;
// bad - allowed but not recommended
if (foo) return false;
// good
if (foo) {
return false;
}1.5 Brace Style
For non-empty blocks, use Egyptian Brackets style:
- Opening brace
{has no line break before it, but has one after it - Closing brace
}has a line break before it - If
}ends the entire statement, it requires a line break after it - If
}is followed byelse,catch,while, or by a comma, semicolon, or closing parenthesis ()), no line break is needed
// bad - else should be on the same line as if's }
if (foo) {
thing1();
}
else
thing2();
}
// good
if (foo) {
thing1();
} else {
thing2();
}For empty blocks that are not part of a multi-block structure like if..else.. or try..catch..finally.., braces may be closed immediately:
// good
function doNothing() {}However, if the empty block is in a multi-block structure, still follow the Egyptian Brackets style as for non-empty blocks:
// bad
if (condition) {
// ...
} else if (otherCondition) {} else {
// ...
}
// good
if (condition) {
// ...
} else if (otherCondition) {
} else {
// ...
}Do not use empty blocks. If an empty block is necessary, include a comment inside it:
// bad
if (condition) {
thing1();
} else {
}
// good
if (condition) {
thing1();
} else {
// TODO I haven't determined what to do.
}1.6 Whitespace
Using whitespace reasonably and consistently improves code readability and maintainability.
There is a space before the opening brace { of a block:
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}There is a space before the opening parenthesis ( of control statements (if, while, etc.):
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}When declaring a function, there is no space between the function name and the parameter list:
// bad
function fight () {
console.log ('Swooosh!');
}
// good
function fight() {
console.log('Swooosh!');
}No spaces inside parentheses and brackets; spaces inside braces:
// bad
function bar( foo ) {
return foo;
}
const foo = [ 1, 2, 3 ];
const obj = {clark: 'kent'};
// good
function bar(foo) {
return foo;
}
const foo = [1, 2, 3];
const obj = { clark: 'kent' };Spaces around operators, except unary operators:
// bad
const x=y+5;
const isRight = result === 0? false: true;
const x = ! y;
// good
const x = y + 5;
const isRight = result === 0 ? false : true;
const x = !y;When defining object literals, exactly one space between key and value; no "horizontal alignment":
// bad
{
a: 'short',
looooongname: 'long',
}
// good
{
a: 'short',
looooongname: 'long',
}1.7 Blank Lines
Keep one blank line at the end of the file:
// bad - no trailing newline at end of file
import { foo } from './Foo';
// ...
export default foo;
// good
import { foo } from './Foo';
// ...
export default foo;Blocks must not begin or end with a blank line:
// bad
function bar() {
console.log(foo);
}
// good
function bar() {
console.log(foo);
}Insert a blank line between the end of a block and the next statement:
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;1.8 Maximum Line Length and Maximum File Lines
A single line should not exceed 100 characters, except for strings, template strings, and regular expressions:
// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
// good
const foo = jsonData
&& jsonData.foo
&& jsonData.foo.bar
&& jsonData.foo.bar.baz
&& jsonData.foo.bar.baz.quux
&& jsonData.foo.bar.baz.quux.xyzzy;Files should not exceed 1000 lines. Overly long files are difficult to read and maintain, and should be split.
Functions should not exceed 80 lines.
2. Variable Declarations
2.1 Use const and let; var is forbidden
Since ES6, you can use let and const keywords to declare variables with block scope. Do not use var:
// bad
var foo = 'foo';
var bar;
// good
const foo = 'foo';
let bar;Do not declare variables without any keyword (this creates global variables and pollutes the global namespace):
// bad
foo = 'foo';
// good
const foo = 'foo';2.2 Prefer const; use let only when reassignment is needed
When declaring variables, prefer const. Only use let when the variable will be reassigned:
// bad - no reassignment occurs; should use const
let flag = true;
if (flag) {
console.log(flag);
}
// good - reassignment occurs; let is used correctly
let flag = true;
if (flag) {
flag = false;
}Note that arrays and objects are references. Modifying an array element or object property is not reassignment, so const should be used in most cases:
// bad
let arr = [];
let obj = {};
arr[0] = 'foo';
obj.name = 'bar';
// good
const arr = [];
const obj = {};
arr.push('foo');
obj.name = 'bar';2.3 Declare only one variable per declaration statement
This makes it easier to add new declarations and simplifies debugging:
// bad
const foo = 1,
bar = 2;
// good
const foo = 1;
const bar = 2;2.4 Declared variables must be used
Declared but unused variables and expressions can cause potential issues and confuse maintainers. Delete them:
// bad - unused variable foo
const foo = 1;
// good
const foo = 1;
doSomethingWith(foo);2.5 No usage before declaration
In ES6, const and let are not hoisted. Using them before declaration causes an error:
// bad
console.log(foo); // => Uncaught ReferenceError: foo is not defined
const foo = 'foo';
// good
const foo = 'foo';
console.log(foo); // => foo2.6 Variables must not shadow outer scope variables
Shadowing outer scope variables reduces readability and prevents access to the outer variable:
// bad
const foo = 1;
if (someCondition) {
const foo = 2;
console.log(foo); // => 2
}
// good
const foo = 1;
if (someCondition) {
const bar = 2;
console.log(bar); // => 2
console.log(foo); // => 1
}2.7 No chained assignments
Chained variable assignments are hard to read and understand, and can cause unexpected results (such as creating global variables):
// bad - in this case, let only works for a; b and c become global variables
(function test() {
let a = b = c = 1; // equivalent to let a = (b = (c = 1));
})();
console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1
// good
(function test() {
let a = 1;
let b = a;
let c = a;
})();2.8 Group let and const separately
Grouping let and const declarations together improves code neatness:
// bad
let a;
const b = 2;
let c;
const d = 4;
let e;
// good
const b = 2;
const d = 4;
let a;
let c;
let e;3. Type System
3.1 Prefer interface over type for object types
interface supports extend/implement/union capabilities and can also describe plain objects:
// bad
type T = { x: number };
// good
type T = string;
type Foo = string | {};
interface T {
x: number;
}3.2 Use T[] for simple arrays, Array<T> for complex arrays
Use T[] or readonly T[] for simple types (numbers, strings, booleans, etc.). Use Array<T> or ReadonlyArray<T> for complex types (unions, intersections, objects, functions, etc.):
// bad
const a: (string | number)[] = ['a', 1];
const b: { prop: string }[] = [{ prop: 'a' }];
const c: (() => void)[] = [() => {}];
const d: Array<MyType> = ['a', 'b'];
const e: Array<string> = ['a', 'b'];
const f: ReadonlyArray<string> = ['a', 'b'];
// good
const a: Array<string | number> = ['a', 1];
const b: Array<{ prop: string }> = [{ prop: 'a' }];
const c: Array<() => void> = [() => {}];
const d: MyType[] = ['a', 'b'];
const e: string[] = ['a', 'b'];
const f: readonly string[] = ['a', 'b'];3.3 Use as Type for type assertions; <Type> is forbidden
Always use the as Type style for type assertions rather than <Type>, which is easily confused with JSX. Type assertions on object literals are forbidden, except when asserting to any:
// bad
const foo = <string>'bar';
// good
const foo = 'bar' as string;// bad
const x = { ... } as T;
const y = { ... } as object;
// good
const x: T = { ... };
const y = { ... } as any;
const z = { ... } as unknown;3.4 No namespace; use ES module instead
Custom TypeScript modules and namespaces are no longer recommended. Prefer ES2015 module syntax for imports and exports:
// bad
module foo {}
namespace foo {}
// good
declare module 'foo' {}
declare module foo {}
declare namespace foo {}
declare global {
namespace foo {}
}
declare module foo {
namespace foo {}
}Do not use module to define namespaces (no restriction on declare module):
// bad
module Foo {}
// good
declare module Foo {}
declare namespace Foo {}3.5 Use semicolons as interface member delimiters
// bad: comma style(JSON style)
interface Foo {
name: string,
greet(): void,
}
type Bar = {
name: string,
greet(): void,
};
// bad: line break style
interface Foo {
name: string
greet(): void
}
// good
interface Foo {
name: string;
greet(): void;
}
type Bar = {
name: string;
greet(): void;
};3.6 Do not explicitly annotate types that can be inferred
Redundant explicit type annotations on easily inferred variables and parameters add code noise:
// bad
const foo: number = 1;
const bar: string = '';
class Foo {
prop: number = 5;
}
function fn(a: number = 5, b: boolean = true) {}
// good
const foo = 1;
const bar = '';
class Foo {
prop = 5;
}
function fn(a = 5, b = true) {}3.7 Type annotations: no space before colon, one space after colon
Appropriate spacing around type annotations effectively improves code readability:
- No space before colon, one space after colon
- One space before and after the arrow
// bad
let foo:string = 'bar';
let foo :string = 'bar';
let foo : string = 'bar';
function foo():string {}
function foo() :string {}
function foo() : string {}
class Foo {
name:string;
}
class Foo {
name :string;
}
class Foo {
name : string;
}
type Foo = ()=> {};
// good
let foo: string = 'bar';
function foo(): string {}
class Foo {
name: string;
}
type Foo = () => {};3.8 No empty interfaces (except extends)
An empty interface is equivalent to an empty object. If it only extends another interface, it is equivalent to the extended type. Avoid defining meaningless interfaces:
// bad
// an empty interface
interface Foo {}
// an interface with only one supertype (Bar === Foo)
interface Bar extends Foo {}
// an interface with an empty list of supertypes
interface Baz {}
// good
// an interface with any number of members
interface Foo {
name: string;
}
// same as above
interface Bar {
age: number;
}
// an interface with more than one supertype
// in this case the interface can be used as a replacement of a union type.
interface Baz extends Foo, Bar {}3.9 Define interface methods using property syntax
Defining interface methods using property syntax enables stricter checking:
// bad
interface T1 {
func(arg: string): number;
}
type T2 = {
func(arg: boolean): void;
};
interface T3 {
func(arg: number): void;
func(arg: string): void;
func(arg: boolean): void;
}
// good
interface T1 {
func: (arg: string) => number;
}
type T2 = {
func: (arg: boolean) => void;
};
// property method overload
interface T3 {
func: ((arg: number) => void) &
((arg: string) => void) &
((arg: boolean) => void);
}3.10 Overloaded functions must be grouped together
Grouping naturally related items improves code readability and organization:
// bad
declare namespace Foo {
export function foo(s: string): void;
export function foo(n: number): void;
export function bar(): void;
export function foo(sn: string | number): void;
}
// good
declare namespace Foo {
export function foo(s: string): void;
export function foo(n: number): void;
export function foo(sn: string | number): void;
export function bar(): void;
}// bad
type Foo = {
foo(s: string): void;
foo(n: number): void;
bar(): void;
foo(sn: string | number): void;
};
interface Foo {
foo(s: string): void;
foo(n: number): void;
bar(): void;
foo(sn: string | number): void;
}
class Foo {
foo(s: string): void;
foo(n: number): void;
bar(): void {}
foo(sn: string | number): void {}
}
// good
type Foo = {
foo(s: string): void;
foo(n: number): void;
foo(sn: string | number): void;
bar(): void;
};
interface Foo {
foo(s: string): void;
foo(n: number): void;
foo(sn: string | number): void;
bar(): void;
}
class Foo {
foo(s: string): void;
foo(n: number): void;
foo(sn: string | number): void {}
bar(): void {}
}3.11 Prefer parameter union types over function overloads
// bad
function f(x: number): void;
function f(x: string): void;
f(): void;
f(...x: number[]): void;
// good
function f(x: number | string): void;
function f(x?: ...number[]): void;3.12 No unnecessary boolean casts
Forcing a boolean conversion on expressions in if and other conditional statements is redundant:
// bad
if (!!foo) {
// ...
}
while (!!foo) {
// ...
}
const a = !!flag ? b : c;
// good
if (foo) {
// ...
}
while (foo) {
// ...
}
const a = flag ? b : c;3.13 type definitions must declare member types
// bad
type Members = {
member;
otherMember;
};
// good
type Members = {
member: boolean;
otherMember: string;
};4. Functions
4.1 Prefer arrow functions over anonymous functions
Arrow functions introduced in ES6 solve the this binding problem and have cleaner syntax:
// 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;
});When the function body is a single return statement, the braces and return may be omitted:
// good - when the body has multiple statements, always use braces
[1, 2, 3].map((number) => {
const nextNumber = number + 1;
return `A string containing the ${nextNumber}.`;
});
// good - when the body is a single return statement, braces and return may be omitted
[1, 2, 3].map(number => `A string containing the ${number + 1}.`);When the return value is an object or spans multiple lines, wrap it in parentheses:
// bad - Uncaught SyntaxError: Unexpected token
[1, 2, 3].map((item) => {
foo: item,
bar: item + 1,
});
// good
[1, 2, 3].map((item) => ({
foo: item,
bar: item + 1,
}));4.2 Prefer function expressions over function declarations
Function declarations are hoisted to the top of their scope, so functions can be called before their declaration, which hurts readability and maintainability:
// bad
function foo() {
// ...
}
// good
const foo = () => {
// ...
};
const foo = function () {
// ...
};4.3 Use default parameter syntax
ES6 introduced default parameter syntax, which is more concise and readable than the old way of assigning defaults:
// bad
const multiple = (a, b) => {
a = a || 0;
b = b || 0;
return a * b;
}
// good
const multiple = (a = 0, b = 0) => {
return a * b;
}4.4 Put default parameters last
Otherwise you cannot benefit from default parameters and must pass undefined to trigger the default:
// bad
function multiply(a = 1, b) {
return a * b;
}
const x = multiply(42); // => NaN
const y = multiply(undefined, 42); // => 42
// good
function multiply(a, b = 1) {
return a * b;
}
const x = multiply(42); // => 424.5 Do not mutate function parameters
Do not mutate reference-type parameters, as this may change the original argument variables. Also do not reassign parameters, which may cause unexpected behavior and optimization issues:
// bad
const f1 = function f1(obj) {
obj.key = 1;
}
const originalObj = { key: 0 };
f1(originalObj);
console.log(originalObj); // => { key: 1 }
// good
const f2 = function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}// bad
function foo(bar, baz) {
if (!baz) {
bar = 1;
}
}
// good
function foo(bar, baz) {
let qux = bar;
if (!baz) {
qux = 1;
}
}4.6 Use ...rest instead of arguments
ES6 provides the rest operator ..., which aggregates remaining arguments more clearly than arguments. Additionally, ... yields a true array, while arguments is array-like:
// bad
function foo(a, b) {
const args = Array.prototype.slice.call(arguments, foo.length);
console.log(args);
}
foo(1, 2, 3, 4); // => [3, 4]
// good
function foo(a, b, ...args) {
console.log(args);
}
foo(1, 2, 3, 4); // => [3, 4]4.7 Wrap IIFEs in parentheses
An IIFE is an independent execution unit; wrapping it in parentheses makes this clearer:
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());4.8 Keep function complexity low (cyclomatic complexity < 10)
Excessive complexity makes code hard to maintain and test. A function's cyclomatic complexity should not exceed 10, and cognitive complexity should not exceed 15.
4.9 Avoid too many function parameters
Too many parameters hurt maintainability and usability. If many parameters are necessary, consider using an object instead:
// bad
function doSomething(param1, param2, param3, param4, param5, param6, param7, param8) {
// ...
}
doSomething(1, 2, 3, 4, 5, 6, 7, 8);
// good
function doSomething({ param1, param2, param3, param4, param5, param6, param7, param8 }) {
// ...
}
doSomething({ param1: 1, param2: 2, param3: 3, param4: 4, param5: 5, param6: 6, param7: 7, param8: 8 });4.10 Do not use function declarations inside blocks
In non-function blocks (such as if, while, etc.), do not use function declarations:
// bad - function declarations are function-scoped, not block-scoped, so the function is usable outside the block, which is confusing
if (true) {
function test() {
console.log('test');
}
}
test(); // => test
// good - function expressions clearly indicate whether the function is accessible outside the block
if (true) {
const test = function () {
console.log('test');
};
}
test(); // => Uncaught ReferenceError: test is not defined4.11 Prefer higher-order functions for iteration
When iterating, prefer JavaScript's built-in higher-order functions over raw for loops:
const numbers = [1, 2, 3, 4, 5];
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
// good
let sum = 0;
numbers.forEach((num) => {
sum += num;
});
// best
const sum = numbers.reduce((total, num) => total + num, 0);
// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
increasedByOne.push(numbers[i] + 1);
}
// good
const increasedByOne = [];
numbers.forEach((num) => {
increasedByOne.push(num + 1);
});
// best
const increasedByOne = numbers.map(num => num + 1);5. Arrays and Objects
5.1 Use literals to create arrays and objects
Do not use new Array() or Array() to create arrays, unless constructing an empty array of a certain length:
// bad
const a = new Array(1, 2, 3);
const b = Array(1, 2, 3);
// good
const a = [1, 2, 3];
const b = new Array(500); // construct an empty array of length 500// bad
const obj = new Object();
// good
const obj = {};5.2 Use the spread operator ... for arrays and objects
Array copying:
// bad
const array1 = [];
for (let i = 0; i < array.length; i += 1) {
array1[i] = array[i];
}
// good
const array1 = [...array];Array concatenation:
// bad
const array1 = [1, 2].concat(array);
// good
const array1 = [1, 2, ...array]Replace apply with ...:
// bad
const args = [1, 2, 3, 4];
Math.max.apply(Math, args);
// good
const args = [1, 2, 3, 4];
Math.max(...args);Replace Object.assign for shallow object copies:
// very bad - original gets mutated
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a;
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 });
// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 };Get a new object excluding certain properties:
// good
const copy = { a: 1, b: 2, c: 3 };
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }5.3 Use destructuring to access properties
Use ES6 destructuring to get array elements:
// bad
const arr = [1, 2, 3, 4];
const first = arr[0];
const second = arr[1];
// good
const arr = [1, 2, 3, 4];
const [first, second] = arr;Use destructuring to get identically named or multiple properties from an object for cleaner code:
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}5.4 Prefer dot notation to access object properties
[] should only be used to access dynamic property names or property names containing special characters:
const obj = {
active: true,
[getDynamicKey()]: 'foo',
'data-bar': 'bar',
};
// bad
const isActive = obj['active'];
// good
const isActive = obj.active;
const foo = obj[getDynamicKey()];
const bar = obj['data-bar'];5.5 map/filter/reduce etc. must have return statements
The callback functions of the following array methods must include a return statement: map, filter, every, find, findIndex, reduce, reduceRight, some, sort:
// bad - map is meant to build a new array; use forEach for simple iteration
myArray.map((item, index) => {
myObj[item] = index;
});
// good
myArray.forEach((item, index) => {
myObj[item] = index;
});// bad => Uncaught TypeError: Cannot set property 'b' of undefined
const myObj = myArray.reduce((memo, item, index) => {
memo[item] = index;
}, {});
// good
const myObj = myArray.reduce((memo, item, index) => {
memo[item] = index;
return memo;
}, {});5.6 Use shorthand syntax for object properties and methods
ES6 provides shorthand syntax for object properties and methods, making code more concise:
const value = 'foo';
// bad
const atom = {
value: value,
addValue: function (value) {
return value + ' added';
},
};
// good
const atom = {
value,
addValue(value) {
return value + ' added';
},
};Place shorthand properties together, at the beginning or end of the object:
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJediWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};5.7 Do not quote object property names (unless containing special characters)
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
'one two': 12,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
'one two': 12,
};6. Classes
6.1 Use class syntax to declare classes
The class syntax introduced in ES6 is syntactic sugar for declaring classes, and is more concise and maintainable:
// bad
function Person() {
this.age = 1;
}
Person.prototype.growOld = function () {
this.age += 1;
}
// good
class Person {
constructor() {
this.age = 1;
}
growOld() {
this.age += 1;
}
}6.2 Use extends for inheritance
extends is the built-in method for prototypal inheritance and does not break instanceof:
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
return this.queue[0];
};
// good
class PeekableQueue extends Queue {
peek() {
return this.queue[0];
}
}6.3 Avoid unnecessary constructor
ES6 classes provide a default constructor. An empty constructor or one that only calls the parent constructor is unnecessary:
// bad - both constructors below can be omitted
class Parent {
constructor() {
}
method() {
// ...
}
}
class Child extends Parent {
constructor (value) {
super(value);
}
method() {
// ...
}
}
// good
class Parent {
method() {
// ...
}
}
class Child extends Parent {
method() {
// ...
}
}6.4 Use super correctly
- Child class
constructormust callsuper(), and it must be called before usingthisandsuperkeywords - Non-child class
constructormust not usesuper()
// bad - non-child class cannot use super
class Parent {
constructor() {
super();
this.name = 'parent';
}
}
// good
class Parent {
constructor() {
this.name = 'parent';
}
}
// bad - child class must use super
class Child extends Parent {
constructor() {
this.name = 'child';
}
}
// bad - this must be used after calling super
class Child extends Parent {
constructor() {
this.name = 'foo';
super();
}
}
// good
class Child extends Parent {
constructor (value) {
super(value);
this.name = 'foo';
}
}6.5 Set member accessibility (public/protected/private)
Setting non-public members to private improves code comprehension and prevents illegal calls. public modifiers may be omitted for public members:
// bad
class Foo {
static foo = 'foo';
static getFoo() {
return Foo.foo;
}
constructor() {}
bar = 'bar';
getBar() {}
get baz() {
return 'baz';
}
set baz(value) {
console.log(value);
}
}
// good
class Foo {
private static foo = 'foo';
public static getFoo() {
return Foo.foo;
}
public constructor() {}
protected bar = 'bar';
public getBar() {}
public get baz() {
return 'baz';
}
public set baz(value) {
console.log(value);
}
}6.6 Member ordering: static -> instance, field -> constructor -> method, public -> protected -> private
- Static methods/properties come before instance methods/properties
- Fields come before constructor, which comes before methods
- Public members come before protected members, which come before private members
// good
class Foo {
public static foo1 = 'foo1';
protected static foo2 = 'foo2';
private static foo3 = 'foo3';
public static getFoo1() {}
protected static getFoo2() {}
private static getFoo3() {
return Foo.foo3;
}
public bar1 = 'bar1';
protected bar2 = 'bar2';
private bar3 = 'bar3';
public constructor() {
console.log(Foo.getFoo3());
console.log(this.getBar3());
}
public getBar1() {}
protected getBar2() {}
private getBar3() {
return this.bar3;
}
}6.7 Use readonly for literal class properties instead of getters
All getter methods on a class that return "literals" should be replaced with the readonly modifier. Literals include strings, template strings, numbers, bigint, regex, and null:
// bad
class Mx {
public static get myField1() {
return 1;
}
private get ['myField2']() {
return 'hello world';
}
}
// good
class Mx {
public readonly myField1 = 1;
public readonly myField2 = [1, 2, 3]; // non-literal
private readonly ['myField3'] = 'hello world';
public get myField4() {
return `hello from ${window.location.href}`;
}
}7. Modules
7.1 Use ES6 import/export instead of require
Use ES6 modules (import/export) rather than non-standard module systems like CommonJS, AMD, or CMD:
// bad
const React = require('react');
module.exports = React.Component;
// good
import React, { Component } from 'react';
export default Component;// bad
const fs = require('fs');
// good
import * as fs from 'fs';7.2 Do not use multiple imports from the same module
Multiple import statements from the same module hurt maintainability. Combine them into one statement:
// bad
import React from 'react';
import { Component } from 'react';
// good
import React, { Component } from 'react';7.3 Place import statements at the top of the file
Since import statements are hoisted, place them at the top of the module to prevent unexpected behavior:
// bad
import foo from 'foo';
foo.init();
import bar from 'bar';
bar.init();
// good
import foo from 'foo';
import bar from 'bar';
foo.init();
bar.init();7.4 Import order: third-party -> internal modules
Sort import statements as follows:
- Third-party modules first, then internal project modules
- Absolute path imports first, then relative path imports
// bad
import foo from 'components/foo';
import './index.scss';
import React from 'react';
// good
import React from 'react';
import foo from 'components/foo';
import './index.scss';7.5 No triple-slash import syntax
The triple-slash syntax is deprecated. Do not use it outside of declaration files (d.ts):
// bad
/// <reference path="./my-module" />
// good
import myModule from './my-module';7.6 Keep a blank line after module imports
// bad
import foo from './foo.js';
const FOO = 'FOO'
// good
import foo from './foo.js';
const FOO = 'FOO'7.7 No self-referencing or circular imports
Modules must not import themselves, and modules must not form circular dependencies.
8. Type Annotations and Directives
8.1 @ts-expect-error/@ts-ignore must include an explanation
TypeScript provides directive comments to suppress compile-time errors. Using these directives in code is allowed, but they must be accompanied by a descriptive comment of reasonable length:
// bad
// @ts-expect-error
console.log('my code');
// @ts-ignore
console.log('my code');
// good
// @ts-expect-error: Unreachable code here
console.log('my code');
// @ts-ignore: It's ok to ignore this compile error
console.log('my code');8.2 No tslint comments
tslint has been deprecated, and its directive comments should no longer appear:
// bad
/* tslint:disable */
/* tslint:enable */
/* tslint:disable:rule1 rule2 rule3... */
/* tslint:enable:rule1 rule2 rule3... */
// tslint:disable-next-line
someCode(); // tslint:disable-line
// tslint:disable-next-line:rule1 rule2 rule3...9. Important Notes
9.1 No meaningless void type (except for return types)
Do not use the void type outside of return types or generic type parameters, and in return types do not union or intersect it with other types. void represents "nothing" or a function that "returns nothing":
// bad
type PossibleValues = string | number | void;
type MorePossibleValues = string | ((number & any) | (string | void));
function logSomething(thing: void) {}
function printArg<T = void>(arg: T) {}
logAndReturn<void>(undefined);
interface Interface {
lambda: () => void;
prop: void;
}
class MyClass {
private readonly propName: void;
}
// good
type NoOp = () => void;
function noop(): void {}
let trulyUndefined = void 0;
async function promiseMeSomething(): Promise<void> {}
type stillVoid = void | never;9.2 No non-null assertion after optional chaining
optional chaining is designed to return undefined. Using a non-null assertion after it is incorrect and introduces serious type safety issues:
// bad
foo?.bar!;
foo?.bar!.baz;
foo?.bar()!;
foo?.bar!();
foo?.bar!().baz;
// good
foo?.bar;
(foo?.bar).baz;
foo?.bar();
foo?.bar();
foo?.bar().baz;9.3 No confusing non-null assertions
Using a non-null assertion (!) before an equality comparison operator (== or ===) can easily be confused with an inequality operator (!= or !==). This is not recommended:
interface Foo {
bar?: string;
num?: number;
}
// bad
const foo: Foo = getFoo();
const isEqualsBar = foo.bar! == 'hello';
const isEqualsNum = 1 + foo.num! == 2;
// good
const foo: Foo = getFoo();
const isEqualsBar = foo.bar == 'hello';
const isEqualsNum = (1 + foo.num!) == 2;9.4 Prefer as const when the value equals the type declaration
// bad
let bar: 2 = 2;
let foo = <'bar'>'bar';
let foo = { bar: 'baz' as 'baz' };
// good
let foo = 'bar';
let foo = 'bar' as const;
let foo: 'bar' = 'bar' as const;
let bar = 'bar' as string;
let foo = <string>'bar';
let foo = { bar: 'baz' };9.5 Use single quotes for string literals
// bad
const foo = "bar";
// good
const foo = 'bar';// bad
const name = "tod";
const name = `tod`; // template literals should contain variables or newlines; otherwise use single quotes
// good
const name = 'tod';9.6 Use template literals instead of string concatenation
Template literals make code more concise and readable:
// bad
function getDisplayName({ nickName, realName }) {
return nickName + ' (' + realName + ')';
}
// good
function getDisplayName({ nickName, realName }) {
return `${nickName} (${realName})`;
}9.7 Both sides of + must be numbers or both strings
Mixing numbers and strings with + often causes unexpected issues:
// bad
var foo = '5.5' + 5;
var foo = 1n + 1;
// good
var foo = parseInt('5.5', 10) + 10;
var foo = 1n + 1n;9.8 Use strict equality operators
Non-strict equality operators (== and !=) convert values to the same type before comparison, which can be a significant pitfall for those unfamiliar with JavaScript. Generally, use strict comparison operators (=== and !==):
const id = '83949';
// bad - intentionally using == to compare with a number to accommodate id potentially being a string
if (id == 83949) {
// do something
}
// good - if id might be a string, convert its type first, then use === to compare
if (Number(id) === 83949) {
// do something
}9.9 Do not use unary increment/decrement operators
++ and -- introduce cognitive overhead about whether the value changes early, and may cause errors due to automatic semicolon insertion. Use num += 1 instead of num++. However, out of habit, increment/decrement operators may still be used in for loop conditions:
let num = 1;
// bad
num++;
--num;
// good
num += 1;
num -= 1;9.10 switch case clauses must end with break
// bad
switch(foo) {
case 1:
doSomething();
case 2:
doSomethingElse();
default:
doSomething();
}
// good
switch(foo) {
case 1:
doSomething();
break;
case 2:
doSomethingElse();
break;
default:
doSomething();
}switch statements must always include a default branch. If default is intentionally omitted, indicate it with a // no default comment at the end of the switch statement.
9.11 Control statement nesting should not exceed 4 levels
// bad
if (condition1) {
// depth = 1
if (condition2) {
// depth = 2
for (let i = 0; i < 10; i++) {
// depth = 3
if (condition4) {
// depth = 4
if (condition5) {
// bad - depth = 5
}
return;
}
}
}
}9.12 If an if block always returns, the final else is unnecessary
// bad
function foo() {
if (x) {
return x;
} else {
return y;
}
}
// good
function foo() {
if (x) {
return x;
}
return y;
}9.13 No unnecessary escape characters
Escape characters greatly reduce code readability, so avoid overusing them:
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `'this' is "quoted"`;9.14 Do not call Object.prototype methods directly on objects
Do not call Object.prototype methods like hasOwnProperty, propertyIsEnumerable, isPrototypeOf directly on objects. These methods may be overridden by properties on the object, causing errors:
const obj = {
foo: 'foo',
hasOwnProperty: false,
};
const objNull = Object.create(null);
// bad => Uncaught TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('foo'));
console.log(objNull.hasOwnProperty('foo'));
// good
console.log(Object.prototype.hasOwnProperty.call(obj, 'foo'));
console.log(Object.prototype.hasOwnProperty.call(objNull, 'foo'));10. Prohibited Actions
- No
var; useconstandletinstead - No
any(scenarios requiring explicit annotation must include a comment explaining why) - No
namespace(use ES modules instead) - No
require()(useimportinstead) - No triple-slash imports
///(useimportinstead) - No empty
interface(except forextends) - No duplicate class members
- No module-level side effects (side effects should be isolated in entry files)
- No
eval - No
debugger - No assigning to native objects or read-only global objects
- No creating functions with the
Functionconstructor - No
new Number/String/Boolean - No assigning
thisto other variables (use arrow functions to preserve function scope) - No
voidoperator - No nested ternary expressions
- No unnecessary ternary expressions
- No mixing multiple operators without grouping with parentheses