--show-settings displays active settings in a far more readable format (#9464)

<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## 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!`.
This commit is contained in:
Jane Lewis 2024-01-12 11:30:29 -08:00 committed by GitHub
parent fee64b52ba
commit 7504bf347b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1455 additions and 43 deletions

View file

@ -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(())
}

View file

@ -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")));
});
}

View file

@ -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 -----

View file

@ -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<u8> 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<u16> for LineWidth {
type Error = TryFromIntError;

View file

@ -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<NonZeroU8> for IndentWidth {
fn from(tab_size: NonZeroU8) -> Self {
Self(tab_size)

View file

@ -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<Rule> for RuleSet {
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
let mut set = RuleSet::empty();

View file

@ -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(())
}
}

View file

@ -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<String> {
["/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(())
}
}

View file

@ -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<String>,
}
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(())
}
}

View file

@ -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<String>,
}
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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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 {

View file

@ -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"),
}
}
}

View file

@ -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(())
}
}

View file

@ -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<String, ApiBan>,
pub banned_module_level_imports: Vec<String>,
}
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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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 {

View file

@ -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(())
}
}

View file

@ -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 {

View file

@ -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<LineLength>,
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(())
}
}

View file

@ -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<Convention>,
pub ignore_decorators: BTreeSet<String>,
pub property_decorators: BTreeSet<String>,
}
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(())
}
}

View file

@ -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<String>,
}
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(())
}
}

View file

@ -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<ConstantType>,
@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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,

View file

@ -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::*;

View file

@ -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<PerFileIgnore>,
) -> Result<Vec<(GlobMatcher, GlobMatcher, RuleSet)>> {
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()
}

View file

@ -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<Rule> for RuleTable {
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
let rules = RuleSet::from_iter(iter);

View file

@ -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<bool> 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<bool> 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<FilePattern>,
}
impl FilePatternSet {
#[allow(clippy::used_underscore_binding)]
pub fn try_from_iter<I>(patterns: I) -> Result<Self, anyhow::Error>
where
I: IntoIterator<Item = FilePattern>,
@ -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<FxHashMap<String, Language>> for ExtensionMapping {
fn from(value: FxHashMap<String, Language>) -> 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<PerFileIgnore>) -> Result<Self> {
let ignores: Result<Vec<_>> = 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
}
}

View file

@ -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>

View file

@ -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()

View file

@ -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"),
}
}
}