mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
Warn the user if an explicitly selected check code is ignored (#356)
This commit is contained in:
parent
99c66d513a
commit
78aafb4b34
4 changed files with 181 additions and 94 deletions
136
src/cli.rs
Normal file
136
src/cli.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::pyproject::StrCheckCodePair;
|
||||
use crate::settings::PythonVersion;
|
||||
use crate::RawSettings;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
pub struct Cli {
|
||||
#[arg(required = true)]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
#[arg(short, long)]
|
||||
pub quiet: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
#[arg(short, long)]
|
||||
pub exit_zero: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
pub watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
#[arg(short, long)]
|
||||
pub fix: bool,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
pub format: SerializationFormat,
|
||||
/// See the files ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
pub show_files: bool,
|
||||
/// See ruff's settings.
|
||||
#[arg(long)]
|
||||
pub show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[arg(long)]
|
||||
pub add_noqa: bool,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long)]
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
pub autoformat: bool,
|
||||
}
|
||||
|
||||
pub enum Warnable {
|
||||
Select,
|
||||
ExtendSelect,
|
||||
}
|
||||
|
||||
impl fmt::Display for Warnable {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Warnable::Select => fmt.write_str("--select"),
|
||||
Warnable::ExtendSelect => fmt.write_str("--extend-select"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Warn the user if they attempt to enable a code that won't be respected.
|
||||
pub fn warn_on(
|
||||
flag: Warnable,
|
||||
codes: &Vec<CheckCode>,
|
||||
cli_ignore: &Vec<CheckCode>,
|
||||
cli_extend_ignore: &Vec<CheckCode>,
|
||||
pyproject_settings: &RawSettings,
|
||||
pyproject_path: &Option<PathBuf>,
|
||||
) {
|
||||
for code in codes {
|
||||
if !cli_ignore.is_empty() {
|
||||
if cli_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
|
||||
}
|
||||
} else if pyproject_settings.ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
|
||||
path.to_string_lossy()
|
||||
)
|
||||
} else {
|
||||
warn!("{code:?} was passed to {flag}, but ignored by the default `ignore` field",)
|
||||
}
|
||||
}
|
||||
if !cli_extend_ignore.is_empty() {
|
||||
if cli_extend_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
|
||||
}
|
||||
} else if pyproject_settings.extend_ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
|
||||
path.to_string_lossy()
|
||||
)
|
||||
} else {
|
||||
warn!("{code:?} was passed to {flag}, but ignored by the default `extend_ignore` field")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ pub mod cache;
|
|||
pub mod check_ast;
|
||||
mod check_lines;
|
||||
pub mod checks;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
|
@ -41,7 +42,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
|
|||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let settings = Settings::from_raw(RawSettings::from_pyproject(pyproject, project_root)?);
|
||||
let settings = Settings::from_raw(RawSettings::from_pyproject(&pyproject, &project_root)?);
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
|
104
src/main.rs
104
src/main.rs
|
@ -5,17 +5,17 @@ use std::sync::mpsc::channel;
|
|||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Parser};
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
use regex::Regex;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use ruff::cache;
|
||||
use ruff::checks::CheckCode;
|
||||
use ruff::checks::CheckKind;
|
||||
use ruff::cli::{warn_on, Cli, Warnable};
|
||||
use ruff::fs::iter_python_files;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
use ruff::linter::autoformat_path;
|
||||
|
@ -23,84 +23,15 @@ use ruff::linter::lint_path;
|
|||
use ruff::logging::set_up_logging;
|
||||
use ruff::message::Message;
|
||||
use ruff::printer::{Printer, SerializationFormat};
|
||||
use ruff::pyproject::{self, StrCheckCodePair};
|
||||
use ruff::pyproject::{self};
|
||||
use ruff::settings::CurrentSettings;
|
||||
use ruff::settings::RawSettings;
|
||||
use ruff::settings::{CurrentSettings, PythonVersion};
|
||||
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
|
||||
use ruff::tell_user;
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
struct Cli {
|
||||
#[arg(required = true)]
|
||||
files: Vec<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
#[arg(short, long)]
|
||||
quiet: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
#[arg(short, long)]
|
||||
exit_zero: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
#[arg(short, long)]
|
||||
fix: bool,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_ignore: Vec<CheckCode>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
exclude: Vec<String>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
per_file_ignores: Vec<StrCheckCodePair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
format: SerializationFormat,
|
||||
/// See the files ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
show_files: bool,
|
||||
/// See ruff's settings.
|
||||
#[arg(long)]
|
||||
show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[arg(long)]
|
||||
add_noqa: bool,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long)]
|
||||
dummy_variable_rgx: Option<Regex>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long)]
|
||||
target_version: Option<PythonVersion>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
autoformat: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
fn check_for_updates() {
|
||||
use update_informer::{registry, Check};
|
||||
|
@ -125,8 +56,11 @@ fn check_for_updates() {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_settings(settings: RawSettings) {
|
||||
println!("{:#?}", CurrentSettings::from_settings(settings));
|
||||
fn show_settings(settings: RawSettings, project_root: Option<PathBuf>, pyproject: Option<PathBuf>) {
|
||||
println!(
|
||||
"{:#?}",
|
||||
CurrentSettings::from_settings(settings, project_root, pyproject)
|
||||
);
|
||||
}
|
||||
|
||||
fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
|
@ -292,7 +226,7 @@ fn inner_main() -> Result<ExitCode> {
|
|||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect();
|
||||
|
||||
let mut settings = RawSettings::from_pyproject(pyproject, project_root)?;
|
||||
let mut settings = RawSettings::from_pyproject(&pyproject, &project_root)?;
|
||||
if !exclude.is_empty() {
|
||||
settings.exclude = exclude;
|
||||
}
|
||||
|
@ -303,9 +237,25 @@ fn inner_main() -> Result<ExitCode> {
|
|||
settings.per_file_ignores = per_file_ignores;
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
warn_on(
|
||||
Warnable::Select,
|
||||
&cli.select,
|
||||
&cli.ignore,
|
||||
&cli.extend_ignore,
|
||||
&settings,
|
||||
&pyproject,
|
||||
);
|
||||
settings.select = cli.select;
|
||||
}
|
||||
if !cli.extend_select.is_empty() {
|
||||
warn_on(
|
||||
Warnable::ExtendSelect,
|
||||
&cli.extend_select,
|
||||
&cli.ignore,
|
||||
&cli.extend_ignore,
|
||||
&settings,
|
||||
&pyproject,
|
||||
);
|
||||
settings.extend_select = cli.extend_select;
|
||||
}
|
||||
if !cli.ignore.is_empty() {
|
||||
|
@ -326,7 +276,7 @@ fn inner_main() -> Result<ExitCode> {
|
|||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(settings);
|
||||
show_settings(settings, project_root, pyproject);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -8,6 +7,7 @@ use anyhow::{anyhow, Result};
|
|||
use glob::Pattern;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
|
||||
use crate::fs;
|
||||
|
@ -94,8 +94,6 @@ pub struct RawSettings {
|
|||
pub ignore: Vec<CheckCode>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub select: Vec<CheckCode>,
|
||||
pub target_version: PythonVersion,
|
||||
}
|
||||
|
@ -129,10 +127,10 @@ static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
|
|||
|
||||
impl RawSettings {
|
||||
pub fn from_pyproject(
|
||||
pyproject: Option<PathBuf>,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: &Option<PathBuf>,
|
||||
project_root: &Option<PathBuf>,
|
||||
) -> Result<Self> {
|
||||
let config = load_config(&pyproject)?;
|
||||
let config = load_config(pyproject)?;
|
||||
Ok(RawSettings {
|
||||
dummy_variable_rgx: match config.dummy_variable_rgx {
|
||||
Some(pattern) => Regex::new(&pattern)
|
||||
|
@ -145,14 +143,14 @@ impl RawSettings {
|
|||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
extend_exclude: config
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect(),
|
||||
extend_ignore: config.extend_ignore,
|
||||
select: config
|
||||
|
@ -164,10 +162,8 @@ impl RawSettings {
|
|||
per_file_ignores: config
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.map(|pair| PerFileIgnore::new(pair, project_root))
|
||||
.collect(),
|
||||
project_root,
|
||||
pyproject,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -278,14 +274,18 @@ pub struct CurrentSettings {
|
|||
pub ignore: Vec<CheckCode>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub select: Vec<CheckCode>,
|
||||
pub target_version: PythonVersion,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub pyproject: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl CurrentSettings {
|
||||
pub fn from_settings(settings: RawSettings) -> Self {
|
||||
pub fn from_settings(
|
||||
settings: RawSettings,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
dummy_variable_rgx: settings.dummy_variable_rgx,
|
||||
exclude: settings
|
||||
|
@ -303,10 +303,10 @@ impl CurrentSettings {
|
|||
ignore: settings.ignore,
|
||||
line_length: settings.line_length,
|
||||
per_file_ignores: settings.per_file_ignores,
|
||||
project_root: settings.project_root,
|
||||
pyproject: settings.pyproject,
|
||||
select: settings.select,
|
||||
target_version: settings.target_version,
|
||||
project_root,
|
||||
pyproject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue