From 7504bf347b3fd286c54550ea1dd004dfeeb41216 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Fri, 12 Jan 2024 11:30:29 -0800 Subject: [PATCH] `--show-settings` displays active settings in a far more readable format (#9464) ## Summary Fixes #8334. `Display` has been implemented for `ruff_workspace::Settings`, which gives a much nicer and more readable output to `--show-settings`. Internally, a `display_settings` utility macro has been implemented to reduce the boilerplate of the display code. ### Work to be done - [x] A lot of formatting for `Vec<_>` and `HashSet<_>` types have been stubbed out, using `Debug` as a fallback. There should be a way to add generic formatting support for these types as a modifier in `display_settings`. - [x] Several complex types were also stubbed out and need proper `Display` implementations rather than falling back on `Debug`. - [x] An open question needs to be answered: how important is it that the output be valid TOML? Some types in settings, such as a hash-map from a glob pattern to a multi-variant enum, will be hard to rework into valid _and_ readable TOML. - [x] Tests need to be implemented. ## Test Plan Tests consist of a snapshot test for the default `--show-settings` output and a doctest for `display_settings!`. --- crates/ruff_cli/src/commands/show_settings.rs | 2 +- crates/ruff_cli/tests/show_settings.rs | 29 ++ ...ow_settings__display_default_settings.snap | 368 ++++++++++++++++++ crates/ruff_formatter/src/lib.rs | 13 + crates/ruff_linter/src/line_width.rs | 13 + crates/ruff_linter/src/registry/rule_set.rs | 17 +- .../src/rules/flake8_annotations/settings.rs | 19 + .../src/rules/flake8_bandit/settings.rs | 16 + .../src/rules/flake8_bugbear/settings.rs | 15 + .../src/rules/flake8_builtins/settings.rs | 15 + .../rules/flake8_comprehensions/settings.rs | 15 + .../src/rules/flake8_copyright/settings.rs | 17 + .../src/rules/flake8_errmsg/settings.rs | 15 + .../src/rules/flake8_gettext/settings.rs | 15 + .../flake8_implicit_str_concat/settings.rs | 15 + .../flake8_import_conventions/settings.rs | 17 + .../src/rules/flake8_pytest_style/settings.rs | 21 + .../src/rules/flake8_quotes/settings.rs | 27 ++ .../src/rules/flake8_self/settings.rs | 15 + .../src/rules/flake8_tidy_imports/settings.rs | 26 ++ .../rules/flake8_type_checking/settings.rs | 19 + .../rules/flake8_unused_arguments/settings.rs | 15 + .../ruff_linter/src/rules/isort/categorize.rs | 37 ++ .../ruff_linter/src/rules/isort/settings.rs | 48 +++ .../ruff_linter/src/rules/mccabe/settings.rs | 15 + .../src/rules/pep8_naming/settings.rs | 17 + .../src/rules/pycodestyle/settings.rs | 17 + .../src/rules/pydocstyle/settings.rs | 27 ++ .../src/rules/pyflakes/settings.rs | 15 + .../ruff_linter/src/rules/pylint/settings.rs | 36 ++ .../src/rules/pyupgrade/settings.rs | 15 + crates/ruff_linter/src/rules/ruff/mod.rs | 7 +- .../src/settings/fix_safety_table.rs | 17 +- crates/ruff_linter/src/settings/mod.rs | 217 +++++++++-- crates/ruff_linter/src/settings/rule_table.rs | 17 +- crates/ruff_linter/src/settings/types.rs | 147 ++++++- crates/ruff_python_formatter/src/options.rs | 51 ++- crates/ruff_workspace/src/configuration.rs | 10 +- crates/ruff_workspace/src/settings.rs | 81 ++++ 39 files changed, 1455 insertions(+), 43 deletions(-) create mode 100644 crates/ruff_cli/tests/show_settings.rs create mode 100644 crates/ruff_cli/tests/snapshots/show_settings__display_default_settings.snap diff --git a/crates/ruff_cli/src/commands/show_settings.rs b/crates/ruff_cli/src/commands/show_settings.rs index 2e27fb344ada3..a23f31a4967a2 100644 --- a/crates/ruff_cli/src/commands/show_settings.rs +++ b/crates/ruff_cli/src/commands/show_settings.rs @@ -35,7 +35,7 @@ pub(crate) fn show_settings( if let Some(settings_path) = pyproject_config.path.as_ref() { writeln!(writer, "Settings path: {settings_path:?}")?; } - writeln!(writer, "{settings:#?}")?; + write!(writer, "{settings}")?; Ok(()) } diff --git a/crates/ruff_cli/tests/show_settings.rs b/crates/ruff_cli/tests/show_settings.rs new file mode 100644 index 0000000000000..e2016e378542b --- /dev/null +++ b/crates/ruff_cli/tests/show_settings.rs @@ -0,0 +1,29 @@ +use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; +use std::path::Path; +use std::process::Command; + +const BIN_NAME: &str = "ruff"; + +#[cfg(not(target_os = "windows"))] +const TEST_FILTERS: &[(&str, &str)] = &[ + ("\"[^\\*\"]*/pyproject.toml", "\"[BASEPATH]/pyproject.toml"), + ("\".*/crates", "\"[BASEPATH]/crates"), + ("\".*/\\.ruff_cache", "\"[BASEPATH]/.ruff_cache"), + ("\".*/ruff\"", "\"[BASEPATH]\""), +]; +#[cfg(target_os = "windows")] +const TEST_FILTERS: &[(&str, &str)] = &[ + (r#""[^\*"]*\\pyproject.toml"#, "\"[BASEPATH]/pyproject.toml"), + (r#"".*\\crates"#, "\"[BASEPATH]/crates"), + (r#"".*\\\.ruff_cache"#, "\"[BASEPATH]/.ruff_cache"), + (r#"".*\\ruff""#, "\"[BASEPATH]\""), + (r#"\\+(\w\w|\s|")"#, "/$1"), +]; + +#[test] +fn display_default_settings() { + insta::with_settings!({ filters => TEST_FILTERS.to_vec() }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(["check", "--show-settings", "unformatted.py"]).current_dir(Path::new("./resources/test/fixtures"))); + }); +} diff --git a/crates/ruff_cli/tests/snapshots/show_settings__display_default_settings.snap b/crates/ruff_cli/tests/snapshots/show_settings__display_default_settings.snap new file mode 100644 index 0000000000000..d4a64f69e9522 --- /dev/null +++ b/crates/ruff_cli/tests/snapshots/show_settings__display_default_settings.snap @@ -0,0 +1,368 @@ +--- +source: crates/ruff_cli/tests/show_settings.rs +info: + program: ruff + args: + - check + - "--show-settings" + - unformatted.py +--- +success: true +exit_code: 0 +----- stdout ----- +Resolved settings for: "[BASEPATH]/crates/ruff_cli/resources/test/fixtures/unformatted.py" +Settings path: "[BASEPATH]/pyproject.toml" + +# General Settings +cache_dir = "[BASEPATH]/.ruff_cache" +fix = false +fix_only = false +output_format = text +show_fixes = false +show_source = false +unsafe_fixes = hint + +# File Resolver Settings +file_resolver.exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] +file_resolver.extend_exclude = [ + "crates/ruff_linter/resources/", + "crates/ruff_python_formatter/resources/", +] +file_resolver.force_exclude = false +file_resolver.include = [ + "*.py", + "*.pyi", + "**/pyproject.toml", +] +file_resolver.extend_include = [] +file_resolver.respect_gitignore = true +file_resolver.project_root = "[BASEPATH]" + +# Linter Settings +linter.exclude = [] +linter.project_root = "[BASEPATH]" +linter.rules.enabled = [ + MultipleImportsOnOneLine, + ModuleImportNotAtTopOfFile, + MultipleStatementsOnOneLineColon, + MultipleStatementsOnOneLineSemicolon, + UselessSemicolon, + NoneComparison, + TrueFalseComparison, + NotInTest, + NotIsTest, + TypeComparison, + BareExcept, + LambdaAssignment, + AmbiguousVariableName, + AmbiguousClassName, + AmbiguousFunctionName, + IOError, + SyntaxError, + UnusedImport, + ImportShadowedByLoopVar, + UndefinedLocalWithImportStar, + LateFutureImport, + UndefinedLocalWithImportStarUsage, + UndefinedLocalWithNestedImportStarUsage, + FutureFeatureNotDefined, + PercentFormatInvalidFormat, + PercentFormatExpectedMapping, + PercentFormatExpectedSequence, + PercentFormatExtraNamedArguments, + PercentFormatMissingArgument, + PercentFormatMixedPositionalAndNamed, + PercentFormatPositionalCountMismatch, + PercentFormatStarRequiresSequence, + PercentFormatUnsupportedFormatCharacter, + StringDotFormatInvalidFormat, + StringDotFormatExtraNamedArguments, + StringDotFormatExtraPositionalArguments, + StringDotFormatMissingArguments, + StringDotFormatMixingAutomatic, + FStringMissingPlaceholders, + MultiValueRepeatedKeyLiteral, + MultiValueRepeatedKeyVariable, + ExpressionsInStarAssignment, + MultipleStarredExpressions, + AssertTuple, + IsLiteral, + InvalidPrintSyntax, + IfTuple, + BreakOutsideLoop, + ContinueOutsideLoop, + YieldOutsideFunction, + ReturnOutsideFunction, + DefaultExceptNotLast, + ForwardAnnotationSyntaxError, + RedefinedWhileUnused, + UndefinedName, + UndefinedExport, + UndefinedLocal, + UnusedVariable, + UnusedAnnotation, + RaiseNotImplemented, +] +linter.rules.should_fix = [ + MultipleImportsOnOneLine, + ModuleImportNotAtTopOfFile, + MultipleStatementsOnOneLineColon, + MultipleStatementsOnOneLineSemicolon, + UselessSemicolon, + NoneComparison, + TrueFalseComparison, + NotInTest, + NotIsTest, + TypeComparison, + BareExcept, + LambdaAssignment, + AmbiguousVariableName, + AmbiguousClassName, + AmbiguousFunctionName, + IOError, + SyntaxError, + UnusedImport, + ImportShadowedByLoopVar, + UndefinedLocalWithImportStar, + LateFutureImport, + UndefinedLocalWithImportStarUsage, + UndefinedLocalWithNestedImportStarUsage, + FutureFeatureNotDefined, + PercentFormatInvalidFormat, + PercentFormatExpectedMapping, + PercentFormatExpectedSequence, + PercentFormatExtraNamedArguments, + PercentFormatMissingArgument, + PercentFormatMixedPositionalAndNamed, + PercentFormatPositionalCountMismatch, + PercentFormatStarRequiresSequence, + PercentFormatUnsupportedFormatCharacter, + StringDotFormatInvalidFormat, + StringDotFormatExtraNamedArguments, + StringDotFormatExtraPositionalArguments, + StringDotFormatMissingArguments, + StringDotFormatMixingAutomatic, + FStringMissingPlaceholders, + MultiValueRepeatedKeyLiteral, + MultiValueRepeatedKeyVariable, + ExpressionsInStarAssignment, + MultipleStarredExpressions, + AssertTuple, + IsLiteral, + InvalidPrintSyntax, + IfTuple, + BreakOutsideLoop, + ContinueOutsideLoop, + YieldOutsideFunction, + ReturnOutsideFunction, + DefaultExceptNotLast, + ForwardAnnotationSyntaxError, + RedefinedWhileUnused, + UndefinedName, + UndefinedExport, + UndefinedLocal, + UnusedVariable, + UnusedAnnotation, + RaiseNotImplemented, +] +linter.per_file_ignores = {} +linter.safety_table.forced_safe = [] +linter.safety_table.forced_unsafe = [] +linter.target_version = Py37 +linter.preview = disabled +linter.explicit_preview_rules = false +linter.extension.mapping = {} +linter.allowed_confusables = [] +linter.builtins = [] +linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$ +linter.external = [] +linter.ignore_init_module_imports = false +linter.logger_objects = [] +linter.namespace_packages = [] +linter.src = ["[BASEPATH]"] +linter.tab_size = 4 +linter.line_length = 88 +linter.task_tags = [ + TODO, + FIXME, + XXX, +] +linter.typing_modules = [] + +# Linter Plugins +linter.flake8_annotations.mypy_init_return = false +linter.flake8_annotations.suppress_dummy_args = false +linter.flake8_annotations.suppress_none_returning = false +linter.flake8_annotations.allow_star_arg_any = false +linter.flake8_annotations.ignore_fully_untyped = false +linter.flake8_bandit.hardcoded_tmp_directory = [ + /tmp, + /var/tmp, + /dev/shm, +] +linter.flake8_bandit.check_typed_exception = false +linter.flake8_bugbear.extend_immutable_calls = [] +linter.flake8_builtins.builtins_ignorelist = [] +linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false +linter.flake8_copyright.notice_rgx = (?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})* +linter.flake8_copyright.author = none +linter.flake8_copyright.min_file_size = 0 +linter.flake8_errmsg.max_string_length = 0 +linter.flake8_gettext.functions_names = [ + _, + gettext, + ngettext, +] +linter.flake8_implicit_str_concat.allow_multiline = true +linter.flake8_import_conventions.aliases = {"matplotlib": "mpl", "matplotlib.pyplot": "plt", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "networkx": "nx", "plotly.express": "px", "polars": "pl", "numpy": "np", "panel": "pn", "pyarrow": "pa", "altair": "alt", "tkinter": "tk", "holoviews": "hv"} +linter.flake8_import_conventions.banned_aliases = {} +linter.flake8_import_conventions.banned_from = [] +linter.flake8_pytest_style.fixture_parentheses = true +linter.flake8_pytest_style.parametrize_names_type = tuple +linter.flake8_pytest_style.parametrize_values_type = list +linter.flake8_pytest_style.parametrize_values_row_type = tuple +linter.flake8_pytest_style.raises_require_match_for = [ + BaseException, + Exception, + ValueError, + OSError, + IOError, + EnvironmentError, + socket.error, +] +linter.flake8_pytest_style.raises_extend_require_match_for = [] +linter.flake8_pytest_style.mark_parentheses = true +linter.flake8_quotes.inline_quotes = double +linter.flake8_quotes.multiline_quotes = double +linter.flake8_quotes.docstring_quotes = double +linter.flake8_quotes.avoid_escape = true +linter.flake8_self.ignore_names = [ + _make, + _asdict, + _replace, + _fields, + _field_defaults, + _name_, + _value_, +] +linter.flake8_tidy_imports.ban_relative_imports = "parents" +linter.flake8_tidy_imports.banned_api = {} +linter.flake8_tidy_imports.banned_module_level_imports = [] +linter.flake8_type_checking.strict = false +linter.flake8_type_checking.exempt_modules = [ + typing, + typing_extensions, +] +linter.flake8_type_checking.runtime_required_base_classes = [] +linter.flake8_type_checking.runtime_required_decorators = [] +linter.flake8_type_checking.quote_annotations = false +linter.flake8_unused_arguments.ignore_variadic_names = false +linter.isort.required_imports = [] +linter.isort.combine_as_imports = false +linter.isort.force_single_line = false +linter.isort.force_sort_within_sections = false +linter.isort.detect_same_package = true +linter.isort.case_sensitive = false +linter.isort.force_wrap_aliases = false +linter.isort.force_to_top = [] +linter.isort.known_modules = {} +linter.isort.order_by_type = true +linter.isort.relative_imports_order = furthest_to_closest +linter.isort.single_line_exclusions = [] +linter.isort.split_on_trailing_comma = true +linter.isort.classes = [] +linter.isort.constants = [] +linter.isort.variables = [] +linter.isort.no_lines_before = [] +linter.isort.lines_after_imports = -1 +linter.isort.lines_between_types = 0 +linter.isort.forced_separate = [] +linter.isort.section_order = [ + known { type = future }, + known { type = standard_library }, + known { type = third_party }, + known { type = first_party }, + known { type = local_folder }, +] +linter.isort.no_sections = false +linter.isort.from_first = false +linter.isort.length_sort = false +linter.isort.length_sort_straight = false +linter.mccabe.max_complexity = 10 +linter.pep8_naming.ignore_names = [ + setUp, + tearDown, + setUpClass, + tearDownClass, + setUpModule, + tearDownModule, + asyncSetUp, + asyncTearDown, + setUpTestData, + failureException, + longMessage, + maxDiff, +] +linter.pep8_naming.classmethod_decorators = [] +linter.pep8_naming.staticmethod_decorators = [] +linter.pycodestyle.max_line_length = 88 +linter.pycodestyle.max_doc_length = none +linter.pycodestyle.ignore_overlong_task_comments = false +linter.pyflakes.extend_generics = [] +linter.pylint.allow_magic_value_types = [ + str, + bytes, +] +linter.pylint.allow_dunder_method_names = [] +linter.pylint.max_args = 5 +linter.pylint.max_positional_args = 5 +linter.pylint.max_returns = 6 +linter.pylint.max_bool_expr = 5 +linter.pylint.max_branches = 12 +linter.pylint.max_statements = 50 +linter.pylint.max_public_methods = 20 +linter.pylint.max_locals = 15 +linter.pyupgrade.keep_runtime_typing = false + +# Formatter Settings +formatter.exclude = [] +formatter.target_version = Py37 +formatter.preview = disabled +formatter.line_width = 88 +formatter.line_ending = auto +formatter.indent_style = space +formatter.indent_width = 4 +formatter.quote_style = double +formatter.magic_trailing_comma = respect +formatter.docstring_code_format = disabled +formatter.docstring_code_line_width = dynamic + +----- stderr ----- + diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index dfac3df3fc345..d443ec17371c2 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -35,6 +35,7 @@ mod source_code; use crate::formatter::Formatter; use crate::group_id::UniqueGroupIdBuilder; use crate::prelude::TagKind; +use std::fmt; use std::fmt::{Debug, Display}; use std::marker::PhantomData; use std::num::{NonZeroU16, NonZeroU8, TryFromIntError}; @@ -113,6 +114,12 @@ impl Default for IndentWidth { } } +impl Display for IndentWidth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + impl TryFrom for IndentWidth { type Error = TryFromIntError; @@ -146,6 +153,12 @@ impl Default for LineWidth { } } +impl Display for LineWidth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + impl TryFrom for LineWidth { type Error = TryFromIntError; diff --git a/crates/ruff_linter/src/line_width.rs b/crates/ruff_linter/src/line_width.rs index 06fe47c94464b..f804fdb2c81b5 100644 --- a/crates/ruff_linter/src/line_width.rs +++ b/crates/ruff_linter/src/line_width.rs @@ -1,4 +1,5 @@ use std::error::Error; +use std::fmt; use std::hash::Hasher; use std::num::{NonZeroU16, NonZeroU8, ParseIntError}; use std::str::FromStr; @@ -39,6 +40,12 @@ impl Default for LineLength { } } +impl fmt::Display for LineLength { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + impl CacheKey for LineLength { fn cache_key(&self, state: &mut CacheKeyHasher) { state.write_u16(self.0.get()); @@ -248,6 +255,12 @@ impl Default for IndentWidth { } } +impl fmt::Display for IndentWidth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + impl From for IndentWidth { fn from(tab_size: NonZeroU8) -> Self { Self(tab_size) diff --git a/crates/ruff_linter/src/registry/rule_set.rs b/crates/ruff_linter/src/registry/rule_set.rs index ff6561740d774..6c15feba9a57f 100644 --- a/crates/ruff_linter/src/registry/rule_set.rs +++ b/crates/ruff_linter/src/registry/rule_set.rs @@ -1,6 +1,6 @@ use crate::registry::Rule; use ruff_macros::CacheKey; -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::iter::FusedIterator; const RULESET_SIZE: usize = 13; @@ -269,6 +269,21 @@ impl Debug for RuleSet { } } +impl Display for RuleSet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + write!(f, "[]")?; + } else { + writeln!(f, "[")?; + for rule in self { + writeln!(f, "\t{rule:?},")?; + } + write!(f, "]")?; + } + Ok(()) + } +} + impl FromIterator for RuleSet { fn from_iter>(iter: T) -> Self { let mut set = RuleSet::empty(); diff --git a/crates/ruff_linter/src/rules/flake8_annotations/settings.rs b/crates/ruff_linter/src/rules/flake8_annotations/settings.rs index 843d18cd4b61c..011cf01f4a926 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `flake-annotations` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, Default, CacheKey)] #[allow(clippy::struct_excessive_bools)] @@ -11,3 +13,20 @@ pub struct Settings { pub allow_star_arg_any: bool, pub ignore_fully_untyped: bool, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_annotations", + fields = [ + self.mypy_init_return, + self.suppress_dummy_args, + self.suppress_none_returning, + self.allow_star_arg_any, + self.ignore_fully_untyped + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/settings.rs b/crates/ruff_linter/src/rules/flake8_bandit/settings.rs index f701916462957..17a018a25c64c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `flake8-bandit` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; pub fn default_tmp_dirs() -> Vec { ["/tmp", "/var/tmp", "/dev/shm"] @@ -22,3 +24,17 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_bandit", + fields = [ + self.hardcoded_tmp_directory | array, + self.check_typed_exception + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs b/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs index 885860565f686..03c4d5cdf1d98 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `flake8-bugbear` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub extend_immutable_calls: Vec, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_bugbear", + fields = [ + self.extend_immutable_calls | array + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs index 694e4b522c859..d3fc3a70f74bd 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `flake8-builtins` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub builtins_ignorelist: Vec, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_builtins", + fields = [ + self.builtins_ignorelist | array + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs index 9646cde790e7e..41110886a5d4e 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `flake8-comprehensions` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub allow_dict_calls_with_keyword_arguments: bool, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_comprehensions", + fields = [ + self.allow_dict_calls_with_keyword_arguments + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_copyright/settings.rs b/crates/ruff_linter/src/rules/flake8_copyright/settings.rs index 0dda17d0edf22..9f76e763cf880 100644 --- a/crates/ruff_linter/src/rules/flake8_copyright/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_copyright/settings.rs @@ -2,7 +2,9 @@ use once_cell::sync::Lazy; use regex::Regex; +use std::fmt::{Display, Formatter}; +use crate::display_settings; use ruff_macros::CacheKey; #[derive(Debug, CacheKey)] @@ -24,3 +26,18 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_copyright", + fields = [ + self.notice_rgx, + self.author | optional, + self.min_file_size, + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs b/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs index d0b82cca605ab..ec239435cee07 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `flake8-errmsg` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub max_string_length: usize, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_errmsg", + fields = [ + self.max_string_length + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_gettext/settings.rs b/crates/ruff_linter/src/rules/flake8_gettext/settings.rs index 1dd35357d80ee..6e3a6c367cd10 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/settings.rs @@ -1,4 +1,6 @@ +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, CacheKey)] pub struct Settings { @@ -20,3 +22,16 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_gettext", + fields = [ + self.functions_names | array + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs index 413118fa317cd..90c6a9a1812fe 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `flake8-implicit-str-concat` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, CacheKey)] pub struct Settings { @@ -14,3 +16,16 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_implicit_str_concat", + fields = [ + self.allow_multiline + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs index 639a627f091ea..70fe742a20e3e 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs @@ -1,7 +1,9 @@ //! Settings for import conventions. use rustc_hash::{FxHashMap, FxHashSet}; +use std::fmt::{Display, Formatter}; +use crate::display_settings; use ruff_macros::CacheKey; const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[ @@ -44,3 +46,18 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_import_conventions", + fields = [ + self.aliases | debug, + self.banned_aliases | debug, + self.banned_from | array, + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs index 22231b6981ab1..cab3d2d5a3eed 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs @@ -1,7 +1,9 @@ //! Settings for the `flake8-pytest-style` plugin. use std::error::Error; use std::fmt; +use std::fmt::Formatter; +use crate::display_settings; use ruff_macros::CacheKey; use crate::settings::types::IdentifierPattern; @@ -47,6 +49,25 @@ impl Default for Settings { } } +impl fmt::Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_pytest_style", + fields = [ + self.fixture_parentheses, + self.parametrize_names_type, + self.parametrize_values_type, + self.parametrize_values_row_type, + self.raises_require_match_for | array, + self.raises_extend_require_match_for | array, + self.mark_parentheses + ] + } + Ok(()) + } +} + /// Error returned by the [`TryFrom`] implementation of [`Settings`]. #[derive(Debug)] pub enum SettingsError { diff --git a/crates/ruff_linter/src/rules/flake8_quotes/settings.rs b/crates/ruff_linter/src/rules/flake8_quotes/settings.rs index 620fb2e53a8b8..a0ddea385c020 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/settings.rs @@ -1,7 +1,9 @@ //! Settings for the `flake8-quotes` plugin. use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use crate::display_settings; use ruff_macros::CacheKey; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey)] @@ -39,6 +41,22 @@ impl Default for Settings { } } +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_quotes", + fields = [ + self.inline_quotes, + self.multiline_quotes, + self.docstring_quotes, + self.avoid_escape + ] + } + Ok(()) + } +} + impl Quote { #[must_use] pub const fn opposite(self) -> Self { @@ -56,3 +74,12 @@ impl Quote { } } } + +impl Display for Quote { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Double => write!(f, "double"), + Self::Single => write!(f, "single"), + } + } +} diff --git a/crates/ruff_linter/src/rules/flake8_self/settings.rs b/crates/ruff_linter/src/rules/flake8_self/settings.rs index 5cd083671756d..c59a0ec89d221 100644 --- a/crates/ruff_linter/src/rules/flake8_self/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_self/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `flake8-self` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; // By default, ignore the `namedtuple` methods and attributes, as well as the // _sunder_ names in Enum, which are underscore-prefixed to prevent conflicts @@ -27,3 +29,16 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_self", + fields = [ + self.ignore_names | array + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs index a1267fbc9b8a8..35d55e2e75b80 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs @@ -1,6 +1,8 @@ use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use crate::display_settings; use ruff_macros::CacheKey; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey)] @@ -22,9 +24,33 @@ pub enum Strictness { All, } +impl Display for Strictness { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Parents => write!(f, "\"parents\""), + Self::All => write!(f, "\"all\""), + } + } +} + #[derive(Debug, CacheKey, Default)] pub struct Settings { pub ban_relative_imports: Strictness, pub banned_api: FxHashMap, pub banned_module_level_imports: Vec, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_tidy_imports", + fields = [ + self.ban_relative_imports, + self.banned_api | debug, + self.banned_module_level_imports | array, + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs b/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs index 16baf1b91edbe..fa8214e5b74af 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `flake8-type-checking` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, CacheKey)] pub struct Settings { @@ -22,3 +24,20 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_type_checking", + fields = [ + self.strict, + self.exempt_modules | array, + self.runtime_required_base_classes | array, + self.runtime_required_decorators | array, + self.quote_annotations + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs index 3f413c2a48ab5..7e13bc6495a9a 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `flake8-unused-arguments` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub ignore_variadic_names: bool, } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.flake8_unused_arguments", + fields = [ + self.ignore_variadic_names + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/isort/categorize.rs b/crates/ruff_linter/src/rules/isort/categorize.rs index 8ab090645ab62..c00640ea12ac2 100644 --- a/crates/ruff_linter/src/rules/isort/categorize.rs +++ b/crates/ruff_linter/src/rules/isort/categorize.rs @@ -1,4 +1,5 @@ use std::collections::BTreeMap; +use std::fmt; use std::hash::BuildHasherDefault; use std::path::{Path, PathBuf}; use std::{fs, iter}; @@ -40,6 +41,18 @@ pub enum ImportType { LocalFolder, } +impl fmt::Display for ImportType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Future => write!(f, "future"), + Self::StandardLibrary => write!(f, "standard_library"), + Self::ThirdParty => write!(f, "third_party"), + Self::FirstParty => write!(f, "first_party"), + Self::LocalFolder => write!(f, "local_folder"), + } + } +} + #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, CacheKey)] #[serde(untagged)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] @@ -48,6 +61,15 @@ pub enum ImportSection { UserDefined(String), } +impl fmt::Display for ImportSection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Known(import_type) => write!(f, "known {{ type = {import_type} }}",), + Self::UserDefined(string) => fmt::Debug::fmt(string, f), + } + } +} + #[derive(Debug)] enum Reason<'a> { NonZeroLevel, @@ -378,3 +400,18 @@ impl KnownModules { user_defined } } + +impl fmt::Display for KnownModules { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.known.is_empty() { + write!(f, "{{}}")?; + } else { + writeln!(f, "{{")?; + for (pattern, import_section) in &self.known { + writeln!(f, "\t{pattern} => {import_section:?},")?; + } + write!(f, "}}")?; + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/isort/settings.rs b/crates/ruff_linter/src/rules/isort/settings.rs index 2a28a3c8b396b..b5b13363b4c4a 100644 --- a/crates/ruff_linter/src/rules/isort/settings.rs +++ b/crates/ruff_linter/src/rules/isort/settings.rs @@ -3,10 +3,12 @@ use std::collections::BTreeSet; use std::error::Error; use std::fmt; +use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; +use crate::display_settings; use ruff_macros::CacheKey; use crate::rules::isort::categorize::KnownModules; @@ -32,6 +34,15 @@ impl Default for RelativeImportsOrder { } } +impl Display for RelativeImportsOrder { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::ClosestToFurthest => write!(f, "closest_to_furthest"), + Self::FurthestToClosest => write!(f, "furthest_to_closest"), + } + } +} + #[derive(Debug, CacheKey)] #[allow(clippy::struct_excessive_bools)] pub struct Settings { @@ -94,6 +105,43 @@ impl Default for Settings { } } +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.isort", + fields = [ + self.required_imports | array, + self.combine_as_imports, + self.force_single_line, + self.force_sort_within_sections, + self.detect_same_package, + self.case_sensitive, + self.force_wrap_aliases, + self.force_to_top | array, + self.known_modules, + self.order_by_type, + self.relative_imports_order, + self.single_line_exclusions | array, + self.split_on_trailing_comma, + self.classes | array, + self.constants | array, + self.variables | array, + self.no_lines_before | array, + self.lines_after_imports, + self.lines_between_types, + self.forced_separate | array, + self.section_order | array, + self.no_sections, + self.from_first, + self.length_sort, + self.length_sort_straight + ] + } + Ok(()) + } +} + /// Error returned by the [`TryFrom`] implementation of [`Settings`]. #[derive(Debug)] pub enum SettingsError { diff --git a/crates/ruff_linter/src/rules/mccabe/settings.rs b/crates/ruff_linter/src/rules/mccabe/settings.rs index f0a06b0f15a35..65abe3c91d1a0 100644 --- a/crates/ruff_linter/src/rules/mccabe/settings.rs +++ b/crates/ruff_linter/src/rules/mccabe/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `mccabe` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt::{Display, Formatter}; #[derive(Debug, CacheKey)] pub struct Settings { @@ -16,3 +18,16 @@ impl Default for Settings { } } } + +impl Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.mccabe", + fields = [ + self.max_complexity + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/pep8_naming/settings.rs b/crates/ruff_linter/src/rules/pep8_naming/settings.rs index 65930f7690927..1a64557a6d2ad 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/settings.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/settings.rs @@ -2,7 +2,9 @@ use std::error::Error; use std::fmt; +use std::fmt::Formatter; +use crate::display_settings; use ruff_macros::CacheKey; use crate::settings::types::IdentifierPattern; @@ -44,6 +46,21 @@ impl Default for Settings { } } +impl fmt::Display for Settings { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.pep8_naming", + fields = [ + self.ignore_names | array, + self.classmethod_decorators | array, + self.staticmethod_decorators | array + ] + } + Ok(()) + } +} + /// Error returned by the [`TryFrom`] implementation of [`Settings`]. #[derive(Debug)] pub enum SettingsError { diff --git a/crates/ruff_linter/src/rules/pycodestyle/settings.rs b/crates/ruff_linter/src/rules/pycodestyle/settings.rs index 7990f6a65646d..1ce1d1c029ea6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/settings.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/settings.rs @@ -1,6 +1,8 @@ //! Settings for the `pycodestyle` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt; use crate::line_width::LineLength; @@ -10,3 +12,18 @@ pub struct Settings { pub max_doc_length: Option, pub ignore_overlong_task_comments: bool, } + +impl fmt::Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.pycodestyle", + fields = [ + self.max_line_length, + self.max_doc_length | optional, + self.ignore_overlong_task_comments, + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/pydocstyle/settings.rs b/crates/ruff_linter/src/rules/pydocstyle/settings.rs index a8025bdff4578..b65172e4102d3 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/settings.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/settings.rs @@ -1,9 +1,11 @@ //! Settings for the `pydocstyle` plugin. use std::collections::BTreeSet; +use std::fmt; use serde::{Deserialize, Serialize}; +use crate::display_settings; use ruff_macros::CacheKey; use crate::registry::Rule; @@ -71,9 +73,34 @@ impl Convention { } } +impl fmt::Display for Convention { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Google => write!(f, "google"), + Self::Numpy => write!(f, "numpy"), + Self::Pep257 => write!(f, "pep257"), + } + } +} + #[derive(Debug, Default, CacheKey)] pub struct Settings { pub convention: Option, pub ignore_decorators: BTreeSet, pub property_decorators: BTreeSet, } + +impl fmt::Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.pydocstyle", + fields = [ + self.convention | optional, + self.ignore_decorators | debug, + self.property_decorators | debug + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/pyflakes/settings.rs b/crates/ruff_linter/src/rules/pyflakes/settings.rs index d87f93da37669..b87c9aebf2108 100644 --- a/crates/ruff_linter/src/rules/pyflakes/settings.rs +++ b/crates/ruff_linter/src/rules/pyflakes/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `Pyflakes` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub extend_generics: Vec, } + +impl fmt::Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.pyflakes", + fields = [ + self.extend_generics | debug + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/pylint/settings.rs b/crates/ruff_linter/src/rules/pylint/settings.rs index 8ea19cdfaf41d..71ff494bf2a7c 100644 --- a/crates/ruff_linter/src/rules/pylint/settings.rs +++ b/crates/ruff_linter/src/rules/pylint/settings.rs @@ -2,7 +2,9 @@ use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; +use std::fmt; +use crate::display_settings; use ruff_macros::CacheKey; use ruff_python_ast::{ExprNumberLiteral, LiteralExpressionRef, Number}; @@ -34,6 +36,18 @@ impl ConstantType { } } +impl fmt::Display for ConstantType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Bytes => write!(f, "bytes"), + Self::Complex => write!(f, "complex"), + Self::Float => write!(f, "float"), + Self::Int => write!(f, "int"), + Self::Str => write!(f, "str"), + } + } +} + #[derive(Debug, CacheKey)] pub struct Settings { pub allow_magic_value_types: Vec, @@ -64,3 +78,25 @@ impl Default for Settings { } } } + +impl fmt::Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.pylint", + fields = [ + self.allow_magic_value_types | array, + self.allow_dunder_method_names | array, + self.max_args, + self.max_positional_args, + self.max_returns, + self.max_bool_expr, + self.max_branches, + self.max_statements, + self.max_public_methods, + self.max_locals + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/pyupgrade/settings.rs b/crates/ruff_linter/src/rules/pyupgrade/settings.rs index 96f5a80513e70..4e228351f3639 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/settings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/settings.rs @@ -1,8 +1,23 @@ //! Settings for the `pyupgrade` plugin. +use crate::display_settings; use ruff_macros::CacheKey; +use std::fmt; #[derive(Debug, Default, CacheKey)] pub struct Settings { pub keep_runtime_typing: bool, } + +impl fmt::Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.pyupgrade", + fields = [ + self.keep_runtime_typing + ] + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 837abed45fd4e..5e5731ca9a4e9 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -16,8 +16,7 @@ mod tests { use crate::pyproject_toml::lint_pyproject_toml; use crate::registry::Rule; - use crate::settings::resolve_per_file_ignores; - use crate::settings::types::{PerFileIgnore, PreviewMode, PythonVersion}; + use crate::settings::types::{PerFileIgnore, PerFileIgnores, PreviewMode, PythonVersion}; use crate::test::{test_path, test_resource_path}; use crate::{assert_messages, settings}; @@ -171,7 +170,7 @@ mod tests { let mut settings = settings::LinterSettings::for_rules(vec![Rule::UnusedNOQA, Rule::UnusedImport]); - settings.per_file_ignores = resolve_per_file_ignores(vec![PerFileIgnore::new( + settings.per_file_ignores = PerFileIgnores::resolve(vec![PerFileIgnore::new( "RUF100_2.py".to_string(), &["F401".parse().unwrap()], None, @@ -228,7 +227,7 @@ mod tests { let diagnostics = test_path( Path::new("ruff/ruff_per_file_ignores.py"), &settings::LinterSettings { - per_file_ignores: resolve_per_file_ignores(vec![PerFileIgnore::new( + per_file_ignores: PerFileIgnores::resolve(vec![PerFileIgnore::new( "ruff_per_file_ignores.py".to_string(), &["F401".parse().unwrap(), "RUF100".parse().unwrap()], None, diff --git a/crates/ruff_linter/src/settings/fix_safety_table.rs b/crates/ruff_linter/src/settings/fix_safety_table.rs index 061b3fe0568e5..e3b280be0cddc 100644 --- a/crates/ruff_linter/src/settings/fix_safety_table.rs +++ b/crates/ruff_linter/src/settings/fix_safety_table.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display, Formatter}; use ruff_diagnostics::Applicability; use ruff_macros::CacheKey; @@ -6,6 +6,7 @@ use rustc_hash::FxHashMap; use strum::IntoEnumIterator; use crate::{ + display_settings, registry::{Rule, RuleSet}, rule_selector::{PreviewOptions, Specificity}, RuleSelector, @@ -95,6 +96,20 @@ impl FixSafetyTable { } } +impl Display for FixSafetyTable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.safety_table", + fields = [ + self.forced_safe, + self.forced_unsafe + ] + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index a1d34de5b1b86..bd6289438eb96 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -2,10 +2,9 @@ //! command-line options. Structure is optimized for internal usage, as opposed //! to external visibility or parsing. +use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; -use anyhow::Result; -use globset::{Glob, GlobMatcher}; use once_cell::sync::Lazy; use path_absolutize::path_dedot; use regex::Regex; @@ -15,7 +14,7 @@ use crate::codes::RuleCodePrefix; use ruff_macros::CacheKey; use crate::line_width::LineLength; -use crate::registry::{Linter, Rule, RuleSet}; +use crate::registry::{Linter, Rule}; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, @@ -23,7 +22,7 @@ use crate::rules::{ flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; -use crate::settings::types::{ExtensionMapping, FilePatternSet, PerFileIgnore, PythonVersion}; +use crate::settings::types::{ExtensionMapping, FilePatternSet, PerFileIgnores, PythonVersion}; use crate::{codes, RuleSelector}; use super::line_width::IndentWidth; @@ -38,6 +37,126 @@ pub mod flags; pub mod rule_table; pub mod types; +/// `display_settings!` is a macro that can display and format struct fields in a readable, +/// namespaced format. It's particularly useful at generating `Display` implementations +/// for types used in settings. +/// +/// # Example +/// ``` +/// use std::fmt; +/// use ruff_linter::display_settings; +/// #[derive(Default)] +/// struct Settings { +/// option_a: bool, +/// sub_settings: SubSettings, +/// option_b: String, +/// } +/// +/// struct SubSettings { +/// name: String +/// } +/// +/// impl Default for SubSettings { +/// fn default() -> Self { +/// Self { name: "Default Name".into() } +/// } +/// +/// } +/// +/// impl fmt::Display for SubSettings { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// display_settings! { +/// formatter = f, +/// namespace = "sub_settings", +/// fields = [ +/// self.name | quoted +/// ] +/// } +/// Ok(()) +/// } +/// +/// } +/// +/// impl fmt::Display for Settings { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// display_settings! { +/// formatter = f, +/// fields = [ +/// self.option_a, +/// self.sub_settings | nested, +/// self.option_b | quoted, +/// ] +/// } +/// Ok(()) +/// } +/// +/// } +/// +/// const EXPECTED_OUTPUT: &str = r#"option_a = false +/// sub_settings.name = "Default Name" +/// option_b = "" +/// "#; +/// +/// fn main() { +/// let settings = Settings::default(); +/// assert_eq!(format!("{settings}"), EXPECTED_OUTPUT); +/// } +/// ``` +#[macro_export] +macro_rules! display_settings { + (formatter = $fmt:ident, namespace = $namespace:literal, fields = [$($settings:ident.$field:ident $(| $modifier:tt)?),* $(,)?]) => { + { + const _PREFIX: &str = concat!($namespace, "."); + $( + display_settings!(@field $fmt, _PREFIX, $settings.$field $(| $modifier)?); + )* + } + }; + (formatter = $fmt:ident, fields = [$($settings:ident.$field:ident $(| $modifier:tt)?),* $(,)?]) => { + { + const _PREFIX: &str = ""; + $( + display_settings!(@field $fmt, _PREFIX, $settings.$field $(| $modifier)?); + )* + } + }; + (@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | debug) => { + writeln!($fmt, "{}{} = {:?}", $prefix, stringify!($field), $settings.$field)?; + }; + (@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | quoted) => { + writeln!($fmt, "{}{} = \"{}\"", $prefix, stringify!($field), $settings.$field)?; + }; + (@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | nested) => { + write!($fmt, "{}", $settings.$field)?; + }; + (@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | optional) => { + { + write!($fmt, "{}{} = ", $prefix, stringify!($field))?; + match &$settings.$field { + Some(value) => writeln!($fmt, "{}", value)?, + None => writeln!($fmt, "none")? + }; + } + }; + (@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | array) => { + { + write!($fmt, "{}{} = ", $prefix, stringify!($field))?; + if $settings.$field.is_empty() { + writeln!($fmt, "[]")?; + } else { + writeln!($fmt, "[")?; + for elem in &$settings.$field { + writeln!($fmt, "\t{elem},")?; + } + writeln!($fmt, "]")?; + } + } + }; + (@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident) => { + writeln!($fmt, "{}{} = {}", $prefix, stringify!($field), $settings.$field)?; + }; +} + #[derive(Debug, CacheKey)] pub struct LinterSettings { pub exclude: FilePatternSet, @@ -45,7 +164,7 @@ pub struct LinterSettings { pub project_root: PathBuf, pub rules: RuleTable, - pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, + pub per_file_ignores: PerFileIgnores, pub fix_safety: FixSafetyTable, pub target_version: PythonVersion, @@ -93,6 +212,73 @@ pub struct LinterSettings { pub pyupgrade: pyupgrade::settings::Settings, } +impl Display for LinterSettings { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "\n# Linter Settings")?; + display_settings! { + formatter = f, + namespace = "linter", + fields = [ + self.exclude, + self.project_root | debug, + + self.rules | nested, + self.per_file_ignores, + self.fix_safety | nested, + + self.target_version | debug, + self.preview, + self.explicit_preview_rules, + self.extension | nested, + + self.allowed_confusables | array, + self.builtins | array, + self.dummy_variable_rgx, + self.external | array, + self.ignore_init_module_imports, + self.logger_objects | array, + self.namespace_packages | debug, + self.src | debug, + self.tab_size, + self.line_length, + self.task_tags | array, + self.typing_modules | array, + ] + } + writeln!(f, "\n# Linter Plugins")?; + display_settings! { + formatter = f, + namespace = "linter", + fields = [ + self.flake8_annotations | nested, + self.flake8_bandit | nested, + self.flake8_bugbear | nested, + self.flake8_builtins | nested, + self.flake8_comprehensions | nested, + self.flake8_copyright | nested, + self.flake8_errmsg | nested, + self.flake8_gettext | nested, + self.flake8_implicit_str_concat | nested, + self.flake8_import_conventions | nested, + self.flake8_pytest_style | nested, + self.flake8_quotes | nested, + self.flake8_self | nested, + self.flake8_tidy_imports | nested, + self.flake8_type_checking | nested, + self.flake8_unused_arguments | nested, + self.isort | nested, + self.mccabe | nested, + self.pep8_naming | nested, + self.pycodestyle | nested, + self.pyflakes | nested, + self.pylint | nested, + self.pyupgrade | nested, + ] + } + Ok(()) + } +} + pub const DEFAULT_SELECTORS: &[RuleSelector] = &[ RuleSelector::Linter(Linter::Pyflakes), // Only include pycodestyle rules that do not overlap with the formatter @@ -152,7 +338,7 @@ impl LinterSettings { logger_objects: vec![], namespace_packages: vec![], - per_file_ignores: vec![], + per_file_ignores: PerFileIgnores::default(), fix_safety: FixSafetyTable::default(), src: vec![path_dedot::CWD.clone()], @@ -204,22 +390,3 @@ impl Default for LinterSettings { Self::new(path_dedot::CWD.as_path()) } } - -/// Given a list of patterns, create a `GlobSet`. -pub fn resolve_per_file_ignores( - per_file_ignores: Vec, -) -> Result> { - per_file_ignores - .into_iter() - .map(|per_file_ignore| { - // Construct absolute path matcher. - let absolute = - Glob::new(&per_file_ignore.absolute.to_string_lossy())?.compile_matcher(); - - // Construct basename matcher. - let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher(); - - Ok((absolute, basename, per_file_ignore.rules)) - }) - .collect() -} diff --git a/crates/ruff_linter/src/settings/rule_table.rs b/crates/ruff_linter/src/settings/rule_table.rs index 898dcb3f7c76c..f6b8482afc71c 100644 --- a/crates/ruff_linter/src/settings/rule_table.rs +++ b/crates/ruff_linter/src/settings/rule_table.rs @@ -1,5 +1,6 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display, Formatter}; +use crate::display_settings; use ruff_macros::CacheKey; use crate::registry::{Rule, RuleSet, RuleSetIterator}; @@ -62,6 +63,20 @@ impl RuleTable { } } +impl Display for RuleTable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.rules", + fields = [ + self.enabled, + self.should_fix + ] + } + Ok(()) + } +} + impl FromIterator for RuleTable { fn from_iter>(iter: T) -> Self { let rules = RuleSet::from_iter(iter); diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 1ade2f5068c6d..18da863b692b6 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -1,3 +1,4 @@ +use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -5,7 +6,7 @@ use std::str::FromStr; use std::string::ToString; use anyhow::{bail, Result}; -use globset::{Glob, GlobSet, GlobSetBuilder}; +use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; use rustc_hash::FxHashMap; use serde::{de, Deserialize, Deserializer, Serialize}; @@ -17,9 +18,9 @@ use ruff_diagnostics::Applicability; use ruff_macros::CacheKey; use ruff_python_ast::PySourceType; -use crate::fs; use crate::registry::RuleSet; use crate::rule_selector::RuleSelector; +use crate::{display_settings, fs}; #[derive( Clone, @@ -121,6 +122,15 @@ impl From for PreviewMode { } } +impl Display for PreviewMode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Disabled => write!(f, "disabled"), + Self::Enabled => write!(f, "enabled"), + } + } +} + /// Toggle for unsafe fixes. /// `Hint` will not apply unsafe fixes but a message will be shown when they are available. /// `Disabled` will not apply unsafe fixes or show a message. @@ -133,6 +143,20 @@ pub enum UnsafeFixes { Enabled, } +impl Display for UnsafeFixes { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Hint => "hint", + Self::Disabled => "disabled", + Self::Enabled => "enabled", + } + ) + } +} + impl From for UnsafeFixes { fn from(value: bool) -> Self { if value { @@ -178,6 +202,19 @@ impl FilePattern { } } +impl Display for FilePattern { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:?}", + match self { + Self::Builtin(pattern) => pattern, + Self::User(pattern, _) => pattern.as_str(), + } + ) + } +} + impl FromStr for FilePattern { type Err = anyhow::Error; @@ -192,9 +229,14 @@ impl FromStr for FilePattern { pub struct FilePatternSet { set: GlobSet, cache_key: u64, + // This field is only for displaying the internals + // of `set`. + #[allow(clippy::used_underscore_binding)] + _set_internals: Vec, } impl FilePatternSet { + #[allow(clippy::used_underscore_binding)] pub fn try_from_iter(patterns: I) -> Result where I: IntoIterator, @@ -202,7 +244,10 @@ impl FilePatternSet { let mut builder = GlobSetBuilder::new(); let mut hasher = CacheKeyHasher::new(); + let mut _set_internals = vec![]; + for pattern in patterns { + _set_internals.push(pattern.clone()); pattern.cache_key(&mut hasher); pattern.add_to(&mut builder)?; } @@ -212,10 +257,26 @@ impl FilePatternSet { Ok(FilePatternSet { set, cache_key: hasher.finish(), + _set_internals, }) } } +impl Display for FilePatternSet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self._set_internals.is_empty() { + write!(f, "[]")?; + } else { + writeln!(f, "[")?; + for pattern in &self._set_internals { + writeln!(f, "\t{pattern},")?; + } + write!(f, "]")?; + } + Ok(()) + } +} + impl Deref for FilePatternSet { type Target = GlobSet; @@ -395,6 +456,19 @@ impl ExtensionMapping { } } +impl Display for ExtensionMapping { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "linter.extension", + fields = [ + self.mapping | debug + ] + } + Ok(()) + } +} + impl From> for ExtensionMapping { fn from(value: FxHashMap) -> Self { Self { mapping: value } @@ -429,6 +503,23 @@ pub enum SerializationFormat { Sarif, } +impl Display for SerializationFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Text => write!(f, "text"), + Self::Json => write!(f, "json"), + Self::JsonLines => write!(f, "json_lines"), + Self::Junit => write!(f, "junit"), + Self::Grouped => write!(f, "grouped"), + Self::Github => write!(f, "github"), + Self::Gitlab => write!(f, "gitlab"), + Self::Pylint => write!(f, "pylint"), + Self::Azure => write!(f, "azure"), + Self::Sarif => write!(f, "sarif"), + } + } +} + impl Default for SerializationFormat { fn default() -> Self { Self::Text @@ -468,3 +559,55 @@ impl Deref for Version { /// [`fnmatch`](https://docs.python.org/3/library/fnmatch.html) for /// pattern matching. pub type IdentifierPattern = glob::Pattern; + +#[derive(Debug, CacheKey, Default)] +pub struct PerFileIgnores { + // Ordered as (absolute path matcher, basename matcher, rules) + ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, +} + +impl PerFileIgnores { + /// Given a list of patterns, create a `GlobSet`. + pub fn resolve(per_file_ignores: Vec) -> Result { + let ignores: Result> = per_file_ignores + .into_iter() + .map(|per_file_ignore| { + // Construct absolute path matcher. + let absolute = + Glob::new(&per_file_ignore.absolute.to_string_lossy())?.compile_matcher(); + + // Construct basename matcher. + let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher(); + + Ok((absolute, basename, per_file_ignore.rules)) + }) + .collect(); + Ok(Self { ignores: ignores? }) + } +} + +impl Display for PerFileIgnores { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + write!(f, "{{}}")?; + } else { + writeln!(f, "{{")?; + for (absolute, basename, rules) in &self.ignores { + writeln!( + f, + "\t{{ absolute = {absolute:#?}, basename = {basename:#?}, rules = {rules} }}," + )?; + } + write!(f, "}}")?; + } + Ok(()) + } +} + +impl Deref for PerFileIgnores { + type Target = Vec<(GlobMatcher, GlobMatcher, RuleSet)>; + + fn deref(&self) -> &Self::Target { + &self.ignores + } +} diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs index 8a7967867afa1..122a9909e943a 100644 --- a/crates/ruff_python_formatter/src/options.rs +++ b/crates/ruff_python_formatter/src/options.rs @@ -2,6 +2,7 @@ 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::fmt; use std::path::Path; use std::str::FromStr; @@ -241,6 +242,16 @@ pub enum QuoteStyle { Preserve, } +impl fmt::Display for QuoteStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Single => write!(f, "single"), + Self::Double => write!(f, "double"), + Self::Preserve => write!(f, "preserve"), + } + } +} + impl FromStr for QuoteStyle { type Err = &'static str; @@ -277,6 +288,15 @@ impl MagicTrailingComma { } } +impl fmt::Display for MagicTrailingComma { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Respect => write!(f, "respect"), + Self::Ignore => write!(f, "ignore"), + } + } +} + impl FromStr for MagicTrailingComma { type Err = &'static str; @@ -306,6 +326,15 @@ impl PreviewMode { } } +impl fmt::Display for PreviewMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Disabled => write!(f, "disabled"), + Self::Enabled => write!(f, "enabled"), + } + } +} + #[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"))] @@ -323,6 +352,15 @@ impl DocstringCode { } } +impl fmt::Display for DocstringCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Disabled => write!(f, "disabled"), + Self::Enabled => write!(f, "enabled"), + } + } +} + #[derive(Copy, Clone, Default, Eq, PartialEq, CacheKey)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] @@ -338,8 +376,8 @@ pub enum DocstringCodeLineWidth { Dynamic, } -impl std::fmt::Debug for DocstringCodeLineWidth { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl fmt::Debug for DocstringCodeLineWidth { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { DocstringCodeLineWidth::Fixed(v) => v.value().fmt(f), DocstringCodeLineWidth::Dynamic => "dynamic".fmt(f), @@ -347,6 +385,15 @@ impl std::fmt::Debug for DocstringCodeLineWidth { } } +impl fmt::Display for DocstringCodeLineWidth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Fixed(width) => width.fmt(f), + Self::Dynamic => write!(f, "dynamic"), + } + } +} + /// Responsible for deserializing the `DocstringCodeLineWidth::Dynamic` /// variant. fn deserialize_docstring_code_line_width_dynamic<'de, D>(d: D) -> Result<(), D::Error> diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 6430f8d481b02..0ac782910e84b 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -25,12 +25,10 @@ use ruff_linter::rule_selector::{PreviewOptions, Specificity}; use ruff_linter::rules::pycodestyle; use ruff_linter::settings::rule_table::RuleTable; use ruff_linter::settings::types::{ - ExtensionMapping, FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, - SerializationFormat, UnsafeFixes, Version, -}; -use ruff_linter::settings::{ - resolve_per_file_ignores, LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS, + ExtensionMapping, FilePattern, FilePatternSet, PerFileIgnore, PerFileIgnores, PreviewMode, + PythonVersion, SerializationFormat, UnsafeFixes, Version, }; +use ruff_linter::settings::{LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS}; use ruff_linter::{ fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION, }; @@ -260,7 +258,7 @@ impl Configuration { line_length, tab_size: self.indent_width.unwrap_or_default(), namespace_packages: self.namespace_packages.unwrap_or_default(), - per_file_ignores: resolve_per_file_ignores( + per_file_ignores: PerFileIgnores::resolve( lint.per_file_ignores .unwrap_or_default() .into_iter() diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index 3e2560285a5b0..a4b5b785d0454 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -1,6 +1,7 @@ use path_absolutize::path_dedot; use ruff_cache::cache_dir; use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth}; +use ruff_linter::display_settings; use ruff_linter::settings::types::{ ExtensionMapping, FilePattern, FilePatternSet, SerializationFormat, UnsafeFixes, }; @@ -12,6 +13,7 @@ use ruff_python_formatter::{ QuoteStyle, }; use ruff_source_file::find_newline; +use std::fmt; use std::path::{Path, PathBuf}; #[derive(Debug, CacheKey)] @@ -55,6 +57,30 @@ impl Default for Settings { } } +impl fmt::Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "\n# General Settings")?; + display_settings! { + formatter = f, + fields = [ + // We want the quotes and lossy UTF8 conversion for this path, so + // using PathBuf's `Debug` formatter suffices. + self.cache_dir | debug, + self.fix, + self.fix_only, + self.output_format, + self.show_fixes, + self.show_source, + self.unsafe_fixes, + self.file_resolver | nested, + self.linter | nested, + self.formatter | nested + ] + } + Ok(()) + } +} + #[derive(Debug, CacheKey)] pub struct FileResolverSettings { pub exclude: FilePatternSet, @@ -66,6 +92,26 @@ pub struct FileResolverSettings { pub project_root: PathBuf, } +impl fmt::Display for FileResolverSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "\n# File Resolver Settings")?; + display_settings! { + formatter = f, + namespace = "file_resolver", + fields = [ + self.exclude, + self.extend_exclude, + self.force_exclude, + self.include, + self.extend_include, + self.respect_gitignore, + self.project_root | debug, + ] + } + Ok(()) + } +} + pub(crate) static EXCLUDE: &[FilePattern] = &[ FilePattern::Builtin(".bzr"), FilePattern::Builtin(".direnv"), @@ -195,6 +241,30 @@ impl Default for FormatterSettings { } } +impl fmt::Display for FormatterSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "\n# Formatter Settings")?; + display_settings! { + formatter = f, + namespace = "formatter", + fields = [ + self.exclude, + self.target_version | debug, + self.preview, + self.line_width, + self.line_ending, + self.indent_style, + self.indent_width, + self.quote_style, + self.magic_trailing_comma, + self.docstring_code_format, + self.docstring_code_line_width, + ] + } + Ok(()) + } +} + #[derive( Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey, serde::Serialize, serde::Deserialize, )] @@ -216,3 +286,14 @@ pub enum LineEnding { /// Line endings will be converted to `\n` on Unix and `\r\n` on Windows. Native, } + +impl fmt::Display for LineEnding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Auto => write!(f, "auto"), + Self::Lf => write!(f, "lf"), + Self::CrLf => write!(f, "crlf"), + Self::Native => write!(f, "native"), + } + } +}