Coding Standards - Syntax-Meridian/TesseractCMS GitHub Wiki

Overview

  • The codebase must remain in strict mode for TypeScript (tsconfig.json) under all circumstances.
  • Prettier should be used to format the code prior to commit.
  • Coding must comply with standard ESLint rules; any violations of ESLint must be addressed before PRs are merged.
  • Additional guidelines are outlined below.

Additional Guidelines

Always Use let and const

Avoid var since let and const are block-scoped and more predictable.

Bad

var x = 10;

Good

let x = 10;

Type Annotations for Variables

Prefer to specify types except where the given type is obvious from assignment.

Bad

let x: string = "Hello"; let y = getConfiguration();

Good

let x = "Hello"; let y: DatabaseConfig = getConfiguration();

Interfaces for Object Shapes

Use interfaces to define shapes of objects for type annotations.

Bad

let x: { name: string; age: number; } = ...

Good

interface Person { name: string; age: number; }
let x: Person = ...

Use Arrow Functions for Callbacks

Defining functions with the function keyword alters the meaning of this in scope. Arrow functions maintain the parent's this binding.

Bad

class Foo {
  const bar = 10;
  calculate(arr: number[]): number[] {
    arr.map(function(item) { return item + this.bar; }); // this belongs to function and will return undefined
  }
}

Good:

class Foo {
  const bar = 10;
  calculate(arr: number[]): number[] {
    arr.map(item => item + this.bar); // this belongs to Foo and returns 10
  }
}

Avoid Using any Type

Use specific types or generics. Define interfaces if necessary.

Bad

let data: any;

Good

let data: string | number;

Use Type Aliases and Union Types

Combine multiple types when variables can have more than one type and the type will be used in multiple locations.

Bad

function display(input: string) {}

Good

type InputType = string | number; function display(input: InputType) {}

Use Modules

Organize and scope your code using modules. Modules should contain classes and components that work together to deliver a specific functionality.

Example:

Given some module foo, make folder src/foo with contents as such:

// src/foo/FooComponent.ts
export class FooComponent {}
// src/foo/FooSound.ts
export class FooSound {}

Avoid Implicit any

Always specify function argument types.

Bad

function greet(person) {}

Good

function greet(person: string) {}

Use Readonly for Immutable Properties

Prevent modification after object creation.

Bad

interface Config { option: string; }

Good

interface Config { readonly option: string; }

Avoid null and Use undefined

Makes your intent clearer and works better with optional chaining. Undefined has better defined behavior under most circumstances and will avoid footguns related to type coercing and equivalencies.

Bad

let name: string | null = null;

Good

let name?: string = undefined;

Avoid Enums for Fixed Sets of Values - Use Unions and Const keyof Instead

Provide a clear and defined set of related constants by using typed unions or const keyof values. This avoids serious problems with the enum type. There are rare occasions where enum may be useful, but in most cases they can cause issues.

Bad

enum Color { RED = "Red", BLUE = "Blue" }

Good

Standard union type, when you don't need to iterate over the values:

type Color = "Red" | "Blue"

Const keyof Alternative, when you need to be able to iterate over the values in the enum-like union:

const Color = {
  Red: 'Red',
  Green: 'Green',
  Blue: 'Blue'
} as const;

type Color = typeof Color[keyof typeof Color]; // 'Red' | 'Green' | 'Blue'

for (const color of Object.values(Color)) {
  // do something
}

Prefer as Type Over <Type> for Casting

This syntax is simply clearer.

Bad

<string>value;

Good

value as string;

Function Return Types

Always specify return types for clarity and type safety.

Bad

function greet() { return "Hello"; }

Good

function greet(): string { return "Hello"; }

Use Optional Chaining

Safely access properties without explicit undefined checks.

Bad

if (obj && obj.prop) { ... }

Good

obj?.prop;

Use Nullish Coalescing

Fall back to default values for undefined and null via nullish coalescing. || incorrectly treats falsey values like 0 as null and will erroneously assign the fallback.

Bad

const result = value || "default"; // if value = 0, result will be "default"

Good

const result = value ?? "default"; // if value = 0, result will be 0

Avoid Magic Numbers

Use named constants instead of repeated number literals.

Bad

if (status === 200) { ... }

Good

const OK_STATUS = 200; if (status === OK_STATUS) { ... }

Use Descriptive Variable and Function Names

Meaningful names improve code readability.

Bad

function fn(n: number): number {}

Good

function square(number: number): number {}

Private Member Prefix

Prefix private class members with an underscore.

Bad

private name: string;

Good

private _name: string;

Do not use global state.

Functions should not modify global variables or states.

Bad

class Foo { 
  setName(name: string) { 
    globalName = name;
  } 
}

Good

class Foo { 
  private _name: string; 
  setName(name: string) { 
    this.name = name; 
  } 
}

†There are rare cases where it is acceptable to use global state. These are generally for shared pools, singleton libraries, etc which are for a specific context (eg, lightweight lambda functions will share globals to reduce garbage collection cycles.) Almost always there is a better way via dependency injection or parameters. Talk it out with the team before you introduce globals anywhere.

Leverage Type Guards

Type guards let you narrow a type down from a type union or parent class to a specific union type or derived class. For classes, this can be done with typeof. For narrowing a union of interfaces, you'll need to use type discriminators like kind for discriminated unions.

Example:

class Foo { foo(): number }; class Bar { bar(): number }; class Baz { baz(): number }
type Input = Foo | Bar | Baz;

function process(input: Input): number {
  if(typeof input === 'Foo') {
    return input.foo();
  } else if(typeof input === 'Bar') {
    return input.bar();
  } else if(typeof input === 'Baz') {
    return input.baz();
  }
}

Explicit Public Modifier

Clearly indicate public class members using the public keyword.

Bad

name: string;

Good

public name: string;

Use Template Strings

More readable when combining variables with strings.

Bad

'Hello, ' + name + '!';

Good Hello, ${name}!;


## **Async/Await Over Promises**

   Async/await is almost always more readable than using Promise chains like `.then()`.

   **Bad**


```typescript
function fetchData(): Promise<Data> { 
  let promise = callApi(); 
  return promise.then(data => { return new Data(data.value + 10) }); 
}

Good

async function fetchData(): Promise<Data> { 
  const result: Data = await callApi();
  return new Data(result.value + 10);
}

Overload Functions Sparingly

Avoid overloading unless necessary for public APIs. As a general rule, a function should do one thing only; if you need to accept multiple input types, use type unions.

Bad

function display(value: string); function display(value: number)

Good

function display(value: string | number)

Use Tuples for Fixed Length Arrays

Tuple types ensure that the semantic meaning carries along with the data being held.

Bad

let x: [string, number] = ["John", 500];

Good

type PlayerScore = [string, number]; let x: PlayerScore = ["John", 500];

Document with JSDoc

Provide context and type information with JSDoc comments.

Example:

/** 
 * Fetches data from the API. Rate limits may vary by environment.
 * @param {string} message The user's chat message
 * @returns {ChatResponse}
**/
function callApi(message: string): ChatResponse {
 // ...
}

Use Default Function Parameters

Provide parameter defaults directly in the function signature rather than assigned in the function body.

Bad

function greet(message) { message = message || "Hello"; }

Good

function greet(message = "Hello") {}

Prefer Spread Operator for Object and Array Cloning

Much more readable way to create a copy of an object or array. (Beware that this is a shallow copy. Use a utility library for deep copying instead.)

Bad

let clone = Object.assign({}, obj);

Good

let clone = { ...obj };

References

Other good references for style can be found below.

⚠️ **GitHub.com Fallback** ⚠️