gruff-rs 0.4.0

Rust static analyzer and quality linter for CI: dead-code, complexity, security, secrets, and architecture rules with deterministic SARIF/JSON output and baseline support.
use super::*;

pub(crate) const ARCHITECTURE_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "architecture.large-module",
        "Large module",
        Pillar::Design,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        ARCHITECTURE_LARGE_MODULE_THRESHOLD,
        "Flags modules with more indexed items than the configured threshold.",
    ),
    rule_definition!(
        "architecture.module-fan-out",
        "Module fan-out",
        Pillar::Design,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        ARCHITECTURE_MODULE_FAN_OUT_THRESHOLD,
        "Flags files that declare many child modules.",
    ),
    rule_definition!(
        "architecture.public-api-surface",
        "Public API surface",
        Pillar::Design,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        ARCHITECTURE_PUBLIC_API_SURFACE_THRESHOLD,
        "Flags modules with many public exports.",
    ),
];

pub(crate) const COMPLEXITY_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "complexity.cognitive",
        "Cognitive complexity",
        Pillar::Complexity,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        COMPLEXITY_COGNITIVE_THRESHOLD,
        "Flags functions with high cognitive complexity.",
        false_positives: &[
            FalsePositiveShape {
                shape: "Match-heavy dispatch functions whose cognitive load is one nested `match` per CLI subcommand or AST node.",
                mitigation: "Raise `rules.complexity.cognitive.threshold` in `.gruff-rs.yaml`, or extract per-arm helpers.",
            },
        ],
        related: &[
            "complexity.cyclomatic",
            "complexity.nesting-depth",
        ],
    ),
    rule_definition!(
        "complexity.cyclomatic",
        "Cyclomatic complexity",
        Pillar::Complexity,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        COMPLEXITY_CYCLOMATIC_THRESHOLD,
        "Flags functions with high branch and decision complexity.",
    ),
    rule_definition!(
        "complexity.nesting-depth",
        "Nesting depth",
        Pillar::Complexity,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        COMPLEXITY_NESTING_DEPTH_THRESHOLD,
        "Flags functions with deeply nested control flow.",
    ),
];

pub(crate) const DEAD_CODE_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "dead-code.unused-private-function",
        "Unused private function",
        Pillar::DeadCode,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Low,
        None,
        "Flags private functions with no same-file call sites.",
    ),
    rule_definition!(
        "dead-code.unused-private-item-candidate",
        "Unused private item candidate",
        Pillar::DeadCode,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags private items whose names are not referenced elsewhere in discovered Rust sources.",
        false_positives: &[
            FalsePositiveShape {
                shape: "Items referenced only via macros or via a build-script-generated file the discovery layer did not see (e.g. proc-macro-generated names).",
                mitigation: "Add the generating path to `paths.ignore` in `.gruff-rs.yaml`, or document with an `exclude:` entry naming the path.",
            },
            FalsePositiveShape {
                shape: "Narrow path or diff runs that do not cover every discoverable Rust source under the selected project root cannot prove an item is unused across the crate.",
                mitigation: "The rule is suppressed on partial-context runs with a `partial-context-rule-suppressed` diagnostic; run `gruff-rs analyse .` from the selected project root for authoritative dead-code signal.",
            },
            FalsePositiveShape {
                shape: "Items exported through FFI/plugin attributes or explicitly kept with `#[allow(dead_code)]`.",
                mitigation: "The rule skips `#[no_mangle]`, `#[export_name]`, `#[pymodule]`, `#[pyfunction]`, and explicit `allow(dead_code)` contexts; use those only where the external entry point or macro contract is real.",
            },
        ],
        related: &["dead-code.unused-private-function"],
    ),
];

pub(crate) const DEPENDENCY_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "dependency.duplicate-locked-version",
        "Duplicate locked dependency version",
        Pillar::Security,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        DEPENDENCY_DUPLICATE_LOCKED_VERSION_THRESHOLD,
        "Flags packages locked at more versions than the configured threshold.",
    ),
    rule_definition!(
        "dependency.git-source",
        "Git dependency source",
        Pillar::Security,
        RuleKind::Project,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags dependencies sourced directly from git repositories.",
    ),
    rule_definition!(
        "dependency.git-unpinned-revision",
        "Git dependency without fixed revision",
        Pillar::Security,
        RuleKind::Project,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags git dependencies that do not pin a fixed rev.",
    ),
    rule_definition!(
        "dependency.missing-package-metadata",
        "Missing package metadata",
        Pillar::Documentation,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        None,
        "Flags packages missing description or license metadata.",
    ),
    rule_definition!(
        "dependency.path-source",
        "Path dependency source",
        Pillar::Security,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        None,
        "Flags dependencies sourced from local filesystem paths.",
    ),
    rule_definition!(
        "dependency.wildcard-version",
        "Wildcard dependency version",
        Pillar::Security,
        RuleKind::Project,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags dependency requirements that use wildcard versions.",
    ),
];

pub(crate) const DOCUMENTATION_AND_DESIGN_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "docs.missing-public-doc",
        "Missing public documentation",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags public Rust API items without doc comments.",
    ),
    rule_definition!(
        "docs.missing-readme",
        "Missing README",
        Pillar::Documentation,
        RuleKind::Project,
        Severity::Advisory,
        Confidence::High,
        None,
        "Flags projects without a root README file.",
    ),
    rule_definition!(
        "docs.stale-todo",
        "Stale TODO marker",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::High,
        None,
        "Flags TODO/FIXME/HACK/XXX comments without an owner, issue reference, or reason.",
    ),
    rule_definition!(
        "docs.commented-out-code",
        "Commented-out code",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags comments whose payload looks like a disabled Rust statement or item.",
    ),
    rule_definition!(
        "docs.weak-safety-rationale",
        "Weak SAFETY rationale",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags unsafe blocks whose nearby SAFETY: rationale is too short or vague.",
    ),
    rule_definition!(
        "docs.missing-errors-section",
        "Missing # Errors rustdoc section",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::High,
        None,
        "Flags externally public functions returning Result without a # Errors rustdoc section.",
    ),
    rule_definition!(
        "docs.missing-panics-section",
        "Missing # Panics rustdoc section",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::High,
        None,
        "Flags public functions containing panic!/unwrap/expect without a # Panics rustdoc section.",
    ),
    rule_definition!(
        "docs.missing-safety-section",
        "Missing # Safety rustdoc section",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags public `unsafe fn` / `unsafe trait` items without a # Safety rustdoc section.",
    ),
    rule_definition!(
        "docs.missing-param-doc",
        "Missing parameter rustdoc",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags public functions whose rustdoc lacks per-parameter documentation.",
    ),
    rule_definition!(
        "docs.missing-return-doc",
        "Missing return rustdoc",
        Pillar::Documentation,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags public functions returning a value whose rustdoc lacks a Returns description.",
    ),
];

pub(crate) const CONCURRENCY_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "concurrency.blocking-call-in-async",
        "Blocking call in async function",
        Pillar::Maintainability,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::Medium,
        None,
        "Flags narrow blocking call patterns inside async functions.",
    ),
    rule_definition!(
        "concurrency.lock-across-await",
        "Lock guard across await",
        Pillar::Maintainability,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::Medium,
        None,
        "Flags lock guard bindings that appear to live across an await point.",
        false_positives: &[
            FalsePositiveShape {
                shape: "Bindings that extract a value from the guard immediately, such as `.lock().await.take()`, before any later await.",
                mitigation: "Keep the extraction chained on the lock expression or explicitly drop the guard before awaiting; the rule only tracks bindings that still represent the guard.",
            },
        ],
        related: &["concurrency.blocking-call-in-async", "concurrency.unbounded-channel"],
    ),
    rule_definition!(
        "concurrency.unbounded-channel",
        "Unbounded channel",
        Pillar::Maintainability,
        RuleKind::Rust,
        Severity::Advisory,
        Confidence::Medium,
        None,
        "Flags unbounded channel constructors in production code.",
    ),
];

pub(crate) const ERROR_HANDLING_RULES: &[RuleDefinition] = &[
    rule_definition!(
        "error-handling.production-panic",
        "Production panic",
        Pillar::Maintainability,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags panic! calls in non-test functions without a local invariant comment.",
    ),
    rule_definition!(
        "error-handling.public-unwrap",
        "Public API unwrap",
        Pillar::Maintainability,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags unwrap or expect calls in public non-test functions.",
    ),
    rule_definition!(
        "error-handling.unimplemented-placeholder",
        "Unimplemented placeholder",
        Pillar::Maintainability,
        RuleKind::Rust,
        Severity::Warning,
        Confidence::High,
        None,
        "Flags todo! and unimplemented! placeholders in non-test functions.",
    ),
];