Appearance
Docs & Comments
This section defines how we document components, composables, utilities, and modules, and how we track work-in-progress with // TODO: and // FIXME:. Keep it short, actionable, and close to the code.
Principles
- JSDoc is required for public surfaces:
- Public functions/utilities, composables (
use*), stores, services, DTO mappers. - Vue components’ props, emits, and slots.
- Exported types/interfaces (especially shared models).
- Public functions/utilities, composables (
- Write clear code before adding comments: use descriptive names, types, and structure first.
- Explain why, not what. Don’t restate the obvious.
- Write for the intended reader, especially the next maintainer. Explain non-obvious context, constraints, and decisions.
- Treat comments as maintained code: update or remove them when behavior or context changes.
- Co-locate: document directly above the target.
- Update docs with code (same PR/commit).
Style
- Keep comments under 120 chars/line.
- Use complete sentences and terminal punctuation for explanatory prose. Short labels, property descriptions, tag fragments, table cells, and test titles may remain concise fragments without trailing periods.
- Use sentence case; no trailing periods for short labels.
- Bullets in JSDoc when listing.
- No noise like
// increment i. - Link to specs/PRs instead of writing long explanations inline.
JSDoc
Module-level documentation
Add module-level documentation only for a substantial exported module or service with a meaningful boundary, invariant, lifecycle, or integration constraint. Explain its purpose, important constraints, and input/output boundaries when they are not clear from the code.
Do not add a module-level block for a trivial utility, private implementation file, or routine Vue SFC. Keep component, composable, and type-specific contract documentation in their dedicated sections and sibling conventions.
What to write
For TypeScript files
@paramis not mandatory (TS + editor tooling already shows param types).@returnsis not mandatory (TS infers it), unless the intent isn’t obvious and the comment adds value.- Prefer a 1–2 sentence description and examples only when they add clarity.
For JavaScript files
@paramand@returnsare mandatory to preserve type intent.- Keep tags minimal but complete.
Recommended tags (keep it lean)
@param,@returns— required for JS; optional for TS if helpful.@throws— for non-trivial error cases.@example— short and realistic (optional).@deprecated— must explain why and point to an alternative. If there is no alternative, do not use@deprecated— the API is not deprecated yet.@see— must include a valid URL to an article, file (with line refs), or doc that provides context.
Vue SFCs (Props, Emits, Slots)
- Add brief descriptions for props and emits close to their definitions.
- Document defaults (with
@default) only when they are not obvious from the code (e.g., computed or complex expressions). - For shared/complex props, document each field’s meaning (especially in shared models used across modules).
- Document slots using
defineSlotswith JSDoc.
vue
<script setup lang="ts">
/** Product card showing title and price; emits `add` on CTA click. */
// Props — short descriptions for clarity (especially shared models)
const props = defineProps<{
/** Product id (numeric PK). */
id: number
/** Display name. @default "New product" */
title: string
/** Price in cents. */
priceCents: number
}>()
// Emits — name and payload tuple
const emit = defineEmits<{
/** User requested adding the product to cart. */
add: [id: number]
}>()
// Slots — document with defineSlots (inline types allowed here)
const slots = defineSlots<{
/**
* Default slot for main content.
* @param props.highlight Highlighted product info for emphasis in the UI.
*/
default(props: {
highlight: {
/** Product name to highlight. */
name: string
/** Product price to highlight. */
price: number
}
}): void
/**
* List slot to render multiple products.
* @param props.products The products to render.
*/
list(props: {
products: Product[]
}): void
}>()
</script>Note: Inline types are allowed for defineSlots, since there’s no strong benefit in extracting a separate interface just for slot payloads.
Composables (use*)
Document intent and stable return surface. In TS, @param/@returns are optional—prefer a concise description.
ts
/**
* Manage a numeric counter with increment and reset helpers.
* Returns reactive `count`, `inc()`, and `reset()`.
*/
export interface UseCounterResult {
/** Current value. */
count: Ref<number>
/** Increment by 1. */
inc: () => void
/** Reset to 0. */
reset: () => void
}
export function useCounter(): UseCounterResult {
const count = ref(0)
const inc = () => { count.value += 1 }
const reset = () => { count.value = 0 }
return { count, inc, reset }
}Functions & Services
- For TS, prefer a descriptive summary; only add
@param/@returnsif it clarifies behavior beyond types. - For JS, always add
@param/@returns. - Always document thrown errors with
@throws, referencing the specific error type.
TypeScript example
ts
class ProductsUnavailableError extends Error {}
interface FetchProductsArgs {
page?: number
pageSize?: number
search?: string
}
/**
* Fetch products with optional pagination and search.
* @throws ProductsUnavailableError when the inventory service cannot be reached.
* @deprecated Use `useProductsQuery` instead — provides caching + reactivity.
* @see https://github.com/org/repo/blob/main/src/services/products.ts#L40-L78
*/
export async function fetchProducts(
{ page, pageSize = 20, search }: FetchProductsArgs = {}
): Promise<Product[]> {
// throw new ProductsUnavailableError() when the downstream service fails
return []
}JavaScript example
js
/**
* Fetch products with optional pagination and search.
* @param {Object} [args]
* @param {number} [args.page]
* @param {number} [args.pageSize=20]
* @param {string} [args.search]
* @returns {Promise<Array<Product>>}
* @throws {NetworkError} When the request fails.
* @see https://example.com/docs/products-api
*/
async function fetchProducts(args = {}) { /* ... */ }Types & Interfaces (Shared Models)
- For shared models and component props types, add property-level descriptions.
- Keep them brief and focused on semantics.
- Prefer names that make abbreviations and units clear. Document an abbreviation or unit at the declaration only when its meaning is not clear from the name and established conventions. Do not duplicate the naming rules in 01. Naming.
ts
/** Domain product (mapped from ProductResponse). */
export interface Product {
/** Numeric primary key. */
id: number
/** Human-readable name. */
name: string
/** Price in cents (minor units). */
priceCents: number
}Line Comments (//)
Inline comments are welcome when they clarify intent or highlight non-obvious behavior.
- Keep them focused on the tricky line or small block—avoid narrating entire functions.
- Prefer descriptive code first; use comments to explain why something must happen, not what the syntax already shows.
- Remove or update comments when the context changes to prevent drift.
- Pair line comments with
TODO/FIXMEwhen they point to follow-up work. - Use
// NOTE:for important footnotes (context, caveats, links) that don’t block execution but future readers should notice.
ts
function createSession(user: User) {
if (!user.isVerified) {
// Aborts session creation until verification flow runs
throw new Error('User must verify email before logging in')
}
// Defensive copy; caller might mutate the input object elsewhere
return { ...user, sessionId: crypto.randomUUID() }
}
// ❌ Too chatty — the code already says it all
count += 1 // increment countts
if (isWeekend(date)) {
// Explain why the special case exists, not just what happens
return applyWeekendDiscount(total)
}ts
// ❌ Don’t comment entire blocks or restate the obvious
// Loop through products and add the prices
total = 0
for (const product of products) {
total += product.price
}ts
if (!config.featureFlag) {
// TODO(team-platform): remove guard once feature flag is permanent [ref: PLAT-42]
return legacyBehaviour()
}ts
// NOTE: Keep in sync with backend validation rules documented in https://example.com/policy
const emailRegex = /.../TODO / FIXME
Use comments to track intent with owner and due date. Prefer opening an issue and link it.
A non-blocking improvement may be deferred during delivery when the code has a TODO and a real, trackable backlog item. The item must name an owner or team, state the follow-up action, and include sufficient scope and acceptance detail. The code comment must explain the deferred behavior and link the ticket. A comment alone is not project tracking.
ts
// TODO(checkout-team): defer tax recalculation until address validation [ref: CHK-318]
// Follow-up: recalculate tax after validation; accept when totals update before payment.
queueTaxRecalculation(order)Required format
js
// TODO(<owner>|<team>): <actionable message> [ref: <tracker-id|url>]
// FIXME(<owner>|<team>): <critical defect message> [ref: <tracker-id|url>]Examples
js
// TODO(orders-team): replace legacy status map with union [ref: ORD-231]
// FIXME(@alice): prevent double submit on slow network [ref: GH-1423]Rules
TODO: planned improvement, non-blocking.FIXME:correctness/security/perf bug; high priority.- Always include owner and a tracker ref; add due dates when timing matters.
- TODO entries must link to the backlog (Jira, Trello, GitHub/GitLab issues).
- A placeholder ticket is fine—just ensure the follow-up work exists.
- If the comment relates to a staged delivery, link the coordinating PR so reviewers can track progress.
- Use TODOs to track follow-up PRs or staged deliveries so nothing is forgotten.
- Remove the comment when resolved.
TypeScript Error Directives
- Prefer
@ts-expect-errorwhen intentionally asserting a type failure (e.g., type tests). @ts-ignoreis allowed only with an explanatory comment and a link to the tracking issue or PR.- Remove directives once the upstream fix lands or the regression is resolved.
ts
// ✅ Type regression test documents the failing input
// @ts-expect-error — CartLine must include `qty`
validateCart({ id: 'abc' })
// ✅ Rare ignore with justification and tracker link
// @ts-ignore — legacy types do not model this overload yet (ref: GH-2045)
useLegacyApi({ mode: 'compat' })
// ❌ Avoid silent ignores — they hide real errors
// @ts-ignore
processUser(undefined)Test Comments
- Capture why the test exists: regression, bug fix, or specification link.
- Reference tickets or PRs so future maintainers understand the scenario.
- Do not narrate every assertion—the code should show the flow.
ts
// Regression for GH-3102: ensure discount applies when cart has mixed products
describe('applyDiscount', () => {
it('applies category promo to eligible items only', () => {
// Previous bug double-counted electronics when fashion items were present
const total = applyDiscount(buildMixedCart())
expect(total).toEqual(4150)
})
})Do / Don’t
| Do | Don’t |
|---|---|
| Document public APIs, props, emits, and exported types. | Add JSDoc to trivial private helpers. |
| Explain why decisions exist. | Narrate obvious what the code already shows. |
| For JS: use @param/@returns. | Skip tags in JS—types will be unclear. |
| Add owner/due/ref to TODO/FIXME. | Leave anonymous TODOs with no date or context. |
| Keep comments ≤ 120 chars/line. | Write paragraph-long inline comments. |
| For @deprecated, explain the reason + point to alternative. | Use @deprecated without guidance. |
| For @see, always add a valid URL. | Add @see with no context link. |
Use focused line comments/// NOTE: for intent. | Narrate entire blocks or restate obvious code. |
Rationale
- TS-aware IDEs already surface parameter and return types—we avoid redundant tags in TS and keep comments focused on intent.
- JS requires @param/@returns to retain type intent and make APIs usable.
- Property descriptions on shared models/props prevent misuse across modules and speed up onboarding.
- Structured TODO/FIXME (with backlog links) makes tech debt visible, owns accountability, and reduces stale comments.
- @deprecated with guidance helps developers migrate instead of leaving them stranded.
- @see with valid links anchors context and prevents guesswork.
- Focused line comments (and
// NOTE:footnotes) preserve intent without drowning the code in narration.