# Code style guide
## Table of Contents
1. [Naming](#naming)
2. [Style](#style)
3. [General and Generic Types](#general-and-generic-types)
4. [Declarations](#declarations)
# Naming
## type names and interfaces
- Use PascalCase
```ts
// ✗ avoid
type nameResolver = () => string;
// ✓ good
type NameResolver = () => string;
interface NameResolver {
(): string;
}
```
## Enum values and initializers
- Use PascalCase
- There will be no intersection with variable names. Let's look at this:
```ts
// ✗ avoid
enum fileAccess {
none,
READ = 1 << 1,
}
function readFile(fileAccess: fileAccess) {
// Compilation error:
// * Property 'none' does not exist on type 'fileAccess'.
if (fileAccess === fileAccess.none) {
}
}
```
And this:
```ts
// ✓ good
enum FileAccess {
None,
Read = 1 << 1,
}
function readFile(fileAccess: FileAccess) {
if (fileAccess === FileAccess.None) {
}
}
```
Google, Microsoft, Facebook, airbnb and other companies encourage this rule.
## Function names, class properties and local variables
- Use camelCase
- Use the appropriate prefix if the subsequent expression is of type boolean or the function declaration that returns boolean value:
```ts
function shouldDownloadXML(): boolean {
return true;
}
function canBePushedIntoArray(): boolean {
return true;
}
const isBrowser = typeof window !== 'undefined';
```
Google, Microsoft, Facebook, airbnb and other companies encourage this rule.
## Interface naming
- Do not use "I" as a prefix for interface names:
```ts
interface IPerson {} // ✗ avoid
interface Person {} // ✓ good
```
Microsoft official guidelines, all world TypeScript coding standards and books discourage the `I` prefix. Links:
* [Microsoft guidelines](https://github.com/microsoft/TypeScript/wiki/Coding-guidelines#names)
* [Basarat TypeScript book](https://github.com/basarat/typescript-book/blob/master/docs/styleguide/styleguide.md#interface) (this is the most popular in the TypeScript world)
* [Learn TypeScript 3 by Building Web Applications](https://subscription.packtpub.com/book/programming/9781789615869/3/ch03lvl1sec37/typescript-interfaces)
```ts
interface ICar {
speed: number;
move(): void;
}
class Car implements ICar {
speed = 100;
move() {}
}
```
The thing is that classes and interfaces should be thought of as interchangeable when passed as types. In theory there is no reason to know that a reference of type `Car` refers to a class and not an interface. Anders Hejlsberg is the creator of C# and TypeScript, he also created guidelines for the TypeScript where he discourages the `I` prefix.
`ICar + Car` is an antipattern, means the interface basically has 1 implementation, which means there shouldn't be an interface at all. You would want specific, concrete identifiers for your implementation, concrete classes, e.g. `SportsCar implements Car`, `SUVCar implements Car` etc. Some other examples: interface `PaymentProcessor` and concrete class `CreditCardPaymentProcessor` instead of `IPaymentProcessor` and `PaymentProcessor`.
## Do not use non-common acronyms and unnecessary abbreviations
- Use whole words in names when possible:
```ts
// ✗ avoid
const msg = 'Hello!';
const appSvc = new AppSvc();
// ✓ good
const message = 'Hello!';
const appService = new AppService();
```
## Do not prefix with underscore
- Do not use "\_" as a prefix for private properties:
```ts
// ✗ avoid
export class AddressComponent {
private _dialogWidth = 400;
private _showDialog() {}
}
// ✓ good
export class AddressComponent {
private dialogWidth = 400;
private showDialog() {}
}
```
Why?
- JavaScript lacks a true private property or method.
- TypeScript tooling makes it easy to identify private vs. public properties and methods.
Google, Microsoft, Facebook, airbnb and other companies encourage this rule.
# Style
## Consider using `const enum`
- enums that do not require "lookup object" can be turned into `const enum` which is not compiled into objects and inlined instead.
```ts
enum FileAccess {
None,
Read = 1 << 1,
}
function getReadFileAccess() {
return FileAccess.Read;
}
// Compiled code
var FileAccess;
(function (FileAccess) {
FileAccess[(FileAccess['None'] = 0)] = 'None';
FileAccess[(FileAccess['Read'] = 2)] = 'Read';
})(FileAccess || (FileAccess = {}));
function getReadFileAccess() {
return FileAccess.Read;
}
```
Using `const enum`:
```ts
const enum FileAccess {
None,
Read = 1 << 1,
}
function getReadFileAccess() {
return FileAccess.Read;
}
// Compiled code
function getReadFileAccess() {
return 2 /* Read */;
}
```
## Return Early Pattern
- Consider using the "Return Early Pattern" to avoid nested code blocks thus making the code easier to read in general:
```ts
class CreateBookComponent {
form = new FormGroup({ ... });
constructor(private booksService: BooksService) {}
createBook(): void {
// ✗ avoid
if (this.form.valid) {
this.booksService.createBook(this.form.value).subscribe(() => {
this.form.reset();
});
}
// ✓ good
if (this.form.invalid) {
return;
}
this.booksService.createBook(this.form.value).subscribe(() => {
this.form.reset();
});
}
}
```
# General and Generic Types
## Infer types of variables/constants
- Allow the compiler to infer types of variables itself:
```ts
// ✗ avoid
const warning: string = 'Hey, this is some warning...';
// ✓ good
const warning = 'Hey, this is some warning...';
```
## Avoid `any` as much as possible
- imagine there is no `any` type. Try to replace it with actual typing if possible or `unknown` if type is really unknown.
## Prefer type value or extend instead of `any` in generics
- When declaring type variables don't allow the compiler to accept the `any` type as a default value. Limit type inference:
```ts
interface User {}
interface Issuer extends User {}
interface Investor extends User {}
// ✗ avoid
class UserCreditCardPaymentProcessor<T = any> {
constructor(public user: T) {}
}
// ✓ good
class UserCreditCardPaymentProcessor<T extends User> {
constructor(public user: T) {}
}
```
- Require to provide type values:
```ts
type RequireGeneric<T, U> = T extends void ? 'Provide a type variable' : U;
interface User {}
interface Issuer extends User {}
interface Investor extends User {}
class UserCreditCardPaymentProcessor<T = void> {
constructor(public user: RequireGeneric<T, User>) {}
}
// Compilation error
new UserCreditCardPaymentProcessor({});
// Successful compilation
new UserCreditCardPaymentProcessor<Issuer>({});
```
## Specify type guards
- Consider using user-defined type guards to narrow type inference. Type guards provide the ability to narrow a variable type inside `if` blocks. The cornerstones of type guards were `typeof` and `instanceof` operators. User-defined type guards are simply functions that return type predicates, let's look at the below code:
```ts
// `el is Element` is a type predicate
function isElement(el: Node | null): el is Element {
return el !== null && el.nodeType === Node.ELEMENT_NODE;
}
function isAnchorElement(el: Node | null): el is HTMLAnchorElement {
return isElement(el) && el.nodeName === 'A';
}
function isImageElement(el: Node | null): el is HTMLImageElement {
return isElement(el) && el.nodeName === 'IMG';
}
function isFrameElement(el: Node | null): el is HTMLFrameElement {
return isElement(el) && el.nodeName === 'IFRAME';
}
function updateUrl(el: Node | null, url: string): void {
// Compilation errors:
// * Object is possibly 'null'.
// * Property 'href' does not exist on type 'Node'.
el.href = url;
if (el !== null && 'href' in el) {
// Compilation error: Property 'href' does not exist on type 'never'.
el.href = url;
}
if (isAnchorElement(el)) {
// `el` is narrowed to `HTMLAnchorElement` type only inside
// this if block and you're able to access `HTMLAnchorElement` specific
// properties
el.href = url;
} else if (isImageElement(el) || isFrameElement(el)) {
// `el` is narrowed to `HTMLImageElement | HTMLFrameElement` union type
el.src = url;
}
}
```
User-defined type guards are a form of runtime checks, which means the variable is of expected type at the code execution moment. `typeof` operator is great for narrowing primitive types such as `number` or `string`. `instanceof` operator is possible to use with constructor functions (or classes). User-defined type guards are possible to use when the type should be narrowed to some user-defined `type` or `interface`, that cannot be narrowed with `typeof` or `instanceof`.
## Don't use the upper-case primitive types
Use the lower-case types for consistency
- `String` => `string`
- `Boolean` => `boolean`
- `Number` => `number`
- Avoid the `Function` type, as it provides little safety for the following reasons:
- It provides no type safety when calling the value, which means it's easy to provide the wrong arguments.
- It accepts class declarations, which will fail when called, as they are called without the new keyword.
- Avoid the `Object` and `object` (`{}`) types, as they mean "any non-nullish value".
- This is a point of confusion for many developers, who think it means "any object type".
[See this comment for more information](https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492).
- The best way to prevent confusion is to specify what object is expected.
- `{ [key: string]: number }` for key (string) value (number) pair gives more clarity than `{}` alone
- Avoid the `object` type, as it is currently hard to use due to not being able to assert that keys exist.
- See [microsoft/TypeScript#21732](https://github.com/microsoft/TypeScript/issues/21732).
# Declarations
- Use module augmentation technique to augment already existing declarations, don't use `any`:
```ts
// ✗ avoid
(window as any).someVariable = 10;
// ✓ good
// The TypeScript compiler recursively reads all declarations files starting from the
// folder root.
// typings.d.ts
declare interface Window {
// Leave some comments here for other developers like
// why this property is needed, its purpose, etc.
someVariable: number;
}
window.someVariable = 10;
```
- Always install type declarations for third-party packages that don't have its own declarations, e.g:
```bash
yarn add backbone
yarn add -D @types/backbone
```