iOffice TypeScript Style Guide
Disclaimer: This guide is inspired by the Airbnb JavaScript Style Guide.
Most sections we see here will be taken straight from their guide and slowly adapted to the typescript language.
Table of Contents
- Types
- Primitives
- Complex
- References
- Prefer Const
- Disallow Var
- Functions
- Unused Parameters
- Classes
- Arrow Functions
- Use Them
- Blocks
- Braces
- Cuddled Elses
- No Else Return
- Whitespace
- Spaces
- In Braces
- Commas
- Leading Commas
- Trailing Commas
- Semicolons
- Required
- Modules
- Use Them
- Single Export
Types
-
1.1 Primitives: When you access a primitive type you work directly on its value.
number
string
boolean
null
undefined
These types can be inferred by the typescript compiler and should not explicitly typed.
Why? Explicit types where they can be easily inferred by the compiler make code more verbose.
const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar);
const foo: number = 1;
let bar: number = foo;
bar = 9;
console.log(foo, bar);
-
1.2 Complex: When you access a complex type you work on a reference to its value.
const foo: number[] = [1, 2];
const bar: number[] = foo;
bar[0] = 9;
console.log(foo[0], bar[0]);
⬆ back to top
References
-
2.1 Prefer Const: Use const
for all of your references; avoid using var
.
Why? This ensures that you can’t reassign your references, which can lead to bugs and difficult
to comprehend code.
var a = 1;
var b = 2;
const a = 1;
const b = 2;
function printPI() {
let pi = 3.14;
console.log(pi);
}
function printPI() {
const pi = 3.14;
console.log(pi);
}
-
2.2 Disallow Var: If you must reassign references, use let
instead of var
/
Why? let
is block-scoped rather than function-scoped like var
.
var count = 1;
if (true) {
count += 1;
}
let count = 1;
if (true) {
count += 1;
}
⬆ back to top
Functions
-
3.1 Unused Parameters: Remove them. To prevent them make sure to use noUnusedParameters
in your
tsconfig.json
file.
We may end up with parameters that are not used when we refactor. If we keep them we risk
having incorrect documentation and all sort of confusions.
In some cases, when creating listeners a function may require a certain signature which will
undoubtedly bring us unused parameters. When this is the case simply name the placeholder
variables with a leading underscore.
function foo(a, b, c) {
return a + b;
}
function foo(a, b) {
return a + b;
}
⬆ back to top
Classes
⬆ back to top
Arrow Functions
-
5.1 Use Them: When you must use function expressions (as when passing an anonymous function), use arrow
function notation.
Why? It creates a version of the function that executes in the context of this, which is usually
what you want, and is a more concise syntax.
Why not? If you have a fairly complicated function, you might move that logic out into its own
function declaration.
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
[0, null, 1, null, 2].filter(x => x !== null);
⬆ back to top
Blocks
-
6.1 Braces: Use braces with all multi-line blocks.
if (test)
return false;
if (test) return false;
if (test) {
return false;
}
function foo() { return false; }
function bar() {
return false;
}
-
6.2 Cuddled Elses: If you're using multi-line blocks with if
and else
, put else
on the same line as
your if
block's closing brace.
if (test) {
thing1();
thing2();
}
else {
thing3();
}
if (test) {
thing1();
thing2();
} else {
thing3();
}
-
6.3 No Else Return: If an if
block always executes a return
statement, the subsequent else
block is
unnecessary. A return
in an else if
block following an if
block that contains a
return
can be separated into multiple if blocks.
function foo() {
if (x) {
return x;
} else {
return y;
}
}
function foo() {
if (x) {
return x;
}
return y;
}
function cats() {
if (x) {
return x;
} else if (y) {
return y;
}
}
function cats() {
if (x) {
return x;
}
if (y) {
return y;
}
}
function dogs() {
if (x) {
return x;
} else {
if (y) {
return y;
}
}
}
function dogs() {
if (x) {
if (z) {
return y;
}
} else {
return z;
}
}
⬆ back to top
Whitespace
⬆ back to top
Commas
-
8.1 Leading Commas: Nope.
const story = [
once
, upon
, aTime
];
const story = [
once,
upon,
aTime,
];
const hero = {
firstName: 'Ada'
, lastName: 'Lovelace'
, birthYear: 1815
, superPower: 'computers'
};
const hero = {
firstName: 'Ada',
lastName: 'Lovelace',
birthYear: 1815,
superPower: 'computers',
};
-
8.2 Trailing Commas: Additional trailing comma: Yup.
Why? This leads to cleaner git diffs. Also, the Typescript transpiler will remove the additional
trailing comma in the transpiled code which means you don’t have to worry about the trailing
comma problem in legacy browsers.
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
const heroes = [
'Batman',
'Superman'
];
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
const heroes = [
'Batman',
'Superman',
];
function createHero(
firstName,
lastName,
inventorOf
) {
}
function createHero(
firstName,
lastName,
inventorOf,
) {
}
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
}
createHero(
firstName,
lastName,
inventorOf
);
createHero(
firstName,
lastName,
inventorOf,
);
createHero(
firstName,
lastName,
inventorOf,
...heroArgs
);
createHero(firstName, lastName, inventorOf);
createHero(firstName, lastName, inventorOf, );
⬆ back to top
Semicolons
-
9.1 Required: Yup.
Why? When JavaScript encounters a line break without a semicolon, it uses a set of rules
called Automatic Semicolon Insertion
to determine whether or not it should regard that line break as the end of a statement, and
(as the name implies) place a semicolon into your code before the line break if it thinks so.
ASI contains a few eccentric behaviors, though, and your code will break if JavaScript
misinterprets your line break. These rules will become more complicated as new features become
a part of JavaScript. Explicitly terminating your statements and configuring your linter to
catch missing semicolons will help prevent you from encountering issues.
const luke = {}
const leia = {}
[luke, leia].forEach(jedi => jedi.father = 'vader')
const reaction = "No! That's impossible!"
(async function meanwhileOnTheFalcon() {
}())
function foo() {
return
'search your feelings, you know it to be foo'
}
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
jedi.father = 'vader';
});
const reaction = "No! That's impossible!";
(async function meanwhileOnTheFalcon() {
}());
function foo() {
return 'search your feelings, you know it to be foo';
}
⬆ back to top
Modules
-
10.1 Use Them: Always use modules (import
/export
) over a non-standard module system. You can always
transpile to your preferred module system.
Why? Modules are the future
const iOfficeStyleGuide = require('./iOfficeStyleGuide');
module.exports = iOfficeStyleGuide.ts;
import * as iOfficeStyleGuide from './iOfficeStyleGuide';
const ts = iOfficeStyleGuide.ts;
export {
ts,
};
import { ts } from './iOfficeStyleGuide';
export {
ts,
};
-
10.2 Single Export: Do not use default exports. Use a single named export
which declares all the classes,
functions, objects and interfaces that the module is exporting.
Why? Named imports
/exports
promote clarity. In addition, current tooling differs on the
correct way to handle default imports
/exports
. Avoiding them all together can help
avoid tooling bugs and conflicts.
Using a single named export
allows us to see in one place all the objects that we are
exporting.
export class A {}
export class B {}
export default A;
class C {}
class D {}
export {
C,
D,
};
export default function() {
}
function A() {
}
export { A };
function A() {
}
export { A };
⬆ back to top
License
(The MIT License)
Copyright (c) 2018 iOFFICE
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the 'Software'), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Amendments
Following Airbnb's advice, we also encourage
you to fork this guide and change the rules to fit your team's style guide.
The code provided should make it easy to make adjustments to the examples since
they are linted with the tslint configuration. If you do not agree with part of the configuration
simply change it, test the guide and make the appropiate changes to it.