Skip to content

3. JavaScript and TypeScript guidelines

File names must be all lowercase and may include underscores (_), but no additional punctuation.

Follow the convention that your project uses. Filenames extension must be .js or .ts.

To separate internal file types it’s recommended to add second extension to the file name.

For example, type.ts for TypeScript types or .spec.js for JavaScript test files.

Do not use double file extensions for React and Vue components. Instead, split the file into multiple files with the same name but different extensions.

All JavaScript source files must be written using ES modules.

ES modules are files that use the import and export keywords.

import and export statements must be sorted in the following order:

  • import statements
  • export statements

In case of re-exporting a module, import and export statements must be separated by an empty line.

Recommended
import { useState } from 'react';
export { useState };
Not recommended
export { useState };
import { useState } from 'react';

Always use single quotes for import paths.

Recommended
import fs from 'node:fs';
Not recommended
import fs from "node:fs";

import statements must be sorted in the following order:

  • Globally scoped source and type files (e.g. bun, node:, @types/node)
  • External modules (e.g. react, react-dom, @types/react, @types/react-dom)
  • Application level modules (e.g. @/components, @/utils, @/hooks, @/types)
  • Local modules or files referred by its relative path (e.g. ../components, ../../constants, ../utils, ../types)
  • Current module files (e.g. ./interface.type, ./constants, ./enums)
Recommended
import { path } from 'node:fs';
import { useState } from 'react';
import Button from '@/components/button';
import { BUTTON_WIDTH } from '../constants/button.const';
import ButtonIcon from './button_icon';
Not recommended
import { Button } from '@/components/button';
import ButtonIcon from './button_icon';
import { BUTTON_WIDTH } from '../constants/button.const';
import { path } from 'node:fs';
import { useState } from 'react';

3.4.3. Import Statements Sorting and Line Wrapping

Section titled “3.4.3. Import Statements Sorting and Line Wrapping”

import statements must be sorted alphabetically within each group. type prefix should be ignored when sorting.

import statements must be wrapped to the next line if they import more than one module.

Recommended
import {
path,
stat,
} from 'node:fs';
import {
useState,
type ReactNode,
} from 'react';
import Button, {
type ButtonEmits,
type ButtonProps,
} from '@/components/button.vue';
import { BUTTON_WIDTH } from '../constants/button.const';
Not recommended
import { path, stat } from 'node:fs';
import { useState, type ReactNode } from 'react';
import Button, { type ButtonEmits, type ButtonProps } from '@/components/button.vue';
import {
BUTTON_WIDTH,
} from '../constants/button.const';

Do not import namespaces, if possible. Instead, import the specific symbols you need.

Recommended
import { path } from 'node:fs';
Not recommended
import * as fs from 'node:fs';

The .js, .jsx, .ts and .tsx file extensions must always be excluded.

The .vue file extension must always be included when importing a single component.

Recommended
import Button from '@/react/components/button';
import Button from '@/vue/components/button.vue';
Not recommended
import Button from '@/react/components/button.tsx';
import Button from '@/vue/components/button';

3.5.1. Export Statements Sorting and Line Wrapping

Section titled “3.5.1. Export Statements Sorting and Line Wrapping”

Exported modules must be sorted alphabetically within each group. type prefix should be ignored when sorting.

Re-exported modules must be placed at the beginning of the exports statement, before other exported modules.

Default export must be placed at the end of the exports statement.

Exported modules must be wrapped to the next line if they export more than one module.

Separate export groups with an empty line (optional).

Recommended
export { BUTTON_WIDTH } from './constants/button.const';
export const BUTTON_HEIGHT = 40;
export {
path,
type ReactNode,
stat,
useState,
};
export type {
ButtonEmits,
ButtonProps,
};
export default Button;
Not recommended
export default Button;
export { BUTTON_WIDTH } from './constants/button.const';
export { path, stat };
export type {
ReactNode,
};
export const BUTTON_HEIGHT = 40;

JavaScript and TypeScript does not support restricting the visibility for exported symbols. Only export symbols that are used outside of the module. Generally minimize the exported API surface of modules.

Mutable variables should not be exported. It can create hard to understand and debug code, in particular with re-exports across multiple modules.

Recommended
let BUTTON_HEIGHT = 40;
// Somewhere in the code
BUTTON_HEIGHT = 50;
export const getButtonHeight = () => {
return BUTTON_HEIGHT;
};
Not recommended
export let BUTTON_HEIGHT = 40;
// Somewhere in the code
BUTTON_HEIGHT = 50;

Do not create container classes with static methods or properties for the sake of namespacing.

Recommended
export const BUTTON_WIDTH = 40;
export function click() {
console.log('Button clicked');
}
Not recommended
export class Button {
static WIDTH = 40;
static click() {
console.log('Button clicked');
}
}

Refer to the General formatting guidelines for the general formatting rules.

Always use single quotes for strings.

Recommended
const foo = 'bar';
Not recommended
const foo = "bar";

For any character that has a special escape sequence (\', \", \\, \b, \f, \n, \r, \t, \v), that sequence is used rather than the corresponding numeric escape (e.g \x0a, \u000a, or \u{a}).

Legacy octal escapes are never used.

For the remaining non-ASCII characters, either the actual Unicode character (e.g. ) or the equivalent hex or Unicode escape (e.g. \u221e) is used, depending only on which makes the code easier to read and understand.

Recommended
const unit = 'μs';
const unit = '\u03bcs'; // 'μs'
const millisecond = '\u03bcs';
Not recommended
const unit = '\u03bcs';

Each statement is followed by a line-break.

Recommended
const a = 1;
const foo = 'bar';
Not recommended
const a = 1; const foo = 'bar';

Semicolons are required for all statements.

Recommended
const a = 1;
Not recommended
const a = 1

Braces are required for all control structures (i.e. if, else, for, do, while, etc.).

Exception: a statement (including arrow function declaration) that fits on a single line with no wrapping.

Recommended
if (condition) {
console.log('condition is true');
} else {
console.log('condition is false');
}
const foo = () => {
console.log('foo');
return 'foo';
};
const bar = (x: number) => x * 2;
Not recommended
if (condition) console.log('condition is true');
else console.log('condition is false');
const foo = () => { console.log('foo'); return 'foo'; };
while (condition) console.log('condition is still true');

An empty block may be closed immediately after it is opened, with no characters, space, or line break in between.

const doNothing = () => {};
try {
doSomething();
} catch {}

Always use const and let for local variable declarations. Avoid using var.

const and let are block scoped, like variables in most other languages. var in JavaScript is function scoped, which can cause difficult to understand bugs. Don’t use it.

Recommended
const name = 'John';
let age = 20;
Not recommended
var name = 'John';
var age = 20;

Declare one variable per declaration.

Separate each declaration with a newline.

Recommended
let name = 'John';
let age = 20;
Not recommended
let name = 'John', age = 20;
let name = 'John'; let age = 20;

Do not use the Array() constructor, with or without new.

Instead, always use bracket notation to initialize arrays, or from() to initialize an Array with a certain size.

Recommended
const a = [2];
const b = [2, 3];
const c = [];
c.length = 2;
const d = Array.from<number>({ length: 5 }).fill(0);
Not recommended
const a = new Array(2); // [undefined, undefined]
const b = new Array(2, 3); // [2, 3];

Always use the array literal syntax ([]) for array initialization.

Wrap each array element on a new line and add a trailing comma, unless the array is empty or contains only one element.

Recommended
const a = [
2,
3,
];
Not recommended
const a = new Array(2, 3);
const b = [2, 3];

Always use the object literal syntax ({}) for object initialization.

Always wrap each object property on a new line and add a trailing comma.

Recommended
const a = {
name: 'John',
age: 20,
};
const b = {
count: 1,
};
Not recommended
const a = { name: 'John', age: 20 };
const b = { count: 1 };

Arrow functions provide a concise function syntax and simplify scoping this for nested functions. Prefer arrow functions over the function keyword for nested functions.

Prefer arrow functions over other this scoping approaches such as f.bind(this) or const self = this.

Functions may contain nested function definitions. If it is useful to give the function a name, it should be assigned to a local const.

Prefer to wrap function arguments by a new line, unless the function has only one argument or all arguments are perfectly fits on a single line.

Recommended
function foo(
email: string,
firstName: string,
lastName: string,
age: number,
): void {
// ...
}
const bar = (email: string): void => {
// ...
};
Not recommended
function foo(email: string, firstName: string, lastName: string, age: number) {}

For arrow functions, omit the parentheses if the function has only one argument and its type is already defined (e.g., for callback functions).

Recommended
userService.onSignUp(user => {
// ...
});
Not recommended
userService.onSignUp((user: User) => {
// ...
});

3.7.2.3. Function Arguments as Object Literal

Section titled “3.7.2.3. Function Arguments as Object Literal”

For those functions that accept a lot of arguments, prefer to pass them as an object literal, rather than positional arguments.

Recommended
function foo(user: {
email: string;
firstName: string;
lastName: string;
dateOfBirth: Date;
country: string;
city: string;
zip: string;
}): void {
// ...
}
Not recommended
function foo(
email: string,
firstName: string,
lastName: string,
dateOfBirth: Date,
country: string,
city: string,
zip: string,
): void {
// ...
}

Always use the class declaration syntax (class) for class declarations.

Do not add semicolons after methods, or after the closing brace of a class declaration. Statements, such as assignments, that contain inline class expressions are still terminated with a semicolon.

Recommended
class Button {
constructor(name) {
this.name = name;
}
}
Not recommended
class Button {
constructor(name) {
this.name = name;
}
}

Always use the new operator to instantiate a class, rather than calling the class as a function.

Also, add parentheses to the class name.

Recommended
const button = new Button();
Not recommended
const button = Button();
const button = new Button;

Wrap each switch case statement by a new line.

Add an extra empty line between break and the following case statement.

Exception: if all case statements are returning a value, the extra empty line, and the break statement, are not needed.

Recommended
switch (condition) {
case 'a':
console.log('a');
break;
case 'b':
console.log('b');
break;
default:
console.log('default');
break;
}
switch (condition) {
case 'a': return 'a';
case 'b': return 'b';
}
Not recommended
switch (condition) {
case 'a': console.log('a'); break;
case 'b': console.log('b'); break;
default: console.log('default'); break;
}

In case of fall-through, add a comment to the case statement to indicate that the fall-through is intentional.

switch (condition) {
case 'a':
console.log('a');
// fall through
case 'b':
console.log('b');
break;
}

Prefer template literals over complex string concatenation, particularly if multiple string literals are involved.

Template literals may span multiple lines.

If a template literal spans multiple lines, it does not need to follow the indentation of the enclosing block, though it may if the added whitespace does not matter.

const foo = `
bar
baz
qux
`;

Prefer to use type annotations over type inference, particularly for complex types.

Exception: if the type is trivially inferred from the context, it is not necessary to annotate it.

Recommended
const foo = 'bar';
const user: {
email: string;
firstName: string;
lastName: string;
} = {
// ...
};
Not recommended
const foo = 'bar';
const user = {
// ...
};

Always annotate the return type of a function. If the function does not return a value, annotate the return type as void.

Recommended
function foo(): string {
return 'bar';
}
function bar(): void {
console.log('bar');
}
Not recommended
function foo() {
return 'bar';
}
function bar() {
console.log('bar');
}

Prefer optional over |undefined, unless the argument is required.

Recommended
const foo = (bar?: string): void => {
// ...
};
const bar = (foo: string | undefined, bar?: string): void => {
// ...
});
Not recommended
const foo = (bar: string | undefined): void => {
// ...
};

Use TypeScript interfaces to define structural types, not classes.

Recommended
type User = {
email: string;
firstName: string;
lastName: string;
}
const user: User = {
// ...
};
Not recommended
class User {
readonly email: string;
readonly firstName: string;
readonly lastName: string;
}
const user: User = {
// ...
};

Prefer using interface over type for type declarations. The reason is that interface is more flexible and easier to extend. Also, there are some performance issues with type when using it with generics.

Recommended
type User = {
email: string;
firstName: string;
lastName: string;
}
Not recommended
interface User {
email: string;
firstName: string;
lastName: string;
}

For trivial types, use the syntax sugar T[] for typing arrays, and [T, T] for typing tuples.

For complex types, use the longer equivalent Array<T> syntax.

Prefer using objects over tuples, since it’s often clearer to provide meaningful names for the properties

Prefer using unknown over any (or {} which is equivalent to any for objects) type, unless it is absolutely necessary. In this case, add a comment to explain why.

Recommended
const validate = (value: unknown): boolean => {
// ...
};
Not recommended
const validate = (value: any): boolean => {
// ...
};

Prefer using generic types over type unions to create reusable types.

Recommended
const foo = <T extends string | number>(value: T): T => {
return value;
};
Not recommended
const foo = (value: string | number): string | number => {
return value;
};

Do not use @ts-ignore nor the variants @ts-expect-error or @ts-nocheck.

It may seem like an easy way to “fix” a compiler error, but in practice, a specific compiler error is often caused by a larger problem that can be fixed more directly.

// @ts-ignore
const foo: string = 5;