Add format setting to pyproject.toml (#964)

This commit is contained in:
Charlie Marsh 2022-11-29 19:22:23 -05:00 committed by GitHub
parent 7c344e8e4c
commit ced7868559
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 95 additions and 45 deletions

View file

@ -1440,6 +1440,25 @@ line-length = 120
--- ---
#### [`format`](#format)
The style in which violation messages should be formatted: `"text"` (default), `"grouped"`
(group messages by file), or `"json"` (machine-readable).
**Default value**: `"text"`
**Type**: `SerializationFormat`
**Example usage**:
```toml
[tool.ruff]
# Group violations by containing file.
format = "grouped"
```
---
#### [`per_file_ignores`](#per_file_ignores) #### [`per_file_ignores`](#per_file_ignores)
A list of mappings from file pattern to check code prefixes to exclude, when considering any A list of mappings from file pattern to check code prefixes to exclude, when considering any

View file

@ -251,6 +251,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: None, line_length: None,
per_file_ignores: None, per_file_ignores: None,
@ -291,6 +292,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: Some(100), line_length: Some(100),
per_file_ignores: None, per_file_ignores: None,
@ -331,6 +333,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: Some(100), line_length: Some(100),
per_file_ignores: None, per_file_ignores: None,
@ -371,6 +374,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: None, line_length: None,
per_file_ignores: None, per_file_ignores: None,
@ -411,6 +415,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: None, line_length: None,
per_file_ignores: None, per_file_ignores: None,
@ -459,6 +464,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: None, line_length: None,
per_file_ignores: None, per_file_ignores: None,
@ -534,6 +540,7 @@ mod tests {
external: None, external: None,
fix: None, fix: None,
fixable: None, fixable: None,
format: None,
ignore: Some(vec![]), ignore: Some(vec![]),
line_length: None, line_length: None,
per_file_ignores: None, per_file_ignores: None,

View file

@ -7,8 +7,9 @@ use rustc_hash::FxHashMap;
use crate::checks::CheckCode; use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel; use crate::logging::LogLevel;
use crate::printer::SerializationFormat; use crate::settings::types::{
use crate::settings::types::{FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion}; FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, about = "Ruff: An extremely fast Python linter.")] #[command(author, about = "Ruff: An extremely fast Python linter.")]
@ -77,8 +78,8 @@ pub struct Cli {
#[arg(long, value_delimiter = ',')] #[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<PatternPrefixPair>, pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages. /// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t = SerializationFormat::Text)] #[arg(long, value_enum)]
pub format: SerializationFormat, pub format: Option<SerializationFormat>,
/// Show violations with source code. /// Show violations with source code.
#[arg(long)] #[arg(long)]
pub show_source: bool, pub show_source: bool,
@ -143,8 +144,6 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
LogLevel::Quiet LogLevel::Quiet
} else if cli.verbose { } else if cli.verbose {
LogLevel::Verbose LogLevel::Verbose
} else if matches!(cli.format, SerializationFormat::Json) {
LogLevel::Quiet
} else { } else {
LogLevel::Default LogLevel::Default
} }

View file

@ -6,7 +6,7 @@ use walkdir::DirEntry;
use crate::checks::CheckCode; use crate::checks::CheckCode;
use crate::fs::iter_python_files; use crate::fs::iter_python_files;
use crate::printer::SerializationFormat; use crate::settings::types::SerializationFormat;
use crate::{Configuration, Settings}; use crate::{Configuration, Settings};
/// Print the user-facing configuration settings. /// Print the user-facing configuration settings.

View file

@ -23,8 +23,9 @@ use ::ruff::fs::iter_python_files;
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics}; use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
use ::ruff::logging::{set_up_logging, LogLevel}; use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::message::Message; use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat}; use ::ruff::printer::Printer;
use ::ruff::settings::configuration::Configuration; use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings}; use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")] #[cfg(feature = "update-informer")]
use ::ruff::updates; use ::ruff::updates;
@ -189,14 +190,7 @@ fn inner_main() -> Result<ExitCode> {
// Extract command-line arguments. // Extract command-line arguments.
let cli = Cli::parse(); let cli = Cli::parse();
let fix = cli.fix(); let fix = cli.fix();
let log_level = extract_log_level(&cli); let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
if let Some(code) = cli.explain {
commands::explain(&code, cli.format)?;
return Ok(ExitCode::SUCCESS);
}
if let Some(shell) = cli.generate_shell_completion { if let Some(shell) = cli.generate_shell_completion {
shell.generate(&mut Cli::command(), &mut std::io::stdout()); shell.generate(&mut Cli::command(), &mut std::io::stdout());
@ -205,17 +199,9 @@ fn inner_main() -> Result<ExitCode> {
// Find the project root and pyproject.toml. // Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&cli.files); let project_root = pyproject::find_project_root(&cli.files);
match &project_root {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
let pyproject = cli let pyproject = cli
.config .config
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref())); .or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
// Reconcile configuration from pyproject.toml and command-line arguments. // Reconcile configuration from pyproject.toml and command-line arguments.
let mut configuration = let mut configuration =
@ -247,6 +233,9 @@ fn inner_main() -> Result<ExitCode> {
if !cli.unfixable.is_empty() { if !cli.unfixable.is_empty() {
configuration.unfixable = cli.unfixable; configuration.unfixable = cli.unfixable;
} }
if let Some(format) = cli.format {
configuration.format = format;
}
if let Some(line_length) = cli.line_length { if let Some(line_length) = cli.line_length {
configuration.line_length = line_length; configuration.line_length = line_length;
} }
@ -279,6 +268,30 @@ fn inner_main() -> Result<ExitCode> {
let fix_enabled: bool = configuration.fix; let fix_enabled: bool = configuration.fix;
let settings = Settings::from_configuration(configuration, project_root.as_ref())?; let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
// If we're using JSON, override the log level.
let log_level = if matches!(settings.format, SerializationFormat::Json) {
LogLevel::Quiet
} else {
log_level
};
set_up_logging(&log_level)?;
// Now that we've inferred the appropriate log level, add some debug
// information.
match &project_root {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
if let Some(code) = cli.explain {
commands::explain(&code, settings.format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files { if cli.show_files {
commands::show_files(&cli.files, &settings); commands::show_files(&cli.files, &settings);
return Ok(ExitCode::SUCCESS); return Ok(ExitCode::SUCCESS);
@ -291,24 +304,21 @@ fn inner_main() -> Result<ExitCode> {
cache_enabled = false; cache_enabled = false;
} }
let printer = Printer::new(&cli.format, &log_level); let printer = Printer::new(&settings.format, &log_level);
if cli.watch { if cli.watch {
if settings.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
if fix_enabled { if fix_enabled {
eprintln!("Warning: --fix is not enabled in watch mode."); eprintln!("Warning: --fix is not enabled in watch mode.");
} }
if cli.add_noqa { if cli.add_noqa {
eprintln!("Warning: --no-qa is not enabled in watch mode."); eprintln!("Warning: --no-qa is not enabled in watch mode.");
} }
if cli.autoformat { if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode."); eprintln!("Warning: --autoformat is not enabled in watch mode.");
} }
if cli.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
// Perform an initial run instantly. // Perform an initial run instantly.
printer.clear_screen()?; printer.clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n"); printer.write_to_user("Starting linter in watch mode...\n");

View file

@ -4,7 +4,6 @@ use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions}; use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use anyhow::Result; use anyhow::Result;
use clap::ValueEnum;
use colored::Colorize; use colored::Colorize;
use itertools::iterate; use itertools::iterate;
use rustpython_parser::ast::Location; use rustpython_parser::ast::Location;
@ -15,15 +14,9 @@ use crate::fs::relativize_path;
use crate::linter::Diagnostics; use crate::linter::Diagnostics;
use crate::logging::LogLevel; use crate::logging::LogLevel;
use crate::message::Message; use crate::message::Message;
use crate::settings::types::SerializationFormat;
use crate::tell_user; use crate::tell_user;
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Debug)]
pub enum SerializationFormat {
Text,
Json,
Grouped,
}
#[derive(Serialize)] #[derive(Serialize)]
struct ExpandedMessage<'a> { struct ExpandedMessage<'a> {
kind: &'a CheckKind, kind: &'a CheckKind,

View file

@ -11,7 +11,7 @@ use regex::Regex;
use crate::checks_gen::{CheckCodePrefix, CATEGORIES}; use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
use crate::settings::pyproject::load_options; use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion}; use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{ use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe, flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming, pep8_naming,
@ -27,6 +27,7 @@ pub struct Configuration {
pub external: Vec<String>, pub external: Vec<String>,
pub fix: bool, pub fix: bool,
pub fixable: Vec<CheckCodePrefix>, pub fixable: Vec<CheckCodePrefix>,
pub format: SerializationFormat,
pub ignore: Vec<CheckCodePrefix>, pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize, pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>, pub per_file_ignores: Vec<PerFileIgnore>,
@ -121,6 +122,7 @@ impl Configuration {
fix: options.fix.unwrap_or_default(), fix: options.fix.unwrap_or_default(),
fixable: options.fixable.unwrap_or_else(|| CATEGORIES.to_vec()), fixable: options.fixable.unwrap_or_else(|| CATEGORIES.to_vec()),
unfixable: options.unfixable.unwrap_or_default(), unfixable: options.unfixable.unwrap_or_default(),
format: options.format.unwrap_or(SerializationFormat::Text),
ignore: options.ignore.unwrap_or_default(), ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88), line_length: options.line_length.unwrap_or(88),
per_file_ignores: options per_file_ignores: options

View file

@ -15,7 +15,7 @@ use rustc_hash::FxHashSet;
use crate::checks::CheckCode; use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity}; use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::settings::configuration::Configuration; use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion}; use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{ use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe, flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming, pep8_naming,
@ -34,6 +34,7 @@ pub struct Settings {
pub extend_exclude: GlobSet, pub extend_exclude: GlobSet,
pub external: BTreeSet<String>, pub external: BTreeSet<String>,
pub fixable: FxHashSet<CheckCode>, pub fixable: FxHashSet<CheckCode>,
pub format: SerializationFormat,
pub line_length: usize, pub line_length: usize,
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)>, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)>,
pub show_source: bool, pub show_source: bool,
@ -72,6 +73,7 @@ impl Settings {
extend_exclude: resolve_globset(config.extend_exclude, project_root)?, extend_exclude: resolve_globset(config.extend_exclude, project_root)?,
external: BTreeSet::from_iter(config.external), external: BTreeSet::from_iter(config.external),
fixable: resolve_codes(&config.fixable, &config.unfixable), fixable: resolve_codes(&config.fixable, &config.unfixable),
format: config.format,
flake8_annotations: config.flake8_annotations, flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear, flake8_bugbear: config.flake8_bugbear,
flake8_quotes: config.flake8_quotes, flake8_quotes: config.flake8_quotes,
@ -91,12 +93,14 @@ impl Settings {
Self { Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter([check_code.clone()]), enabled: FxHashSet::from_iter([check_code.clone()]),
fixable: FxHashSet::from_iter([check_code]),
exclude: GlobSet::empty(), exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(), extend_exclude: GlobSet::empty(),
external: BTreeSet::default(), external: BTreeSet::default(),
fixable: FxHashSet::from_iter([check_code]),
format: SerializationFormat::Text,
line_length: 88, line_length: 88,
per_file_ignores: vec![], per_file_ignores: vec![],
show_source: false,
src: vec![path_dedot::CWD.clone()], src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310, target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_annotations: flake8_annotations::settings::Settings::default(),
@ -106,7 +110,6 @@ impl Settings {
isort: isort::settings::Settings::default(), isort: isort::settings::Settings::default(),
mccabe: mccabe::settings::Settings::default(), mccabe: mccabe::settings::Settings::default(),
pep8_naming: pep8_naming::settings::Settings::default(), pep8_naming: pep8_naming::settings::Settings::default(),
show_source: false,
} }
} }
@ -114,12 +117,14 @@ impl Settings {
Self { Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter(check_codes.clone()), enabled: FxHashSet::from_iter(check_codes.clone()),
fixable: FxHashSet::from_iter(check_codes),
exclude: GlobSet::empty(), exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(), extend_exclude: GlobSet::empty(),
external: BTreeSet::default(), external: BTreeSet::default(),
fixable: FxHashSet::from_iter(check_codes),
format: SerializationFormat::Text,
line_length: 88, line_length: 88,
per_file_ignores: vec![], per_file_ignores: vec![],
show_source: false,
src: vec![path_dedot::CWD.clone()], src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310, target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_annotations: flake8_annotations::settings::Settings::default(),
@ -129,7 +134,6 @@ impl Settings {
isort: isort::settings::Settings::default(), isort: isort::settings::Settings::default(),
mccabe: mccabe::settings::Settings::default(), mccabe: mccabe::settings::Settings::default(),
pep8_naming: pep8_naming::settings::Settings::default(), pep8_naming: pep8_naming::settings::Settings::default(),
show_source: false,
} }
} }
} }

View file

@ -4,7 +4,7 @@ use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::PythonVersion; use crate::settings::types::{PythonVersion, SerializationFormat};
use crate::{ use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe, flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
pep8_naming, pep8_naming,
@ -21,6 +21,7 @@ pub struct Options {
pub external: Option<Vec<String>>, pub external: Option<Vec<String>>,
pub fix: Option<bool>, pub fix: Option<bool>,
pub fixable: Option<Vec<CheckCodePrefix>>, pub fixable: Option<Vec<CheckCodePrefix>>,
pub format: Option<SerializationFormat>,
pub ignore: Option<Vec<CheckCodePrefix>>, pub ignore: Option<Vec<CheckCodePrefix>>,
pub line_length: Option<usize>, pub line_length: Option<usize>,
pub select: Option<Vec<CheckCodePrefix>>, pub select: Option<Vec<CheckCodePrefix>>,

View file

@ -148,6 +148,7 @@ mod tests {
show_source: None, show_source: None,
src: None, src: None,
target_version: None, target_version: None,
format: None,
unfixable: None, unfixable: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
@ -186,6 +187,7 @@ line-length = 79
show_source: None, show_source: None,
src: None, src: None,
target_version: None, target_version: None,
format: None,
unfixable: None, unfixable: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
@ -219,6 +221,7 @@ exclude = ["foo.py"]
ignore: None, ignore: None,
extend_ignore: None, extend_ignore: None,
fixable: None, fixable: None,
format: None,
unfixable: None, unfixable: None,
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
@ -262,6 +265,7 @@ select = ["E501"]
show_source: None, show_source: None,
src: None, src: None,
target_version: None, target_version: None,
format: None,
unfixable: None, unfixable: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
@ -301,6 +305,7 @@ ignore = ["E501"]
show_source: None, show_source: None,
src: None, src: None,
target_version: None, target_version: None,
format: None,
unfixable: None, unfixable: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
@ -378,6 +383,7 @@ other-attribute = 1
ignore: None, ignore: None,
extend_ignore: None, extend_ignore: None,
fixable: None, fixable: None,
format: None,
unfixable: None, unfixable: None,
per_file_ignores: Some(FxHashMap::from_iter([( per_file_ignores: Some(FxHashMap::from_iter([(
"__init__.py".to_string(), "__init__.py".to_string(),

View file

@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use clap::ValueEnum;
use globset::{Glob, GlobSetBuilder}; use globset::{Glob, GlobSetBuilder};
use serde::{de, Deserialize, Deserializer, Serialize}; use serde::{de, Deserialize, Deserializer, Serialize};
@ -141,3 +142,11 @@ impl FromStr for PatternPrefixPair {
Ok(Self { pattern, prefix }) Ok(Self { pattern, prefix })
} }
} }
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum SerializationFormat {
Text,
Json,
Grouped,
}