Open-source project
drahnr/cargo-spellcheck avatar
drahnr/cargo-spellcheck

cargo-spellcheck reads the doc comments nobody else looks at

Checks all your documentation for spelling and grammar mistakes with hunspell and a nlprule based checker for grammar

364 stars39 forksRustApache-2.0

At a glance

What is it?
cargo-spellcheck runs hunspell and nlprule over documentation comments and reports them as cargo-style diagnostics you can fix interactively, one suggestion at a time. Its own documentation carries a misspelled environment variable, which is a fair summary of a tool whose value depends on getting past the first typo.
Who is it for?
cargo-spellcheck suits a Rust project that has already accumulated typos in its public API documentation and wants them caught in review or in continuous integration rather than by a reader. It does not suit a project that has not yet decided which words are domain vocabulary, since the checker will flag every internal term until you teach it.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 138 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The scope is documentation comments

The check is narrow on purpose. It reads documentation comments in a Rust source tree, not your strings, not your identifiers, and not your commit messages. Spelling comes from hunspell and grammar from nlprule, and you can use either one or both.

The output is shaped like a compiler error, which is the reason it fits into an existing workflow. A misspelling in a doc comment produces a path, a line number, the offending line reproduced with caret markers under the exact span, and a suggestion. The worked example in the documentation is a doc comment reading that fun facets shall cause some errors, where the misspelling is flagged and offered as a split of the word into its two parts.

That span-level precision is the feature. A checker that reports a whole line is noise in review, while one that underlines six characters gives the reviewer something to accept or reject.

The suggestion list is the other half of the diagnostic. When a checker only offers dictionary words, a project-specific term produces noise on every single run, and the only cures are rewording the comment or teaching the dictionary. Offering a run-together word split into its two correct halves, as the example does, turns one wrong word into two right ones and makes the fix a single keystroke.

fix is a prompt, not a batch rewrite

There are two ways to apply changes, both spelled the same in practice: `cargo spellcheck --fix` and `cargo spellcheck fix`. The difference from check is that fix asks.

The prompt shows its own state, with the current suggestion numbered out of the total and the set of answers spelled out, offering the options to apply, skip, quit, apply the rest, and a few navigation and editing choices. Underneath is the candidate list for that token, which includes the word split into two halves as one candidate alongside ordinary dictionary words, with a custom replacement entry highlighted for editing.

That last option is the whole point of the tool. A batch rewrite turns a domain-specific term into a diff nobody reviewed, while an interactive prompt makes each rejection a decision, and a vocabulary you keep rejecting is a vocabulary worth adding to the checker's own word list.

The example output leaks an absolute path

The second worked example in the documentation reports a misspelling in a file under an absolute path that includes a mount point and a username directory, in a project directory named after the tool itself. The suggestion prompt follows on the next line with a numbered list of fourteen candidates for the word that was meant to be literal.

It is a small thing, and it says something real: the output was pasted in from a run on one machine without scrubbing paths. Anyone who recognises the shape will assume a CI log or a bug report will do the same, which is a habit worth forming before this tool runs in a pipeline that attaches logs to issues.

It also makes the example less useful as documentation. A reader cannot tell whether the path is meaningful, because it is not, and a neutral placeholder would have communicated the same thing without publishing a directory layout.

The macOS variable name in the prose is wrong

Installing is one command, and the flag matters:

zsh
cargo install --locked cargo-spellcheck

The documentation explains that the locked flag is preferred because it installs the tested set of dependencies rather than resolving to whatever is newest, which for a tool that links against a native library is the difference between working and not.

Then come the platform notes, and the macOS one contains an error worth quoting carefully, because copying from the sentence rather than from the code block gives you a variable that does nothing. The prose tells you to set `DYLB_FALLBACK_LIBRARY_PATH`, with the letters transposed, while the command beneath it exports `DYLD_FALLBACK_LIBRARY_PATH`:

sh
export DYLD_FALLBACK_LIBRARY_PATH= \
    "$(xcode-select --print-path)/Toolchains/XcodeDefault.xctoolchain/usr/lib/"

The Linux instructions are the clearer of the two: install the development package, then set the path variable, with an example under a version 14 directory. Nothing in the visible text says which crate requires libclang, so on a machine that otherwise matches the tested target, that variable is the first thing to check.

A learning phase is expected before CI enforcement

The intended workflow is stated more precisely than most tools state theirs. It is meant as a helper that simplifies review and improves continuous integration checks, and that comes after a learning phase for custom and topic specific language.

Read in order, that is a two-stage plan. First you run it locally, fix the genuine typos, and spend the rejections building up the vocabulary the checker will accept, so that the words unique to your domain stop being noise. Then you enforce it, in a commit hook or in a pipeline, when a flag means a real mistake.

The repository is arranged for both stages. There is a pre-commit hooks configuration at the top level for the local half, a `.concourse.yml` for the continuous integration half, a remedies page for common issues, and a separate configuration page for the settings file. Getting this order wrong is the failure mode: enforcing first produces a diff nobody trusts and a hook people disable.

A demo crate in the tree carries its own manifest, its own configuration directory, and a member folder, which is the quickest way to see what a configured project looks like without reverse engineering somebody else's setup. There is a configuration directory at the top level too, so the demo and the tests have somewhere to point.

Completions detect your shell from an environment variable

Shell integration is one subcommand with two forms. Running `cargo spellcheck completions` autodetects the current shell through `$SHELL`, and passing the shell explicitly, for example with a zsh flag, overrides that guess.

The documented way to use it is from the shell's own startup file, sourcing the generated output so the completions exist in every new shell:

zsh
source <(cargo spellcheck completions)

Process substitution is the mechanism, and it is worth being deliberate about it. That line runs on every new shell, spawning a process to print a script, which is a small price for having the subcommands available. The alternative of copying the completion output into the startup file costs nothing at shell start and costs you when the subcommands change, which is the trade the project has chosen to make explicit rather than hide.

The dependency list reveals what gets parsed

The manifest is more informative about the design than the feature list is. Parsing Rust source for doc comments with precise locations is what a full-features syntax parser plus a span-locations procedural macro is for, and rendering the offending span in a diagnostic is why a terminal styling crate is present. Parsing the markdown inside those comments is a separate parser dependency, and walking a repository while respecting ignore rules is another, which is what makes the tool skip build output and vendored directories without being told to.

Two dependencies explain the packaging. The grammar checker has a build-time generator pinned to an exact version rather than a range, and its generated artifacts are compressed with an xz compressor because of a hard package size limit that the manifest comments on directly. The repository also carries its own workspace member, a small crate shared with the documentation build, so the doc comments and the rendered manual stay in step.

For the interactive mode, a terminal event crate and a console output crate do the prompt rendering, and a derive-based argument parser with environment support handles the subcommands and verbosity flags.

The smaller entries answer smaller questions. A TOML parser and a crate dedicated to reading Cargo manifests handle the package definition, an async runtime and a signal handling crate let the interactive prompt be interrupted cleanly, a regular expression crate and a lookaround-capable variant tokenise prose, an ordered map with parallel iteration plus an iterator adapter cover bulk work, a filesystem error wrapper carries IO safety, structured error types and a fancy error reporter shape the terminal output, and a logger with environment filtering is what makes the verbosity flags do anything.

Two stated licenses and three license files

The repository metadata reports a single Apache 2.0 license, while the manifest states the dual MIT or Apache-2.0 expression, and the tree holds separate Apache, LGPL, and MIT license files. The dual expression in the manifest is the more specific of the two statements, and the LGPL text is there for a bundled component rather than for this crate, but nothing in the visible documentation says which, so anyone shipping this crate should confirm which text applies before relying on it.

The build setup is stated precisely. The crate targets the 2021 edition and requires Rust 1.85.0 or newer, and the package manifest enumerates exactly what gets published, which is why the compressed grammar data and the hunspell data are both inside the package and the tests are shipped alongside the source.

The release rhythm is uneven and worth noting. The three most recent tags are 0.15.4 and 0.15.5 in March 2025 and 0.15.7 in May 2026, so the two most recent versions are fourteen months apart, and the last push to the main branch is dated 2026-05-18. That is enough for the tool to be usable and not enough to assume a new version will fix a platform problem for you.

The changelog is generated rather than written, judging by the changelog configuration sitting beside it, which means release notes come from commit messages. That is consistent with the documented contribution process, where an issue is claimed in a comment and an initial pull request is refined iteratively rather than authored in one careful sitting.

Editorial conclusion

cargo-spellcheck suits a Rust project that has already accumulated typos in its public API documentation and wants them caught in review or in continuous integration rather than by a reader. It does not suit a project that has not yet decided which words are domain vocabulary, since the checker will flag every internal term until you teach it. Before wiring it into a pipeline, read the remedies and configuration pages, and follow the documented order of a learning phase first and enforcement second.

Frequently asked questions

What does cargo-spellcheck actually check?

Documentation comments in your Rust source tree, using hunspell for spelling and nlprule for grammar. Findings are reported as cargo-style diagnostics with a path, a line, a marked span, and a suggestion.

How do I install cargo-spellcheck, and what does --locked do?

The documented install is cargo install --locked cargo-spellcheck, and the locked flag is preferred because it installs the tested set of dependencies instead of resolving to the newest versions available.

Why does cargo-spellcheck need libclang?

The linker has to find the native library. On macOS that means libclang.dylib through DYLD_FALLBACK_LIBRARY_PATH, and on Linux it means installing libclang-dev and then pointing LIBCLANG_PATH at the library directory.

How do I get shell completions for cargo-spellcheck?

Run cargo spellcheck completions, which autodetects the current shell through $SHELL, or pass the shell explicitly. The documented pattern is sourcing the output from the shell startup file with source <(cargo spellcheck completions).

What do the prompt options mean in cargo-spellcheck fix?

The interactive prompt shows its position as a count out of the total and lists the answers, which include applying or skipping, quitting, applying the rest, and an editing option. That editing option is how a project teaches the checker its own domain vocabulary.

Official sources

  1. drahnr/cargo-spellcheck on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/drahnr-cargo-spellcheck.svg)](https://hysenlabs.com/projects/drahnr-cargo-spellcheck)