ddts
v3.3.0
Published
A typescript style guide with teeth
Downloads
204
Readme
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.
// BAD
const bar = (a,
b,
c) => {
...
};
// GOOD
const bar = (a, b, c) => {
...
};
// BAD having to scroll horizontally is shitty
const foo = (parameter, otherReallyLongParameter, superDuperLongParameter, tooManyParametersForOneLine) => {
...
};
// GOOD
const foo = (
parameter,
otherReallyLongParameter,
superDuperLongParameter,
tooManyParametersForOneLine
) => {
...
};
Arrow Function Parentheses - Prettier
Always use parentheses around arrow function parameters, for consistency.
// BAD
const foo = x => x*x;
const lesserThings = things.map(thing => thing - 1);
// GOOD
const foo = (x) => x*x;
const lesserThings = things.map((thing) => thing -1);
Banned methods
fdescribe
/fit
(focusing blocks/tests silently kills test suites' usefulness)
Naming Convention
Class and Interface Names
Class and Interface names should always be pascal-case for ease of identification.
// BAD
interface user {
...
}
class accountInfo {
...
}
// GOOD
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.
// BAD
const _pretending_im_private = false;
// GOOD
const notPretending = true;
Single-line Comments
There should be a space between the //
and the first word.
//not a pleasant
//reading experience
// a much better
// reading experience
Conditional Curly Braces
Curly braces should always be used for if/for/do/while
statements for clarity on what is included in the statement.
// BAD
if(thing < other)
thing++;
return thing;
// it's unclear whether the author knows the return
// statement is not included in the conditional block
// GOOD
if(thing < other) {
thing++;
return thing;
}
// OR
if(thing < other) {
thing++;
}
return thing;
// BAD
if(foo) return true;
// it's much easier to scan for return statements
// when 'return' is the left-most word on the line
// GOOD
if(foo) {
return true;
}
End of File Newline
End every file with a newline character, because otherwise it's not a line.
Indent With Spaces
Mixing tabs and spaces is insanity, and spaces are interpreted the same universally whereas tabs are not.
Interface Name Prefixing
Why mangle names to save time in differentiating an interface from a class when it's an arguably superfluous distinction in many cases.
// BAD
interface IIllusion { // kill me now please
...
}
// GOOD
interface Illusion {
...
}
Labels
Labels only belong in do/for/while/switch
statements, if at all.
// BAD
happy:
if(true) {
console.log('happy days');
if(condition) {
break happy;
}
}
// ACCEPTABLE, albeit contrived
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}`);
}
}
One Class Per File
Classes deserve their own files (which should be named after them).
// BAD
/* in file 'hurrdy-durr.ts' */
class Foo {
...
}
class Bar {
...
}
// GOOD
/* in file 'foo.ts' */
class Foo {
...
}
/* in file 'bar.ts' */
class Bar {
...
}
Class Member Ordering
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() {
...
}
}
New Parentheses
Use parentheses when invoking a constructor function with new
for consistency with other function calls.
class Foo {
...
}
// BAD
const bad = new Foo;
// you'll have to use parentheses to pass arguments to other
// constructors, so use them all the time for consistency.
// GOOD
const good = new Foo();
No arguments.callee
References
Using arguments.callee
makes various performance optimizations impossible.
// BAD
[1, 2, 3, 4, 5].map(function(n) {
return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
});
// GOOD
const factorial = (n) => {
return !(n > 1) ? 1 : factorial(n - 1) * n;
};
[1, 2, 3, 4, 5].map(factorial);
No Bitwise Operations
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.
No Conditional Assignments
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.
// BAD
let foo;
if(foo = bar) { // either a typo or someone being sneaky (a.k.a. inducing headaches)
...
}
// GOOD
const foo = bar;
if(foo) {
...
}
// OR
...
if(foo === bar) {
...
}
Maximum Two Consecutive Blank Lines
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
No Primitive Type Constructors
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.
// BAD
const condition = new Boolean('false');
if(condition) {
// this will always execute, because 'condition' is
// an object (therefore truthy) regardless of content
...
}
// GOOD
const condition = Boolean('false');
if(condition) {
...
}
No Default Exports
Default exports have a tumultuous history (and present) with transiplation tooling, and naming all exports promotes clarity by disallowing the exporting of anonymous functions.
// BAD
export default function() {
...
};
// GOOD
export const foo = () => {
...
};
No Eval
Arbitrary code execution is a no-no.
Infer Primitive Types
Explicitly declaring types on constants assigned primitives is needless clutter.
// BAD
const foo:boolean = true;
// GOOD
const foo = true;
let bar:number = 0; // since let allows reassignment, type assertion is valid
const bazz = (buzz:boolean = false) => {
// parameters are reassignable, so type assertion is valid (but not required)
...
};
No Property Mutation
Avoiding property mutation makes debugging easier by limiting side effects.
// BAD
const foo = {
bar: 1,
baz: true,
};
foo.bar = 2;
doSomething(foo);
// GOOD
const foo = {
bar: 1,
baz: true,
}
const updatedFoo = {
...foo,
bar: 2,
};
doSomething(updatedFoo);
No Require Imports
You're using a transpiler that understands ES6 import syntax, so use it.
// BAD
const foo = require('foo');
// good
import { foo } from 'foo';
No Trailing Whitespace
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.
No Unused Expressions
Unused expressions are most frequently typos.
BAD
const bar = () => {
...
};
bar; // no-op probably meant to be a function invocation
No Var Keyword
Use let
or const
for greater clarity.
Object Literal Keys In Quotes Only When Necessary
If possible, avoid quotation marks around object literal keys to make them easier to read (less superfluous characters to parse).
// BAD
const foo = {
'bar': true,
};
// GOOD
const foo = {
bar: true,
'fizz-buzz': 3,
};
Use Object Literal Shorthand
Reducing clutter by removing duplication.
const bar = true;
const bazz = false;
// BAD
const foo = {
bar: bar,
bazz: bazz,
};
// GOOD
const foo = {
bar,
bazz,
};
Sort Object Keys
Sorted objects allow readers to visually binary search for keys, and helps prevent merge conflicts.
// BAD
const foo = {
marbles: 5,
'carrot cake': [],
xylophone: true,
boo: 'ahhhhhhh',
};
// GOOD
const foo = {
boo: 'ahhhhhhh',
'carrot cake': [],
marbles: 5,
xylophone: true,
};
Block Formatting
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.
// BAD
if( ... ) { ... }
const foo = () => { return ... };
[...].map((x) => { return ... });
// starting/ending block braces on the same line
// GOOD
if( ... ) {
...
}
const foo = () => {
return ...
};
[...].map((x) => {
return ...
});
// BAD
if( ... ) {
...
}
else // not on the same line as the preceding brace or the following brace
{ ... }
// BAD
if( ... ) {
...
}else{ // no space between braces and 'else'
...
}
// GOOD
if( ... ) {
...
} else {
...
}
// BAD
try {
...
}catch(error){ ... } // starting/ending braces on the same line, no spaces around catch
finally { // not on the same line as the preceding brace
...
}
// GOOD
try {
...
} catch(error) {
...
} finally {
}
One Assignment Per Declaration Prefix
Reduce diff clutter and avoid easy typos by not chaining assignments. Not enforced in for
loops.
// BAD
const a = 5,
b = true,
c = null;
// removing 'c' requires changing the above comma to a semicolon. chaining
// 'd' after 'c' requires updating the semicolon to a comma. both operations
// pollute the diff and risk an easy typo headache.
// GOOD
const a = 5;
const b = true;
const c = null;
// delete any of the above or add another anywhere within/around and
// you'll always have a one line diff with no chance of a comma updating
// typo.
// OK
for(let a = 0, b = false, c; ... ; ...) {
...
}
Only Arrow Functions
(After ESlint migration - this rule changed)
Traditional anonymous functions don't bind lexical scope, so their behavior can be unexpected when referencing this
.
// BAD
const foo = {
bar: function() {
return this; // 'this' depends on the context in which 'bar' is called
}
};
// GOOD
const foo = {
bar: () => {
return this; // 'this' will always be the context in which 'foo' was defined
}
};
Group And Alphabetize Imports
(After ESlint migration - rule changed)
Within groups (delineated by blank lines) imports should be alphabetized, case-insensitive.
// BAD
import { ... } from 'foo';
import { ... } from 'bar';
import { ... } from 'fizz';
import { ... } from 'buzz';
import { ... } from 'aardvark';
// GOOD
import { ... } from 'aardvark';
import { ... } from 'bar';
import { ... } from 'buzz';
import { ... } from 'fizz';
import { ... } from 'foo';
// OR
import { ... } from 'buzz';
import { ... } from 'fizz';
import { ... } from 'aardvark';
import { ... } from 'bar';
import { ... } from 'foo';
Use Const Whenever Possible
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.
// BAD
const half = (value) => {
let divisor = 2; // gives the false impression 'divisor' might change
return value / divisor;
};
// GOOD
const half = (value) => {
const divisor = 2;
return value / divisor;
};
Prefer for(... of ...)
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 = [...];
// BAD
for(let i = 0, item; i < arr.length; i++) {
item = arr[i];
console.log(item);
}
// GOOD
for(let item of arr) {
console.log(item);
}
Single Quotation Marks For Strings - Prettier
Consistency is king, and single quotation marks are less clutter.
Default Switch Cases
Always include a default case for switch statements either first (preferable) or last, not stuck between other cases.
// BAD
switch(foo) {
case bar:
return false;
case fizz:
return true;
}
// GOOD
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.
// BAD
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, }) => {
...
};
// GOOD
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 }) => {
...
}
Triple Equals
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;
// BAD
if(foo == bar) {
...
}
// GOOD
if(Number(foo) === bar) {
...
}
Use isNaN
NaN !== NaN
, so comparing to NaN
doesn't work.
// BAD
if(foo === NaN) {
...
}
// GOOD
if(isNaN(foo)) {
...
}
Breathing Room Is Good - Prettier
Whitespace between operands, separators, and assignment precedents and antecedents promotes legibility.
// BAD
const yes = no||maybe&& 2+6;
const bar = [1,'oh god',3,false];
const foo=true;
// GOOD
const yes = no || maybe && 2 + 6;
const bar = [1, 'oh god', 3, false];
const foo = true;
No Fall-Through On Switch Statements
Reasoning about switch statements making use of fall-through cases that aren't empty is hard, and often a case falling through is accidental.
// BAD
switch(foo) {
case a:
buzz(foo);
case b:
fizz(foo);
}
// what are the implications of buzz on foo given that
// fizz could be receiving it next? why is the author
// making us worry about that? did they even mean for
// that to be possible? do they hate us?
// GOOD
switch(foo) {
case a:
return buzz(foo);
case b:
return fizz(foo);
}
// OK
switch(foo) {
case a:
case b:
return buzz(foo);
// obvious that a and b both trigger the same behavior
case c:
return fizz(foo);
}