Skip to content

Latest commit

 

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

OComment

CI CodeQL MSRV 1.88 License: MIT OR Apache-2.0

OComment is a fast, byte-preserving comment checker, formatter and remover. The production tool is the Rust ocomment binary and the public ocomment-core library. ocomment-ref is an independent OCaml implementation used to check the scanner, classification, diagnostics, edits, transformed bytes, and source maps.

OComment supports Rust, OCaml, C, C++, Go, Java, JavaScript, TypeScript, Python, Shell, HTML, CSS, JSONC, SQL, Kotlin, TOML, Lua, YAML, PHP, Ruby, Zig, R, Dart, Swift, C#, Scala, Vue, Svelte, Markdown, and Perl. JSX/TSX, Objective-C/C++, GNU C/C++, CUDA, POSIX sh, Bash 5.3, zsh, PostgreSQL, MySQL, SQLite, T-SQL, and Oracle are explicit dialects. HTML <script> and <style> contents are scanned recursively, and a PHP file is scanned inside its <?php ... ?> tags.

The complete documentation is the Markdown in docs/, which GitHub renders as you read it, and every documentation link below points there. The same pages are built into a book at https://p4suta.github.io/OComment/ (once published).

Quick start

cargo install ocomment --locked

ocomment                 # NOTE: check the current directory
ocomment check src tests
ocomment diff src
ocomment fix --dry-run src
ocomment fix src
printf '%s\n' 'let x = 1; // remove' | ocomment strip --language rust

Or install nothing: docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment:0.1.0 check runs the same CLI from the container image.

A command that names no path checks the current directory, so running it from a subdirectory checks that subdirectory; a bare ocomment run from the repository root already checks the whole repository, under the ordinary walk limits — cd "$(git rev-parse --show-toplevel)" gets there from anywhere inside it. Naming a path explicitly (ocomment ., ocomment src) is a request rather than a default, so it bypasses the hidden-file and size limits, as configuration describes.

A human run previews each removable comment and closes with a summary:

$ ocomment check src
src/main.rs:2:5: removable line comment: // TODO: drop this
Found 1 removable comment in 1 file (1 file scanned). Run `ocomment fix` to remove it.

Findings, patches, and machine formats go to standard output; the summary and every note go to standard error, so ocomment diff src > fix.patch keeps the patch clean. -q drops the summary and leaves check to answer with its exit code, while diff and scan still write the patch or listing they exist for; -v traces what was scanned and counts every comment kind; --no-preview drops the previewed text. A - target reads standard input under the <stdin> pseudo-path, and fix --dry-run prints the patch fix would apply without writing a file.

fix -i (--interactive) asks before each removal instead of applying them all. Every comment fix would take out is shown where it starts, with three lines of context either side and the line as the removal would leave it, above a prompt: y removes it, n keeps it, a and d answer for the rest of the file at once, q stops asking and applies what was accepted, x abandons the run without writing anything, and ? lists them again. A comment taller than the window is shown as its first and last three lines with a marker for the rest, so the question stays in view. The accepted removals are written through the same rollback-backed transaction a plain fix uses. Because it is a conversation, it needs a terminal on both standard input and standard output and refuses --staged, --dry-run, -q, and every machine --format; ocomment diff is the way to review the same changes without one.

Why was this comment kept?

--explain lists every comment a human check or scan met — the kept ones included — and puts the rule that decided each one, together with the setting behind that rule, on the line under it:

$ ocomment check --explain
gen/api.rs:1:1: kept block comment: /* generated */
    kept: matched keep_regex #0 `(?i)generated` ([[overrides]] #0, paths = ["gen/**"])
src/app.js:1:1: kept directive comment: // eslint-disable-next-line
    kept: tool or language directive `eslint`; use --remove-kind directive or --policy all to remove it
src/app.js:3:12: removable line comment: // TODO
    removed: policy `conservative` removes ordinary comments ([policy] in .ocomment.toml)

A setting is named where it was written: the [policy] table of a file, a [languages.<name>] table, the [[overrides]] entry whose globs matched the path, or the flag on the command line. A comment no setting decided is left with the flag that would overrule the built-in rule instead. --explain annotates a report of comments, so it belongs to the two commands that write one and to the one format with room for prose: asking for it with --format json, or any other machine format, or with any command that writes no report of comments, is a usage error rather than a flag that quietly does nothing, and -q silences check altogether, explanations included.

check exits 0 when clean, 1 when removable comments exist, and 2 for an invalid source, configuration, plugin, or I/O failure. diff and fix --dry-run exit 1 when they print a change. Successful fix and strip operations exit 0. JSON, JSONL, SARIF, and GitHub annotation output are available through --format. Run ocomment --help for every option and ocomment man for the manual page.

The default conservative policy removes ordinary comments and keeps the ones something else depends on: documentation, licence notices, tool and language directives, shebangs and encoding lines. standard removes documentation, licence and copyright comments as well. all removes every comment token, but still needs --force-protected before touching a shebang, an encoding line, or a directive the language itself reads. The three are named in the order of how much they take. HTML comments are kept unless all or --remove-kind html-comment is explicit.

none removes nothing at all. It is the mode for a repository that wants the other axis and not the removals:

[policy]
mode = "none"

[style]
wrap = "sentence"
space_after_marker = true
trailing_whitespace = false

wrap = "sentence" puts one sentence on each line of a comment paragraph, undoing the breaks that only exist to keep a line short and leaving the ones somebody meant — a break after a clause stays where its writer put it. A fenced code block, a table, a list item's indentation, a rustdoc section heading and a link reference definition are passed through byte for byte. The other two rewrite //text as // text, leaving a ruler like //////// alone, and strip white space from the end of every line a comment covers.

[style] decides how a comment that survives is written, which is a different question from whether it survives: a comment that fails one of the rules under [policy.allow] is removed, and a comment that fails one of these is rewritten. check reports both, fix applies both, and diff writes a patch for both. Every style rule is off unless you turn it on.

The style rules reach documentation comments, which the length and position rules deliberately do not: a doc comment is exempt from a length limit because it is documentation, and that is exactly why it is the prose most worth tidying. They do not reach a licence notice, a directive, or the preamble — a legal text is quoted verbatim and a directive is read by a tool.

The lines layout keeps every line where it was, columns keeps every column as well, and compact drops the lines a removed comment had to itself.

version = 1

[policy]
mode = "conservative"
layout = "lines"

[[overrides]]
paths = ["generated/**"]
policy = "all"

files.include, files.exclude, and every [[overrides]].paths glob is relative to the project root — the directory holding .ocomment.toml, or the repository above it — however deep in the tree the command is run from.

Run ocomment init config for the complete default file or ocomment config schema for its JSON Schema. See configuration, editor/LSP setup, plugins, and hooks and CI.

The VS Code client is currently source-only and is not distributed through the Visual Studio Marketplace, Open VSX, or GitHub Releases. Its source remains in editors/vscode and is tested and packaged by CI, but its version and eventual publication are independent of the CLI release. It launches this binary, so a local extension build still needs ocomment installed separately.

Partially staged changes

ocomment fix --staged reads and rewrites Git index blobs, then maps only those edits to the working tree when the mapping is unique. It never stages unrelated working-tree changes. Use --index-only when a working-tree mapping is ambiguous. A staged run that rewrote the index exits 1: the bytes the commit will carry have stopped being the ones that were staged, and the hook that called it has no other way to find that out.

ocomment init lefthook --tidy
lefthook install

--tidy is the hook worth having on every commit: it writes what the style rules settle and leaves every removal to you.

The generated hook deliberately does not use Lefthook stage_fixed, because that setting would add the complete working-tree file and destroy partial staging.

Hooks and CI

.pre-commit-hooks.yaml publishes ocomment-check and ocomment-fix for pre-commit. The hooks are language: system, so install the CLI first, then point a .pre-commit-config.yaml at this repository:

repos:
  - repo: https://github.com/P4suta/OComment
    rev: v0.1.0
    hooks:
      - id: ocomment-check

ocomment-check exits 1 and blocks the commit while a staged file still has a removable comment. args: ["--staged"] judges the index blobs rather than the working tree, which is what a partially staged file needs.

action.yml is a composite GitHub Action. It downloads the release archive for the runner, verifies its SHA-256 and its build-provenance attestation, and annotates the pull request:

      - uses: P4suta/OComment@v0.1.0
        with:
          paths: src tests

format: sarif with upload-sarif: "true" sends the findings to code scanning instead, and fail-on-findings: "false" leaves the verdict to a later step reading the exit-code output. CI and hooks documents every input and output, the --staged caveats, and how to run the action where no release archive is published.

Library

use ocomment_core::{scan, transform, Language, ScanOptions, TransformOptions};

let report = scan(b"let x = 1; // note\n", Language::Rust, ScanOptions::default());
assert_eq!(report.comments.len(), 1);

let result = transform(
    b"let x = 1; // note\n",
    Language::Rust,
    TransformOptions::default(),
);
assert_eq!(result.output, b"let x = 1; \n");

Spans are half-open byte ranges. Edits are sorted and non-overlapping. The engine never requires the complete source to be UTF-8, so BOMs, CRLF, trailing newlines, and non-UTF-8 bytes outside edited spans are preserved.

Repository and verification

  • spec/ contains the shared schemas, language table, WIT interface, and differential fixtures.
  • rust/ contains the library, CLI, Git integration, LSP server, WASM host, and plugin SDK.
  • ocaml/ contains the pure reference library and JSONL verification CLI.
cargo test --manifest-path rust/Cargo.toml --workspace --all-targets --locked
dune runtest --root ocaml
cargo xtask differential
python3 tools/check_embedded_specs.py

The release process and performance gates are documented in Releasing.

Contributing and support

See CONTRIBUTING.md before opening a pull request. Use Discussions for support and design questions, and follow SECURITY.md for private vulnerability reports.

License

OComment is available under either the MIT license or the Apache License 2.0, at your option.

About

Fast, byte-preserving comment checker and remover for 15 languages

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages