ddts
A style guide for writing better typescript, enforced via eslint pre-commit hook.
Enforced Rules
Alignment - Prettier
Function parameters and arguments should be on the same line until they surpass the max line length(80), at which point they should be aligned vertically each on their own line.
const bar = (a,
b,
c) => {
...
};
const bar = (a, b, c) => {
...
};
const foo = (parameter, otherReallyLongParameter, superDuperLongParameter, tooManyParametersForOneLine) => {
...
};
const foo = (
parameter,
otherReallyLongParameter,
superDuperLongParameter,
tooManyParametersForOneLine
) => {
...
};
Arrow Function Parentheses - Prettier
Always use parentheses around arrow function parameters, for consistency.
const foo = x => x*x;
const lesserThings = things.map(thing => thing - 1);
const foo = (x) => x*x;
const lesserThings = things.map((thing) => thing -1);
fdescribe
/fit
(focusing blocks/tests silently kills test suites' usefulness)
Class and Interface Names
Class and Interface names should always be pascal-case for ease of identification.
interface user {
...
}
class accountInfo {
...
}
interface User {
...
}
class AccountInfo {
...
}
Camel- And Pascal-Case Variable Names Only
No leading or trailing underscores or keywords (any, Number, number, String, string, Boolean, boolean, undefined) either.
const _pretending_im_private = false;
const notPretending = true;
There should be a space between the //
and the first word.
Curly braces should always be used for if/for/do/while
statements for clarity on what is included in the statement.
if(thing < other)
thing++;
return thing;
if(thing < other) {
thing++;
return thing;
}
if(thing < other) {
thing++;
}
return thing;
if(foo) return true;
if(foo) {
return true;
}
End every file with a newline character, because otherwise it's not a line.
Mixing tabs and spaces is insanity, and spaces are interpreted the same universally whereas tabs are not.
Why mangle names to save time in differentiating an interface from a class when it's an arguably superfluous distinction in many cases.
interface IIllusion {
...
}
interface Illusion {
...
}
Labels only belong in do/for/while/switch
statements, if at all.
happy:
if(true) {
console.log('happy days');
if(condition) {
break happy;
}
}
let i = 0;
let j = 0;
loop1:
for (i = 0; i < 3; i++) {
loop2:
for (j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break loop1;
}
console.log(`i = ${i}, j = ${j}`);
}
}
Classes deserve their own files (which should be named after them).
class Foo {
...
}
class Bar {
...
}
class Foo {
...
}
class Bar {
...
}
Order the members of a class consistently, for discoverability. Priority rules are:
- fields
- static public
- static private
- public
- private
- functions
- constructor
- static public
- static private
- public
- private
Members are public by default, so avoid clutter by omitting the designation.
class Alphabet {
static a = true;
static private b = false;
c = true;
private d = false;
constructor() {
...
}
static e() {
...
}
static private f() {
...
}
g() {
...
}
private h() {
...
}
}
Use parentheses when invoking a constructor function with new
for consistency with other function calls.
class Foo {
...
}
const bad = new Foo;
const good = new Foo();
Using arguments.callee
makes various performance optimizations impossible.
[1, 2, 3, 4, 5].map(function(n) {
return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
});
const factorial = (n) => {
return !(n > 1) ? 1 : factorial(n - 1) * n;
};
[1, 2, 3, 4, 5].map(factorial);
In most cases these are typos (i.e. foo & bar()
when meaning foo && bar()
) or overly clever/opaque. If there is a great reason for a bitwise operation, locally overriding the rule is fine.
Like bitwise operations, these are often merely typos. If used purposefully, they're harder to notice and therefore a potential for great pain and suffering.
let foo;
if(foo = bar) {
...
}
const foo = bar;
if(foo) {
...
}
...
if(foo === bar) {
...
}
Think of one blank line as a comma, two blank lines as a period. Three (or more) blank lines would be an exclamation point (or series of points). Exclamation points are bad.
"One should never use exclamation points in writing. It is like laughing at your own joke." -- Mark Twain
In almost every case the intention of something like new Number('0')
or new Boolean('false')
is to perform a type conversion, not to create a wrapper object.
const condition = new Boolean('false');
if(condition) {
...
}
const condition = Boolean('false');
if(condition) {
...
}
Default exports have a tumultuous history (and present) with transiplation tooling, and naming all exports promotes clarity by disallowing the exporting of anonymous functions.
export default function() {
...
};
export const foo = () => {
...
};
Arbitrary code execution is a no-no.
Explicitly declaring types on constants assigned primitives is needless clutter.
const foo:boolean = true;
const foo = true;
let bar:number = 0;
const bazz = (buzz:boolean = false) => {
...
};
No Property Mutation
Avoiding property mutation makes debugging easier by limiting side effects.
const foo = {
bar: 1,
baz: true,
};
foo.bar = 2;
doSomething(foo);
const foo = {
bar: 1,
baz: true,
}
const updatedFoo = {
...foo,
bar: 2,
};
doSomething(updatedFoo);
You're using a transpiler that understands ES6 import syntax, so use it.
const foo = require('foo');
import { foo } from 'foo';
Wash your hands after using the bathroom, cover your mouth when you snenoeze, and don't commit trailing whitespace for other peoples' text editors to remove bloating everyone's diffs.
Unused expressions are most frequently typos.
BAD
const bar = () => {
...
};
bar;
Use let
or const
for greater clarity.
If possible, avoid quotation marks around object literal keys to make them easier to read (less superfluous characters to parse).
const foo = {
'bar': true,
};
const foo = {
bar: true,
'fizz-buzz': 3,
};
Reducing clutter by removing duplication.
const bar = true;
const bazz = false;
const foo = {
bar: bar,
bazz: bazz,
};
const foo = {
bar,
bazz,
};
Sorted objects allow readers to visually binary search for keys, and helps prevent merge conflicts.
const foo = {
marbles: 5,
'carrot cake': [],
xylophone: true,
boo: 'ahhhhhhh',
};
const foo = {
boo: 'ahhhhhhh',
'carrot cake': [],
marbles: 5,
xylophone: true,
};
The catch/finally/else
statements should all be on the same line as their preceding and following block braces with a single space separating them. All blocks should be at least three lines.
if( ... ) { ... }
const foo = () => { return ... };
[...].map((x) => { return ... });
if( ... ) {
...
}
const foo = () => {
return ...
};
[...].map((x) => {
return ...
});
if( ... ) {
...
}
else
{ ... }
if( ... ) {
...
}else{
...
}
if( ... ) {
...
} else {
...
}
try {
...
}catch(error){ ... }
finally {
...
}
try {
...
} catch(error) {
...
} finally {
}
Reduce diff clutter and avoid easy typos by not chaining assignments. Not enforced in for
loops.
const a = 5,
b = true,
c = null;
const a = 5;
const b = true;
const c = null;
for(let a = 0, b = false, c; ... ; ...) {
...
}
(After ESlint migration - this rule changed)
Traditional anonymous functions don't bind lexical scope, so their behavior can be unexpected when referencing this
.
const foo = {
bar: function() {
return this;
}
};
const foo = {
bar: () => {
return this;
}
};
(After ESlint migration - rule changed)
Within groups (delineated by blank lines) imports should be alphabetized, case-insensitive.
import { ... } from 'foo';
import { ... } from 'bar';
import { ... } from 'fizz';
import { ... } from 'buzz';
import { ... } from 'aardvark';
import { ... } from 'aardvark';
import { ... } from 'bar';
import { ... } from 'buzz';
import { ... } from 'fizz';
import { ... } from 'foo';
import { ... } from 'buzz';
import { ... } from 'fizz';
import { ... } from 'aardvark';
import { ... } from 'bar';
import { ... } from 'foo';
Reap the semantic benefits of your declarations; when assigning a name to a value, if that value won't/can't/shouldn't change it should use a const
declaration, not a let
declaration.
const half = (value) => {
let divisor = 2;
return value / divisor;
};
const half = (value) => {
const divisor = 2;
return value / divisor;
};
When iterating through an array with a for
loop, prefer the for(... of ...)
construction over for(let i =0; i < length; i++)
unless the index is used for something other than accessing items. for...of
better conveys intent.
const arr = [...];
for(let i = 0, item; i < arr.length; i++) {
item = arr[i];
console.log(item);
}
for(let item of arr) {
console.log(item);
}
Single Quotation Marks For Strings - Prettier
Consistency is king, and single quotation marks are less clutter.
Always include a default case for switch statements either first (preferable) or last, not stuck between other cases.
switch(foo) {
case bar:
return false;
case fizz:
return true;
}
switch(foo) {
default:
break;
case bar:
return false;
case fizz:
return true;
}
Trailing Comma (Comma dangle) - Prettier
Always include trailing commas for the last item in multi-line object and array literals. Never for single-line literals. Your diffs will thank you.
const foo = {
a: true,
b: false
};
const merp = [
1,
2,
3
];
const bar = ({
c,
d,
e
}) => {
...
};
const ugh = { x: 1, y: 2, };
const shhh = ({ quiet, time, }) => {
...
};
const foo = {
a: true,
b: false,
};
const merp = [
1,
2,
3,
];
const bar = ({
c,
d,
e,
}) => {
...
};
const ugh = { x: 1, y: 2 };
const shhh = ({ quiet, time }) => {
...
}
Implicit type conversion is not your friend. Strict comparisons are easier to reason about, so be explicit with any conversions you plan to make before comparing.
const foo = '4';
const bar = 4;
if(foo == bar) {
...
}
if(Number(foo) === bar) {
...
}
NaN !== NaN
, so comparing to NaN
doesn't work.
if(foo === NaN) {
...
}
if(isNaN(foo)) {
...
}
Breathing Room Is Good - Prettier
Whitespace between operands, separators, and assignment precedents and antecedents promotes legibility.
const yes = no||maybe&& 2+6;
const bar = [1,'oh god',3,false];
const foo=true;
const yes = no || maybe && 2 + 6;
const bar = [1, 'oh god', 3, false];
const foo = true;
Reasoning about switch statements making use of fall-through cases that aren't empty is hard, and often a case falling through is accidental.
switch(foo) {
case a:
buzz(foo);
case b:
fizz(foo);
}
switch(foo) {
case a:
return buzz(foo);
case b:
return fizz(foo);
}
switch(foo) {
case a:
case b:
return buzz(foo);
case c:
return fizz(foo);
}