Skip to main content
New aislop v0.5.0. New CLI, own AST fix engine, stable output, better experience Read more →

You shouldn't be writing comments. And if you do, here's how.

The best comment is the one you didn't need to write. This page covers when a comment earns its place, when it's dead weight, and what aislop removes for you.

When a comment earns its place

Run every comment through this decision tree before you keep it.

  • 1.Does the function or variable name already say this? If yes, delete the comment. Rename the symbol if the name is weak.
  • 2.Is this describing WHAT the code does? Delete it. The code describes what the code does.
  • 3.Is this the WHY? A non-obvious constraint, an external bug workaround, an invariant the compiler can't prove. Keep it. Keep it short.

Good comments answer questions the reader will ask and the code can't answer. Bad comments answer questions nobody was going to ask.

Good vs bad. Five pairs

Each example is real. Every "bad" block is something we removed from our own codebase during aislop 0.5 development.

1. Trivial restatement
before
// Initialize the user service
const userService = new UserService()
after
const userService = new UserService()

The name already says what the comment says. The comment is a tax on every future read.

2. Narrative JSDoc vs meaningful JSDoc
before
/** * Heuristic side-effect detection. Walks an expression subtree * and flags anything that could invoke code when the declaration * initializes. */
export const hasSideEffect = (node: Node): boolean => { ... }
after
/** * @deprecated Use {@link hasObservableEffect}. This predicate * under-counts dynamic property access. */
export const hasSideEffect = (node: Node): boolean => { ... }

The first is a summary of what the function does. The reader could deduce that from the name and body. The second tells the reader something they cannot see.

3. Decorative separator vs file structure
before
// ─── Classification ─────────────────────────────── // Per-engine planning, building the rows from a // discovered project // ──────────────────────────────────────────────────
export const classify = ...
after
// classification.ts
export const classify = ...

If you need to separate sections of a file, it's two files. The filesystem is a much better structural tool than ASCII art.

4. Phase header vs whitespace
before
// Phase 1: Code changes (imports, lint, dependencies)
await runLint() await runDeps()
// Phase 2: Formatting (runs last to clean up after all code changes)
await runFormat()
after
await runLint() await runDeps() await runFormat()

The blank line already says "these are two groups." A reader who needs to know that formatting runs last reads the code top-to-bottom.

5. Cross-reference commentary vs trust
before
// Emit the header up front so it always appears above any progress // output (including the verification spinner, if present). // buildFixRender will then be called with includeHeader: false so // the header isn't duplicated.
printHeader(state)
after
printHeader(state) // ... buildFixRender(state, { includeHeader: false })

The comment describes how two lines of code coordinate. The code itself already shows the coordination. When one line changes the comment rots silently, and nobody updates it.

JSDoc: when and when not

JSDoc earns its place when it carries machine-readable signal that the code cannot. Keep these:

  • + @deprecated. The linter enforces it, editors underline callers.
  • + @see / @link. Typed cross-references that survive renames.
  • + @example. Shown in hover tooltips; especially valuable on external APIs.
  • + @param / @returns. Only when the parameter's semantic meaning isn't obvious from its type.

Delete these:

  • A prose paragraph that summarizes the function body.
  • @param name The name. Restating the parameter name is not documentation.
  • JSDoc on internal-only helpers. If nobody outside the file calls it, it doesn't need a public contract.

What aislop catches automatically

Two rules handle the common cases:

Rule What it flags
ai-slop/trivial-commentComments that restate their adjacent line of code.
ai-slop/narrative-commentDecorative separators, phase headers, multi-line preambles, cross-reference commentary, JSDoc narration.

Run aislop fix and both rules will strip matches in place. The fixer AST-verifies the file still parses before writing, and reverts on any mismatch. See the full rules reference.

We caught ourselves. Here's how.

We wrote aislop. Then, during the 0.5 rehaul, we extended it with new engines, a new fix pipeline, a CLI redesign. In the process, an AI agent (working with one of us) added about seventy narrative and decorative comments to the aislop source: section banners, phase headers, multi-line preambles above function declarations, JSDoc paragraphs that summarized what the function body did.

We noticed. We added the ai-slop/narrative-comment rule. Then we ran our own tool against our own source:

# before two fix passes
319 line comments, 58 JSDoc blocks
# after
256 line comments, 39 JSDoc blocks
net: 82 comments removed

The tool-builder needed the rule. You probably do too.