Skip to content

feat: add cargo xtask lint-extended subcommand for comment/repetition smells - #11

Closed
jordigilh wants to merge 1 commit into
praxis-proxy:mainfrom
jordigilh:feat/extended-lint
Closed

feat: add cargo xtask lint-extended subcommand for comment/repetition smells#11
jordigilh wants to merge 1 commit into
praxis-proxy:mainfrom
jordigilh:feat/extended-lint

Conversation

@jordigilh

@jordigilh jordigilh commented Aug 18, 2026

Copy link
Copy Markdown

Summary

Adds a cargo xtask lint-extended subcommand, a small diff-scoped
heuristic checker for low-quality-code patterns that clippy can't catch
structurally: comment content (leftover TODO/FIXME markers,
commented-out code, "what"-narrating comments) and diff-local
repetition (a literal repeated 3+ times instead of a named constant),
plus a couple of lighter-weight signals (weak identifier names, new
clippy suppressions). It only scans lines added/changed versus the diff
base, so pre-existing code is never relitigated.

This originally shipped as a Python script (scripts/extended-lint.py)
-- wrong tool for a Rust-native codebase, since it needs its own
interpreter/toolchain in CI and locally, has no compile-time checking,
and can't share types or test harnesses with the rest of the repo. It's
now a proper xtask binary crate, added as a new workspace member
(this repo had no existing workspace; the root Cargo.toml gained a
[workspace] table with members = ["xtask"], with . remaining an
implicit member as the workspace's root package). The heuristics
themselves are unchanged -- same regexes, same block/warn split, same
diff-base resolution order (CLI arg, then $EXTENDED_LINT_BASE, then
origin/$GITHUB_BASE_REF in a GitHub Actions PR, then origin/main).

clap was considered for argument parsing but is only ever pulled into
this workspace transitively (via criterion, a dev-dependency of the
main crate's benchmarks) -- not a direct dependency of the operator's
own CLI, which has none. Since it's not otherwise part of this
workspace, xtask parses its one subcommand with plain
std::env::args() to keep its own dependency footprint minimal.
anyhow and regex are added as direct xtask-only dependencies
(regex was already present transitively at 1.13.1).

Wired in as:

  • make extended-lint (Makefile target now runs
    cargo run -p xtask -- lint-extended)
  • The existing step in the CI lint job (.github/workflows/tests.yaml)
    is unchanged in content -- it still fetches the PR's base branch first,
    then runs make extended-lint
  • docs/conventions.md ("Important Tools") and CONTRIBUTING.md's
    quick reference now point at xtask lint-extended instead of the
    Python script

Also fixes a Containerfile break surfaced by CI on this PR's first
push: its dependency-pre-caching stage only copies Cargo.toml
manifests before the real source, and cargo needs every workspace
member's manifest (plus a target file) present just to resolve the
workspace graph, even when building a single package. xtask now gets
a permanent stub manifest + src/main.rs in that stage, and both
cargo build invocations use -p praxis-operator so the dev-only
xtask crate is resolved but never actually compiled into the
production image. Verified with a local podman build.

And a self-reference bug surfaced by CI on the second push: scanning
*.rs files now also matches lint-extended's own source, whose doc
comments and test fixtures necessarily spell out the literal
TODO/FIXME markers and commented-code shapes it looks for -- something
the original .py script never hit, since .py files never matched
its *.rs glob. xtask/** is now excluded from its own git-diff scan
(documented as a known, deliberate limitation in the module docs); a
genuine leftover TODO added to xtask itself won't be caught by this
tool, but everything else in the workspace still is.

Closes #10.

Test plan

  • cargo clippy --workspace --all-targets -- -D warnings passes
    (whole workspace, including the new xtask member)
  • cargo +nightly fmt --all -- --check passes
  • cargo test -p xtask -- 4 unit tests covering the ported regexes
    pass
  • cargo doc --no-deps --document-private-items --workspace with
    RUSTDOCFLAGS="-D warnings" passes
  • cargo machete reports no unused dependencies
  • podman build -f Containerfile . succeeds end-to-end
  • cargo run -p xtask -- lint-extended origin/main against this
    PR's full diff -- confirmed no blocking findings from xtask's
    own source after the self-exclusion fix
  • End-to-end sanity check: added a throwaway // TODO: ... comment
    to src/lib.rs, ran cargo run -p xtask -- lint-extended origin/main -- confirmed it correctly flagged and blocked
    (exit 1), then reverted the throwaway change
  • Ran cargo run -p xtask -- lint-extended HEAD (no diff) --
    confirmed it reports "no added Rust lines" and exits 0
  • make extended-lint (via EXTENDED_LINT_BASE=HEAD) invokes the
    subcommand correctly
  • CI: lint, test, coverage, audit, DCO all pass on the
    updated push (see checks on this PR)

@jordigilh jordigilh changed the title feat: add diff-scoped extended-lint script for comment/repetition smells feat: add cargo xtask lint-extended subcommand for comment/repetition smells Aug 18, 2026
Python is the wrong tool for a Rust-native codebase: it needs its own
interpreter/toolchain in CI and locally, has no compile-time checking,
and can't share types or test harnesses with the rest of the repo.

Introduce a minimal `xtask` binary crate as a new workspace member
(this repo had no existing workspace) and port scripts/extended-lint.py
to it verbatim as the `lint-extended` subcommand, using regex + anyhow
instead of ad hoc string handling. clap was not added as a dependency:
it is only ever pulled in transitively (via criterion, a dev-dependency
of the main crate's benchmarks), so plain std::env::args() parsing for
a single subcommand keeps xtask's own dependency footprint minimal.

`make extended-lint` and the CI lint step now run
`cargo run -p xtask -- lint-extended` instead of `python3
scripts/extended-lint.py`; the diff-base-fetch CI step is unchanged
since the tool still needs a base ref to diff against.

The Containerfile's dependency pre-caching stage copies only Cargo.toml
manifests before the real source, which broke once the workspace grew
a second member: cargo needs every member's manifest (and a target
file) present just to resolve the workspace graph, even to build a
single package. Give xtask a permanent stub manifest+main.rs in that
stage and build with `-p praxis-operator` so the dev-only xtask crate
is resolved but never actually compiled into the production image.

Porting the checker into a `.rs` file also exposed a new self-reference
problem the Python version never had: `*.rs`-scoped diff scanning now
matches the tool's own source, whose doc comments and test fixtures
spell out the literal TODO/FIXME markers and commented-code shapes it
looks for. Exclude `xtask/**` from its own git-diff scan to fix it.

Fixes praxis-proxy#10

Signed-off-by: Jordi Gil <jgil@redhat.com>
@jordigilh

Copy link
Copy Markdown
Author

Closing this out without merging.

After digging into what actually exists in the Rust ecosystem for this class of check, most of the "extended lint" heuristics either have no reliable off-the-shelf equivalent in any language's tooling (commented-out-code detection, "narrating" comment detection), or only partial overlap that does not justify maintaining bespoke regex/diff-scoped Rust across six repos (e.g. TODO markers are trivially a one-line git diff | rg in CI rather than a dedicated tool; new-clippy-suppression tracking is already better covered by denying clippy::allow_attributes outright rather than diff-scoping it).

Decision: we do not want to own and maintain custom heuristic lint logic per-repo. If this capability is worth having, the better path is contributing it upstream to a maintained linter (clippy itself, or a dylint/ast-grep rule set) rather than a bespoke xtask subcommand we would need to keep in sync across the org. Not pursuing this further for now.

@jordigilh jordigilh closed this Aug 18, 2026
@jordigilh

Copy link
Copy Markdown
Author

Follow-up on the reasoning above, now that all six ports are done: implementing this surfaced a concrete illustration of exactly the maintenance burden we did not want to sign up for. Every one of the six independent Rust ports had to hand-write a workaround for the same problem -- the linter's own source (its regex definitions, doc comments, and test fixtures) legitimately contains the words TODO/FIXME/etc. as text, so without a manual self-exclusion pathspec the tool blocks on itself. That is not a one-time fix; any future contributor touching this logic (or writing a similar comment-content check) will hit the same trap and need to know to work around it by hand -- exactly the kind of "requires follow-up before it can be safely enabled" friction that is not simple to apply, and part of why we are not carrying this ourselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add diff-scoped extended-lint script for comment-content and repetition smells clippy can't catch

1 participant