use std::collections::BTreeSet; use std::hash::BuildHasherDefault; use regex::Regex; use rustc_hash::{FxHashMap, FxHashSet}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use ruff_formatter::IndentStyle; use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::rules::flake8_import_conventions::settings::BannedAliases; use ruff_linter::rules::flake8_pytest_style::settings::SettingsError; use ruff_linter::rules::flake8_pytest_style::types; use ruff_linter::rules::flake8_quotes::settings::Quote; use ruff_linter::rules::flake8_tidy_imports::settings::{ApiBan, Strictness}; use ruff_linter::rules::isort::settings::RelativeImportsOrder; use ruff_linter::rules::isort::{ImportSection, ImportType}; use ruff_linter::rules::pep8_naming::settings::IgnoreNames; use ruff_linter::rules::pydocstyle::settings::Convention; use ruff_linter::rules::pylint::settings::ConstantType; use ruff_linter::rules::{ flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; use ruff_linter::settings::types::{ IdentifierPattern, PythonVersion, RequiredVersion, SerializationFormat, }; use ruff_linter::{warn_user_once, RuleSelector}; use ruff_macros::{CombineOptions, OptionsMetadata}; use ruff_python_formatter::{DocstringCodeLineWidth, QuoteStyle}; use crate::options_base::{OptionsMetadata, Visit}; use crate::settings::LineEnding; #[derive(Clone, Debug, PartialEq, Eq, Default, OptionsMetadata, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Options { /// A path to the cache directory. /// /// By default, Ruff stores cache results in a `.ruff_cache` directory in /// the current project root. /// /// However, Ruff will also respect the `RUFF_CACHE_DIR` environment /// variable, which takes precedence over that default. /// /// This setting will override even the `RUFF_CACHE_DIR` environment /// variable, if set. #[option( default = ".ruff_cache", value_type = "str", example = r#"cache-dir = "~/.cache/ruff""# )] pub cache_dir: Option, /// A path to a local `pyproject.toml` file to merge into this /// configuration. User home directory and environment variables will be /// expanded. /// /// To resolve the current `pyproject.toml` file, Ruff will first resolve /// this base configuration file, then merge in any properties defined /// in the current configuration file. #[option( default = r#"null"#, value_type = "str", example = r#" # Extend the `pyproject.toml` file in the parent directory. extend = "../pyproject.toml" # But use a different line length. line-length = 100 "# )] pub extend: Option, /// The style in which violation messages should be formatted: `"full"` /// (shows source),`"concise"` (default), `"grouped"` (group messages by file), `"json"` /// (machine-readable), `"junit"` (machine-readable XML), `"github"` (GitHub /// Actions annotations), `"gitlab"` (GitLab CI code quality report), /// `"pylint"` (Pylint text format) or `"azure"` (Azure Pipeline logging commands). #[option( default = r#""concise""#, value_type = r#""full" | "concise" | "grouped" | "json" | "junit" | "github" | "gitlab" | "pylint" | "azure""#, example = r#" # Group violations by containing file. output-format = "grouped" "# )] pub output_format: Option, /// Enable fix behavior by-default when running `ruff` (overridden /// by the `--fix` and `--no-fix` command-line flags). /// Only includes automatic fixes unless `--unsafe-fixes` is provided. #[option(default = "false", value_type = "bool", example = "fix = true")] pub fix: Option, /// Enable application of unsafe fixes. /// If excluded, a hint will be displayed when unsafe fixes are available. /// If set to false, the hint will be hidden. #[option( default = r#"null"#, value_type = "bool", example = "unsafe-fixes = true" )] pub unsafe_fixes: Option, /// Like `fix`, but disables reporting on leftover violation. Implies `fix`. #[option(default = "false", value_type = "bool", example = "fix-only = true")] pub fix_only: Option, /// Whether to show source code snippets when reporting lint violations /// (overridden by the `--show-source` command-line flag). #[option( default = "false", value_type = "bool", example = r#" # By default, always show source code snippets. show-source = true "# )] #[deprecated( note = "`show-source` is deprecated and is now part of `output-format` in the form of `full` or `concise` options. Please update your configuration." )] pub show_source: Option, /// Whether to show an enumeration of all fixed lint violations /// (overridden by the `--show-fixes` command-line flag). #[option( default = "false", value_type = "bool", example = r#" # Enumerate all fixed violations. show-fixes = true "# )] pub show_fixes: Option, /// Enforce a requirement on the version of Ruff, to enforce at runtime. /// If the version of Ruff does not meet the requirement, Ruff will exit /// with an error. /// /// Useful for unifying results across many environments, e.g., with a /// `pyproject.toml` file. /// /// Accepts a PEP 440 specifier, like `==0.3.1` or `>=0.3.1`. #[option( default = "null", value_type = "str", example = r#" required-version = ">=0.0.193" "# )] pub required_version: Option, /// Whether to enable preview mode. When preview mode is enabled, Ruff will /// use unstable rules, fixes, and formatting. #[option( default = "false", value_type = "bool", example = r#" # Enable preview features. preview = true "# )] pub preview: Option, // File resolver options /// A list of file patterns to exclude from formatting and linting. /// /// Exclusions are based on globs, and can be either: /// /// - Single-path patterns, like `.mypy_cache` (to exclude any directory /// named `.mypy_cache` in the tree), `foo.py` (to exclude any file named /// `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). /// - Relative patterns, like `directory/foo.py` (to exclude that specific /// file) or `directory/*.py` (to exclude any Python files in /// `directory`). Note that these paths are relative to the project root /// (e.g., the directory containing your `pyproject.toml`). /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). /// /// Note that you'll typically want to use /// [`extend-exclude`](#extend-exclude) to modify the excluded paths. #[option( default = r#"[".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".mypy_cache", ".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "dist", "node_modules", "venv"]"#, value_type = "list[str]", example = r#" exclude = [".venv"] "# )] pub exclude: Option>, /// A list of file patterns to omit from formatting and linting, in addition to those /// specified by `exclude`. /// /// Exclusions are based on globs, and can be either: /// /// - Single-path patterns, like `.mypy_cache` (to exclude any directory /// named `.mypy_cache` in the tree), `foo.py` (to exclude any file named /// `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). /// - Relative patterns, like `directory/foo.py` (to exclude that specific /// file) or `directory/*.py` (to exclude any Python files in /// `directory`). Note that these paths are relative to the project root /// (e.g., the directory containing your `pyproject.toml`). /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = "[]", value_type = "list[str]", example = r#" # In addition to the standard set of exclusions, omit all tests, plus a specific file. extend-exclude = ["tests", "src/bad.py"] "# )] pub extend_exclude: Option>, /// A list of file patterns to include when linting, in addition to those /// specified by `include`. /// /// Inclusion are based on globs, and should be single-path patterns, like /// `*.pyw`, to include any file with the `.pyw` extension. /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = "[]", value_type = "list[str]", example = r#" # In addition to the standard set of inclusions, include `.pyw` files. extend-include = ["*.pyw"] "# )] pub extend_include: Option>, /// Whether to enforce `exclude` and `extend-exclude` patterns, even for /// paths that are passed to Ruff explicitly. Typically, Ruff will lint /// any paths passed in directly, even if they would typically be /// excluded. Setting `force-exclude = true` will cause Ruff to /// respect these exclusions unequivocally. /// /// This is useful for [`pre-commit`](https://pre-commit.com/), which explicitly passes all /// changed files to the [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit) /// plugin, regardless of whether they're marked as excluded by Ruff's own /// settings. #[option( default = r#"false"#, value_type = "bool", example = r#" force-exclude = true "# )] pub force_exclude: Option, /// A list of file patterns to include when linting. /// /// Inclusion are based on globs, and should be single-path patterns, like /// `*.pyw`, to include any file with the `.pyw` extension. `pyproject.toml` is /// included here not for configuration but because we lint whether e.g. the /// `[project]` matches the schema. /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"["*.py", "*.pyi", "**/pyproject.toml"]"#, value_type = "list[str]", example = r#" include = ["*.py"] "# )] pub include: Option>, /// Whether to automatically exclude files that are ignored by `.ignore`, /// `.gitignore`, `.git/info/exclude`, and global `gitignore` files. /// Enabled by default. #[option( default = "true", value_type = "bool", example = r#" respect-gitignore = false "# )] pub respect_gitignore: Option, // Generic python options /// A list of builtins to treat as defined references, in addition to the /// system builtins. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" builtins = ["_"] "# )] pub builtins: Option>, /// Mark the specified directories as namespace packages. For the purpose of /// module resolution, Ruff will treat those directories and all their subdirectories /// as if they contained an `__init__.py` file. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" namespace-packages = ["airflow/providers"] "# )] pub namespace_packages: Option>, /// The minimum Python version to target, e.g., when considering automatic /// code upgrades, like rewriting type annotations. Ruff will not propose /// changes using features that are not available in the given version. /// /// For example, to represent supporting Python >=3.10 or ==3.10 /// specify `target-version = "py310"`. /// /// If you're already using a `pyproject.toml` file, we recommend /// `project.requires-python` instead, as it's based on Python packaging /// standards, and will be respected by other tools. For example, Ruff /// treats the following as identical to `target-version = "py38"`: /// /// ```toml /// [project] /// requires-python = ">=3.8" /// ``` /// /// If both are specified, `target-version` takes precedence over /// `requires-python`. #[option( default = r#""py38""#, value_type = r#""py37" | "py38" | "py39" | "py310" | "py311" | "py312""#, example = r#" # Always generate Python 3.7-compatible code. target-version = "py37" "# )] pub target_version: Option, /// The directories to consider when resolving first- vs. third-party /// imports. /// /// As an example: given a Python package structure like: /// /// ```text /// my_project /// ├── pyproject.toml /// └── src /// └── my_package /// ├── __init__.py /// ├── foo.py /// └── bar.py /// ``` /// /// The `./src` directory should be included in the `src` option /// (e.g., `src = ["src"]`), such that when resolving imports, /// `my_package.foo` is considered a first-party import. /// /// When omitted, the `src` directory will typically default to the /// directory containing the nearest `pyproject.toml`, `ruff.toml`, or /// `.ruff.toml` file (the "project root"), unless a configuration file /// is explicitly provided (e.g., via the `--config` command-line flag). /// /// This field supports globs. For example, if you have a series of Python /// packages in a `python_modules` directory, `src = ["python_modules/*"]` /// would expand to incorporate all of the packages in that directory. User /// home directory and environment variables will also be expanded. #[option( default = r#"["."]"#, value_type = "list[str]", example = r#" # Allow imports relative to the "src" and "test" directories. src = ["src", "test"] "# )] pub src: Option>, // Global Formatting options /// The line length to use when enforcing long-lines violations (like `E501`) /// and at which `isort` and the formatter prefers to wrap lines. /// /// The length is determined by the number of characters per line, except for lines containing East Asian characters or emojis. /// For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length. /// /// The value must be greater than `0` and less than or equal to `320`. /// /// Note: While the formatter will attempt to format lines such that they remain /// within the `line-length`, it isn't a hard upper bound, and formatted lines may /// exceed the `line-length`. /// /// See [`pycodestyle.max-line-length`](#lint_pycodestyle_max-line-length) to configure different lengths for `E501` and the formatter. #[option( default = "88", value_type = "int", example = r#" # Allow lines to be as long as 120. line-length = 120 "# )] pub line_length: Option, /// The number of spaces per indentation level (tab). /// /// Used by the formatter and when enforcing long-line violations (like `E501`) to determine the visual /// width of a tab. /// /// This option changes the number of spaces the formatter inserts when /// using soft-tabs (`indent-style = space`). /// /// PEP 8 recommends using 4 spaces per [indentation level](https://peps.python.org/pep-0008/#indentation). #[option( default = "4", value_type = "int", example = r#" indent-width = 2 "# )] pub indent_width: Option, /// The number of spaces a tab is equal to when enforcing long-line violations (like `E501`) /// or formatting code with the formatter. /// /// This option changes the number of spaces inserted by the formatter when /// using soft-tabs (`indent-style = space`). #[option( default = "4", value_type = "int", example = r#" tab-size = 2 "# )] #[deprecated( since = "0.1.2", note = "The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = ` instead." )] pub tab_size: Option, #[option_group] pub lint: Option, /// The lint sections specified at the top level. #[serde(flatten)] pub lint_top_level: DeprecatedTopLevelLintOptions, /// Options to configure code formatting. #[option_group] pub format: Option, } /// Configures how ruff checks your code. /// /// Options specified in the `lint` section take precedence over the deprecated top-level settings. #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Clone, Debug, PartialEq, Eq, Default, OptionsMetadata, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct LintOptions { #[serde(flatten)] pub common: LintCommonOptions, /// A list of file patterns to exclude from linting in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)). /// /// Exclusions are based on globs, and can be either: /// /// - Single-path patterns, like `.mypy_cache` (to exclude any directory /// named `.mypy_cache` in the tree), `foo.py` (to exclude any file named /// `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). /// - Relative patterns, like `directory/foo.py` (to exclude that specific /// file) or `directory/*.py` (to exclude any Python files in /// `directory`). Note that these paths are relative to the project root /// (e.g., the directory containing your `pyproject.toml`). /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" exclude = ["generated"] "# )] pub exclude: Option>, /// Whether to enable preview mode. When preview mode is enabled, Ruff will /// use unstable rules and fixes. #[option( default = "false", value_type = "bool", example = r#" # Enable preview features. preview = true "# )] pub preview: Option, } /// Newtype wrapper for [`LintCommonOptions`] that allows customizing the JSON schema and omitting the fields from the [`OptionsMetadata`]. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(transparent)] pub struct DeprecatedTopLevelLintOptions(pub LintCommonOptions); impl OptionsMetadata for DeprecatedTopLevelLintOptions { fn record(_visit: &mut dyn Visit) { // Intentionally empty. Omit all fields from the documentation and instead promote the options under the `lint.` section. // This doesn't create an empty 'common' option because the field in the `Options` struct is marked with `#[serde(flatten)]`. // Meaning, the code here flattens no-properties into the parent, which is what we want. } } #[cfg(feature = "schemars")] impl schemars::JsonSchema for DeprecatedTopLevelLintOptions { fn schema_name() -> std::string::String { "DeprecatedTopLevelLintOptions".to_owned() } fn schema_id() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(std::concat!( std::module_path!(), "::", "DeprecatedTopLevelLintOptions" )) } fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { use schemars::schema::Schema; let common_schema = LintCommonOptions::json_schema(gen); let mut schema_obj = common_schema.into_object(); if let Some(object) = schema_obj.object.as_mut() { for property in object.properties.values_mut() { if let Schema::Object(property_object) = property { if let Some(metadata) = &mut property_object.metadata { metadata.deprecated = true; } else { property_object.metadata = Some(Box::new(schemars::schema::Metadata { deprecated: true, ..schemars::schema::Metadata::default() })); } } } } Schema::Object(schema_obj) } } // Note: This struct should be inlined into [`LintOptions`] once support for the top-level lint settings // is removed. // Don't add any new options to this struct. Add them to [`LintOptions`] directly to avoid exposing them in the // global settings. #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive( Clone, Debug, PartialEq, Eq, Default, OptionsMetadata, CombineOptions, Serialize, Deserialize, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct LintCommonOptions { // WARNING: Don't add new options to this type. Add them to `LintOptions` instead. /// A list of allowed "confusable" Unicode characters to ignore when /// enforcing `RUF001`, `RUF002`, and `RUF003`. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" # Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and the asterisk-operator (U+2217), # which could be confused for "-", "p", and "*", respectively. allowed-confusables = ["−", "ρ", "∗"] "# )] pub allowed_confusables: Option>, /// A regular expression used to identify "dummy" variables, or those which /// should be ignored when enforcing (e.g.) unused-variable rules. The /// default expression matches `_`, `__`, and `_var`, but not `_var_`. #[option( default = r#""^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$""#, value_type = "re.Pattern", example = r#" # Only ignore variables named "_". dummy-variable-rgx = "^_$" "# )] pub dummy_variable_rgx: Option, /// A list of rule codes or prefixes to ignore, in addition to those /// specified by `ignore`. #[option( default = "[]", value_type = "list[RuleSelector]", example = r#" # Skip unused variable rules (`F841`). extend-ignore = ["F841"] "# )] #[deprecated( note = "The `extend-ignore` option is now interchangeable with `ignore`. Please update your configuration to use the `ignore` option instead." )] pub extend_ignore: Option>, /// A list of rule codes or prefixes to enable, in addition to those /// specified by `select`. #[option( default = "[]", value_type = "list[RuleSelector]", example = r#" # On top of the default `select` (`E4`, E7`, `E9`, and `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`). extend-select = ["B", "Q"] "# )] pub extend_select: Option>, /// A list of rule codes or prefixes to consider fixable, in addition to those /// specified by `fixable`. #[option( default = r#"[]"#, value_type = "list[RuleSelector]", example = r#" # Enable fix for flake8-bugbear (`B`), on top of any rules specified by `fixable`. extend-fixable = ["B"] "# )] pub extend_fixable: Option>, /// A list of rule codes or prefixes to consider non-auto-fixable, in addition to those /// specified by `unfixable`. #[deprecated( note = "The `extend-unfixable` option is now interchangeable with `unfixable`. Please update your configuration to use the `unfixable` option instead." )] pub extend_unfixable: Option>, /// A list of rule codes or prefixes that are unsupported by Ruff, but should be /// preserved when (e.g.) validating `# noqa` directives. Useful for /// retaining `# noqa` directives that cover plugins not yet implemented /// by Ruff. #[option( default = "[]", value_type = "list[str]", example = r#" # Avoiding flagging (and removing) any codes starting with `V` from any # `# noqa` directives, despite Ruff's lack of support for `vulture`. external = ["V"] "# )] pub external: Option>, /// A list of rule codes or prefixes to consider fixable. By default, /// all rules are considered fixable. #[option( default = r#"["ALL"]"#, value_type = "list[RuleSelector]", example = r#" # Only allow fix behavior for `E` and `F` rules. fixable = ["E", "F"] "# )] pub fixable: Option>, /// A list of rule codes or prefixes to ignore. Prefixes can specify exact /// rules (like `F841`), entire categories (like `F`), or anything in /// between. /// /// When breaking ties between enabled and disabled rules (via `select` and /// `ignore`, respectively), more specific prefixes override less /// specific prefixes. #[option( default = "[]", value_type = "list[RuleSelector]", example = r#" # Skip unused variable rules (`F841`). ignore = ["F841"] "# )] pub ignore: Option>, /// A list of rule codes or prefixes for which unsafe fixes should be considered /// safe. #[option( default = "[]", value_type = "list[RuleSelector]", example = r#" # Allow applying all unsafe fixes in the `E` rules and `F401` without the `--unsafe-fixes` flag extend-safe-fixes = ["E", "F401"] "# )] pub extend_safe_fixes: Option>, /// A list of rule codes or prefixes for which safe fixes should be considered /// unsafe. #[option( default = "[]", value_type = "list[RuleSelector]", example = r#" # Require the `--unsafe-fixes` flag when fixing the `E` rules and `F401` extend-unsafe-fixes = ["E", "F401"] "# )] pub extend_unsafe_fixes: Option>, /// Avoid automatically removing unused imports in `__init__.py` files. Such /// imports will still be flagged, but with a dedicated message suggesting /// that the import is either added to the module's `__all__` symbol, or /// re-exported with a redundant alias (e.g., `import os as os`). /// /// This option is enabled by default, but you can opt-in to removal of imports /// via an unsafe fix. #[option( default = "true", value_type = "bool", example = r#" ignore-init-module-imports = false "# )] #[deprecated( since = "0.4.4", note = "`ignore-init-module-imports` will be removed in a future version because F401 now recommends appropriate fixes for unused imports in `__init__.py` (currently in preview mode). See documentation for more information and please update your configuration." )] pub ignore_init_module_imports: Option, /// A list of objects that should be treated equivalently to a /// `logging.Logger` object. /// /// This is useful for ensuring proper diagnostics (e.g., to identify /// `logging` deprecations and other best-practices) for projects that /// re-export a `logging.Logger` object from a common module. /// /// For example, if you have a module `logging_setup.py` with the following /// contents: /// ```python /// import logging /// /// logger = logging.getLogger(__name__) /// ``` /// /// Adding `"logging_setup.logger"` to `logger-objects` will ensure that /// `logging_setup.logger` is treated as a `logging.Logger` object when /// imported from other modules (e.g., `from logging_setup import logger`). #[option( default = r#"[]"#, value_type = "list[str]", example = r#"logger-objects = ["logging_setup.logger"]"# )] pub logger_objects: Option>, /// A list of rule codes or prefixes to enable. Prefixes can specify exact /// rules (like `F841`), entire categories (like `F`), or anything in /// between. /// /// When breaking ties between enabled and disabled rules (via `select` and /// `ignore`, respectively), more specific prefixes override less /// specific prefixes. #[option( default = r#"["E4", "E7", "E9", "F"]"#, value_type = "list[RuleSelector]", example = r#" # On top of the defaults (`E4`, E7`, `E9`, and `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`). select = ["E4", "E7", "E9", "F", "B", "Q"] "# )] pub select: Option>, /// Whether to require exact codes to select preview rules. When enabled, /// preview rules will not be selected by prefixes — the full code of each /// preview rule will be required to enable the rule. #[option( default = "false", value_type = "bool", example = r#" # Require explicit selection of preview rules. explicit-preview-rules = true "# )] pub explicit_preview_rules: Option, /// A list of task tags to recognize (e.g., "TODO", "FIXME", "XXX"). /// /// Comments starting with these tags will be ignored by commented-out code /// detection (`ERA`), and skipped by line-length rules (`E501`) if /// `ignore-overlong-task-comments` is set to `true`. #[option( default = r#"["TODO", "FIXME", "XXX"]"#, value_type = "list[str]", example = r#" task-tags = ["HACK"] "# )] pub task_tags: Option>, /// A list of modules whose exports should be treated equivalently to /// members of the `typing` module. /// /// This is useful for ensuring proper type annotation inference for /// projects that re-export `typing` and `typing_extensions` members /// from a compatibility module. If omitted, any members imported from /// modules apart from `typing` and `typing_extensions` will be treated /// as ordinary Python objects. #[option( default = r#"[]"#, value_type = "list[str]", example = r#"typing-modules = ["airflow.typing_compat"]"# )] pub typing_modules: Option>, /// A list of rule codes or prefixes to consider non-fixable. #[option( default = "[]", value_type = "list[RuleSelector]", example = r#" # Disable fix for unused imports (`F401`). unfixable = ["F401"] "# )] pub unfixable: Option>, // WARNING: Don't add new options to this type. Add them to `LintOptions` instead. /// Options for the `flake8-annotations` plugin. #[option_group] pub flake8_annotations: Option, /// Options for the `flake8-bandit` plugin. #[option_group] pub flake8_bandit: Option, /// Options for the `flake8-boolean-trap` plugin. #[option_group] pub flake8_boolean_trap: Option, /// Options for the `flake8-bugbear` plugin. #[option_group] pub flake8_bugbear: Option, /// Options for the `flake8-builtins` plugin. #[option_group] pub flake8_builtins: Option, /// Options for the `flake8-comprehensions` plugin. #[option_group] pub flake8_comprehensions: Option, /// Options for the `flake8-copyright` plugin. #[option_group] pub flake8_copyright: Option, /// Options for the `flake8-errmsg` plugin. #[option_group] pub flake8_errmsg: Option, /// Options for the `flake8-quotes` plugin. #[option_group] pub flake8_quotes: Option, /// Options for the `flake8_self` plugin. #[option_group] pub flake8_self: Option, /// Options for the `flake8-tidy-imports` plugin. #[option_group] pub flake8_tidy_imports: Option, /// Options for the `flake8-type-checking` plugin. #[option_group] pub flake8_type_checking: Option, /// Options for the `flake8-gettext` plugin. #[option_group] pub flake8_gettext: Option, /// Options for the `flake8-implicit-str-concat` plugin. #[option_group] pub flake8_implicit_str_concat: Option, /// Options for the `flake8-import-conventions` plugin. #[option_group] pub flake8_import_conventions: Option, /// Options for the `flake8-pytest-style` plugin. #[option_group] pub flake8_pytest_style: Option, /// Options for the `flake8-unused-arguments` plugin. #[option_group] pub flake8_unused_arguments: Option, /// Options for the `isort` plugin. #[option_group] pub isort: Option, /// Options for the `mccabe` plugin. #[option_group] pub mccabe: Option, /// Options for the `pep8-naming` plugin. #[option_group] pub pep8_naming: Option, /// Options for the `pycodestyle` plugin. #[option_group] pub pycodestyle: Option, /// Options for the `pydocstyle` plugin. #[option_group] pub pydocstyle: Option, /// Options for the `pyflakes` plugin. #[option_group] pub pyflakes: Option, /// Options for the `pylint` plugin. #[option_group] pub pylint: Option, /// Options for the `pyupgrade` plugin. #[option_group] pub pyupgrade: Option, // WARNING: Don't add new options to this type. Add them to `LintOptions` instead. // Tables are required to go last. /// A list of mappings from file pattern to rule codes or prefixes to /// exclude, when considering any matching files. An initial '!' negates /// the file pattern. #[option( default = "{}", value_type = "dict[str, list[RuleSelector]]", scope = "per-file-ignores", example = r#" # Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. "__init__.py" = ["E402"] "path/to/file.py" = ["E402"] # Ignore `D` rules everywhere except for the `src/` directory. "!src/**.py" = ["D"] "# )] pub per_file_ignores: Option>>, /// A list of mappings from file pattern to rule codes or prefixes to /// exclude, in addition to any rules excluded by `per-file-ignores`. #[option( default = "{}", value_type = "dict[str, list[RuleSelector]]", scope = "extend-per-file-ignores", example = r#" # Also ignore `E402` in all `__init__.py` files. "__init__.py" = ["E402"] "# )] pub extend_per_file_ignores: Option>>, // WARNING: Don't add new options to this type. Add them to `LintOptions` instead. } #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive( Clone, Debug, PartialEq, Eq, Default, OptionsMetadata, CombineOptions, Serialize, Deserialize, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct Flake8AnnotationsOptions { /// Whether to allow the omission of a return type hint for `__init__` if at /// least one argument is annotated. #[option( default = "false", value_type = "bool", example = "mypy-init-return = true" )] pub mypy_init_return: Option, /// Whether to suppress `ANN000`-level violations for arguments matching the /// "dummy" variable regex (like `_`). #[option( default = "false", value_type = "bool", example = "suppress-dummy-args = true" )] pub suppress_dummy_args: Option, /// Whether to suppress `ANN200`-level violations for functions that meet /// either of the following criteria: /// /// - Contain no `return` statement. /// - Explicit `return` statement(s) all return `None` (explicitly or /// implicitly). #[option( default = "false", value_type = "bool", example = "suppress-none-returning = true" )] pub suppress_none_returning: Option, /// Whether to suppress `ANN401` for dynamically typed `*args` and /// `**kwargs` arguments. #[option( default = "false", value_type = "bool", example = "allow-star-arg-any = true" )] pub allow_star_arg_any: Option, /// Whether to suppress `ANN*` rules for any declaration /// that hasn't been typed at all. /// This makes it easier to gradually add types to a codebase. #[option( default = "false", value_type = "bool", example = "ignore-fully-untyped = true" )] pub ignore_fully_untyped: Option, } impl Flake8AnnotationsOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_annotations::settings::Settings { ruff_linter::rules::flake8_annotations::settings::Settings { mypy_init_return: self.mypy_init_return.unwrap_or(false), suppress_dummy_args: self.suppress_dummy_args.unwrap_or(false), suppress_none_returning: self.suppress_none_returning.unwrap_or(false), allow_star_arg_any: self.allow_star_arg_any.unwrap_or(false), ignore_fully_untyped: self.ignore_fully_untyped.unwrap_or(false), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8BanditOptions { /// A list of directories to consider temporary. #[option( default = "[\"/tmp\", \"/var/tmp\", \"/dev/shm\"]", value_type = "list[str]", example = "hardcoded-tmp-directory = [\"/foo/bar\"]" )] pub hardcoded_tmp_directory: Option>, /// A list of directories to consider temporary, in addition to those /// specified by `hardcoded-tmp-directory`. #[option( default = "[]", value_type = "list[str]", example = "hardcoded-tmp-directory-extend = [\"/foo/bar\"]" )] pub hardcoded_tmp_directory_extend: Option>, /// Whether to disallow `try`-`except`-`pass` (`S110`) for specific /// exception types. By default, `try`-`except`-`pass` is only /// disallowed for `Exception` and `BaseException`. #[option( default = "false", value_type = "bool", example = "check-typed-exception = true" )] pub check_typed_exception: Option, } impl Flake8BanditOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_bandit::settings::Settings { ruff_linter::rules::flake8_bandit::settings::Settings { hardcoded_tmp_directory: self .hardcoded_tmp_directory .unwrap_or_else(ruff_linter::rules::flake8_bandit::settings::default_tmp_dirs) .into_iter() .chain(self.hardcoded_tmp_directory_extend.unwrap_or_default()) .collect(), check_typed_exception: self.check_typed_exception.unwrap_or(false), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8BooleanTrapOptions { /// Additional callable functions with which to allow boolean traps. /// /// Expects to receive a list of fully-qualified names (e.g., `pydantic.Field`, rather than /// `Field`). #[option( default = "[]", value_type = "list[str]", example = "extend-allowed-calls = [\"pydantic.Field\", \"django.db.models.Value\"]" )] pub extend_allowed_calls: Option>, } impl Flake8BooleanTrapOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_boolean_trap::settings::Settings { ruff_linter::rules::flake8_boolean_trap::settings::Settings { extend_allowed_calls: self.extend_allowed_calls.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8BugbearOptions { /// Additional callable functions to consider "immutable" when evaluating, e.g., the /// `function-call-in-default-argument` rule (`B008`) or `function-call-in-dataclass-defaults` /// rule (`RUF009`). /// /// Expects to receive a list of fully-qualified names (e.g., `fastapi.Query`, rather than /// `Query`). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" # Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"] "# )] pub extend_immutable_calls: Option>, } impl Flake8BugbearOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_bugbear::settings::Settings { ruff_linter::rules::flake8_bugbear::settings::Settings { extend_immutable_calls: self.extend_immutable_calls.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8BuiltinsOptions { #[option( default = r#"[]"#, value_type = "list[str]", example = "builtins-ignorelist = [\"id\"]" )] /// Ignore list of builtins. pub builtins_ignorelist: Option>, } impl Flake8BuiltinsOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_builtins::settings::Settings { ruff_linter::rules::flake8_builtins::settings::Settings { builtins_ignorelist: self.builtins_ignorelist.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8ComprehensionsOptions { /// Allow `dict` calls that make use of keyword arguments (e.g., `dict(a=1, b=2)`). #[option( default = "false", value_type = "bool", example = "allow-dict-calls-with-keyword-arguments = true" )] pub allow_dict_calls_with_keyword_arguments: Option, } impl Flake8ComprehensionsOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_comprehensions::settings::Settings { ruff_linter::rules::flake8_comprehensions::settings::Settings { allow_dict_calls_with_keyword_arguments: self .allow_dict_calls_with_keyword_arguments .unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8CopyrightOptions { /// The regular expression used to match the copyright notice, compiled /// with the [`regex`](https://docs.rs/regex/latest/regex/) crate. /// Defaults to `(?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})*`, which matches /// the following: /// /// - `Copyright 2023` /// - `Copyright (C) 2023` /// - `Copyright 2021-2023` /// - `Copyright (C) 2021-2023` /// - `Copyright (C) 2021, 2023` #[option( default = r#"(?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})*"#, value_type = "str", example = r#"notice-rgx = "(?i)Copyright \\(C\\) \\d{4}""# )] pub notice_rgx: Option, /// Author to enforce within the copyright notice. If provided, the /// author must be present immediately following the copyright notice. #[option(default = "null", value_type = "str", example = r#"author = "Ruff""#)] pub author: Option, /// A minimum file size (in bytes) required for a copyright notice to /// be enforced. By default, all files are validated. #[option( default = r#"0"#, value_type = "int", example = r#" # Avoid enforcing a header on files smaller than 1024 bytes. min-file-size = 1024 "# )] pub min_file_size: Option, } impl Flake8CopyrightOptions { pub fn try_into_settings(self) -> anyhow::Result { Ok(flake8_copyright::settings::Settings { notice_rgx: self .notice_rgx .map(|pattern| Regex::new(&pattern)) .transpose()? .unwrap_or_else(|| flake8_copyright::settings::COPYRIGHT.clone()), author: self.author, min_file_size: self.min_file_size.unwrap_or_default(), }) } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8ErrMsgOptions { /// Maximum string length for string literals in exception messages. #[option(default = "0", value_type = "int", example = "max-string-length = 20")] pub max_string_length: Option, } impl Flake8ErrMsgOptions { pub fn into_settings(self) -> flake8_errmsg::settings::Settings { flake8_errmsg::settings::Settings { max_string_length: self.max_string_length.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8GetTextOptions { /// The function names to consider as internationalization calls. #[option( default = r#"["_", "gettext", "ngettext"]"#, value_type = "list[str]", example = r#"function-names = ["_", "gettext", "ngettext", "ugettetxt"]"# )] pub function_names: Option>, /// Additional function names to consider as internationalization calls, in addition to those /// included in `function-names`. #[option( default = r#"[]"#, value_type = "list[str]", example = r#"extend-function-names = ["ugettetxt"]"# )] pub extend_function_names: Option>, } impl Flake8GetTextOptions { pub fn into_settings(self) -> flake8_gettext::settings::Settings { flake8_gettext::settings::Settings { functions_names: self .function_names .unwrap_or_else(flake8_gettext::settings::default_func_names) .into_iter() .chain(self.extend_function_names.unwrap_or_default()) .collect(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8ImplicitStrConcatOptions { /// Whether to allow implicit string concatenations for multiline strings. /// By default, implicit concatenations of multiline strings are /// allowed (but continuation lines, delimited with a backslash, are /// prohibited). /// /// Note that setting `allow-multiline = false` should typically be coupled /// with disabling `explicit-string-concatenation` (`ISC003`). Otherwise, /// both explicit and implicit multiline string concatenations will be seen /// as violations. #[option( default = r#"true"#, value_type = "bool", example = r#" allow-multiline = false "# )] pub allow_multiline: Option, } impl Flake8ImplicitStrConcatOptions { pub fn into_settings(self) -> flake8_implicit_str_concat::settings::Settings { flake8_implicit_str_concat::settings::Settings { allow_multiline: self.allow_multiline.unwrap_or(true), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8ImportConventionsOptions { /// The conventional aliases for imports. These aliases can be extended by /// the `extend-aliases` option. #[option( default = r#"{"altair": "alt", "matplotlib": "mpl", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "tkinter": "tk", "holoviews": "hv", "panel": "pn", "plotly.express": "px", "polars": "pl", "pyarrow": "pa"}"#, value_type = "dict[str, str]", scope = "aliases", example = r#" # Declare the default aliases. altair = "alt" "matplotlib.pyplot" = "plt" numpy = "np" pandas = "pd" seaborn = "sns" scipy = "sp" "# )] pub aliases: Option>, /// A mapping from module to conventional import alias. These aliases will /// be added to the `aliases` mapping. #[option( default = r#"{}"#, value_type = "dict[str, str]", scope = "extend-aliases", example = r#" # Declare a custom alias for the `matplotlib` module. "dask.dataframe" = "dd" "# )] pub extend_aliases: Option>, /// A mapping from module to its banned import aliases. #[option( default = r#"{}"#, value_type = "dict[str, list[str]]", scope = "banned-aliases", example = r#" # Declare the banned aliases. "tensorflow.keras.backend" = ["K"] "# )] pub banned_aliases: Option>, /// A list of modules that should not be imported from using the /// `from ... import ...` syntax. /// /// For example, given `banned-from = ["pandas"]`, `from pandas import DataFrame` /// would be disallowed, while `import pandas` would be allowed. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" # Declare the banned `from` imports. banned-from = ["typing"] "# )] pub banned_from: Option>, } impl Flake8ImportConventionsOptions { pub fn into_settings(self) -> flake8_import_conventions::settings::Settings { let mut aliases = match self.aliases { Some(options_aliases) => options_aliases, None => flake8_import_conventions::settings::default_aliases(), }; if let Some(extend_aliases) = self.extend_aliases { aliases.extend(extend_aliases); } flake8_import_conventions::settings::Settings { aliases, banned_aliases: self.banned_aliases.unwrap_or_default(), banned_from: self.banned_from.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8PytestStyleOptions { /// Boolean flag specifying whether `@pytest.fixture()` without parameters /// should have parentheses. If the option is set to `true` (the /// default), `@pytest.fixture()` is valid and `@pytest.fixture` is /// invalid. If set to `false`, `@pytest.fixture` is valid and /// `@pytest.fixture()` is invalid. #[option( default = "true", value_type = "bool", example = "fixture-parentheses = true" )] pub fixture_parentheses: Option, /// Expected type for multiple argument names in `@pytest.mark.parametrize`. /// The following values are supported: /// /// - `csv` — a comma-separated list, e.g. /// `@pytest.mark.parametrize('name1,name2', ...)` /// - `tuple` (default) — e.g. /// `@pytest.mark.parametrize(('name1', 'name2'), ...)` /// - `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)` #[option( default = "tuple", value_type = r#""csv" | "tuple" | "list""#, example = "parametrize-names-type = \"list\"" )] pub parametrize_names_type: Option, /// Expected type for the list of values rows in `@pytest.mark.parametrize`. /// The following values are supported: /// /// - `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))` /// - `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])` #[option( default = "list", value_type = r#""tuple" | "list""#, example = "parametrize-values-type = \"tuple\"" )] pub parametrize_values_type: Option, /// Expected type for each row of values in `@pytest.mark.parametrize` in /// case of multiple parameters. The following values are supported: /// /// - `tuple` (default) — e.g. /// `@pytest.mark.parametrize(('name1', 'name2'), [(1, 2), (3, 4)])` /// - `list` — e.g. /// `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2], [3, 4]])` #[option( default = "tuple", value_type = r#""tuple" | "list""#, example = "parametrize-values-row-type = \"list\"" )] pub parametrize_values_row_type: Option, /// List of exception names that require a match= parameter in a /// `pytest.raises()` call. /// /// Supports glob patterns. For more information on the glob syntax, refer /// to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"["BaseException", "Exception", "ValueError", "OSError", "IOError", "EnvironmentError", "socket.error"]"#, value_type = "list[str]", example = "raises-require-match-for = [\"requests.RequestException\"]" )] pub raises_require_match_for: Option>, /// List of additional exception names that require a match= parameter in a /// `pytest.raises()` call. This extends the default list of exceptions /// that require a match= parameter. /// This option is useful if you want to extend the default list of /// exceptions that require a match= parameter without having to specify /// the entire list. /// Note that this option does not remove any exceptions from the default /// list. /// /// Supports glob patterns. For more information on the glob syntax, refer /// to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = "[]", value_type = "list[str]", example = "raises-extend-require-match-for = [\"requests.RequestException\"]" )] pub raises_extend_require_match_for: Option>, /// Boolean flag specifying whether `@pytest.mark.foo()` without parameters /// should have parentheses. If the option is set to `true` (the /// default), `@pytest.mark.foo()` is valid and `@pytest.mark.foo` is /// invalid. If set to `false`, `@pytest.fixture` is valid and /// `@pytest.mark.foo()` is invalid. #[option( default = "true", value_type = "bool", example = "mark-parentheses = true" )] pub mark_parentheses: Option, } impl Flake8PytestStyleOptions { pub fn try_into_settings(self) -> anyhow::Result { Ok(flake8_pytest_style::settings::Settings { fixture_parentheses: self.fixture_parentheses.unwrap_or(true), parametrize_names_type: self.parametrize_names_type.unwrap_or_default(), parametrize_values_type: self.parametrize_values_type.unwrap_or_default(), parametrize_values_row_type: self.parametrize_values_row_type.unwrap_or_default(), raises_require_match_for: self .raises_require_match_for .map(|patterns| { patterns .into_iter() .map(|pattern| IdentifierPattern::new(&pattern)) .collect() }) .transpose() .map_err(SettingsError::InvalidRaisesRequireMatchFor)? .unwrap_or_else(flake8_pytest_style::settings::default_broad_exceptions), raises_extend_require_match_for: self .raises_extend_require_match_for .map(|patterns| { patterns .into_iter() .map(|pattern| IdentifierPattern::new(&pattern)) .collect() }) .transpose() .map_err(SettingsError::InvalidRaisesExtendRequireMatchFor)? .unwrap_or_default(), mark_parentheses: self.mark_parentheses.unwrap_or(true), }) } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8QuotesOptions { /// Quote style to prefer for inline strings (either "single" or /// "double"). /// /// When using the formatter, ensure that `format.quote-style` is set to /// the same preferred quote style. #[option( default = r#""double""#, value_type = r#""single" | "double""#, example = r#" inline-quotes = "single" "# )] pub inline_quotes: Option, /// Quote style to prefer for multiline strings (either "single" or /// "double"). /// /// When using the formatter, only "double" is compatible, as the formatter /// enforces double quotes for multiline strings. #[option( default = r#""double""#, value_type = r#""single" | "double""#, example = r#" multiline-quotes = "single" "# )] pub multiline_quotes: Option, /// Quote style to prefer for docstrings (either "single" or "double"). /// /// When using the formatter, only "double" is compatible, as the formatter /// enforces double quotes for docstrings strings. #[option( default = r#""double""#, value_type = r#""single" | "double""#, example = r#" docstring-quotes = "single" "# )] pub docstring_quotes: Option, /// Whether to avoid using single quotes if a string contains single quotes, /// or vice-versa with double quotes, as per [PEP 8](https://peps.python.org/pep-0008/#string-quotes). /// This minimizes the need to escape quotation marks within strings. #[option( default = r#"true"#, value_type = "bool", example = r#" # Don't bother trying to avoid escapes. avoid-escape = false "# )] pub avoid_escape: Option, } impl Flake8QuotesOptions { pub fn into_settings(self) -> flake8_quotes::settings::Settings { flake8_quotes::settings::Settings { inline_quotes: self.inline_quotes.unwrap_or_default(), multiline_quotes: self.multiline_quotes.unwrap_or_default(), docstring_quotes: self.docstring_quotes.unwrap_or_default(), avoid_escape: self.avoid_escape.unwrap_or(true), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8SelfOptions { /// A list of names to ignore when considering `flake8-self` violations. #[option( default = r#"["_make", "_asdict", "_replace", "_fields", "_field_defaults", "_name_", "_value_"]"#, value_type = "list[str]", example = r#" ignore-names = ["_new"] "# )] pub ignore_names: Option>, /// Additional names to ignore when considering `flake8-self` violations, /// in addition to those included in `ignore-names`. #[option( default = r#"[]"#, value_type = "list[str]", example = r#"extend-ignore-names = ["_base_manager", "_default_manager", "_meta"]"# )] pub extend_ignore_names: Option>, } impl Flake8SelfOptions { pub fn into_settings(self) -> flake8_self::settings::Settings { let defaults = flake8_self::settings::Settings::default(); flake8_self::settings::Settings { ignore_names: self .ignore_names .unwrap_or(defaults.ignore_names) .into_iter() .chain(self.extend_ignore_names.unwrap_or_default()) .collect(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8TidyImportsOptions { /// Whether to ban all relative imports (`"all"`), or only those imports /// that extend into the parent module or beyond (`"parents"`). #[option( default = r#""parents""#, value_type = r#""parents" | "all""#, example = r#" # Disallow all relative imports. ban-relative-imports = "all" "# )] pub ban_relative_imports: Option, /// Specific modules or module members that may not be imported or accessed. /// Note that this rule is only meant to flag accidental uses, /// and can be circumvented via `eval` or `importlib`. #[option( default = r#"{}"#, value_type = r#"dict[str, { "msg": str }]"#, scope = "banned-api", example = r#" "cgi".msg = "The cgi module is deprecated, see https://peps.python.org/pep-0594/#cgi." "typing.TypedDict".msg = "Use typing_extensions.TypedDict instead." "# )] pub banned_api: Option>, /// List of specific modules that may not be imported at module level, and should instead be /// imported lazily (e.g., within a function definition, or an `if TYPE_CHECKING:` /// block, or some other nested context). #[option( default = r#"[]"#, value_type = r#"list[str]"#, example = r#" # Ban certain modules from being imported at module level, instead requiring # that they're imported lazily (e.g., within a function definition). banned-module-level-imports = ["torch", "tensorflow"] "# )] pub banned_module_level_imports: Option>, } impl Flake8TidyImportsOptions { pub fn into_settings(self) -> flake8_tidy_imports::settings::Settings { flake8_tidy_imports::settings::Settings { ban_relative_imports: self.ban_relative_imports.unwrap_or(Strictness::Parents), banned_api: self.banned_api.unwrap_or_default(), banned_module_level_imports: self.banned_module_level_imports.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8TypeCheckingOptions { /// Enforce TC001, TC002, and TC003 rules even when valid runtime imports /// are present for the same module. /// /// See flake8-type-checking's [strict](https://github.com/snok/flake8-type-checking#strict) option. #[option( default = "false", value_type = "bool", example = r#" strict = true "# )] pub strict: Option, /// Exempt certain modules from needing to be moved into type-checking /// blocks. #[option( default = "[\"typing\"]", value_type = "list[str]", example = r#" exempt-modules = ["typing", "typing_extensions"] "# )] pub exempt_modules: Option>, /// Exempt classes that list any of the enumerated classes as a base class /// from needing to be moved into type-checking blocks. /// /// Common examples include Pydantic's `pydantic.BaseModel` and SQLAlchemy's /// `sqlalchemy.orm.DeclarativeBase`, but can also support user-defined /// classes that inherit from those base classes. For example, if you define /// a common `DeclarativeBase` subclass that's used throughout your project /// (e.g., `class Base(DeclarativeBase) ...` in `base.py`), you can add it to /// this list (`runtime-evaluated-base-classes = ["base.Base"]`) to exempt /// models from being moved into type-checking blocks. #[option( default = "[]", value_type = "list[str]", example = r#" runtime-evaluated-base-classes = ["pydantic.BaseModel", "sqlalchemy.orm.DeclarativeBase"] "# )] pub runtime_evaluated_base_classes: Option>, /// Exempt classes and functions decorated with any of the enumerated /// decorators from being moved into type-checking blocks. /// /// Common examples include Pydantic's `@pydantic.validate_call` decorator /// (for functions) and attrs' `@attrs.define` decorator (for classes). #[option( default = "[]", value_type = "list[str]", example = r#" runtime-evaluated-decorators = ["pydantic.validate_call", "attrs.define"] "# )] pub runtime_evaluated_decorators: Option>, /// Whether to add quotes around type annotations, if doing so would allow /// the corresponding import to be moved into a type-checking block. /// /// For example, in the following, Python requires that `Sequence` be /// available at runtime, despite the fact that it's only used in a type /// annotation: /// /// ```python /// from collections.abc import Sequence /// /// /// def func(value: Sequence[int]) -> None: /// ... /// ``` /// /// In other words, moving `from collections.abc import Sequence` into an /// `if TYPE_CHECKING:` block above would cause a runtime error, as the /// type would no longer be available at runtime. /// /// By default, Ruff will respect such runtime semantics and avoid moving /// the import to prevent such runtime errors. /// /// Setting `quote-annotations` to `true` will instruct Ruff to add quotes /// around the annotation (e.g., `"Sequence[int]"`), which in turn enables /// Ruff to move the import into an `if TYPE_CHECKING:` block, like so: /// /// ```python /// from typing import TYPE_CHECKING /// /// if TYPE_CHECKING: /// from collections.abc import Sequence /// /// /// def func(value: "Sequence[int]") -> None: /// ... /// ``` /// /// Note that this setting has no effect when `from __future__ import annotations` /// is present, as `__future__` annotations are always treated equivalently /// to quoted annotations. #[option( default = "false", value_type = "bool", example = r#" # Add quotes around type annotations, if doing so would allow # an import to be moved into a type-checking block. quote-annotations = true "# )] pub quote_annotations: Option, } impl Flake8TypeCheckingOptions { pub fn into_settings(self) -> flake8_type_checking::settings::Settings { flake8_type_checking::settings::Settings { strict: self.strict.unwrap_or(false), exempt_modules: self .exempt_modules .unwrap_or_else(|| vec!["typing".to_string()]), runtime_required_base_classes: self.runtime_evaluated_base_classes.unwrap_or_default(), runtime_required_decorators: self.runtime_evaluated_decorators.unwrap_or_default(), quote_annotations: self.quote_annotations.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8UnusedArgumentsOptions { /// Whether to allow unused variadic arguments, like `*args` and `**kwargs`. #[option( default = "false", value_type = "bool", example = "ignore-variadic-names = true" )] pub ignore_variadic_names: Option, } impl Flake8UnusedArgumentsOptions { pub fn into_settings(self) -> flake8_unused_arguments::settings::Settings { flake8_unused_arguments::settings::Settings { ignore_variadic_names: self.ignore_variadic_names.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct IsortOptions { /// Force `import from` statements with multiple members and at least one /// alias (e.g., `import A as B`) to wrap such that every line contains /// exactly one member. For example, this formatting would be retained, /// rather than condensing to a single line: /// /// ```python /// from .utils import ( /// test_directory as test_directory, /// test_id as test_id /// ) /// ``` /// /// Note that this setting is only effective when combined with /// `combine-as-imports = true`. When `combine-as-imports` isn't /// enabled, every aliased `import from` will be given its own line, in /// which case, wrapping is not necessary. /// /// When using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) /// when enabling `force-wrap-aliases` to avoid that the formatter collapses members if they all fit on a single line. #[option( default = r#"false"#, value_type = "bool", example = r#" force-wrap-aliases = true combine-as-imports = true "# )] pub force_wrap_aliases: Option, /// Forces all from imports to appear on their own line. #[option( default = r#"false"#, value_type = "bool", example = r#"force-single-line = true"# )] pub force_single_line: Option, /// One or more modules to exclude from the single line rule. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" single-line-exclusions = ["os", "json"] "# )] pub single_line_exclusions: Option>, /// Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports) /// option. #[option( default = r#"false"#, value_type = "bool", example = r#" combine-as-imports = true "# )] pub combine_as_imports: Option, /// If a comma is placed after the last member in a multi-line import, then /// the imports will never be folded into one line. /// /// See isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option. /// /// When using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) when enabling `split-on-trailing-comma` /// to avoid that the formatter removes the trailing commas. #[option( default = r#"true"#, value_type = "bool", example = r#" split-on-trailing-comma = false "# )] pub split_on_trailing_comma: Option, /// Order imports by type, which is determined by case, in addition to /// alphabetically. #[option( default = r#"true"#, value_type = "bool", example = r#" order-by-type = true "# )] pub order_by_type: Option, /// Don't sort straight-style imports (like `import sys`) before from-style /// imports (like `from itertools import groupby`). Instead, sort the /// imports by module, independent of import style. #[option( default = r#"false"#, value_type = "bool", example = r#" force-sort-within-sections = true "# )] pub force_sort_within_sections: Option, /// Sort imports taking into account case sensitivity. #[option( default = r#"false"#, value_type = "bool", example = r#" case-sensitive = true "# )] pub case_sensitive: Option, /// Force specific imports to the top of their appropriate section. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" force-to-top = ["src"] "# )] pub force_to_top: Option>, /// A list of modules to consider first-party, regardless of whether they /// can be identified as such via introspection of the local filesystem. /// /// Supports glob patterns. For more information on the glob syntax, refer /// to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" known-first-party = ["src"] "# )] pub known_first_party: Option>, /// A list of modules to consider third-party, regardless of whether they /// can be identified as such via introspection of the local filesystem. /// /// Supports glob patterns. For more information on the glob syntax, refer /// to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" known-third-party = ["src"] "# )] pub known_third_party: Option>, /// A list of modules to consider being a local folder. /// Generally, this is reserved for relative imports (`from . import module`). /// /// Supports glob patterns. For more information on the glob syntax, refer /// to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" known-local-folder = ["src"] "# )] pub known_local_folder: Option>, /// A list of modules to consider standard-library, in addition to those /// known to Ruff in advance. /// /// Supports glob patterns. For more information on the glob syntax, refer /// to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" extra-standard-library = ["path"] "# )] pub extra_standard_library: Option>, /// Whether to place "closer" imports (fewer `.` characters, most local) /// before "further" imports (more `.` characters, least local), or vice /// versa. /// /// The default ("furthest-to-closest") is equivalent to isort's /// `reverse-relative` default (`reverse-relative = false`); setting /// this to "closest-to-furthest" is equivalent to isort's /// `reverse-relative = true`. #[option( default = r#"furthest-to-closest"#, value_type = r#""furthest-to-closest" | "closest-to-furthest""#, example = r#" relative-imports-order = "closest-to-furthest" "# )] pub relative_imports_order: Option, /// Add the specified import line to all files. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" required-imports = ["from __future__ import annotations"] "# )] pub required_imports: Option>, /// An override list of tokens to always recognize as a Class for /// `order-by-type` regardless of casing. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" classes = ["SVC"] "# )] pub classes: Option>, /// An override list of tokens to always recognize as a CONSTANT /// for `order-by-type` regardless of casing. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" constants = ["constant"] "# )] pub constants: Option>, /// An override list of tokens to always recognize as a var /// for `order-by-type` regardless of casing. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" variables = ["VAR"] "# )] pub variables: Option>, /// A list of sections that should _not_ be delineated from the previous /// section via empty lines. #[option( default = r#"[]"#, value_type = r#"list["future" | "standard-library" | "third-party" | "first-party" | "local-folder" | str]"#, example = r#" no-lines-before = ["future", "standard-library"] "# )] pub no_lines_before: Option>, /// The number of blank lines to place after imports. /// Use `-1` for automatic determination. /// /// Ruff uses at most one blank line after imports in typing stub files (files with `.pyi` extension) in accordance to /// the typing style recommendations ([source](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)). /// /// When using the formatter, only the values `-1`, `1`, and `2` are compatible because /// it enforces at least one empty and at most two empty lines after imports. #[option( default = r#"-1"#, value_type = "int", example = r#" # Use a single line after each import block. lines-after-imports = 1 "# )] pub lines_after_imports: Option, /// The number of lines to place between "direct" and `import from` imports. /// /// When using the formatter, only the values `0` and `1` are compatible because /// it preserves up to one empty line after imports in nested blocks. #[option( default = r#"0"#, value_type = "int", example = r#" # Use a single line between direct and from import. lines-between-types = 1 "# )] pub lines_between_types: Option, /// A list of modules to separate into auxiliary block(s) of imports, /// in the order specified. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" forced-separate = ["tests"] "# )] pub forced_separate: Option>, /// Override in which order the sections should be output. Can be used to move custom sections. #[option( default = r#"["future", "standard-library", "third-party", "first-party", "local-folder"]"#, value_type = r#"list["future" | "standard-library" | "third-party" | "first-party" | "local-folder" | str]"#, example = r#" section-order = ["future", "standard-library", "first-party", "local-folder", "third-party"] "# )] pub section_order: Option>, /// Define a default section for any imports that don't fit into the specified `section-order`. #[option( default = r#"third-party"#, value_type = "str", example = r#" default-section = "third-party" "# )] pub default_section: Option, /// Put all imports into the same section bucket. /// /// For example, rather than separating standard library and third-party imports, as in: /// ```python /// import os /// import sys /// /// import numpy /// import pandas /// ``` /// /// Setting `no-sections = true` will instead group all imports into a single section: /// ```python /// import os /// import numpy /// import pandas /// import sys /// ``` #[option( default = r#"false"#, value_type = "bool", example = r#" no-sections = true "# )] pub no_sections: Option, /// Whether to automatically mark imports from within the same package as first-party. /// For example, when `detect-same-package = true`, then when analyzing files within the /// `foo` package, any imports from within the `foo` package will be considered first-party. /// /// This heuristic is often unnecessary when `src` is configured to detect all first-party /// sources; however, if `src` is _not_ configured, this heuristic can be useful to detect /// first-party imports from _within_ (but not _across_) first-party packages. #[option( default = r#"true"#, value_type = "bool", example = r#" detect-same-package = false "# )] pub detect_same_package: Option, /// Whether to place `import from` imports before straight imports when sorting. /// /// For example, by default, imports will be sorted such that straight imports appear /// before `import from` imports, as in: /// ```python /// import os /// import sys /// from typing import List /// ``` /// /// Setting `from-first = true` will instead sort such that `import from` imports appear /// before straight imports, as in: /// ```python /// from typing import List /// import os /// import sys /// ``` #[option( default = r#"false"#, value_type = "bool", example = r#" from-first = true "# )] pub from_first: Option, /// Sort imports by their string length, such that shorter imports appear /// before longer imports. For example, by default, imports will be sorted /// alphabetically, as in: /// ```python /// import collections /// import os /// ``` /// /// Setting `length-sort = true` will instead sort such that shorter imports /// appear before longer imports, as in: /// ```python /// import os /// import collections /// ``` #[option( default = r#"false"#, value_type = "bool", example = r#" length-sort = true "# )] pub length_sort: Option, /// Sort straight imports by their string length. Similar to `length-sort`, /// but applies only to straight imports and doesn't affect `from` imports. #[option( default = r#"false"#, value_type = "bool", example = r#" length-sort-straight = true "# )] pub length_sort_straight: Option, // Tables are required to go last. /// A list of mappings from section names to modules. /// /// By default, imports are categorized according to their type (e.g., `future`, `third-party`, /// and so on). This setting allows you to group modules into custom sections, to augment or /// override the built-in sections. /// /// For example, to group all testing utilities, you could create a `testing` section: /// ```toml /// testing = ["pytest", "hypothesis"] /// ``` /// /// The values in the list are treated as glob patterns. For example, to match all packages in /// the LangChain ecosystem (`langchain-core`, `langchain-openai`, etc.): /// ```toml /// langchain = ["langchain-*"] /// ``` /// /// Custom sections should typically be inserted into the `section-order` list to ensure that /// they're displayed as a standalone group and in the intended order, as in: /// ```toml /// section-order = [ /// "future", /// "standard-library", /// "third-party", /// "first-party", /// "local-folder", /// "testing" /// ] /// ``` /// /// If a custom section is omitted from `section-order`, imports in that section will be /// assigned to the `default-section` (which defaults to `third-party`). #[option( default = "{}", value_type = "dict[str, list[str]]", scope = "sections", example = r#" # Group all Django imports into a separate section. "django" = ["django"] "# )] pub sections: Option>>, } impl IsortOptions { pub fn try_into_settings( self, ) -> Result { // Verify that if `no_sections` is set, then `section_order` is empty. let no_sections = self.no_sections.unwrap_or_default(); if no_sections && self.section_order.is_some() { warn_user_once!("`section-order` is ignored when `no-sections` is set to `true`"); } if no_sections && self.default_section.is_some() { warn_user_once!("`default-section` is ignored when `no-sections` is set to `true`"); } if no_sections && self.sections.is_some() { warn_user_once!("`sections` is ignored when `no-sections` is set to `true`"); } // Verify that if `force_sort_within_sections` is `True`, then `lines_between_types` is set to `0`. let force_sort_within_sections = self.force_sort_within_sections.unwrap_or_default(); let lines_between_types = self.lines_between_types.unwrap_or_default(); if force_sort_within_sections && lines_between_types != 0 { warn_user_once!("`lines-between-types` is ignored when `force-sort-within-sections` is set to `true`"); } // Extract any configuration options that deal with user-defined sections. let mut section_order: Vec<_> = self .section_order .unwrap_or_else(|| ImportType::iter().map(ImportSection::Known).collect()); let default_section = self .default_section .unwrap_or(ImportSection::Known(ImportType::ThirdParty)); let known_first_party = self .known_first_party .map(|names| { names .into_iter() .map(|name| IdentifierPattern::new(&name)) .collect() }) .transpose() .map_err(isort::settings::SettingsError::InvalidKnownFirstParty)? .unwrap_or_default(); let known_third_party = self .known_third_party .map(|names| { names .into_iter() .map(|name| IdentifierPattern::new(&name)) .collect() }) .transpose() .map_err(isort::settings::SettingsError::InvalidKnownThirdParty)? .unwrap_or_default(); let known_local_folder = self .known_local_folder .map(|names| { names .into_iter() .map(|name| IdentifierPattern::new(&name)) .collect() }) .transpose() .map_err(isort::settings::SettingsError::InvalidKnownLocalFolder)? .unwrap_or_default(); let extra_standard_library = self .extra_standard_library .map(|names| { names .into_iter() .map(|name| IdentifierPattern::new(&name)) .collect() }) .transpose() .map_err(isort::settings::SettingsError::InvalidExtraStandardLibrary)? .unwrap_or_default(); let no_lines_before = self.no_lines_before.unwrap_or_default(); let from_first = self.from_first.unwrap_or_default(); let sections = self.sections.unwrap_or_default(); // Verify that `sections` doesn't contain any built-in sections. let sections: FxHashMap> = sections .into_iter() .filter_map(|(section, modules)| match section { ImportSection::Known(section) => { warn_user_once!("`sections` contains built-in section: `{:?}`", section); None } ImportSection::UserDefined(section) => Some((section, modules)), }) .map(|(section, modules)| { let modules = modules .into_iter() .map(|module| { IdentifierPattern::new(&module) .map_err(isort::settings::SettingsError::InvalidUserDefinedSection) }) .collect::, isort::settings::SettingsError>>()?; Ok((section, modules)) }) .collect::>()?; // Verify that `section_order` doesn't contain any duplicates. let mut seen = FxHashSet::with_capacity_and_hasher(section_order.len(), BuildHasherDefault::default()); for section in §ion_order { if !seen.insert(section) { warn_user_once!( "`section-order` contains duplicate section: `{:?}`", section ); } } // Verify that all sections listed in `section_order` are defined in `sections`. for section in §ion_order { if let ImportSection::UserDefined(section_name) = section { if !sections.contains_key(section_name) { warn_user_once!("`section-order` contains unknown section: `{:?}`", section,); } } } // Verify that all sections listed in `no_lines_before` are defined in `sections`. for section in &no_lines_before { if let ImportSection::UserDefined(section_name) = section { if !sections.contains_key(section_name) { warn_user_once!( "`no-lines-before` contains unknown section: `{:?}`", section, ); } } } // Verify that `default_section` is in `section_order`. if !section_order.contains(&default_section) { warn_user_once!( "`section-order` must contain `default-section`: {:?}", default_section, ); section_order.push(default_section.clone()); } Ok(isort::settings::Settings { required_imports: BTreeSet::from_iter(self.required_imports.unwrap_or_default()), combine_as_imports: self.combine_as_imports.unwrap_or(false), force_single_line: self.force_single_line.unwrap_or(false), force_sort_within_sections, case_sensitive: self.case_sensitive.unwrap_or(false), force_wrap_aliases: self.force_wrap_aliases.unwrap_or(false), detect_same_package: self.detect_same_package.unwrap_or(true), force_to_top: FxHashSet::from_iter(self.force_to_top.unwrap_or_default()), known_modules: isort::categorize::KnownModules::new( known_first_party, known_third_party, known_local_folder, extra_standard_library, sections, ), order_by_type: self.order_by_type.unwrap_or(true), relative_imports_order: self.relative_imports_order.unwrap_or_default(), single_line_exclusions: FxHashSet::from_iter( self.single_line_exclusions.unwrap_or_default(), ), split_on_trailing_comma: self.split_on_trailing_comma.unwrap_or(true), classes: FxHashSet::from_iter(self.classes.unwrap_or_default()), constants: FxHashSet::from_iter(self.constants.unwrap_or_default()), variables: FxHashSet::from_iter(self.variables.unwrap_or_default()), no_lines_before: FxHashSet::from_iter(no_lines_before), lines_after_imports: self.lines_after_imports.unwrap_or(-1), lines_between_types, forced_separate: Vec::from_iter(self.forced_separate.unwrap_or_default()), section_order, default_section, no_sections, from_first, length_sort: self.length_sort.unwrap_or(false), length_sort_straight: self.length_sort_straight.unwrap_or(false), }) } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct McCabeOptions { /// The maximum McCabe complexity to allow before triggering `C901` errors. #[option( default = "10", value_type = "int", example = r#" # Flag errors (`C901`) whenever the complexity level exceeds 5. max-complexity = 5 "# )] pub max_complexity: Option, } impl McCabeOptions { pub fn into_settings(self) -> mccabe::settings::Settings { mccabe::settings::Settings { max_complexity: self .max_complexity .unwrap_or(mccabe::settings::DEFAULT_MAX_COMPLEXITY), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Pep8NamingOptions { /// A list of names (or patterns) to ignore when considering `pep8-naming` violations. /// /// Supports glob patterns. For example, to ignore all names starting with /// or ending with `_test`, you could use `ignore-names = ["test_*", "*_test"]`. /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]"#, value_type = "list[str]", example = r#" ignore-names = ["callMethod"] "# )] pub ignore_names: Option>, /// Additional names (or patterns) to ignore when considering `pep8-naming` violations, /// in addition to those included in `ignore-names` /// /// Supports glob patterns. For example, to ignore all names starting with /// or ending with `_test`, you could use `ignore-names = ["test_*", "*_test"]`. /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#"extend-ignore-names = ["callMethod"]"# )] pub extend_ignore_names: Option>, /// A list of decorators that, when applied to a method, indicate that the /// method should be treated as a class method (in addition to the builtin /// `@classmethod`). /// /// For example, Ruff will expect that any method decorated by a decorator /// in this list takes a `cls` argument as its first argument. /// /// Expects to receive a list of fully-qualified names (e.g., `pydantic.validator`, /// rather than `validator`) or alternatively a plain name which is then matched against /// the last segment in case the decorator itself consists of a dotted name. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" classmethod-decorators = [ # Allow Pydantic's `@validator` decorator to trigger class method treatment. "pydantic.validator", # Allow SQLAlchemy's dynamic decorators, like `@field.expression`, to trigger class method treatment. "declared_attr", "expression", "comparator", ] "# )] pub classmethod_decorators: Option>, /// A list of decorators that, when applied to a method, indicate that the /// method should be treated as a static method (in addition to the builtin /// `@staticmethod`). /// /// For example, Ruff will expect that any method decorated by a decorator /// in this list has no `self` or `cls` argument. /// /// Expects to receive a list of fully-qualified names (e.g., `belay.Device.teardown`, /// rather than `teardown`) or alternatively a plain name which is then matched against /// the last segment in case the decorator itself consists of a dotted name. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" # Allow Belay's `@Device.teardown` decorator to trigger static method treatment. staticmethod-decorators = ["belay.Device.teardown"] "# )] pub staticmethod_decorators: Option>, } impl Pep8NamingOptions { pub fn try_into_settings( self, ) -> Result { Ok(pep8_naming::settings::Settings { ignore_names: IgnoreNames::from_options(self.ignore_names, self.extend_ignore_names)?, classmethod_decorators: self.classmethod_decorators.unwrap_or_default(), staticmethod_decorators: self.staticmethod_decorators.unwrap_or_default(), }) } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PycodestyleOptions { /// The maximum line length to allow for [`line-too-long`](https://docs.astral.sh/ruff/rules/line-too-long/) violations. By default, /// this is set to the value of the [`line-length`](#line-length) option. /// /// Use this option when you want to detect extra-long lines that the formatter can't automatically split by setting /// `pycodestyle.line-length` to a value larger than [`line-length`](#line-length). /// /// ```toml /// line-length = 88 # The formatter wraps lines at a length of 88 /// /// [pycodestyle] /// max-line-length = 100 # E501 reports lines that exceed the length of 100. /// ``` /// /// The length is determined by the number of characters per line, except for lines containing East Asian characters or emojis. /// For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length. /// /// See the [`line-too-long`](https://docs.astral.sh/ruff/rules/line-too-long/) rule for more information. #[option( default = "null", value_type = "int", example = r#" max-line-length = 100 "# )] pub max_line_length: Option, /// The maximum line length to allow for [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) violations within /// documentation (`W505`), including standalone comments. By default, /// this is set to null which disables reporting violations. /// /// The length is determined by the number of characters per line, except for lines containing Asian characters or emojis. /// For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length. /// /// See the [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) rule for more information. #[option( default = "null", value_type = "int", example = r#" max-doc-length = 88 "# )] pub max_doc_length: Option, /// Whether line-length violations (`E501`) should be triggered for /// comments starting with `task-tags` (by default: \["TODO", "FIXME", /// and "XXX"\]). #[option( default = "false", value_type = "bool", example = r#" ignore-overlong-task-comments = true "# )] pub ignore_overlong_task_comments: Option, } impl PycodestyleOptions { pub fn into_settings(self, global_line_length: LineLength) -> pycodestyle::settings::Settings { pycodestyle::settings::Settings { max_doc_length: self.max_doc_length, max_line_length: self.max_line_length.unwrap_or(global_line_length), ignore_overlong_task_comments: self.ignore_overlong_task_comments.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PydocstyleOptions { /// Whether to use Google-style or NumPy-style conventions or the [PEP 257](https://peps.python.org/pep-0257/) /// defaults when analyzing docstring sections. /// /// Enabling a convention will disable all rules that are not included in /// the specified convention. As such, the intended workflow is to enable a /// convention and then selectively enable or disable any additional rules /// on top of it. /// /// For example, to use Google-style conventions but avoid requiring /// documentation for every function parameter: /// /// ```toml /// [tool.ruff.lint] /// # Enable all `pydocstyle` rules, limiting to those that adhere to the /// # Google convention via `convention = "google"`, below. /// select = ["D"] /// /// # On top of the Google convention, disable `D417`, which requires /// # documentation for every function parameter. /// ignore = ["D417"] /// /// [tool.ruff.lint.pydocstyle] /// convention = "google" /// ``` /// /// To enable an additional rule that's excluded from the convention, /// select the desired rule via its fully qualified rule code (e.g., /// `D400` instead of `D4` or `D40`): /// /// ```toml /// [tool.ruff.lint] /// # Enable D400 on top of the Google convention. /// extend-select = ["D400"] /// /// [tool.ruff.lint.pydocstyle] /// convention = "google" /// ``` #[option( default = r#"null"#, value_type = r#""google" | "numpy" | "pep257""#, example = r#" # Use Google-style docstrings. convention = "google" "# )] pub convention: Option, /// Ignore docstrings for functions or methods decorated with the /// specified fully-qualified decorators. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" ignore-decorators = ["typing.overload"] "# )] pub ignore_decorators: Option>, /// A list of decorators that, when applied to a method, indicate that the /// method should be treated as a property (in addition to the builtin /// `@property` and standard-library `@functools.cached_property`). /// /// For example, Ruff will expect that any method decorated by a decorator /// in this list can use a non-imperative summary line. #[option( default = r#"[]"#, value_type = "list[str]", example = r#" property-decorators = ["gi.repository.GObject.Property"] "# )] pub property_decorators: Option>, } impl PydocstyleOptions { pub fn into_settings(self) -> pydocstyle::settings::Settings { pydocstyle::settings::Settings { convention: self.convention, ignore_decorators: BTreeSet::from_iter(self.ignore_decorators.unwrap_or_default()), property_decorators: BTreeSet::from_iter(self.property_decorators.unwrap_or_default()), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PyflakesOptions { /// Additional functions or classes to consider generic, such that any /// subscripts should be treated as type annotation (e.g., `ForeignKey` in /// `django.db.models.ForeignKey["User"]`. /// /// Expects to receive a list of fully-qualified names (e.g., `django.db.models.ForeignKey`, /// rather than `ForeignKey`). #[option( default = r#"[]"#, value_type = "list[str]", example = "extend-generics = [\"django.db.models.ForeignKey\"]" )] pub extend_generics: Option>, } impl PyflakesOptions { pub fn into_settings(self) -> pyflakes::settings::Settings { pyflakes::settings::Settings { extend_generics: self.extend_generics.unwrap_or_default(), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PylintOptions { /// Constant types to ignore when used as "magic values" (see: `PLR2004`). #[option( default = r#"["str", "bytes"]"#, value_type = r#"list["str" | "bytes" | "complex" | "float" | "int"]"#, example = r#" allow-magic-value-types = ["int"] "# )] pub allow_magic_value_types: Option>, /// Dunder methods name to allow, in addition to the default set from the /// Python standard library (see: `PLW3201`). #[option( default = r#"[]"#, value_type = r#"list[str]"#, example = r#" allow-dunder-method-names = ["__tablename__", "__table_args__"] "# )] pub allow_dunder_method_names: Option>, /// Maximum number of branches allowed for a function or method body (see: /// `PLR0912`). #[option(default = r"12", value_type = "int", example = r"max-branches = 12")] pub max_branches: Option, /// Maximum number of return statements allowed for a function or method /// body (see `PLR0911`) #[option(default = r"6", value_type = "int", example = r"max-returns = 6")] pub max_returns: Option, /// Maximum number of arguments allowed for a function or method definition /// (see: `PLR0913`). #[option(default = r"5", value_type = "int", example = r"max-args = 5")] pub max_args: Option, /// Maximum number of positional arguments allowed for a function or method definition /// (see: `PLR0917`). /// /// If not specified, defaults to the value of `max-args`. #[option( default = r"5", // Needs to be in sync with default of `max-args`. value_type = "int", example = r"max-positional-args = 3" )] pub max_positional_args: Option, /// Maximum number of local variables allowed for a function or method body (see: /// `PLR0914`). #[option(default = r"15", value_type = "int", example = r"max-locals = 15")] pub max_locals: Option, /// Maximum number of statements allowed for a function or method body (see: /// `PLR0915`). #[option(default = r"50", value_type = "int", example = r"max-statements = 50")] pub max_statements: Option, /// Maximum number of public methods allowed for a class (see: `PLR0904`). #[option( default = r"20", value_type = "int", example = r"max-public-methods = 20" )] pub max_public_methods: Option, /// Maximum number of Boolean expressions allowed within a single `if` statement /// (see: `PLR0916`). #[option(default = r"5", value_type = "int", example = r"max-bool-expr = 5")] pub max_bool_expr: Option, /// Maximum number of nested blocks allowed within a function or method body /// (see: `PLR1702`). #[option(default = r"5", value_type = "int", example = r"max-nested-blocks = 5")] pub max_nested_blocks: Option, } impl PylintOptions { pub fn into_settings(self) -> pylint::settings::Settings { let defaults = pylint::settings::Settings::default(); pylint::settings::Settings { allow_magic_value_types: self .allow_magic_value_types .unwrap_or(defaults.allow_magic_value_types), allow_dunder_method_names: self.allow_dunder_method_names.unwrap_or_default(), max_args: self.max_args.unwrap_or(defaults.max_args), max_positional_args: self .max_positional_args .or(self.max_args) .unwrap_or(defaults.max_positional_args), max_bool_expr: self.max_bool_expr.unwrap_or(defaults.max_bool_expr), max_returns: self.max_returns.unwrap_or(defaults.max_returns), max_branches: self.max_branches.unwrap_or(defaults.max_branches), max_statements: self.max_statements.unwrap_or(defaults.max_statements), max_public_methods: self .max_public_methods .unwrap_or(defaults.max_public_methods), max_locals: self.max_locals.unwrap_or(defaults.max_locals), max_nested_blocks: self.max_nested_blocks.unwrap_or(defaults.max_nested_blocks), } } } #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PyUpgradeOptions { /// Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 /// (`Union[str, int]` -> `str | int`) rewrites even if a file imports /// `from __future__ import annotations`. /// /// This setting is only applicable when the target Python version is below /// 3.9 and 3.10 respectively, and is most commonly used when working with /// libraries like Pydantic and FastAPI, which rely on the ability to parse /// type annotations at runtime. The use of `from __future__ import annotations` /// causes Python to treat the type annotations as strings, which typically /// allows for the use of language features that appear in later Python /// versions but are not yet supported by the current version (e.g., `str | /// int`). However, libraries that rely on runtime type annotations will /// break if the annotations are incompatible with the current Python /// version. /// /// For example, while the following is valid Python 3.8 code due to the /// presence of `from __future__ import annotations`, the use of `str| int` /// prior to Python 3.10 will cause Pydantic to raise a `TypeError` at /// runtime: /// /// ```python /// from __future__ import annotations /// /// import pydantic /// /// class Foo(pydantic.BaseModel): /// bar: str | int /// ``` /// /// #[option( default = r#"false"#, value_type = "bool", example = r#" # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true "# )] pub keep_runtime_typing: Option, } impl PyUpgradeOptions { pub fn into_settings(self) -> pyupgrade::settings::Settings { pyupgrade::settings::Settings { keep_runtime_typing: self.keep_runtime_typing.unwrap_or_default(), } } } /// Configures the way ruff formats your code. #[derive( Clone, Debug, PartialEq, Eq, Default, Deserialize, Serialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FormatOptions { /// A list of file patterns to exclude from formatting in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)). /// /// Exclusions are based on globs, and can be either: /// /// - Single-path patterns, like `.mypy_cache` (to exclude any directory /// named `.mypy_cache` in the tree), `foo.py` (to exclude any file named /// `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). /// - Relative patterns, like `directory/foo.py` (to exclude that specific /// file) or `directory/*.py` (to exclude any Python files in /// `directory`). Note that these paths are relative to the project root /// (e.g., the directory containing your `pyproject.toml`). /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( default = r#"[]"#, value_type = "list[str]", example = r#" exclude = ["generated"] "# )] pub exclude: Option>, /// Whether to enable the unstable preview style formatting. #[option( default = "false", value_type = "bool", example = r#" # Enable preview style formatting. preview = true "# )] pub preview: Option, /// Whether to use spaces or tabs for indentation. /// /// `indent-style = "space"` (default): /// /// ```python /// def f(): /// print("Hello") # Spaces indent the `print` statement. /// ``` /// /// `indent-style = "tab""`: /// /// ```python /// def f(): /// print("Hello") # A tab `\t` indents the `print` statement. /// ``` /// /// PEP 8 recommends using spaces for [indentation](https://peps.python.org/pep-0008/#indentation). /// We care about accessibility; if you do not need tabs for accessibility, we do not recommend you use them. /// /// See [`indent-width`](#indent-width) to configure the number of spaces per indentation and the tab width. #[option( default = "space", value_type = r#""space" | "tab""#, example = r#" # Use tabs instead of 4 space indentation. indent-style = "tab" "# )] pub indent_style: Option, /// Configures the preferred quote character for strings. The recommended options are /// /// * `double` (default): Use double quotes `"` /// * `single`: Use single quotes `'` /// /// In compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), /// Ruff prefers double quotes for triple quoted strings and docstrings even when using `quote-style = "single"`. /// /// Ruff deviates from using the configured quotes if doing so prevents the need for /// escaping quote characters inside the string: /// /// ```python /// a = "a string without any quotes" /// b = "It's monday morning" /// ``` /// /// Ruff will change the quotes of the string assigned to `a` to single quotes when using `quote-style = "single"`. /// However, ruff uses double quotes for he string assigned to `b` because using single quotes would require escaping the `'`, /// which leads to the less readable code: `'It\'s monday morning'`. /// /// In addition, Ruff supports the quote style `preserve` for projects that already use /// a mixture of single and double quotes and can't migrate to the `double` or `single` style. /// The quote style `preserve` leaves the quotes of all strings unchanged. #[option( default = r#"double"#, value_type = r#""double" | "single" | "preserve""#, example = r#" # Prefer single quotes over double quotes. quote-style = "single" "# )] pub quote_style: Option, /// Ruff uses existing trailing commas as an indication that short lines should be left separate. /// If this option is set to `true`, the magic trailing comma is ignored. /// /// For example, Ruff leaves the arguments separate even though /// collapsing the arguments to a single line doesn't exceed the line length if `skip-magic-trailing-comma = false`: /// /// ```python /// # The arguments remain on separate lines because of the trailing comma after `b` /// def test( /// a, /// b, /// ): pass /// ``` /// /// Setting `skip-magic-trailing-comma = true` changes the formatting to: /// /// ```python /// # The arguments remain on separate lines because of the trailing comma after `b` /// def test(a, b): /// pass /// ``` #[option( default = r#"false"#, value_type = r#"bool"#, example = "skip-magic-trailing-comma = true" )] pub skip_magic_trailing_comma: Option, /// The character Ruff uses at the end of a line. /// /// * `auto`: The newline style is detected automatically on a file per file basis. Files with mixed line endings will be converted to the first detected line ending. Defaults to `\n` for files that contain no line endings. /// * `lf`: Line endings will be converted to `\n`. The default line ending on Unix. /// * `cr-lf`: Line endings will be converted to `\r\n`. The default line ending on Windows. /// * `native`: Line endings will be converted to `\n` on Unix and `\r\n` on Windows. #[option( default = r#"auto"#, value_type = r#""auto" | "lf" | "cr-lf" | "native""#, example = r#" # Use `\n` line endings for all files line-ending = "lf" "# )] pub line_ending: Option, /// Whether to format code snippets in docstrings. /// /// When this is enabled, Python code examples within docstrings are /// automatically reformatted. /// /// For example, when this is enabled, the following code: /// /// ```python /// def f(x): /// """ /// Something about `f`. And an example in doctest format: /// /// >>> f( x ) /// /// Markdown is also supported: /// /// ```py /// f( x ) /// ``` /// /// As are reStructuredText literal blocks:: /// /// f( x ) /// /// /// And reStructuredText code blocks: /// /// .. code-block:: python /// /// f( x ) /// """ /// pass /// ``` /// /// ... will be reformatted (assuming the rest of the options are set to /// their defaults) as: /// /// ```python /// def f(x): /// """ /// Something about `f`. And an example in doctest format: /// /// >>> f(x) /// /// Markdown is also supported: /// /// ```py /// f(x) /// ``` /// /// As are reStructuredText literal blocks:: /// /// f(x) /// /// /// And reStructuredText code blocks: /// /// .. code-block:: python /// /// f(x) /// """ /// pass /// ``` /// /// If a code snippet in a docstring contains invalid Python code or if the /// formatter would otherwise write invalid Python code, then the code /// example is ignored by the formatter and kept as-is. /// /// Currently, doctest, Markdown, reStructuredText literal blocks, and /// reStructuredText code blocks are all supported and automatically /// recognized. In the case of unlabeled fenced code blocks in Markdown and /// reStructuredText literal blocks, the contents are assumed to be Python /// and reformatted. As with any other format, if the contents aren't valid /// Python, then the block is left untouched automatically. #[option( default = "false", value_type = "bool", example = r#" # Enable reformatting of code snippets in docstrings. docstring-code-format = true "# )] pub docstring_code_format: Option, /// Set the line length used when formatting code snippets in docstrings. /// /// This only has an effect when the `docstring-code-format` setting is /// enabled. /// /// The default value for this setting is `"dynamic"`, which has the effect /// of ensuring that any reformatted code examples in docstrings adhere to /// the global line length configuration that is used for the surrounding /// Python code. The point of this setting is that it takes the indentation /// of the docstring into account when reformatting code examples. /// /// Alternatively, this can be set to a fixed integer, which will result /// in the same line length limit being applied to all reformatted code /// examples in docstrings. When set to a fixed integer, the indent of the /// docstring is not taken into account. That is, this may result in lines /// in the reformatted code example that exceed the globally configured /// line length limit. /// /// For example, when this is set to `20` and `docstring-code-format` is /// enabled, then this code: /// /// ```python /// def f(x): /// ''' /// Something about `f`. And an example: /// /// .. code-block:: python /// /// foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear) /// ''' /// pass /// ``` /// /// ... will be reformatted (assuming the rest of the options are set /// to their defaults) as: /// /// ```python /// def f(x): /// """ /// Something about `f`. And an example: /// /// .. code-block:: python /// /// ( /// foo, /// bar, /// quux, /// ) = this_is_a_long_line( /// lion, /// hippo, /// lemur, /// bear, /// ) /// """ /// pass /// ``` #[option( default = r#""dynamic""#, value_type = r#"int | "dynamic""#, example = r#" # Format all docstring code snippets with a line length of 60. docstring-code-line-length = 60 "# )] pub docstring_code_line_length: Option, } #[cfg(test)] mod tests { use ruff_linter::rules::flake8_self; use crate::options::Flake8SelfOptions; #[test] fn flake8_self_options() { let default_settings = flake8_self::settings::Settings::default(); // Uses defaults if no options are specified. let options = Flake8SelfOptions { ignore_names: None, extend_ignore_names: None, }; let settings = options.into_settings(); assert_eq!(settings.ignore_names, default_settings.ignore_names); // Uses ignore_names if specified. let options = Flake8SelfOptions { ignore_names: Some(vec!["_foo".to_string()]), extend_ignore_names: None, }; let settings = options.into_settings(); assert_eq!(settings.ignore_names, vec!["_foo".to_string()]); // Appends extend_ignore_names to defaults if only extend_ignore_names is specified. let options = Flake8SelfOptions { ignore_names: None, extend_ignore_names: Some(vec!["_bar".to_string()]), }; let settings = options.into_settings(); assert_eq!( settings.ignore_names, default_settings .ignore_names .into_iter() .chain(["_bar".to_string()]) .collect::>() ); // Appends extend_ignore_names to ignore_names if both are specified. let options = Flake8SelfOptions { ignore_names: Some(vec!["_foo".to_string()]), extend_ignore_names: Some(vec!["_bar".to_string()]), }; let settings = options.into_settings(); assert_eq!( settings.ignore_names, vec!["_foo".to_string(), "_bar".to_string()] ); } }