Skip to content

Latest commit

 

History

History
563 lines (377 loc) · 10.7 KB

ts_style_guide.md

File metadata and controls

563 lines (377 loc) · 10.7 KB

TypeScript StyleGuide and Coding Conventions

Key Sections:

Case

Variable and Function

  • Use camelCase for variable and function names

Reason: Conventional JavaScript

Bad

var FooVar;
function BarFunc() {}

Good

var fooVar;
function barFunc() {}

Component

  • Use PascalCase for component name, even if function component.

Reason: Follows React naming convention.

Bad

function component(): ReactElement {
  return <subComponent />;
}

Good

function Component(): ReactElement {
  return <SubComponent />;
}

Class

  • Use PascalCase for class names.

Reason: This is actually fairly conventional in standard JavaScript.

Bad

class foo {}

Good

class Foo {}
  • Use camelCase of class members and methods

Reason: Naturally follows from variable and function naming convention.

Bad

class Foo {
  Bar: number;
  Baz() {}
}

Good

class Foo {
  bar: number;
  baz() {}
}

Interface

  • Use PascalCase for name.

Reason: Similar to class

  • Use camelCase for members.

Reason: Similar to class

Type

  • Use PascalCase for name.

Reason: Similar to class

  • Use camelCase for members.

Reason: Similar to class

  • Use ALL_CAPS for action types.

Reason: Follows Redux naming convention

Namespace

  • Use PascalCase for names

Reason: Convention followed by the TypeScript team. Namespaces are effectively just a class with static members. Class names are PascalCase => Namespace names are PascalCase

Bad

namespace foo {}

Good

namespace Foo {}

Enum

  • Use PascalCase for enum names

Reason: Similar to Class. Is a Type.

Bad

enum color {}

Good

enum Color {}
  • Use PascalCase for enum member

Reason: Convention followed by TypeScript team i.e. the language creators e.g SyntaxKind.StringLiteral. Also helps with translation (code generation) of other languages into TypeScript.

Bad

enum Color {
  red,
}

Good

enum Color {
  Red,
}

Filename

Name files and folders with camelCase (e.g., utilities.ts, index.tsx, tractor.png, components/), unless it is the name of the contained Component (e.g., DataEntryTable.tsx, ReviewEntries/).

Reason: camelCase is conventional across many JS teams.

Interface names

  • Don't prefix with I

Reason: Unconventional. lib.d.ts defines important interfaces without an I (e.g. Window, Document etc).

Bad

interface IFoo {}

Good

interface Foo {}

Test filenames

  • The test file associated with path/to/Component.tsx should be path/to/tests/Component.test.tsx.

  • Auxiliary test files with data or mock objects should have filenames ending in Mock.ts, e.g., path/to/tests/DataMock.ts.

Reason: *.test.* and *Mock.ts files are ignored by our CodeCov settings.

Type files

  • Separate type files should contain types, classes, interfaces, enums but not any classes or functions that need unit testings. Such files should have filenames ending in Types.ts, e.g., MergeDupReduxTypes.ts.

Reason: *Types.ts files are ignored by our CodeCov settings.

null vs. undefined

  • Prefer not to use either for explicit unavailability

Reason: these values are commonly used to keep a consistent structure between values. In TypeScript you use types to denote the structure

Bad

let foo = { x: 123, y: undefined };

Good

let foo: { x: number; y?: number } = { x: 123 };
  • Use undefined in general (do consider returning an object like {valid:boolean,value?:Foo} instead)

Bad

return null;

Good

return undefined;
  • Use null where its a part of the API or conventional

Reason: It is conventional in Node.js e.g. error is null for NodeBack style callbacks.

Bad

cb(undefined);

Good

cb(null);
  • Use truthy check for objects being null or undefined

Bad

if (error === null)

Good

if (error)

Remark: Use === / !== to check for null / undefined on primitives that might be other falsy values (like '',0,false).

const vs. let vs. var

  • const allows the variable to be mutated, but doesn't allow it to be redeclared; prefer const any time your variable only needs to be declared once.

  • let and var both allow redeclaration, but var is a global variable and let is limited to the scope of its declaration; prefer let to var.

Bad

var shouldClapHands = false;
for (var i = 0; i < 3; i++) {
  var you = getYouAtTime(i);
  shouldClapHands ||= you.isHappy() && you.knowsIt();
}

Good

let shouldClapHands = false;
for (let i = 0; i < 3; i++) {
  const you = getYouAtTime(i);
  shouldClapHands ||= you.isHappy() && you.knowsIt();
}

Formatting

Use Prettier to format TypeScript code as described in the README.

Reason: Reduce the cognitive overload on the team.

Quotes

  • Prefer double quotes ("your text") to single quotes ('your text').

Reason: Follows Prettier naming convention.

Semicolons

  • Use semicolons.

Reasons: Explicit semicolons helps language formatting tools give consistent results. Missing ASI (automatic semicolon insertion) can trip new devs e.g. foo() \n (function(){}) will be a single statement (not two). TC39 warning on this as well. Example teams: airbnb, idiomatic, google/angular, facebook/react, Microsoft/TypeScript.

Arrays

  • Annotate arrays as foos:Foo[] instead of foos:Array<Foo>.

Reasons: Its easier to read. Its used by the TypeScript team. Makes easier to know something is an array as the mind is trained to detect [].

type vs. interface

  • Use type when you might need a union or intersection:
type Foo = number | { someProperty: number }
  • Use interface when you want extends or implements e.g
interface Foo {
  foo: string;
}
interface FooBar extends Foo {
  bar: string;
}
class X implements FooBar {
  foo: string;
  bar: string;
}
  • Otherwise use whatever makes you happy that day.

One line if statements

  • Add braces to one-line if statements;

Good

if (isEmpty) {
  callFun();
}

Bad

if (isEmpty)
  callFun();

Reason: Avoiding braces can cause developers to miss bugs, such as Apple's infamous goto-fail bug

imports

Absolute vs. relative paths

  • Use absolute import statements everywhere for consistency.

Good

import { type Project } from "api/models";
import { getAllProjects } from "backend";

Bad

import { type Project } from "../../../../api/models";
import { getAllProjects } from "../../../../backend";

Reason: Provides consistency for imports across all files and shortens imports of commonly used top level modules. Developers don't have to count ../ to know where a module is, they can simply start from the root of src/.

Specificity preferred

  • Generally import the specific things needed (e.g., not React when { type ReactElement } will do), and from a more specific target (e.g., from "api/models" rather than from "api"):

Good

import { type ReactElement } from "react";

import { type Project } from "api/models";

function Component(props: { project: Project }): ReactElement {}

Bad

import React from "react";

import { type Project } from "api";

function Component(props: { project: Project }): React.ReactElement {}

KeyboardEvents

  • Use ts-key-enum when comparing to React.KeyboardEvents.

Good

import { Key } from "ts-key-enum";

if (event.key === Key.Enter) {
}

Bad

if (event.key === "Enter") {
}

Reason: Avoid typos and increase the number of mistakes that can be caught at compile time.

Components

  • Prefer functional components to class components.

Good

function Component(props: ComponentProps): ReactElement {
  return <SubComponent />;
}

Bad

class Component extends React.Component<ComponentProps, ComponentState> {
  render() {
    return <SubComponent />;
  }
}

Function return type

  • Specify the return type of a named function, whether declared as a function or const.
  • The return type of a functional component should be ReactElement.

Good

import {ReactElement} from React;

function Component(props: { name: string }): ReactElement {
  const sayHi = (name: string): string => {
    return `Hi, ${name}!`;
  }

  return sayHi(props.name);
}

Bad

function Component(props: { name: string }) {
  const sayHi = (name: string) => {
    return `Hi, ${name}!`;
  };

  return sayHi(props.name);
}

Reason: Ensure all return paths are the expected type.

Hooks

  • Prefer useAppDispatch to useDispatch and useAppSelector to useSelector.

Good

import { useAppDispatch, useAppSelector } from "rootRedux/hooks";
import { type StoreState } from "rootRedux/types";

function Component(): ReactElement {
  const dispatch = useAppDispatch();
  const subState = useAppSelector((state: StoreState) => state.subState);
}

Bad

import { useDispatch, useSelector } from "react-redux";

function Component(): ReactElement {
  const dispatch = useDispatch();
  const subState = useSelector((state) => state.subState);
}

Reason: See types/hooks.ts.