incl. guidelines for unit-testing & docs
author: nicholaswmin, MIT license
An example structure
// A queue with concurrency limit
class Queue extends Array {
enqueue = (...values) => (this.push(...values), this)
dequeue = () => this.shift()
peek = () => this.at(0)
race = async (fn, { max = 3 } = {}) => {
const exec = async (inactive, inflight = [], finished = []) => {
if (!inactive.length && !inflight.length)
return finished
const dispatch = elem => fn(elem).then(result => ({ result }))
const required = Math.min(max - inflight.length, inactive.length)
const starting = inactive.slice(0, required).map(dispatch)
const tracking = [ ...inflight, ...starting ],
resolved = await Promise.race(tracking)
return exec(
inactive.slice(required),
tracking.filter(p => p !== resolved),
[...finished, resolved.result]
)
}
return new Queue(...await exec([...this]))
}
}Contents:
Code organization:
- Layer functions by purpose
- Separate logic from side effects
- Skip needless intermediates
- Avoid nesting; use early returns
- Separate function steps with blank lines
Naming:
- Name concisely and precisely
- Use short, contextual names
- Drop redundant qualifiers
- Use namespacing for structure
Syntax:
- Avoid needless semis, parens or braces
- Keep short controls inline
- Prefer arrows & implicit return
- Avoid pointless exotic syntax
- Consider iteration over repetition
- Keep lines ≤80; wrap by structure
- Use 70 characters for comments
Paradigms:
Functional:
- Prefer expressions over statements
- Stay immutable where possible
- Avoid (most) imperative loops
- Chain methods over nesting calls
Object-oriented:
Error handling:
Testing:
- Use built-in test runner
- Structure tests hierarchically
- Write focused tests
- Attach fixtures to test context
- Use context for test utilities
- Assert the minimum required
- Test for keywords, not sentences
Documentation:
- Line breaks and spacing
- Write concisely and directly
- Use reference-style links
- Add whitespace between headers and content
Hard-wrap at 80. Width is a ceiling, not a target. Wrap early for structure—even if the line is 79.
- No
elseblocks. Use guards, locals, or nested ternaries. - Operators at EOL. Break after
||,&&,+,-, etc. - Chains vertical. One call per line with a leading dot.
- Ternaries by structure. Split when nested; keep simple inline.
// ✅ guard without else
if (!ready) return
start()
// ❌ fits under 80 but dense
const ok = () => foobarbaz() || foofoofoo() && barbarbar(10) > 5
// ✅ wrap by structure; make precedence explicit
const ok = () =>
foobarbaz() ||
(foofoofoo() &&
barbarbar(10) > 5)
// ❌ nested ternary crammed on one line
const level = u => u.admin ? 'admin' : u.mod ? 'mod' : 'user'
// ✅ split nested ternary
const level = u =>
u.admin
? 'admin'
: u.mod
? 'mod'
: 'user'
// ❌ long chain kept inline because "it fits"
foo().bar().baz().qux() + extra()
// ✅ verticalize chains; operator at end
foo()
.bar()
.baz()
.qux() + extra()ES modules are the JavaScript standard. CommonJS is Node's legacy.
// ✅ ES modules
import { readFile } from 'fs/promises'
import { users, products } from './data.js'
import config from './config.js'
export const process = data => transform(data)
export const validate = input => check(input)
export default { process, validate }
// ❌ CommonJS
const fs = require('fs')
const { users } = require('./data')
module.exports = { process, validate }Every dependency is a liability. Prefer built-ins and writing small utilities.
// ✅ Use built-ins
const unique = [...new Set(items)]
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const pick = (obj, keys) =>
keys.reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {})
// ❌ Don't install a package for one-liners
import uniq from 'lodash.uniq' // 4.4KB for [...new Set()]
import delay from 'delay' // 2.8KB for Promise + setTimeoutHowever: avoid DIY in production for domains that are:
- Too complex (game engines, CRDTs, date/time math)
- Too critical (cryptography, validation)
- Utility: Generic non-domain helpers
- Domain: Domain-specific helpers
- Orchestration: Usually the
mainof the module, program etc
// ✅ Utility functions
const gzip = file => compress(file, 'gzip', { lvl: 1 })
const type = file => ext => file.filename.includes(ext)
// ✅ Domain functions
const upload = async (file, n = 0) => {
const result = await s3.upload(file)
return result.error
? n <= 3
? upload(file, ++n)
: ignore(file)
: result
}
// ✅ Orchestration functions
const synchronize = async files => {
const timedout = delay(30000)
const eligible = files.filter(type('png')).map(gzip)
const uploaded = await Promise.all(eligible.map(upload))
const response = await Promise.race([uploaded, timedout])
return response === timedout
? ontimeout(eligible)
: normalize(response)
}- Prefer pure functions over side effects
- Move side effects to boundaries
- Extract dependencies for easier mocking
// ✅ Testable - pure function
const discount = (price, pct) => price * (pct / 100)
// ✅ Testable - dependency injection
const notify = (user, emailer) => emailer.send(user.email, 'Welcome!')
// ❌ Hard to test - side effects
const applyDiscount = (price, percentage) => {
const discount = price * (percentage / 100)
updateDatabase(discount)
sendEmail(discount)
return discount
}
// ❌ Hard to test - hard-coded dependency
const notify = (user) => EmailService.send(user.email, 'Welcome!')Extract dependencies when the function:
- Reaches outside your process space, e.g: network, filesystem
- Depends on uncontrollable factors (timers, accelerometers)
- Is slow or generally awkward to test
- Needs different implementations in different contexts
However, every indirection adds complexity. Extract only when the benefit outweighs the cost.
// ✅ Simple operations don't need extraction
const formatName = (first, last) => `${first} ${last}`
// ❌ Needless indirection
const formatName = (first, last, formatter) =>
formatter.format(first, last)Chain or inline unless the intermediate clarifies complex logic.
// ✅ No intermediates needed
const promote = users =>
users.map(review).filter(passed)
// ❌ Needless intermediates
const promote = users => {
const reviewed = users.map(review)
const eligible = reviewed.filter(passed)
return eligible
}However, some functions naturally orchestrate others. Don't mangle business logic for brevity—but don't make it intentionally verbose either.
Early returns flatten code and eliminate edge cases upfront. Each guard clause removes a level of indentation, making the happy path obvious.
- Prefer guard returns; avoid
elseblocks where possible.
// ✅ Early return
onUpdate(fence) {
if (this.insulated)
return
if (!this.intersects(fence))
return this.electrocute()
// ... fancy sync logic
}
// ❌ Nested conditions
onUpdate(fence) {
if (!this.insulated) {
if (this.intersects(fence)) {
// ... fancy sync logic
} else {
this.electrocute()
}
}
}However, don't use them defensively.
Fix the caller instead of guarding against bad inputs.
Group guards, declarations, and logic into distinct visual blocks.
One blank line between groups makes structure scannable.
// ✅ Good: clear visual grouping
const processUser = user => {
if (user.id === 10) return null
const retries = 3
const timeout = 5000
for (const email of getUserEmails(user))
sendNotification(email)
if (isActive(user))
updateLastSeen(user)
return fetchProfile(user)
}// ❌ Bad: no visual separation
const processUser = user => {
if (user.id === 10) return null
const retries = 3
const timeout = 5000
for (const email of getUserEmails(user))
sendNotification(email)
if (isActive(user))
updateLastSeen(user)
return fetchProfile(user)
}Pattern:
guards → declarations → logic → return, each separated by blank line.
However, avoid blank lines to preserve intermediates; prefer chaining:
// ✅ Functional: no intermediates needed
const promote = users =>
users
.map(review)
.filter(passed)
// ❌ Avoid forcing intermediates just for spacing
const promote = users => {
const reviewed = users.map(review)
const eligible = reviewed.filter(passed)
return eligible
}Name by role, not by type.
Choose single words when precise alternatives exist, however...
// ✅ Good
users
active
name
overdue // more precise than isPaid
// ❌ Avoid
userList
isUserActive
firstName
lastName
isPaid // when overdue is clearerAssume the surroundings provide the context.
// ✅ Context eliminates redundancy
const user = { name, email, age }
const { data, status } = response// ❌ Redundant qualifiers
const user = { userName, userEmail, userAge }
const { responseData, responseStatus } = response- Strongly prefer domain-relevant names; if none exist, don't force jargon—pick the clearest generic name.
- Avoid single-letter params; use domain nouns; shorten with scope.
- Name by intent; drop type/structure noise.
- Exit condition: if a domain term would be obscure or disputed, use a clear generic name now; rename when a stable term emerges.
// ❌ negated passive
invoice.hasNotBeenPaid
// ✅ domain term
invoice.overdue// ❌ forced domain jargon in a generic util
const chunk = (ledgerEntries, size) => { /* ... */ }
// ✅ clear generic when no domain term fits
const chunk = (items, size) => { /* ... */ }// ❌ single-letter params, vague predicate
const canEdit = (u, r) =>
u.role === 'admin' || u.id === r.ownerId
// ✅ domain nouns, adjective predicate
const editable = (user, resource) =>
user.role === 'admin' || user.id === resource.ownerId// ❌ type/structure noise
const userArray = await repo.list()
// ✅ intent + context
const users = await repo.list()Group related properties under logical objects. Avoid meaningless wrappers or technical groupings.
// ✅ Appropriate namespacing
const user = {
name: { first, last, full },
contact: { email, phone },
status: { active, verified }
}
const config = {
server: { port, host },
database: { url, timeout }
}
// ❌ Inappropriate namespacing
const user = {
data: { value: 'John' }, // Meaningless wrapper
info: { type: 'admin' }, // Generic grouping
props: { id: 123 } // Technical grouping
}Minimal syntax, maximum signal.
// ✅ Minimal syntax
const name = 'Alice'
const double = x => x * 2
const greet = name => `Hello ${name}`
users.map(user => user.name)
if (subscribers.length)
throw new Error('Session is subscribed')
const user = { name, email, age }
// ❌ Unnecessary syntax
const name = 'Alice';
const double = x => { return x * 2; }
users.map((user) => user.name);
const user = { 'name': name, 'email': email };Brief control structures stay readable on one line; longer ones break to preserve scannability and enable line-by-line review.
// ✅ Good: under ~40 chars, keep inline
if (authenticated) redirect('/dashboard')
if (!user) return
// Ternaries when simple
const price = member ? applyDiscount(base) : base
// Over ~40 chars: break to next line
if (authenticated && verified && !suspended)
redirect('/dashboard')
for (const item of cart.items)
calculateTotal(item.price, item.quantity, tax)// ❌ Bad: long conditions crammed on one line
if (authenticated && verified && !suspended) redirect('/dashboard')
for (const item of cart.items) calculateTotal(item.price, item.quantity, tax)
// Simple enough for one line but unnecessarily broken
if (ready)
start()However:
- Never inline
elseblocks (use early returns instead) - Break nested structures regardless of length (flatten with guards)
- Break
try/catch/finallyfor clarity - Break when total line exceeds 80 characters
Skip braces and return for single expressions.
// ✅ Implicit return
const double = x => x * 2
const getName = user => user.name
const sum = (a, b) => a + b
const makeUser = (name, age) => ({ name, age })
// ❌ Explicit return for single expressions
const double = x => { return x * 2 }
const getName = user => { return user.name }
const makeUser = (name, age) => { return { name, age } }Choose clarity over cleverness. Use language features when they improve readability, not just because they exist.
// ✅ Clear intent
const isEven = n => n % 2 === 0
// ❌ Clever but unclear
const isEven = n => !(n & 1)Two items? Maybe copy-paste. Three or more? Use a loop for easier extension.
// ✅ Dynamic generation
return ['get', 'put', 'post', 'patch']
.reduce((acc, method) => ({ ...acc, [method]: send }), {})
// ❌ Manual enumeration
return { get: send, put: send, post: send, patch: send }Avoid overuse, especially in tests; loops need mental parsing.
// ❌ Overabstracted test
['admin', 'user', 'guest'].forEach(role => {
test(`${role} access`, t => {
const result = checkAccess(role)
t.assert.strictEqual(result, role === 'admin')
})
})
// ✅ Clear, explicit tests
test('admin has access', t => {
t.assert.strictEqual(checkAccess('admin'), true)
})
test('user lacks access', t => {
t.assert.strictEqual(checkAccess('user'), false)
})
test('guest lacks access', t => {
t.assert.strictEqual(checkAccess('guest'), false)
})- Strongly prefer wrapping <60.
- Break by structure even under 80; add hard line breaks at natural boundaries (operators, delimiters, chain links); keep grouping obvious; don't collapse.
// ❌ dense, mixed precedence
const ok = () => a() || b() && c(10) > 5
// ✅ wrap by structure; keep group parens; hard breaks
const ok = () =>
a() ||
(b() &&
c(10) > 5)// ❌ long chain kept inline because "it fits"
api().get().map().filter().reduce() + extra()
// ✅ one call per line; operator at end; hard breaks per link
api()
.get()
.map()
.filter()
.reduce() + extra()Comments are a maintenance burden. Reserve them for explaining "why", not "what".
// ✅ Explaining unusual approach
// Polling needed due to API limitations
while (!response.complete)
await request(query, { delay: 200 })
// ❌ Obvious comments
// sum prices
const total = prices.reduce((sum, price) => sum + price, 0)When converting data, think pipelines not procedures.
// ✅ Functional for transformations
const users = data
.filter(active)
.map(normalize)
.sort(by.name)If it has identity and changes over time (User, Game, Session), make it a class.
// ✅ OOP for entities
class User {
constructor(name, role) {
this.name = name
this.role = role
}
evaluate = () => this.score > 80
promote = () => {
this.role = 'senior'
return this
}
}Expressions return values and compose.
// ✅ Good: ternaries over if
const upload = async (file, n = 0) => {
const result = await s3.upload(file)
return result.err
? n <= 3
? upload(file, ++n)
: ignore(file)
: result
}
// ✅ Good: object lookup over switch/case
const handle = action => ({
create: () => save(data),
update: () => modify(data),
delete: () => remove(data)
})[action]?.() || cancel()// ❌ Avoid
switch (action) {
case 'create':
return save(data)
case 'update':
return modify(data)
case 'delete':
return remove(data)
default:
return defaultAction()
}Use a concise arrow only when the body is a single clear expression (≈≤60 chars); otherwise keep the grouped steps.
// from above: paginate
const paginate = (xs, page = 1, size = 25) =>
!Array.isArray(xs) || page < 1 || size < 1
? []
: xs.slice((page - 1) * size, page * size)[...arr, item] not arr.push(item).
New values are easier to debug than mutations.
// ✅ Immutable
const add = (users, user) => [...users, user]
const update = (user, data) => ({ ...user, ...data })
// ❌ Mutating
const add = (users, user) => users.push(user)
const update = (user, data) => Object.assign(user, data)prices.filter(discounted) tells what's happening.
A for loop makes you figure it out.
// ✅ Functional approach
const active = users.filter(user => user.active)
const names = users.map(user => user.name)
const total = prices.reduce((sum, price) => sum + price, 0)
// ✅ Sequential async operations
for (const file of files)
await fetch(file)
// ❌ Imperative loops
const active = []
for (let i = 0; i < users.length; i++) {
if (users[i].active) {
active.push(users[i])
}
}Inside-out is hard. Top-to-bottom is natural.
// ✅ Multi-line chaining
const process = data =>
data
.filter(x => x > 0)
.map(x => x * 2)
.reduce((a, b) => a + b)
// ❌ Nested function calls
const process = data =>
reduce(map(filter(data, x => x > 0), x => x * 2), (a, b) => a + b)animation.fadeIn().wait(300).slideOut()
Each method returns the object for the next.
// ✅ Method chaining
class Calculator {
constructor(val = 0) { this.val = val }
add = n => new Calculator(this.val + n)
mul = n => new Calculator(this.val * n)
get = () => this.val
}
// Usage: new Calculator(5).add(3).mul(2).get() // 16
// ✅ Builder pattern for async workflows
const query = select('name', 'email')
.from('users')
.where('active', true)
.where('role', 'admin')
.orderBy('name')
.limit(10)
// ✅ Domain-specific naming
const animation = animate(element)
.to({ opacity: 0, x: 100 })
.duration(300)
.easing('ease-out')
.then(() => element.remove())Trust your code; if you can't, write tests. Definitely don't litter your code with guard clauses.
// ✅ Trust internal functions
const process = data =>
data
.filter(active)
.map(normalize)
// ❌ Defensive programming within internal functions
const process = data => {
if (!data || !Array.isArray(data)) return []
return data
.filter(item => item && active(item))
.map(item => item ? normalize(item) : null)
.filter(Boolean)
}
// ❌ Defensive early returns
const formatName = (first, last) => {
if (!first) return '' // Why is caller passing empty names?
if (!last) return '' // Fix the caller instead
return `${first} ${last}`
}
// ✅ Trust the caller
const formatName = (first, last) => `${first} ${last}`Exception:
External data must be validated.
Be specific:
- Wrong type?
TypeError - Out of bounds?
RangeError - Everything else?
Errorwith a clear message
// ✅ Specific error types
if (typeof value !== 'number')
throw new TypeError(`Expected number, got ${typeof value}`)
if (value < 0 || value > 100)
throw new RangeError('Value must be between 0 and 100')
if (!process.env.API_KEY)
throw new Error('API_KEY missing')
// ❌ String errors
throw 'Invalid value'
// ❌ Generic errors
throw new Error('Invalid input')Users add spaces. Environment variables arrive as strings. Clean everything at the borders.
// ✅ Environment variables
const port = parseInt(process.env.PORT || '3000', 10)
const env = (process.env.NODE_ENV || 'development').toLowerCase()
// ✅ Booleans
const enabled = /^(true|1|yes|on)$/i.test(process.env.ENABLED || '')
// ✅ Arrays from CSV
const hosts = (process.env.HOSTS || '')
.split(',')
.map(s => s.trim())
.filter(Boolean)
// ✅ User input
const email = (input || '').trim().toLowerCase()
// ❌ No normalization
const port = process.env.PORT // Could be undefined/string
const enabled = process.env.ENABLED // String, not booleanValidation:
// ✅ Validate at boundaries
const handleRequest = req => {
if (!req.body?.userId)
throw new Error('userId required')
if (typeof req.body.userId !== 'string')
throw new TypeError('userId must be string')
return processUser(req.body.userId)
}
// ✅ Early validation
const createUser = data => {
if (!data.name) throw new Error('Name required')
if (!data.email) throw new Error('Email required')
// Trust internal processing
return save(normalize(data))
}
// ❌ Late validation
const processUser = data => {
const result = expensiveOperation(data)
if (!data.name) throw new Error('Invalid data') // Too late
return result
}Since Node v19: node --test.
Zero dependencies.
Node v24+ automatically awaits subtests, eliminating a major source of flaky tests.
// ✅ Built-in test runner
// Run with: node --test
import { test } from 'node:test'
// ❌ External dependencies
import { describe, it } from 'jest'
import { expect } from 'chai'Tests tell a story: component → scenario → expectation. Nest them that way.
Write for non-technical readers—including future you. You won't remember the context when it breaks.
// ✅ Hierarchical structure
test('#withdraw', t => {
t.test('amount within balance', t => {
t.test('disperses requested amount', async t => {
// assertion with await
})
})
t.test('amount exceeds balance', t => {
t.test('throws appropriate error', async t => {
// assertion with await
})
})
})
// ❌ Flat structure
test('withdraw disperses amount when within balance', t => {})
test('withdraw throws when amount exceeds balance', t => {})One test, one assertion, one failure reason.
// ✅ Granular tests
test('#withdraw', t => {
t.test('within balance', t => {
t.test('returns updated balance', async t => {
t.assert.strictEqual(await t.atm.withdraw(30), 70)
})
})
})
// ❌ Multiple assertions, redundant titles
test('ATM withdrawal when amount is within available balance should disperse the requested amount and update balance', async t => {
const initial = t.atm.balance
const result = await t.atm.withdraw(30)
t.assert.strictEqual(result, 70)
t.assert.strictEqual(t.atm.balance, 70)
t.assert.strictEqual(initial - 30, t.atm.balance)
t.assert.ok(t.atm.lastTransaction)
})Avoid global shared variables for passing data between tests. Attach shared test data directly to the context using:
t.beforeEach(t => tr.atm = new ATM(100)).
// ✅ Context fixtures
test('#withdraw', t => {
t.beforeEach(t => t.atm = new ATM(100))
t.test('disperses amount', async t => {
await t.atm.withdraw(30) // t.atm available
})
})
// ❌ External fixtures
let atm // Shared global
beforeEach(() => {
atm = new ATM(100)
})
test('disperses amount', async t => {
await atm.withdraw(30)
})Everything's on t: t.assert, t.mock, t.beforeEach.
t.mock resets automatically between tests.
// ✅ Context utilities
test('#notify', t => {
t.test('sends email', t => {
const emailer = t.mock.fn()
notify(user, { send: emailer })
t.assert.strictEqual(emailer.mock.calls.length, 1)
})
})
// ❌ Imported utilities
import { assert, mock } from 'node:test'
test('sends email', t => {
const emailer = mock.fn()
// ...
assert.strictEqual(emailer.mock.calls.length, 1)
})Don't test what you don't care about,
especially if the object is expected to be extended.
// ✅ Partial assertions
test('#user properties', t => {
t.test('has correct details', t => {
t.assert.partialDeepStrictEqual(t.user, {
name: 'Alice', role: 'admin'
})
})
})
// ❌ Full object matching
test('has correct details', t => {
t.assert.deepStrictEqual(t.user, {
id: '123',
name: 'Alice',
role: 'admin',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
// ... all properties
})
})Match keywords that survive rephrasing: "Insufficient balance" → "You have insufficient funds"
However: pick keywords unique to the test:
- ✓
/insufficient/- specific to this error - ✗
/balance/or/invalid/- too generic
Otherwise your test becomes junk.
// ✅ Flexible matching
test('#withdraw', t => {
t.test('exceeds balance', async t => {
await t.assert.rejects(() => t.atm.withdraw(150), {
name: 'Error', message: /insufficient/i
})
})
})
// ❌ Exact string matching
await t.assert.rejects(() => t.atm.withdraw(150), {
name: 'Error',
message: 'Insufficient funds: cannot withdraw 150 from balance of 100'
})Complete test example:
test('#atm', t => {
t.beforeEach(t => t.atm = new ATM(100))
t.test('#withdraw', t => {
t.test('within balance', t => {
t.test('returns updated balance', async t => {
t.assert.strictEqual(await t.atm.withdraw(30), 70)
})
})
t.test('exceeds balance', t => {
t.test('throws error', async t => {
await t.assert.rejects(() => t.atm.withdraw(150), {
name: 'Error', message: /insufficient/i
})
})
t.test('preserves balance', async t => {
await t.assert.rejects(() => t.atm.withdraw(150))
t.assert.strictEqual(t.atm.balance, 100)
})
})
})
t.test('#properties', t => {
t.test('has location and currency', t => {
t.assert.partialDeepStrictEqual(t.atm, {
location: 'Main Street', currency: 'USD'
})
})
})
})- Headers: 3-5 words max
- 70-85 chars per line, break at 50+ to newline
- AVOID WRAPPING - start new line instead of wrapping long text
- One empty line before and after all headers and bold: labels
- 2-space hard line breaks where applicable
// ✅ Proper breaks and spacing
Configure the database.
Set DB_URL in your environment.
**Required:**
- Node 18+
- PostgreSQL
```js
const db = connect(url)
```
Next section starts here.
// ❌ Poor formatting
Configure the database connection by setting the DB_URL
environment variable in your shell or configuration file.
**Required:**
- Node 18+
```js
const db = connect(url)
```
Next section starts here.Active voice. Matter-of-fact tone. Under 20 words. Lists over paragraphs. No emojis. Reference-style links.
// ✅ Direct, scannable, technical
The server validates requests.
Requirements:
- Node 18+
- 2GB RAM
Processes 1000 requests/second.
See [node-docs] for details.
[node-docs]: https://nodejs.org/api
// ❌ Passive, verbose, marketing
Requests are validated by the server infrastructure 🚀
In order to run this powerful enterprise solution, you'll
need Node.js version 18 or higher and at least 2GB of RAM.
Lightning-fast performance at enterprise scale.Link to authoritative sources. Use a short, consistent, non-numeric format:
// ✅ Descriptive, consistent references
See the [node-docs] for details.
Based on [rfc-7231].
[node-docs]: https://nodejs.org/api/documentation.html
[node-util]: https://nodejs.org/api/util.html
[rfc-7231]: https://tools.ietf.org/html/rfc7231
// ❌ Numeric or inline URLs
See the [docs][1] for details.
See the [docs](https://nodejs.org/api/documentation.html).
[1]: https://nodejs.org/api/documentation.html// ✅ Linebreak before and after header
Lorem Ipsum Dolor sit amet,
Lorem Ipsum Dolor, lorem Ipsum Dolor sit amet..
### Overview
Lorem Ipsum Dolor sit amet. Lorem Ipsum Dolor,
lorem Ipsum Dolor sit amet...
**lorem:**
Lorem Ipsum Dolor sit amet. Lorem Ipsum Dolor,
lorem Ipsum Dolor sit amet...
// ❌ content immediately follows a header/label.
Lorem Ipsum Dolor sit amet,
Lorem Ipsum Dolor, lorem Ipsum Dolor sit amet..
### Overview.
Lorem Ipsum Dolor sit amet. Lorem Ipsum Dolor,
lorem Ipsum Dolor sit amet...
**lorem:**.
Lorem Ipsum Dolor sit amet. Lorem Ipsum Dolor,
lorem Ipsum Dolor sit amet...author: nicholaswmin, MIT