|
| 1 | +# Comment Format Guidelines |
| 2 | + |
| 3 | +This guideline defines **how to write code comments** so that they are useful for both AI coding assistants and human readers, while remaining mechanically verifiable through lint rules. |
| 4 | + |
| 5 | +It is the concrete expansion of [`code.md`](./code.md) §6 (Comment Standards: "do not explain logic, only document intent, side effects, and exceptional conditions"). |
| 6 | + |
| 7 | +## 1. Two-layer Language Rule |
| 8 | + |
| 9 | +Comments live in two distinct layers: |
| 10 | + |
| 11 | +| Layer | Surface | Language | Rationale | |
| 12 | +|---|---|---|---| |
| 13 | +| **JSDoc `/** ... */`** | Hover, TypeDoc, MCP tool descriptions, IDE IntelliSense | **English (ASCII-only)** | Consumed directly by LLMs and tooling; English maximizes ecosystem compatibility | |
| 14 | +| **Line comments `//`** | Source-only (does not appear in hover/TypeDoc) | **Team's native language allowed** | "Why" reasoning is denser when written in the author's working language | |
| 15 | +| **Log / exception messages** | Runtime, end-user facing | **Team's native language** | Match the operator's language | |
| 16 | +| **Test descriptions (`describe` / `it`)** | CI logs, reporters | **English** | Consistent CI output across teams and CI tools | |
| 17 | + |
| 18 | +**Core invariant**: a JSDoc block must contain only ASCII characters; a `//` line comment may contain any language. |
| 19 | + |
| 20 | +This split is enforced by lint (see §5). It avoids the common failure mode of mixing English summaries and native-language paragraphs inside the same JSDoc block, which destabilizes AI assistants that mimic surrounding comment style. |
| 21 | + |
| 22 | +## 2. Four Templates |
| 23 | + |
| 24 | +### 2.1 File Header |
| 25 | + |
| 26 | +```ts |
| 27 | +/** |
| 28 | + * Module orchestration: dependency resolution, parallel group execution, |
| 29 | + * and cascade-skip on upstream failure. |
| 30 | + */ |
| 31 | +// 責務境界: 拡張の実行順序の決定とエラー伝搬のみを担う。 |
| 32 | +// 個々のロジックは extensions/<name>.ts 側、依存グラフ計算は dependency-resolver.ts 側。 |
| 33 | +``` |
| 34 | + |
| 35 | +* JSDoc: one English sentence describing the module's responsibility. |
| 36 | +* Optional `//` block immediately below: native-language note on responsibility boundaries (what this module does **not** do). |
| 37 | +* Forbidden: argument/return descriptions, step-by-step procedure, scattered TODOs. |
| 38 | + |
| 39 | +### 2.2 Exported Function |
| 40 | + |
| 41 | +```ts |
| 42 | +/** |
| 43 | + * Compute module-wide confidence from data sources and dimension count. |
| 44 | + * |
| 45 | + * @param dataSources - Provenance types contributing to this module. |
| 46 | + * @param dimensionCount - Number of analysis dimensions evaluated. |
| 47 | + * @returns Confidence in `[0, 1]`, rounded to 2 decimals. |
| 48 | + * @throws Never. Returns `0.1` for empty input by design. |
| 49 | + */ |
| 50 | +// なぜ 0.1 を返すか: 呼び出し側で「データ未投入」と |
| 51 | +// 「データはあるが信頼度0」を区別する用途。 |
| 52 | +export function computeModuleConfidence(...) { |
| 53 | + // 重複除去前に length を見ると過大評価になる |
| 54 | + const uniqueSources = [...new Set(dataSources)]; |
| 55 | + ... |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +| Element | Language | Required? | |
| 60 | +|---|---|---| |
| 61 | +| One-line summary | English | required | |
| 62 | +| `@param` description | English | required when type cannot express the unit/range | |
| 63 | +| `@returns` description | English | required (units, range, meaning of `null`) | |
| 64 | +| `@throws` | English | required when function throws; otherwise `Never.` recommended | |
| 65 | +| Pre-function `//` block | Native | optional, only when "why" is non-obvious | |
| 66 | +| Inline `//` | Native | optional, only at counter-intuitive points | |
| 67 | + |
| 68 | +**Avoid the "thin English summary" trap.** A summary that is merely the function name in prose is a §6 violation: |
| 69 | + |
| 70 | +```ts |
| 71 | +// ❌ Logic restatement; equivalent to the function name |
| 72 | +/** Calculate weighted average of dimension scores. */ |
| 73 | +export function weightedAverage(...) { ... } |
| 74 | + |
| 75 | +// ✅ States a contract the type signature cannot |
| 76 | +/** |
| 77 | + * Aggregate dimension scores. Returns `0` for empty input as a sentinel, |
| 78 | + * so callers can distinguish "not yet evaluated" from "evaluated as zero". |
| 79 | + */ |
| 80 | +export function weightedAverage(...) { ... } |
| 81 | +``` |
| 82 | + |
| 83 | +If a function is so trivial that the summary cannot say more than the name, omit JSDoc entirely (lint allows this for non-exported helpers). |
| 84 | + |
| 85 | +### 2.3 Exported Constants and Type Definitions |
| 86 | + |
| 87 | +```ts |
| 88 | +/** Confidence bands per provenance type, expressed as `[min, max]` ranges in `[0, 1]`. */ |
| 89 | +const PROVENANCE_CONFIDENCE: Record<ProvenanceType, { min: number; max: number }> = { ... }; |
| 90 | + |
| 91 | +/** Outcome of a single extension run. `skipReason` is set only when `status === "skipped"`. */ |
| 92 | +export interface ExtensionOutcome { ... } |
| 93 | +``` |
| 94 | + |
| 95 | +* One-line JSDoc, English. |
| 96 | +* Document only contracts the type cannot express (invariants, nullability semantics, units). |
| 97 | +* Do not enumerate values; the literal is right there in code. |
| 98 | + |
| 99 | +### 2.4 Internal (Non-exported) Functions |
| 100 | + |
| 101 | +* JSDoc is **not required**. Lint runs with `publicOnly: true`. |
| 102 | +* If a comment helps, prefer a single `//` line in the team's native language. |
| 103 | +* Reserve a multi-line `//` block above the function only when the function is 5+ lines long **and** the "why" is non-obvious. |
| 104 | + |
| 105 | +## 3. JSDoc Tag Adoption |
| 106 | + |
| 107 | +| Tag | Adoption | Rationale | |
| 108 | +|---|:-:|---| |
| 109 | +| `@param` | required | Capture units / ranges / nullability that types cannot | |
| 110 | +| `@returns` | required | Same | |
| 111 | +| `@throws` | required when throwing; `Never.` recommended otherwise | Make the error contract explicit; cannot be derived from types | |
| 112 | +| `@deprecated` | required when deprecated | Migration target must be named in prose | |
| 113 | +| `@example` | optional | Use sparingly on public utility APIs; avoid where doc-tests don't exist | |
| 114 | +| `@module` | not adopted | A TS file already is a module; redundant | |
| 115 | +| `@remarks` | not adopted | Use a regular paragraph in the description body | |
| 116 | +| `@see` | not adopted | A short `// see also: ...` line below the JSDoc is denser | |
| 117 | +| `@internal` | not adopted | If it is internal, do not `export` it | |
| 118 | +| `@public` / `@beta` | not adopted | Premature unless running semver-disciplined releases | |
| 119 | +| `@description` | not adopted | The body line already serves this purpose | |
| 120 | + |
| 121 | +## 4. Anti-patterns to Detect |
| 122 | + |
| 123 | +These patterns indicate a §6 violation regardless of language. They can be flagged in CI: |
| 124 | + |
| 125 | +| Pattern | Example | Why it fails | |
| 126 | +|---|---|---| |
| 127 | +| Procedure description | `First, validate input. Then, compute. Finally, return.` | Restates code; redundant | |
| 128 | +| Logic restatement | `If userId is empty, return null.` | Visible in the next 3 lines of code | |
| 129 | +| Function-name paraphrase | `Calculate weighted average.` for `weightedAverage()` | Zero new information | |
| 130 | +| Pseudo-spec | `This function takes A and returns B.` | The signature already states this | |
| 131 | +| Stale `// TODO` without owner / ticket | `// TODO: fix this` | Not actionable; track in a ticket system | |
| 132 | + |
| 133 | +The first three patterns can be detected mechanically via regex (`\b(first|then|finally|next),\s/i`, `\bIf .{1,30}, (return|set|use)\b/i`) and Jaccard similarity between the function name and the JSDoc first line. |
| 134 | + |
| 135 | +## 5. Lint Configuration |
| 136 | + |
| 137 | +Use [`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc). |
| 138 | + |
| 139 | +```jsonc |
| 140 | +// eslint.config.js (excerpt) |
| 141 | +{ |
| 142 | + plugins: { jsdoc }, |
| 143 | + rules: { |
| 144 | + "jsdoc/require-jsdoc": ["error", { |
| 145 | + "publicOnly": true, |
| 146 | + "require": { "FunctionDeclaration": true, "ArrowFunctionExpression": false } |
| 147 | + }], |
| 148 | + "jsdoc/require-param": "error", |
| 149 | + "jsdoc/require-param-description": "error", |
| 150 | + "jsdoc/require-returns": "error", |
| 151 | + "jsdoc/require-returns-description": "error", |
| 152 | + "jsdoc/require-throws": "error", |
| 153 | + "jsdoc/check-param-names": "error", |
| 154 | + "jsdoc/check-tag-names": "error", |
| 155 | + "jsdoc/no-types": "error", // TS already provides types |
| 156 | + "jsdoc/tag-lines": ["error", "any", { "startLines": 1 }], |
| 157 | + "jsdoc/require-description": "error", |
| 158 | + |
| 159 | + // Core of the two-layer rule: ASCII-only inside JSDoc blocks |
| 160 | + "jsdoc/match-description": ["error", { |
| 161 | + "matchDescription": "^[\\x00-\\x7F]+$", |
| 162 | + "tags": { |
| 163 | + "param": "^[\\x00-\\x7F]+$", |
| 164 | + "returns": "^[\\x00-\\x7F]+$", |
| 165 | + "throws": "^[\\x00-\\x7F]+$" |
| 166 | + } |
| 167 | + }] |
| 168 | + } |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +`jsdoc/match-description` enforces ASCII-only inside the JSDoc body and the major tags. `//` line comments are outside its scope, so the team's native language remains free for design-intent commentary. |
| 173 | + |
| 174 | +### Rollout (recommended) |
| 175 | + |
| 176 | +1. Day 0: install `eslint-plugin-jsdoc`, set all rules to `warn`, record baseline violation count. |
| 177 | +2. Pilot: migrate `src/utils/` (or your most central module) to the new format manually; refine templates as needed. |
| 178 | +3. Bulk migration: convert remaining exported symbols in dependency order, allowing the LLM to translate JSDoc with the team's domain glossary as context. |
| 179 | +4. Promote `warn` to `error` and add `--max-warnings 0` to CI. |
| 180 | + |
| 181 | +## 6. Where Deeper Design Intent Belongs |
| 182 | + |
| 183 | +When the "why" exceeds what fits in a `//` block (more than ~5 lines, or a decision worth recording for future readers), promote it out of source code: |
| 184 | + |
| 185 | +| Length / nature | Destination | |
| 186 | +|---|---| |
| 187 | +| One-liner explaining a non-obvious choice | `//` line comment above the function | |
| 188 | +| 2 – 5 lines of context | `//` block above the function | |
| 189 | +| A decision with trade-offs and rejected alternatives | An ADR (Architecture Decision Record) in `docs/architecture/decisions/` | |
| 190 | +| Cross-module invariant | A dedicated invariants document | |
| 191 | +| Domain term that needs a canonical translation | A project glossary / domain dictionary | |
| 192 | + |
| 193 | +The exact filenames and locations are project-specific; the rule is that JSDoc and source comments should not become a dumping ground for content that belongs in long-form documentation. |
| 194 | + |
| 195 | +## 7. Why This Format |
| 196 | + |
| 197 | +* **Mechanically verifiable** — the language split is enforced by a single ESLint rule, not by reviewer discipline. |
| 198 | +* **AI-stable** — LLMs that mimic surrounding comment style see only English inside JSDoc blocks, removing a common source of inconsistent generation. |
| 199 | +* **Tool-friendly** — TypeDoc, IDE hover, MCP tool descriptions, and code search all consume the JSDoc surface in their native language (English). |
| 200 | +* **Author-friendly** — design intent can be written in the team's working language, where nuance is densest, in the form most likely to actually be written (`//` next to the code). |
| 201 | +* **§6-aligned** — separating English contract (JSDoc) from native-language reasoning (`//`) makes it harder to write the thin "function-name paraphrase" summaries that violate §6. |
0 commit comments