From e9cbfabe0b4849476ebf917cb7d1b2188633396f Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 20 Sep 2023 14:22:11 +0200 Subject: [PATCH 1/2] Introduce `FormatterSettings` --- Cargo.lock | 6 +++ crates/ruff_cache/src/cache_key.rs | 32 +++++++++--- crates/ruff_cli/src/commands/format.rs | 17 +------ crates/ruff_cli/src/commands/format_stdin.rs | 20 ++------ crates/ruff_dev/src/format_dev.rs | 7 +-- crates/ruff_formatter/Cargo.toml | 2 + crates/ruff_formatter/src/lib.rs | 11 +++- .../src/printer/printer_options/mod.rs | 3 +- crates/ruff_python_formatter/Cargo.toml | 2 + crates/ruff_python_formatter/src/lib.rs | 2 + crates/ruff_python_formatter/src/options.rs | 9 ++-- crates/ruff_python_formatter/src/settings.rs | 49 ++++++++++++++++++ crates/ruff_wasm/src/lib.rs | 19 +++---- crates/ruff_workspace/Cargo.toml | 2 + crates/ruff_workspace/src/configuration.rs | 50 +++++++++++++------ crates/ruff_workspace/src/settings.rs | 3 ++ 16 files changed, 159 insertions(+), 75 deletions(-) create mode 100644 crates/ruff_python_formatter/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index d202d037e9be9..a3de902a409c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2178,6 +2178,8 @@ version = "0.0.0" dependencies = [ "drop_bomb", "insta", + "ruff_cache", + "ruff_macros", "ruff_text_size", "rustc-hash", "schemars", @@ -2336,7 +2338,9 @@ dependencies = [ "itertools 0.11.0", "memchr", "once_cell", + "ruff_cache", "ruff_formatter", + "ruff_macros", "ruff_python_ast", "ruff_python_index", "ruff_python_parser", @@ -2528,8 +2532,10 @@ dependencies = [ "pep440_rs", "regex", "ruff_cache", + "ruff_formatter", "ruff_linter", "ruff_macros", + "ruff_python_formatter", "rustc-hash", "schemars", "serde", diff --git a/crates/ruff_cache/src/cache_key.rs b/crates/ruff_cache/src/cache_key.rs index ccc0e41c0e654..076d1e42d778a 100644 --- a/crates/ruff_cache/src/cache_key.rs +++ b/crates/ruff_cache/src/cache_key.rs @@ -1,7 +1,10 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::hash::{Hash, Hasher}; -use std::num::NonZeroU8; +use std::num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, + NonZeroU32, NonZeroU64, NonZeroU8, +}; use std::path::{Path, PathBuf}; use glob::Pattern; @@ -177,14 +180,29 @@ impl CacheKey for i8 { state.write_i8(*self); } } - -impl CacheKey for NonZeroU8 { - #[inline] - fn cache_key(&self, state: &mut CacheKeyHasher) { - state.write_u8(self.get()); - } +macro_rules! impl_cache_key_non_zero { + ($name:ident) => { + impl CacheKey for $name { + #[inline] + fn cache_key(&self, state: &mut CacheKeyHasher) { + self.get().cache_key(state) + } + } + }; } +impl_cache_key_non_zero!(NonZeroU8); +impl_cache_key_non_zero!(NonZeroU16); +impl_cache_key_non_zero!(NonZeroU32); +impl_cache_key_non_zero!(NonZeroU64); +impl_cache_key_non_zero!(NonZeroU128); + +impl_cache_key_non_zero!(NonZeroI8); +impl_cache_key_non_zero!(NonZeroI16); +impl_cache_key_non_zero!(NonZeroI32); +impl_cache_key_non_zero!(NonZeroI64); +impl_cache_key_non_zero!(NonZeroI128); + macro_rules! impl_cache_key_tuple { () => ( impl CacheKey for () { diff --git a/crates/ruff_cli/src/commands/format.rs b/crates/ruff_cli/src/commands/format.rs index e90b0bbb7e0b2..1cf389062e278 100644 --- a/crates/ruff_cli/src/commands/format.rs +++ b/crates/ruff_cli/src/commands/format.rs @@ -1,6 +1,5 @@ use std::fmt::{Display, Formatter}; use std::io; -use std::num::NonZeroU16; use std::path::{Path, PathBuf}; use std::time::Instant; @@ -10,12 +9,10 @@ use log::error; use rayon::iter::Either::{Left, Right}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use thiserror::Error; -use tracing::{debug, warn}; +use tracing::debug; -use ruff_formatter::LineWidth; use ruff_linter::fs; use ruff_linter::logging::LogLevel; -use ruff_linter::settings::types::PreviewMode; use ruff_linter::warn_user_once; use ruff_python_ast::{PySourceType, SourceType}; use ruff_python_formatter::{format_module, FormatModuleError, PyFormatOptions}; @@ -76,17 +73,7 @@ pub(crate) fn format( }; let resolved_settings = resolver.resolve(path, &pyproject_config); - - // TODO(micha): Use `formatter` settings instead - let preview = match resolved_settings.linter.preview { - PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled, - PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled, - }; - let line_length = resolved_settings.linter.line_length; - - let options = PyFormatOptions::from_source_type(source_type) - .with_line_width(LineWidth::from(NonZeroU16::from(line_length))) - .with_preview(preview); + let options = resolved_settings.formatter.to_format_options(source_type); debug!("Formatting {} with {:?}", path.display(), options); Some(match catch_unwind(|| format_path(path, options, mode)) { diff --git a/crates/ruff_cli/src/commands/format_stdin.rs b/crates/ruff_cli/src/commands/format_stdin.rs index b5e8009ad4d60..b428d3d14724d 100644 --- a/crates/ruff_cli/src/commands/format_stdin.rs +++ b/crates/ruff_cli/src/commands/format_stdin.rs @@ -1,12 +1,10 @@ use std::io::{stdout, Write}; -use std::num::NonZeroU16; use std::path::Path; use anyhow::Result; use log::warn; -use ruff_formatter::LineWidth; -use ruff_linter::settings::types::PreviewMode; +use ruff_python_ast::PySourceType; use ruff_python_formatter::{format_module, PyFormatOptions}; use ruff_workspace::resolver::python_file_at_path; @@ -39,18 +37,10 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R // Format the file. let path = cli.stdin_filename.as_deref(); - // TODO(micha): Use Formatter settings - let preview = match pyproject_config.settings.linter.preview { - PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled, - PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled, - }; - let line_length = pyproject_config.settings.linter.line_length; - - let options = path - .map(PyFormatOptions::from_extension) - .unwrap_or_default() - .with_line_width(LineWidth::from(NonZeroU16::from(line_length))) - .with_preview(preview); + let options = pyproject_config + .settings + .formatter + .to_format_options(path.map(PySourceType::from).unwrap_or_default()); match format_source(path, options, mode) { Ok(result) => match mode { diff --git a/crates/ruff_dev/src/format_dev.rs b/crates/ruff_dev/src/format_dev.rs index fb6d4650d3ca9..3934a11958ef0 100644 --- a/crates/ruff_dev/src/format_dev.rs +++ b/crates/ruff_dev/src/format_dev.rs @@ -18,7 +18,6 @@ use imara_diff::{diff, Algorithm}; use indicatif::ProgressStyle; #[cfg_attr(feature = "singlethreaded", allow(unused_imports))] use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use ruff_linter::line_width::LineLength; use serde::Deserialize; use similar::{ChangeTag, TextDiff}; use tempfile::NamedTempFile; @@ -551,10 +550,8 @@ fn format_dir_entry( let settings = resolver.resolve(&path, pyproject_config); // That's a bad way of doing this but it's not worth doing something better for format_dev // TODO(micha) use formatter settings instead - if settings.linter.line_length != LineLength::default() { - options = options.with_line_width(LineWidth::from(NonZeroU16::from( - settings.linter.line_length, - ))); + if settings.formatter.line_width != LineWidth::default() { + options = options.with_line_width(settings.formatter.line_width); } // Handle panics (mostly in `debug_assert!`) diff --git a/crates/ruff_formatter/Cargo.toml b/crates/ruff_formatter/Cargo.toml index 88f5bbf892df4..8e8a433294123 100644 --- a/crates/ruff_formatter/Cargo.toml +++ b/crates/ruff_formatter/Cargo.toml @@ -11,6 +11,8 @@ repository = { workspace = true } license = { workspace = true } [dependencies] +ruff_cache = { path = "../ruff_cache" } +ruff_macros = { path = "../ruff_macros" } ruff_text_size = { path = "../ruff_text_size" } drop_bomb = { version = "0.1.5" } diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index 3ef9b94e61de2..63c4a5a6042a2 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -51,9 +51,10 @@ pub use source_code::{SourceCode, SourceCodeSlice}; pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, PrintError}; pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS}; pub use group_id::GroupId; +use ruff_macros::CacheKey; use ruff_text_size::{TextRange, TextSize}; -#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Default)] @@ -116,8 +117,14 @@ impl TryFrom for IndentWidth { } } +impl From for IndentWidth { + fn from(value: NonZeroU8) -> Self { + Self(value) + } +} + /// The maximum visual width to which the formatter should try to limit a line. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct LineWidth(NonZeroU16); diff --git a/crates/ruff_formatter/src/printer/printer_options/mod.rs b/crates/ruff_formatter/src/printer/printer_options/mod.rs index efbe850cbf22e..e3fca43c2bee8 100644 --- a/crates/ruff_formatter/src/printer/printer_options/mod.rs +++ b/crates/ruff_formatter/src/printer/printer_options/mod.rs @@ -1,4 +1,5 @@ use crate::{FormatOptions, IndentStyle, IndentWidth, LineWidth}; +use ruff_macros::CacheKey; /// Options that affect how the [`crate::Printer`] prints the format tokens #[derive(Clone, Debug, Eq, PartialEq, Default)] @@ -120,7 +121,7 @@ impl SourceMapGeneration { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum LineEnding { /// Line Feed only (\n), common on Linux and macOS as well as inside git repos diff --git a/crates/ruff_python_formatter/Cargo.toml b/crates/ruff_python_formatter/Cargo.toml index 8e7a3b55ad648..639e350d1f6d3 100644 --- a/crates/ruff_python_formatter/Cargo.toml +++ b/crates/ruff_python_formatter/Cargo.toml @@ -11,7 +11,9 @@ repository = { workspace = true } license = { workspace = true } [dependencies] +ruff_cache = { path = "../ruff_cache" } ruff_formatter = { path = "../ruff_formatter" } +ruff_macros = { path = "../ruff_macros" } ruff_python_trivia = { path = "../ruff_python_trivia" } ruff_source_file = { path = "../ruff_source_file" } ruff_python_ast = { path = "../ruff_python_ast" } diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 5ba8e3dde0278..09d3a07d7858f 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -17,6 +17,7 @@ use crate::comments::{ pub use crate::context::PyFormatContext; pub use crate::options::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle}; use crate::verbatim::suppressed_node; +pub use settings::FormatterSettings; pub(crate) mod builders; pub mod cli; @@ -29,6 +30,7 @@ mod options; pub(crate) mod other; pub(crate) mod pattern; mod prelude; +mod settings; pub(crate) mod statement; pub(crate) mod type_param; mod verbatim; diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs index 457b2f9884c4e..7d0ae3d0b218f 100644 --- a/crates/ruff_python_formatter/src/options.rs +++ b/crates/ruff_python_formatter/src/options.rs @@ -1,9 +1,12 @@ use ruff_formatter::printer::{LineEnding, PrinterOptions, SourceMapGeneration}; use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth}; +use ruff_macros::CacheKey; use ruff_python_ast::PySourceType; use std::path::Path; use std::str::FromStr; +/// Resolved options for formatting one individual file. This is different from [`crate::FormatterSettings`] which +/// represents the formatting settings for multiple files (the whole project, a subdirectory, ...) #[derive(Clone, Debug)] #[cfg_attr( feature = "serde", @@ -176,7 +179,7 @@ impl FormatOptions for PyFormatOptions { } } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, CacheKey)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -230,7 +233,7 @@ impl FromStr for QuoteStyle { } } -#[derive(Copy, Clone, Debug, Default)] +#[derive(Copy, Clone, Debug, Default, CacheKey)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -261,7 +264,7 @@ impl FromStr for MagicTrailingComma { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] pub enum PreviewMode { diff --git a/crates/ruff_python_formatter/src/settings.rs b/crates/ruff_python_formatter/src/settings.rs new file mode 100644 index 0000000000000..4a9f42d20afe0 --- /dev/null +++ b/crates/ruff_python_formatter/src/settings.rs @@ -0,0 +1,49 @@ +use std::path::PathBuf; + +use ruff_formatter::{FormatOptions, IndentStyle, LineWidth}; +use ruff_macros::CacheKey; +use ruff_python_ast::PySourceType; + +use crate::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle}; + +#[derive(CacheKey, Clone, Debug)] +pub struct FormatterSettings { + /// The files that are excluded from formatting (but may be linted). + pub exclude: Vec, + + pub preview: PreviewMode, + + pub line_width: LineWidth, + + pub indent_style: IndentStyle, + + pub quote_style: QuoteStyle, + + pub magic_trailing_comma: MagicTrailingComma, +} + +impl FormatterSettings { + pub fn to_format_options(&self, source_type: PySourceType) -> PyFormatOptions { + PyFormatOptions::from_source_type(source_type) + .with_indent_style(self.indent_style) + .with_quote_style(self.quote_style) + .with_magic_trailing_comma(self.magic_trailing_comma) + .with_preview(self.preview) + .with_line_width(self.line_width) + } +} + +impl Default for FormatterSettings { + fn default() -> Self { + let default_options = PyFormatOptions::default(); + + Self { + exclude: Vec::default(), + preview: PreviewMode::Disabled, + line_width: default_options.line_width(), + indent_style: default_options.indent_style(), + quote_style: default_options.quote_style(), + magic_trailing_comma: default_options.magic_trailing_comma(), + } + } +} diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 22774f27ba1ca..e30407af5aab4 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -1,21 +1,20 @@ -use std::num::NonZeroU16; use std::path::Path; use js_sys::Error; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -use ruff_formatter::{FormatResult, Formatted, LineWidth}; +use ruff_formatter::{FormatResult, Formatted}; use ruff_linter::directives; use ruff_linter::line_width::{LineLength, TabSize}; use ruff_linter::linter::{check_path, LinterResult}; use ruff_linter::registry::AsRule; -use ruff_linter::settings::types::{PreviewMode, PythonVersion}; +use ruff_linter::settings::types::PythonVersion; use ruff_linter::settings::{flags, DUMMY_VARIABLE_RGX, PREFIXES}; use ruff_linter::source_kind::SourceKind; use ruff_python_ast::{Mod, PySourceType}; use ruff_python_codegen::Stylist; -use ruff_python_formatter::{format_node, pretty_comments, PyFormatContext, PyFormatOptions}; +use ruff_python_formatter::{format_node, pretty_comments, PyFormatContext}; use ruff_python_index::{CommentRangesBuilder, Indexer}; use ruff_python_parser::lexer::LexResult; use ruff_python_parser::{parse_tokens, AsMode, Mode}; @@ -302,15 +301,9 @@ impl<'a> ParsedModule<'a> { fn format(&self, settings: &Settings) -> FormatResult> { // TODO(konstin): Add an options for py/pyi to the UI (2/2) - // TODO(micha): Use formatter settings instead - let options = PyFormatOptions::from_source_type(PySourceType::default()) - .with_preview(match settings.linter.preview { - PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled, - PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled, - }) - .with_line_width(LineWidth::from(NonZeroU16::from( - settings.linter.line_length, - ))); + let options = settings + .formatter + .to_format_options(PySourceType::default()); format_node( &self.module, diff --git a/crates/ruff_workspace/Cargo.toml b/crates/ruff_workspace/Cargo.toml index dd741254eb25e..3be2dca20fbdb 100644 --- a/crates/ruff_workspace/Cargo.toml +++ b/crates/ruff_workspace/Cargo.toml @@ -14,6 +14,8 @@ license = { workspace = true } [dependencies] ruff_linter = { path = "../ruff_linter" } +ruff_formatter = { path = "../ruff_formatter" } +ruff_python_formatter = { path = "../ruff_python_formatter" } ruff_cache = { path = "../ruff_cache" } ruff_macros = { path = "../ruff_macros" } diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index c1445276f7023..a71a445fdb619 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -4,21 +4,19 @@ use std::borrow::Cow; use std::env::VarError; +use std::num::NonZeroU16; use std::path::{Path, PathBuf}; -use crate::options::{ - Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, - Flake8ComprehensionsOptions, Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions, - Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions, - Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions, - Flake8UnusedArgumentsOptions, IsortOptions, McCabeOptions, Options, Pep8NamingOptions, - PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions, PyflakesOptions, PylintOptions, -}; -use crate::settings::{FileResolverSettings, Settings, EXCLUDE, INCLUDE}; use anyhow::{anyhow, Result}; use glob::{glob, GlobError, Paths, PatternError}; use regex::Regex; +use rustc_hash::{FxHashMap, FxHashSet}; +use shellexpand; +use shellexpand::LookupError; +use strum::IntoEnumIterator; + use ruff_cache::cache_dir; +use ruff_formatter::{IndentStyle, LineWidth}; use ruff_linter::line_width::{LineLength, TabSize}; use ruff_linter::registry::RuleNamespace; use ruff_linter::registry::{Rule, RuleSet, INCOMPATIBLE_CODES}; @@ -34,10 +32,17 @@ use ruff_linter::settings::{ use ruff_linter::{ fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION, }; -use rustc_hash::{FxHashMap, FxHashSet}; -use shellexpand; -use shellexpand::LookupError; -use strum::IntoEnumIterator; +use ruff_python_formatter::{FormatterSettings, MagicTrailingComma, QuoteStyle}; + +use crate::options::{ + Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, + Flake8ComprehensionsOptions, Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions, + Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions, + Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions, + Flake8UnusedArgumentsOptions, IsortOptions, McCabeOptions, Options, Pep8NamingOptions, + PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions, PyflakesOptions, PylintOptions, +}; +use crate::settings::{FileResolverSettings, Settings, EXCLUDE, INCLUDE}; #[derive(Debug, Default)] pub struct RuleSelection { @@ -149,6 +154,7 @@ impl Configuration { respect_gitignore: self.respect_gitignore.unwrap_or(true), project_root: project_root.to_path_buf(), }, + linter: LinterSettings { target_version, project_root: project_root.to_path_buf(), @@ -283,6 +289,21 @@ impl Configuration { .map(PyUpgradeOptions::into_settings) .unwrap_or_default(), }, + + formatter: FormatterSettings { + exclude: vec![], + preview: self + .preview + .map(|preview| match preview { + PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled, + PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled, + }) + .unwrap_or_default(), + line_width: LineWidth::from(NonZeroU16::from(self.line_length.unwrap_or_default())), + indent_style: IndentStyle::default(), + quote_style: QuoteStyle::default(), + magic_trailing_comma: MagicTrailingComma::default(), + }, }) } @@ -805,12 +826,13 @@ pub fn resolve_src(src: &[String], project_root: &Path) -> Result> #[cfg(test)] mod tests { - use crate::configuration::{Configuration, RuleSelection}; use ruff_linter::codes::{Flake8Copyright, Pycodestyle, Refurb}; use ruff_linter::registry::{Linter, Rule, RuleSet}; use ruff_linter::settings::types::PreviewMode; use ruff_linter::RuleSelector; + use crate::configuration::{Configuration, RuleSelection}; + const NURSERY_RULES: &[Rule] = &[ Rule::MissingCopyrightNotice, Rule::IndentationWithInvalidMultiple, diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index f166a62ff4afc..83c1d6ab65698 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -3,6 +3,7 @@ use ruff_cache::cache_dir; use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFormat}; use ruff_linter::settings::LinterSettings; use ruff_macros::CacheKey; +use ruff_python_formatter::FormatterSettings; use std::path::{Path, PathBuf}; #[derive(Debug, CacheKey)] @@ -23,6 +24,7 @@ pub struct Settings { pub file_resolver: FileResolverSettings, pub linter: LinterSettings, + pub formatter: FormatterSettings, } impl Default for Settings { @@ -37,6 +39,7 @@ impl Default for Settings { show_source: false, linter: LinterSettings::new(project_root), file_resolver: FileResolverSettings::new(project_root), + formatter: FormatterSettings::default(), } } } From c0b2a72aa8c06a7643a1dd6e82670e541274200d Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 20 Sep 2023 16:34:10 +0200 Subject: [PATCH 2/2] Create `LintConfiguration` --- crates/ruff_cli/src/args.rs | 6 +- crates/ruff_workspace/src/configuration.rs | 405 ++++++++++++--------- 2 files changed, 236 insertions(+), 175 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 887f935d026cb..16bb9ec5f43d1 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -610,7 +610,7 @@ impl ConfigurationTransformer for CliOverrides { config.cache_dir = Some(cache_dir.clone()); } if let Some(dummy_variable_rgx) = &self.dummy_variable_rgx { - config.dummy_variable_rgx = Some(dummy_variable_rgx.clone()); + config.lint.dummy_variable_rgx = Some(dummy_variable_rgx.clone()); } if let Some(exclude) = &self.exclude { config.exclude = Some(exclude.clone()); @@ -624,7 +624,7 @@ impl ConfigurationTransformer for CliOverrides { if let Some(fix_only) = &self.fix_only { config.fix_only = Some(*fix_only); } - config.rule_selections.push(RuleSelection { + config.lint.rule_selections.push(RuleSelection { select: self.select.clone(), ignore: self .ignore @@ -657,7 +657,7 @@ impl ConfigurationTransformer for CliOverrides { config.preview = Some(*preview); } if let Some(per_file_ignores) = &self.per_file_ignores { - config.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone())); + config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone())); } if let Some(respect_gitignore) = &self.respect_gitignore { config.respect_gitignore = Some(*respect_gitignore); diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index a71a445fdb619..a30972f18952e 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -56,63 +56,36 @@ pub struct RuleSelection { #[derive(Debug, Default)] pub struct Configuration { - pub rule_selections: Vec, - pub per_file_ignores: Option>, - - pub allowed_confusables: Option>, - pub builtins: Option>, + // Global options pub cache_dir: Option, - pub dummy_variable_rgx: Option, + pub output_format: Option, + pub fix: Option, + pub fix_only: Option, + pub show_fixes: Option, + pub show_source: Option, + pub required_version: Option, + pub preview: Option, + + // File resolver options pub exclude: Option>, pub extend: Option, pub extend_exclude: Vec, pub extend_include: Vec, - pub extend_per_file_ignores: Vec, - pub external: Option>, - pub fix: Option, - pub fix_only: Option, pub force_exclude: Option, - pub output_format: Option, - pub ignore_init_module_imports: Option, - pub include: Option>, - pub line_length: Option, - pub logger_objects: Option>, - pub namespace_packages: Option>, - pub preview: Option, - pub required_version: Option, pub respect_gitignore: Option, - pub show_fixes: Option, - pub show_source: Option, - pub src: Option>, - pub tab_size: Option, + + // Generic python options settings + pub builtins: Option>, + pub namespace_packages: Option>, pub target_version: Option, - pub task_tags: Option>, + pub src: Option>, pub typing_modules: Option>, - // Plugins - pub flake8_annotations: Option, - pub flake8_bandit: Option, - pub flake8_bugbear: Option, - pub flake8_builtins: Option, - pub flake8_comprehensions: Option, - pub flake8_copyright: Option, - pub flake8_errmsg: Option, - pub flake8_gettext: Option, - pub flake8_implicit_str_concat: Option, - pub flake8_import_conventions: Option, - pub flake8_pytest_style: Option, - pub flake8_quotes: Option, - pub flake8_self: Option, - pub flake8_tidy_imports: Option, - pub flake8_type_checking: Option, - pub flake8_unused_arguments: Option, - pub isort: Option, - pub mccabe: Option, - pub pep8_naming: Option, - pub pycodestyle: Option, - pub pydocstyle: Option, - pub pyflakes: Option, - pub pylint: Option, - pub pyupgrade: Option, + + // Global formatting options + pub line_length: Option, + pub tab_size: Option, + + pub lint: LintConfiguration, } impl Configuration { @@ -128,7 +101,8 @@ impl Configuration { } let target_version = self.target_version.unwrap_or_default(); - let rules = self.as_rule_table(); + let preview = self.preview.unwrap_or_default(); + let rules = self.lint.as_rule_table(preview); Ok(Settings { cache_dir: self @@ -149,7 +123,7 @@ impl Configuration { extend_include: FilePatternSet::try_from_iter(self.extend_include)?, force_exclude: self.force_exclude.unwrap_or(false), include: FilePatternSet::try_from_iter( - self.include.unwrap_or_else(|| INCLUDE.to_vec()), + self.lint.include.unwrap_or_else(|| INCLUDE.to_vec()), )?, respect_gitignore: self.respect_gitignore.unwrap_or(true), project_root: project_root.to_path_buf(), @@ -160,131 +134,162 @@ impl Configuration { project_root: project_root.to_path_buf(), rules, allowed_confusables: self + .lint .allowed_confusables .map(FxHashSet::from_iter) .unwrap_or_default(), builtins: self.builtins.unwrap_or_default(), dummy_variable_rgx: self + .lint .dummy_variable_rgx .unwrap_or_else(|| DUMMY_VARIABLE_RGX.clone()), - external: FxHashSet::from_iter(self.external.unwrap_or_default()), - ignore_init_module_imports: self.ignore_init_module_imports.unwrap_or_default(), + external: FxHashSet::from_iter(self.lint.external.unwrap_or_default()), + ignore_init_module_imports: self + .lint + .ignore_init_module_imports + .unwrap_or_default(), line_length: self.line_length.unwrap_or_default(), tab_size: self.tab_size.unwrap_or_default(), namespace_packages: self.namespace_packages.unwrap_or_default(), per_file_ignores: resolve_per_file_ignores( - self.per_file_ignores + self.lint + .per_file_ignores .unwrap_or_default() .into_iter() - .chain(self.extend_per_file_ignores) + .chain(self.lint.extend_per_file_ignores) .collect(), )?, src: self.src.unwrap_or_else(|| vec![project_root.to_path_buf()]), task_tags: self + .lint .task_tags .unwrap_or_else(|| TASK_TAGS.iter().map(ToString::to_string).collect()), - logger_objects: self.logger_objects.unwrap_or_default(), + logger_objects: self.lint.logger_objects.unwrap_or_default(), preview: self.preview.unwrap_or_default(), typing_modules: self.typing_modules.unwrap_or_default(), // Plugins flake8_annotations: self + .lint .flake8_annotations .map(Flake8AnnotationsOptions::into_settings) .unwrap_or_default(), flake8_bandit: self + .lint .flake8_bandit .map(Flake8BanditOptions::into_settings) .unwrap_or_default(), flake8_bugbear: self + .lint .flake8_bugbear .map(Flake8BugbearOptions::into_settings) .unwrap_or_default(), flake8_builtins: self + .lint .flake8_builtins .map(Flake8BuiltinsOptions::into_settings) .unwrap_or_default(), flake8_comprehensions: self + .lint .flake8_comprehensions .map(Flake8ComprehensionsOptions::into_settings) .unwrap_or_default(), flake8_copyright: self + .lint .flake8_copyright .map(Flake8CopyrightOptions::try_into_settings) .transpose()? .unwrap_or_default(), flake8_errmsg: self + .lint .flake8_errmsg .map(Flake8ErrMsgOptions::into_settings) .unwrap_or_default(), flake8_implicit_str_concat: self + .lint .flake8_implicit_str_concat .map(Flake8ImplicitStrConcatOptions::into_settings) .unwrap_or_default(), flake8_import_conventions: self + .lint .flake8_import_conventions .map(Flake8ImportConventionsOptions::into_settings) .unwrap_or_default(), flake8_pytest_style: self + .lint .flake8_pytest_style .map(Flake8PytestStyleOptions::try_into_settings) .transpose()? .unwrap_or_default(), flake8_quotes: self + .lint .flake8_quotes .map(Flake8QuotesOptions::into_settings) .unwrap_or_default(), flake8_self: self + .lint .flake8_self .map(Flake8SelfOptions::into_settings) .unwrap_or_default(), flake8_tidy_imports: self + .lint .flake8_tidy_imports .map(Flake8TidyImportsOptions::into_settings) .unwrap_or_default(), flake8_type_checking: self + .lint .flake8_type_checking .map(Flake8TypeCheckingOptions::into_settings) .unwrap_or_default(), flake8_unused_arguments: self + .lint .flake8_unused_arguments .map(Flake8UnusedArgumentsOptions::into_settings) .unwrap_or_default(), flake8_gettext: self + .lint .flake8_gettext .map(Flake8GetTextOptions::into_settings) .unwrap_or_default(), isort: self + .lint .isort .map(IsortOptions::try_into_settings) .transpose()? .unwrap_or_default(), mccabe: self + .lint .mccabe .map(McCabeOptions::into_settings) .unwrap_or_default(), pep8_naming: self + .lint .pep8_naming .map(Pep8NamingOptions::try_into_settings) .transpose()? .unwrap_or_default(), pycodestyle: self + .lint .pycodestyle .map(PycodestyleOptions::into_settings) .unwrap_or_default(), pydocstyle: self + .lint .pydocstyle .map(PydocstyleOptions::into_settings) .unwrap_or_default(), pyflakes: self + .lint .pyflakes .map(PyflakesOptions::into_settings) .unwrap_or_default(), pylint: self + .lint .pylint .map(PylintOptions::into_settings) .unwrap_or_default(), pyupgrade: self + .lint .pyupgrade .map(PyUpgradeOptions::into_settings) .unwrap_or_default(), @@ -309,25 +314,6 @@ impl Configuration { pub fn from_options(options: Options, project_root: &Path) -> Result { Ok(Self { - rule_selections: vec![RuleSelection { - select: options.select, - ignore: options - .ignore - .into_iter() - .flatten() - .chain(options.extend_ignore.into_iter().flatten()) - .collect(), - extend_select: options.extend_select.unwrap_or_default(), - fixable: options.fixable, - unfixable: options - .unfixable - .into_iter() - .flatten() - .chain(options.extend_unfixable.into_iter().flatten()) - .collect(), - extend_fixable: options.extend_fixable.unwrap_or_default(), - }], - allowed_confusables: options.allowed_confusables, builtins: options.builtins, cache_dir: options .cache_dir @@ -337,11 +323,7 @@ impl Configuration { }) .transpose() .map_err(|e| anyhow!("Invalid `cache-dir` value: {e}"))?, - dummy_variable_rgx: options - .dummy_variable_rgx - .map(|pattern| Regex::new(&pattern)) - .transpose() - .map_err(|e| anyhow!("Invalid `dummy-variable-rgx` value: {e}"))?, + exclude: options.exclude.map(|paths| { paths .into_iter() @@ -383,32 +365,11 @@ impl Configuration { .collect() }) .unwrap_or_default(), - extend_per_file_ignores: options - .extend_per_file_ignores - .map(|per_file_ignores| { - per_file_ignores - .into_iter() - .map(|(pattern, prefixes)| { - PerFileIgnore::new(pattern, &prefixes, Some(project_root)) - }) - .collect() - }) - .unwrap_or_default(), - external: options.external, + fix: options.fix, fix_only: options.fix_only, output_format: options.output_format.or(options.format), force_exclude: options.force_exclude, - ignore_init_module_imports: options.ignore_init_module_imports, - include: options.include.map(|paths| { - paths - .into_iter() - .map(|pattern| { - let absolute = fs::normalize_path_to(&pattern, project_root); - FilePattern::User(pattern, absolute) - }) - .collect() - }), line_length: options.line_length, tab_size: options.tab_size, namespace_packages: options @@ -416,14 +377,6 @@ impl Configuration { .map(|namespace_package| resolve_src(&namespace_package, project_root)) .transpose()?, preview: options.preview.map(PreviewMode::from), - per_file_ignores: options.per_file_ignores.map(|per_file_ignores| { - per_file_ignores - .into_iter() - .map(|(pattern, prefixes)| { - PerFileIgnore::new(pattern, &prefixes, Some(project_root)) - }) - .collect() - }), required_version: options.required_version, respect_gitignore: options.respect_gitignore, show_source: options.show_source, @@ -433,40 +386,176 @@ impl Configuration { .map(|src| resolve_src(&src, project_root)) .transpose()?, target_version: options.target_version, - task_tags: options.task_tags, - logger_objects: options.logger_objects, typing_modules: options.typing_modules, - // Plugins - flake8_annotations: options.flake8_annotations, - flake8_bandit: options.flake8_bandit, - flake8_bugbear: options.flake8_bugbear, - flake8_builtins: options.flake8_builtins, - flake8_comprehensions: options.flake8_comprehensions, - flake8_copyright: options.flake8_copyright, - flake8_errmsg: options.flake8_errmsg, - flake8_gettext: options.flake8_gettext, - flake8_implicit_str_concat: options.flake8_implicit_str_concat, - flake8_import_conventions: options.flake8_import_conventions, - flake8_pytest_style: options.flake8_pytest_style, - flake8_quotes: options.flake8_quotes, - flake8_self: options.flake8_self, - flake8_tidy_imports: options.flake8_tidy_imports, - flake8_type_checking: options.flake8_type_checking, - flake8_unused_arguments: options.flake8_unused_arguments, - isort: options.isort, - mccabe: options.mccabe, - pep8_naming: options.pep8_naming, - pycodestyle: options.pycodestyle, - pydocstyle: options.pydocstyle, - pyflakes: options.pyflakes, - pylint: options.pylint, - pyupgrade: options.pyupgrade, + + lint: LintConfiguration { + rule_selections: vec![RuleSelection { + select: options.select, + ignore: options + .ignore + .into_iter() + .flatten() + .chain(options.extend_ignore.into_iter().flatten()) + .collect(), + extend_select: options.extend_select.unwrap_or_default(), + fixable: options.fixable, + unfixable: options + .unfixable + .into_iter() + .flatten() + .chain(options.extend_unfixable.into_iter().flatten()) + .collect(), + extend_fixable: options.extend_fixable.unwrap_or_default(), + }], + allowed_confusables: options.allowed_confusables, + dummy_variable_rgx: options + .dummy_variable_rgx + .map(|pattern| Regex::new(&pattern)) + .transpose() + .map_err(|e| anyhow!("Invalid `dummy-variable-rgx` value: {e}"))?, + extend_per_file_ignores: options + .extend_per_file_ignores + .map(|per_file_ignores| { + per_file_ignores + .into_iter() + .map(|(pattern, prefixes)| { + PerFileIgnore::new(pattern, &prefixes, Some(project_root)) + }) + .collect() + }) + .unwrap_or_default(), + external: options.external, + ignore_init_module_imports: options.ignore_init_module_imports, + include: options.include.map(|paths| { + paths + .into_iter() + .map(|pattern| { + let absolute = fs::normalize_path_to(&pattern, project_root); + FilePattern::User(pattern, absolute) + }) + .collect() + }), + per_file_ignores: options.per_file_ignores.map(|per_file_ignores| { + per_file_ignores + .into_iter() + .map(|(pattern, prefixes)| { + PerFileIgnore::new(pattern, &prefixes, Some(project_root)) + }) + .collect() + }), + task_tags: options.task_tags, + logger_objects: options.logger_objects, + // Plugins + flake8_annotations: options.flake8_annotations, + flake8_bandit: options.flake8_bandit, + flake8_bugbear: options.flake8_bugbear, + flake8_builtins: options.flake8_builtins, + flake8_comprehensions: options.flake8_comprehensions, + flake8_copyright: options.flake8_copyright, + flake8_errmsg: options.flake8_errmsg, + flake8_gettext: options.flake8_gettext, + flake8_implicit_str_concat: options.flake8_implicit_str_concat, + flake8_import_conventions: options.flake8_import_conventions, + flake8_pytest_style: options.flake8_pytest_style, + flake8_quotes: options.flake8_quotes, + flake8_self: options.flake8_self, + flake8_tidy_imports: options.flake8_tidy_imports, + flake8_type_checking: options.flake8_type_checking, + flake8_unused_arguments: options.flake8_unused_arguments, + isort: options.isort, + mccabe: options.mccabe, + pep8_naming: options.pep8_naming, + pycodestyle: options.pycodestyle, + pydocstyle: options.pydocstyle, + pyflakes: options.pyflakes, + pylint: options.pylint, + pyupgrade: options.pyupgrade, + }, }) } - pub fn as_rule_table(&self) -> RuleTable { - let preview = self.preview.unwrap_or_default(); + #[must_use] + pub fn combine(self, config: Self) -> Self { + Self { + builtins: self.builtins.or(config.builtins), + cache_dir: self.cache_dir.or(config.cache_dir), + exclude: self.exclude.or(config.exclude), + extend: self.extend.or(config.extend), + extend_exclude: config + .extend_exclude + .into_iter() + .chain(self.extend_exclude) + .collect(), + extend_include: config + .extend_include + .into_iter() + .chain(self.extend_include) + .collect(), + fix: self.fix.or(config.fix), + fix_only: self.fix_only.or(config.fix_only), + output_format: self.output_format.or(config.output_format), + force_exclude: self.force_exclude.or(config.force_exclude), + line_length: self.line_length.or(config.line_length), + tab_size: self.tab_size.or(config.tab_size), + namespace_packages: self.namespace_packages.or(config.namespace_packages), + required_version: self.required_version.or(config.required_version), + respect_gitignore: self.respect_gitignore.or(config.respect_gitignore), + show_source: self.show_source.or(config.show_source), + show_fixes: self.show_fixes.or(config.show_fixes), + src: self.src.or(config.src), + target_version: self.target_version.or(config.target_version), + preview: self.preview.or(config.preview), + typing_modules: self.typing_modules.or(config.typing_modules), + lint: self.lint.combine(config.lint), + } + } +} +#[derive(Debug, Default)] +pub struct LintConfiguration { + // Rule selection + pub rule_selections: Vec, + pub per_file_ignores: Option>, + pub extend_per_file_ignores: Vec, + + // Global lint settings + pub dummy_variable_rgx: Option, + pub allowed_confusables: Option>, + pub external: Option>, + pub ignore_init_module_imports: Option, + pub include: Option>, + pub logger_objects: Option>, + pub task_tags: Option>, + + // Plugins + pub flake8_annotations: Option, + pub flake8_bandit: Option, + pub flake8_bugbear: Option, + pub flake8_builtins: Option, + pub flake8_comprehensions: Option, + pub flake8_copyright: Option, + pub flake8_errmsg: Option, + pub flake8_gettext: Option, + pub flake8_implicit_str_concat: Option, + pub flake8_import_conventions: Option, + pub flake8_pytest_style: Option, + pub flake8_quotes: Option, + pub flake8_self: Option, + pub flake8_tidy_imports: Option, + pub flake8_type_checking: Option, + pub flake8_unused_arguments: Option, + pub isort: Option, + pub mccabe: Option, + pub pep8_naming: Option, + pub pycodestyle: Option, + pub pydocstyle: Option, + pub pyflakes: Option, + pub pylint: Option, + pub pyupgrade: Option, +} + +impl LintConfiguration { + fn as_rule_table(&self, preview: PreviewMode) -> RuleTable { // The select_set keeps track of which rules have been selected. let mut select_set: RuleSet = PREFIXES .iter() @@ -706,49 +795,20 @@ impl Configuration { .chain(self.rule_selections) .collect(), allowed_confusables: self.allowed_confusables.or(config.allowed_confusables), - builtins: self.builtins.or(config.builtins), - cache_dir: self.cache_dir.or(config.cache_dir), dummy_variable_rgx: self.dummy_variable_rgx.or(config.dummy_variable_rgx), - exclude: self.exclude.or(config.exclude), - extend: self.extend.or(config.extend), - extend_exclude: config - .extend_exclude - .into_iter() - .chain(self.extend_exclude) - .collect(), - extend_include: config - .extend_include - .into_iter() - .chain(self.extend_include) - .collect(), extend_per_file_ignores: config .extend_per_file_ignores .into_iter() .chain(self.extend_per_file_ignores) .collect(), external: self.external.or(config.external), - fix: self.fix.or(config.fix), - fix_only: self.fix_only.or(config.fix_only), - output_format: self.output_format.or(config.output_format), - force_exclude: self.force_exclude.or(config.force_exclude), include: self.include.or(config.include), ignore_init_module_imports: self .ignore_init_module_imports .or(config.ignore_init_module_imports), - line_length: self.line_length.or(config.line_length), logger_objects: self.logger_objects.or(config.logger_objects), - tab_size: self.tab_size.or(config.tab_size), - namespace_packages: self.namespace_packages.or(config.namespace_packages), per_file_ignores: self.per_file_ignores.or(config.per_file_ignores), - required_version: self.required_version.or(config.required_version), - respect_gitignore: self.respect_gitignore.or(config.respect_gitignore), - show_source: self.show_source.or(config.show_source), - show_fixes: self.show_fixes.or(config.show_fixes), - src: self.src.or(config.src), - target_version: self.target_version.or(config.target_version), - preview: self.preview.or(config.preview), task_tags: self.task_tags.or(config.task_tags), - typing_modules: self.typing_modules.or(config.typing_modules), // Plugins flake8_annotations: self.flake8_annotations.combine(config.flake8_annotations), flake8_bandit: self.flake8_bandit.combine(config.flake8_bandit), @@ -788,6 +848,8 @@ impl Configuration { } } +pub struct FormatConfiguration {} + pub(crate) trait CombinePluginOptions { #[must_use] fn combine(self, other: Self) -> Self; @@ -831,7 +893,7 @@ mod tests { use ruff_linter::settings::types::PreviewMode; use ruff_linter::RuleSelector; - use crate::configuration::{Configuration, RuleSelection}; + use crate::configuration::{LintConfiguration, RuleSelection}; const NURSERY_RULES: &[Rule] = &[ Rule::MissingCopyrightNotice, @@ -895,12 +957,11 @@ mod tests { selections: impl IntoIterator, preview: Option, ) -> RuleSet { - Configuration { + LintConfiguration { rule_selections: selections.into_iter().collect(), - preview, - ..Configuration::default() + ..LintConfiguration::default() } - .as_rule_table() + .as_rule_table(preview.unwrap_or_default()) .iter_enabled() // Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is off-by-default .filter(|rule| rule.noqa_code() != "RUF014")