# alint://bundled/rust@v1
#
# Hygiene checks for Rust projects. Adopt it with:
#
#     extends:
#       - alint://bundled/rust@v1
#
# This ruleset is gated with `when: facts.has_rust` (true if any
# `Cargo.toml` exists anywhere in the tree) plus a per-rule
# `scope_filter: { has_ancestor: Cargo.toml }` on per-file content
# rules so they only apply to files inside a Rust package — useful
# in polyglot monorepos where Rust packages sit alongside
# Node / Python / Go subdirectories. Rules that don't apply stay
# quiet. (`has_rust` is declared below; override with your own
# `facts:` block if you need a different heuristic.)

version: 1

facts:
  - id: has_rust
    any_file_exists: [Cargo.toml, "**/Cargo.toml"]

rules:
  # --- Workspace / package manifest ---------------------------------
  - id: rust-cargo-toml-exists
    when: facts.has_rust
    kind: file_exists
    paths: Cargo.toml
    root_only: true
    level: error
    message: "Rust project: Cargo.toml at the repo root is required."

  - id: rust-cargo-lock-exists
    when: facts.has_rust
    kind: file_exists
    paths: Cargo.lock
    root_only: true
    level: warning
    message: >-
      Committing Cargo.lock ensures reproducible builds for binary
      crates. Library-only workspaces may legitimately opt out; set
      this rule's `level: off` in that case.
    policy_url: "https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html"

  - id: rust-toolchain-pinned
    when: facts.has_rust
    kind: file_exists
    paths: ["rust-toolchain.toml", "rust-toolchain"]
    root_only: true
    level: info
    message: "Pinning a toolchain (rust-toolchain.toml) makes local and CI builds reproducible."

  # --- Build artefacts must not be tracked --------------------------
  - id: rust-no-tracked-target
    when: facts.has_rust
    kind: dir_absent
    paths: "**/target"
    level: error
    message: "`target/` is Cargo's build dir and must never be committed."

  # --- Source-file conventions --------------------------------------
  # v0.9.18: default-exclude subtrees with deliberately non-snake-case
  # filenames. Three categories surface in rust-lang/rust:
  #
  #   src/doc/**              — doc examples + reference snippets
  #                             use English-style hyphenation
  #                             (`rustc-driver-example.rs`,
  #                             `rust-by-example/...`).
  #   src/tools/miri/tests/** — Miri test fixtures use CamelCase /
  #                             intentional naming for litmus tests
  #                             and concurrency-spec semantics
  #                             (`abi_mismatch_repr_C.rs`,
  #                             `libc_pthread_mutex_NULL_reentrant.rs`).
  #   src/tools/clippy/tests/ui/** — clippy UI test fixtures use
  #                             curly braces, hyphens, and other
  #                             non-snake-case markers as part of
  #                             the test grammar.
  #   **/*.miri.rs            — `.miri.rs` dot-suffix is a Miri-only
  #                             override file pattern; the dot in
  #                             the basename trips snake-case.
  #
  # Without these excludes, rust-lang/rust alone surfaced 1,091
  # false positives — the largest single bundled-rule FP class in
  # the case-study corpus. The excludes are unique to rust-lang/rust
  # + similar compiler/test-fixture projects; they're a no-op for
  # most repos.
  - id: rust-sources-snake-case
    when: facts.has_rust
    kind: filename_case
    paths:
      include: ["**/src/**/*.rs"]
      exclude:
        - "src/doc/**"
        - "src/tools/miri/tests/**"
        - "src/tools/clippy/tests/ui/**"
        - "src/tools/rustfmt/tests/**"
        - "tests/rustdoc-gui/**"
        - "**/*.miri.rs"
    case: snake
    level: error
    message: "Rust module filenames must be snake_case."
    fix:
      file_rename: {}

  - id: rust-sources-final-newline
    when: facts.has_rust
    kind: final_newline
    paths: "**/*.rs"
    scope_filter:
      has_ancestor: Cargo.toml
    level: warning
    fix:
      file_append_final_newline: {}

  - id: rust-sources-no-trailing-whitespace
    when: facts.has_rust
    kind: no_trailing_whitespace
    paths: "**/*.rs"
    scope_filter:
      has_ancestor: Cargo.toml
    level: info
    fix:
      file_trim_trailing_whitespace: {}

  # --- Trojan Source defense on Rust sources ------------------------
  - id: rust-sources-no-bidi
    when: facts.has_rust
    kind: no_bidi_controls
    paths: "**/*.rs"
    scope_filter:
      has_ancestor: Cargo.toml
    level: error
    message: "Trojan Source (CVE-2021-42574): bidi override chars in Rust sources are rejected."
    policy_url: "https://trojansource.codes/"

  - id: rust-sources-no-zero-width
    when: facts.has_rust
    kind: no_zero_width_chars
    paths: "**/*.rs"
    scope_filter:
      has_ancestor: Cargo.toml
    level: error
    message: "Zero-width characters in Rust sources are rejected (review hazard)."

  # --- Workspace-level niceties -------------------------------------
  - id: rust-no-merge-markers-in-manifests
    when: facts.has_rust
    kind: no_merge_conflict_markers
    paths: ["Cargo.toml", "**/Cargo.toml", "Cargo.lock", "**/Cargo.lock"]
    scope_filter:
      has_ancestor: Cargo.toml
    level: error
