1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use std::path::PathBuf;
use clap::Parser;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) enum OutputFormat {
#[default]
Text,
Json,
Github,
Dot,
Sarif,
Html,
}
impl std::str::FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"text" => Ok(Self::Text),
"json" => Ok(Self::Json),
"github" => Ok(Self::Github),
"dot" => Ok(Self::Dot),
"sarif" => Ok(Self::Sarif),
"html" => Ok(Self::Html),
_ => Err(format!(
"Unknown format: {s}. Expected: text, json, github, dot, sarif, html"
)),
}
}
}
#[derive(Parser, Debug)]
#[command(
name = "rustqual",
about = "Comprehensive Rust code quality analyzer — six dimensions: IOSP, Complexity, DRY, SRP, Coupling, Test Quality.",
version
)]
pub(crate) struct Cli {
/// Path to analyze (file or directory). Defaults to current directory.
#[arg(default_value = ".")]
pub path: PathBuf,
/// Show all functions, not just findings.
#[arg(short, long)]
pub verbose: bool,
/// Show only findings with file:line locations (one per line).
#[arg(long)]
pub findings: bool,
/// Output results as JSON (for CI integration).
#[arg(long)]
pub json: bool,
/// Output format: text (default), json, github, dot, sarif, html.
#[arg(long, value_name = "FORMAT")]
pub format: Option<OutputFormat>,
/// Path to config file. Defaults to `rustqual.toml` in the target directory.
#[arg(short, long)]
pub config: Option<PathBuf>,
/// Treat closures as logic (stricter analysis).
#[arg(long)]
pub strict_closures: bool,
/// Treat iterator chains (.map, .filter, ...) as logic.
#[arg(long)]
pub strict_iterators: bool,
/// Allow recursive calls (don't count as violations).
#[arg(long)]
pub allow_recursion: bool,
/// Count ? operator as logic (implicit control flow).
#[arg(long)]
pub strict_error_propagation: bool,
/// Do not exit with code 1 on quality findings (useful for local exploration).
#[arg(long)]
pub no_fail: bool,
/// Treat warnings (e.g. suppression ratio exceeded) as errors (exit code 1).
#[arg(long)]
pub fail_on_warnings: bool,
/// Generate a default rustqual.toml configuration file.
#[arg(long)]
pub init: bool,
/// Generate shell completions for the given shell.
#[arg(long, value_name = "SHELL")]
pub completions: Option<clap_complete::Shell>,
/// Save current analysis results as a baseline for future comparison.
#[arg(long, value_name = "FILE")]
pub save_baseline: Option<PathBuf>,
/// Compare current results against a previously saved baseline.
#[arg(long, value_name = "FILE")]
pub compare: Option<PathBuf>,
/// Return exit code 1 only if the quality score regressed compared to baseline.
#[arg(long)]
pub fail_on_regression: bool,
/// Watch for file changes and re-analyze continuously.
#[arg(long)]
pub watch: bool,
/// Show refactoring suggestions for IOSP violations.
#[arg(long)]
pub suggestions: bool,
/// Minimum quality score (0–100). Exit code 1 if below threshold.
#[arg(long, value_name = "SCORE")]
pub min_quality_score: Option<f64>,
/// Sort IOSP violations by refactoring effort (highest first).
#[arg(long)]
pub sort_by_effort: bool,
/// Analyze only files changed vs a git ref (default: HEAD).
/// Conflicts with --watch.
#[arg(long, value_name = "REF", num_args = 0..=1, default_missing_value = "HEAD", conflicts_with = "watch")]
pub diff: Option<String>,
/// Path to an LCOV coverage file for test quality analysis (TQ-004, TQ-005).
#[arg(long, value_name = "LCOV_FILE")]
pub coverage: Option<PathBuf>,
}