npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

comment-fmt

v0.1.11

Published

Reformats comments in JS/TS, CSS/SCSS, HTML, and Astro to a fixed width with a consistent block shape.

Readme

comment-fmt

npm version CI

Wraps over-width comments to a fixed line length and normalizes block-comment shape, for JS/TS/JSX/TSX, CSS/SCSS, HTML, and Astro. Runs from a pre-commit hook, next to whatever formatter you already use.

npm install --save-dev comment-fmt

Requires Node 20+ or Bun. Ships as ESM only, zero runtime dependencies.

Before / after

-/** Retries a flaky network call up to `maxAttempts` times, doubling the delay between each attempt before giving up. */
+/**
+ * Retries a flaky network call up to `maxAttempts` times, doubling the delay
+ * between each attempt before giving up.
+ */
 export function withRetry(fn: () => Promise<void>, maxAttempts = 3) {

Everything else about the comment is untouched. Wording, directive comments, hand-aligned tables, ASCII diagrams — none of it is comment-fmt's business. It only ever acts on a comment that's already over the width limit.

Why this exists

A human writing a comment by hand tends to eyeball the column and self-wrap as they go. Generated code often doesn't — nothing in an LLM's token-by-token output is tracking where the print width falls, so comments routinely blow past it or wrap unevenly. comment-fmt turns that into a deterministic fix instead of a recurring note in review.

It isn't only for generated code. Anyone who wants a hard width limit enforced without hand-wrapping gets the same benefit, regardless of who wrote the comment.

What it does

  • Width. No comment line exceeds maxLength (110 by default). A line that's too long gets wrapped.
  • Block shape. A multi-line block comment never carries content on its opening (/**, /*, <!--) or closing (*/, -->) line. One-way ratchet: a comment collapses to a single line if it fits, expands to a starred (JS/CSS) or plain-indented (HTML/Astro) block if it doesn't, and once expanded it stays expanded even if a later edit shortens the content.
  • Directive-aware. eslint-disable, @ts-expect-error, prettier-ignore, biome-ignore, and the rest of the usual tool directives are left alone even when they overflow, so wrapping one can never silently change what it disables.
  • Structure-aware. Hand-aligned tables, ASCII/box-drawing diagrams, and fenced code blocks inside JSDoc are detected and passed through untouched.
  • Multi-language. JS, TS, JSX, TSX, CSS, SCSS, HTML, and Astro (frontmatter and template both) in one tool.

Why it's safe on hand-formatted content

The tool never inspects a comment that already fits. That single gate does most of the safety work: nearly every hand-aligned table, box diagram, or deliberate line break in a real codebase already sits under the width limit, so it's never touched in the first place. The table/diagram heuristics only have to catch the rare over-width case, a much smaller and more forgiving problem than trying to correctly classify every structure a human might invent.

A trailing // comment that's still too long after wrapping is left as one over-width line, never split. Splitting it would put a //-only continuation right where the next statement's own leading comment could plausibly sit, genuinely ambiguous to a reader. It gets worse across a run of similar declarations, where only the lines that happen to overflow would grow an extra line, for a reason invisible on the page.

An @tag buried mid-sentence in a JSDoc comment gets hoisted onto its own line. JSDoc's own spec expects a block tag to be followed by a line break; a tag embedded in running prose was never really functioning as a tag to begin with, as far as any JSDoc tooling is concerned. An @ inside an email address, or inside backticks (`@ts-expect-error` naming a directive rather than invoking one), is left alone.

How this compares

Most formatters either don't touch comment prose at all, or treat it as an afterthought:

| Tool | Reflows comment prose? | | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | eslint-plugin-comment-length | The only other tool here with the same goal, as an ESLint rule. JS/TS only. | | Prettier | Repositions and re-indents comments, never reflows their text. By design. | | Biome | Same stance as Prettier, on purpose. | | gofmt | Leaves comment text untouched entirely. | | rustfmt | wrap_comments exists, but defaults off and sits behind the nightly-only feature gate. | | clang-format | The exception: ReflowComments defaults to Always in the base LLVM style. C/C++ only, no overlap here. |

comment-fmt covers JS/TS/JSX/TSX, CSS/SCSS, HTML, and Astro in one tool, instead of leaving comment width to whichever per-language formatter happens to run.

Escape hatches

Three forms, checked before anything else:

| Form | Syntax | Effect | | -------------- | -------------------------------------------------------- | ----------------------------- | | Preceding line | // comment-fmt-ignore on its own line before a comment | The next comment is untouched | | Inline | comment-fmt-ignore anywhere inside a comment's body | That comment is untouched | | File | comment-fmt-ignore-file within the first 5 lines | The whole file is skipped |

Per language: // comment-fmt-ignore (JS), /* comment-fmt-ignore */ (JS/CSS/JSX/TSX), <!-- comment-fmt-ignore --> (HTML/Astro). An optional reason may follow after -- or : , and comment-fmt ignores it.

extraDirectives in comment-fmt.json (below) adds repo-specific directive prefixes to the built-in protected list without needing this escape hatch at all.

Usage

import {format} from 'comment-fmt';

format(source, {lang: 'js'}); // 'js' | 'css' | 'html' | 'astro', defaults to 'js'
// reflows over-width // and /* */ comments; everything else is untouched

lang picks the lexer, not the file extension: JSX and TSX both use 'js', SCSS uses 'css'. The CLI maps extensions to the right lang for you; a direct format() call needs the mapping above.

...and via the CLI. With no file arguments it discovers every tracked file through git ls-files, not a directory walk — a trailing . is treated as one explicit, literal file path that matches nothing and silently no-ops, so don't pass one:

comment-fmt --check   # print a diff of what's wrong and exit 1 if anything would change
comment-fmt --write   # rewrite over-width comments in place
comment-fmt --diff    # same output as --check, minus the "run --write to fix" tip

Both --check and --diff print a standard diff -U3-style unified diff, windowed to a few lines of context around each change with a @@ -line,count +line,count @@ header, so a CI failure is readable straight from the log without re-running anything locally:

Run comment-fmt --write to apply the fix shown below.

--- src/network/withRetry.ts
+++ src/network/withRetry.ts
@@ -1,4 +1,7 @@
-/** Retries a flaky network call up to `maxAttempts` times, doubling the delay between each attempt before giving up. */
+/**
+ * Retries a flaky network call up to `maxAttempts` times,
+ * doubling the delay between each attempt before giving up.
+ */
 export function withRetry(fn: () => Promise<void>, maxAttempts = 3) {
   return fn();
 }

Or pass explicit files, the pre-commit hook path, where the staged-file runner already narrowed the list down and no discovery is needed:

comment-fmt --write src/foo.ts src/bar.css

comment-fmt --report-overwidth [files...] is a separate manual-review tool. It prints every over-width comment that format() actually changed, grouped by shape (pipe-delimited, box-drawing, aligned-space, tag-line, prose), so a human can sample the output and judge whether the heuristics above are missing anything. It's not a pass/fail check, and it isn't part of the normal --check/--write workflow.

Configuration

An optional comment-fmt.json in the repo root. Most repos don't need one:

| Key | Default | Notes | | ----------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | maxLength | 110 | Hard cap. No output line ever exceeds it. | | targetLength | 105 | Soft wrap target, always <= maxLength. | | ignore | [] | Glob patterns. Applies to a file whether it was discovered or named explicitly, so a hook that feeds every staged path still respects these. | | extraDirectives | [] | Extra prefixes appended to the built-in protected-directive list. |

Everything else is hardcoded on purpose: no wrap-strategy option, no per-file overrides, no ignoreUrls toggle, no tabSize.

A common use for ignore is excluding generated files and test fixtures, content nothing should reformat, comment-fmt included:

{
  "ignore": ["**/routeTree.gen.ts", "test/fixtures/**"]
}

Pre-commit hooks

comment-fmt --write rewrites files as part of the hook, which puts real weight on getting re-staging right. Run it last, after your language formatter. Prettier, Biome, oxfmt, whatever you use, re-indents comments as part of formatting the surrounding code, and this tool's width math depends on that final indentation. Run it first, and the formatter's own re-indent can push a line back over the limit right after you fixed it, producing a diff on the very next commit for no reason.

// package.json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx,css,scss,html}": [
      "your-formatter --write",
      "comment-fmt --write" // last, after the formatter
    ]
  }
}

lint-staged needs something to actually invoke it on commit. If nothing does yet, simple-git-hooks is a lightweight installer for that:

{
  "simple-git-hooks": {"pre-commit": "npx lint-staged"},
  "scripts": {"prepare": "simple-git-hooks"}
}

Vite+'s own staged config is built on lint-staged and works the same way, one array, comment-fmt last:

// vite.config.ts
export default defineConfig({
  staged: {
    '*': ['vp check --fix', 'comment-fmt --write']
  }
});

stage_fixed: true is required. It's off by default, and without it a rewrite never reaches the commit:

pre-commit:
  commands:
    comment-fmt:
      glob: '*.{ts,tsx,js,jsx,css,scss,html}'
      run: your-formatter --write {staged_files} && comment-fmt --write {staged_files}
      stage_fixed: true

In a monorepo, wiring this once at the workspace root is usually enough. File discovery runs from wherever the hook runs, so a single root-level config already reaches every package without per-workspace setup.

In CI, comment-fmt --check is independent of your formatter's own check. There's no ordering constraint the way there is for the write-side hook, since --check never rewrites:

your-formatter --check . && comment-fmt --check .

Development

  • Install dependencies:
vp install
  • Run the unit tests:
vp test
  • Build the library:
vp pack

License

MIT