# 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 ```