Code Conventions
Summary
This page covers code style guidelines and conventions.
Auto Styling
We use eslint and prettier to autoformat all Dendron code before a commit. You can see our styling options here
Conventions
The general guideline is to follow Airbnb's Javascript Style Guide. There may be a few differences since we're using Typescript instead of Javascript. Any differences or particular points of emphasis are covered in the below sections.
one export per file
See Airbnb guide 10.6
Prefer to only have one export per file, and to break functionality into different files. Classes should generally always be in their own files.
file name should match the default export exactly
See Airbnb guide 23.6
This file should be called makeStyleGuide.ts
function makeStyleGuide() {
// ...
}
export default makeStyleGuide;
Note: We currently have a lot of utility files that have multiple exports, which means they can't follow this rule. For those files, pick as descriptive of a name as possible, and avoid having utility files with multiple exports in the future.
prefer capital snake case for constants
// good
A_SNAKE = "PYTHON"
// bad
ASNAKE = "python"
prefer using camelCase for variables
// good
const camelCaseIsPreferred = true
// bad
const CamelCaseIsPreferred = false
prefer returning objects instead of tuples
This make it easier to update return signatures and makes it explicit what is being returned
// good
const {fooValue, fooError} = foo()
// bad
const [fooValue, fooError] = foo()
prefer [is, does, has, should] prefixes for boolean usages.
This makes it clear that a given variable is a boolean. And communicates finality of expression.
// good
const isFriendly = isFriendly({person});
// bad
const friendly = friendly({person});
// could be improved.
//
// does NOT read as finalized value since maybeFriendly=true logically
// still reads as 'maybe' while we want to communicate that it is in
// fact friendly.
const maybeFriendly = maybeFriendly({person});
prefer enum over strings
When we have a small set of constants, prefer using enums
// good
enum ReturnStatus {
ok = "ok",
fail = "fail",
}
function foo(): ReturnStatus {
...
}
// bad
function foo(): "ok"|"fail"{
...
}
Typescript
prefer using type
over interface
- types and interfaces are mostly interchangable,
types
are a bit more flexible and so we've standardized on using it - see official typescript docs here
// good
type Foo {
}
// bad
interface Foo {
}
avoid using @ts-ignore
Unless there is a special circumstance, avoid using this statement since it skips typechecking. Alternatives to some common use cases:
- unused variables: prefix the variable with a
_
(eg._foo
) to have typescript ignore it
prefer using async/await
and Promises
over callbacks
Makes code more readable
prefer using object notation for methods ith multiple arguments
This makes code easier to refactor
// good
function foo(opts: {arg1: string, arg2: string}) {
}
// bad
function foo(arg1: string, arg2: string) {
}
prefer undefined
and null
over implicitly incorrect values
Type checking can warn you about a potentially undefined value, but not a value that's just implied to be undefined like ""
or -1
.
//good
function findSomething(...) {
if (...) return 4;
return undefined; // not found!
}
// bad
function findSomething(...) {
if (...) return 4;
return -1; // not found!
}
prefer compile time checks for exhaustive patterns
If you have a switch
or a chain of if ... else if
statements where you check all possible cases, add a static assertion so that if a revision breaks this in the future it will be easily caught.
import { assertUnreachable } from "@dendronhq/common-all";
type MyOptions = "one" | "two";
// bad
function myFunction(var: MyOptions) {
if (var === "one") {/* ... */}
else {/* ... */}
}
// good
function myFunction(var: MyOptions) {
if (var === "one") {/* ... */}
else if (var === "two") {/* ... */}
else assertUnreachable(var);
}
This works with properties within objects (e.g. node.type
) too! One hint is that if the type of the object shows up as never
in the editor, then you can use this assertion.
prefer forEach
when iterating through an array
This is more concise and avoids some unexpected behaviors that can arise from other methods of iteration.
// good
[1,2,3].forEach(elem => console.log(elem))
prefer TS getter and setter syntax
When possible, use getter and setter syntax.
private _foo: string;
// bad
getMyProperty(): string {
return this._foo;
}
setMyProperty(val: string) {
this._foo = val;
}
// good
get myProperty(): string {
return this._foo;
}
set myProperty(val: string) {
this._foo = val;
}
Regular Expressions
Prefer negated sets
\w
matches ASCII alphanumeric characters only, this means any unicode characters will not match.
> "oo".match(/[\w]+/)
[ 'oo', index: 0, input: 'oo', groups: undefined ]
> "öö".match(/[\w]+/)
null
As a result, describing the set of characters that should match is practically impossible if you want unicode characters to match as well. Instead, use a negated set to describe which should not match.
> "öö".match(/[^\s]+/)
[ 'öö', index: 0, input: 'öö', groups: undefined ]
Async operations
If you have many async operations to perform, decide if they need to be done in parallel or in serial.
In parallel
const outputs = await Promise.all(
vaults.map(async () => {
// Do async thing here
})
);
In series
// this creates directories 'one' and 'two'
await asyncLoopOneAtATime<string>(["one", "two"], (ent) => {
return fs.ensureDir(ent);
});
Time
We use luxon
for all time related tasks. This is the successor of moment.js
Children
- Best Practices
- Checklist
- Code Utilities and how to find them
- Config
- Cook
- Dev Env
- Git
- Gotchas
- Monorepo
- React
Backlinks