pub mod abc;
pub mod baseline;
pub mod cache;
pub mod cli;
pub mod config;
pub mod cop;
pub mod correction;
pub mod diagnostic;
pub mod formatter;
pub mod fs;
pub mod linter;
pub mod mcp;
pub mod model;
pub mod parse;
pub mod testutil;
use std::io::Read;
use std::process::ExitCode;
use anyhow::Result;
use cli::Args;
use config::{
CopFilterSet, ResolvedConfig, load_config, load_default_config, set_rubocop_version_override,
validate_rubocop_version,
};
use cop::registry::CopRegistry;
use diagnostic::Diagnostic;
use formatter::color::Color;
use formatter::{create_formatter, Formatter, ProgressSink};
use cli::AutocorrectMode;
use linter::{lint_bytes_autocorrect, run_linter, run_linter_with, should_fail};
pub fn run() -> Result<ExitCode> {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
let args = Args::parse_cli();
if args.mcp {
return mcp::run();
}
set_rubocop_version_override(args.rubocop_version.clone());
if let Some(version) = &args.rubocop_version {
validate_rubocop_version(version)?;
}
let registry = CopRegistry::default_registry();
if let Some(code) = list_and_exit(&args, ®istry) {
return Ok(code);
}
let config = resolved_config(&args)?;
if args.stdin.is_some() {
return lint_stdin(&args, &config, ®istry);
}
lint_paths(&args, &config, ®istry)
}
fn resolved_config(args: &Args) -> Result<ResolvedConfig> {
let target = args.paths.first().map(|p| p.as_path());
let mut config = if args.force_default_config {
let mut cfg = load_default_config(target, None);
if !args.only.is_empty() {
cfg.register_departments_from_only(&args.only);
}
cfg
} else {
load_config(args.config.as_deref(), target, None)?
};
config.apply_display_cli(args.display_cop_names_override());
Ok(config)
}
fn list_and_exit(args: &Args, registry: &CopRegistry) -> Option<ExitCode> {
if args.list_cops {
for name in registry.names() {
println!("{name}");
}
return Some(ExitCode::SUCCESS);
}
if args.list_autocorrectable_cops {
for cop in registry.cops() {
if cop.supports_autocorrect() {
println!("{}", cop.name());
}
}
return Some(ExitCode::SUCCESS);
}
None
}
fn exit_from_diags(diags: &[Diagnostic], fail_level: &str) -> ExitCode {
if should_fail(diags, fail_level) {
ExitCode::from(1)
} else {
ExitCode::SUCCESS
}
}
fn print_results(args: &Args, diags: &[Diagnostic], files: &[std::path::PathBuf]) -> ExitCode {
create_formatter(&args.format, Color::resolve(args.color_force())).print(diags, files);
exit_from_diags(diags, &args.fail_level)
}
fn list_target_files(args: &Args, config: &ResolvedConfig) -> Result<ExitCode> {
let discovered = fs::discover_files_filtered(
&args.paths,
&CopFilterSet::for_discover(config),
args.force_exclusion,
)?;
for f in &discovered.files {
println!("{}", f.display());
}
Ok(ExitCode::SUCCESS)
}
fn lint_paths(
args: &Args,
config: &ResolvedConfig,
registry: &CopRegistry,
) -> Result<ExitCode> {
if args.list_target_files {
return list_target_files(args, config);
}
let fmt = create_formatter(&args.format, Color::resolve(args.color_force()));
if fmt.streams_marks() {
return lint_paths_streaming(args, config, registry, fmt.as_ref());
}
let result = run_linter(args, config, registry, &args.paths)?;
fmt.print(&result.diagnostics, &result.files);
Ok(exit_from_diags(&result.diagnostics, &args.fail_level))
}
fn lint_paths_streaming(
args: &Args,
config: &ResolvedConfig,
registry: &CopRegistry,
fmt: &dyn Formatter,
) -> Result<ExitCode> {
let sink = ProgressSink::new(fmt);
let result = run_linter_with(
args,
config,
registry,
&args.paths,
|n| sink.started(n),
|diags| sink.file_finished(diags),
)?;
sink.finished(&result.diagnostics, &result.files);
Ok(exit_from_diags(&result.diagnostics, &args.fail_level))
}
fn lint_stdin(args: &Args, config: &ResolvedConfig, registry: &CopRegistry) -> Result<ExitCode> {
let path = args.stdin.as_ref().unwrap();
let mut buf = Vec::new();
std::io::stdin().read_to_end(&mut buf)?;
let diags = lint_stdin_source(args, config, registry, path, &mut buf)?;
Ok(print_results(args, &diags, std::slice::from_ref(path)))
}
fn lint_stdin_source(
args: &Args,
config: &ResolvedConfig,
registry: &CopRegistry,
path: &std::path::Path,
bytes: &mut Vec<u8>,
) -> Result<Vec<Diagnostic>> {
let filters = CopFilterSet::build(config, registry);
let only = (!args.only.is_empty()).then(|| args.only.clone());
let mode = args.autocorrect_mode();
let diags = lint_bytes_autocorrect(
path,
bytes,
config,
registry,
&filters,
only.as_deref(),
&args.except,
mode,
args.ignore_disable_comments,
)?;
if mode != AutocorrectMode::Off && path.is_file() {
std::fs::write(path, bytes)?;
}
Ok(diags)
}