Add lint section to Ruff configuration

## Summary

This PR adds a new `lint` section to the configuration that groups all linter-specific settings. The existing top-level configurations continue to work without any warning because the `lint.*` settings are experimental. 

The configuration merges the top level and `lint.*` settings where the settings in `lint` have higher precedence (override the top-level settings). The reasoning behind this is that the settings in `lint.` are more specific and more specific settings should override less specific settings.

I decided against showing the new `lint.*` options on our website because it would make the page extremely long (it's technically easy to do, just attribute `lint` with `[option_group`]). We may want to explore adding an `alias` field to the `option` attribute and show the alias on the website along with its regular name. 

## Test Plan

* I added new integration tests
* I verified that the generated `options.md` is identical
* Verified the default settings in the playground

![Screenshot from 2023-09-22 13-52-23](7b4d9689-aa88-402e-9199-9c43c8d8cc2d)
This commit is contained in:
Micha Reiser 2023-09-27 08:46:27 +02:00 committed by GitHub
parent 15f3d8c8e0
commit 0c65d0c8a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1301 additions and 611 deletions

View file

@ -39,9 +39,9 @@ use crate::options::{
Flake8ComprehensionsOptions, Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions,
Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions,
Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions,
Flake8UnusedArgumentsOptions, FormatOptions, FormatOrOutputFormat, IsortOptions, McCabeOptions,
Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions,
PyflakesOptions, PylintOptions,
Flake8UnusedArgumentsOptions, FormatOptions, FormatOrOutputFormat, IsortOptions, LintOptions,
McCabeOptions, Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions,
PydocstyleOptions, PyflakesOptions, PylintOptions,
};
use crate::settings::{
FileResolverSettings, FormatterSettings, LineEnding, Settings, EXCLUDE, INCLUDE,
@ -59,64 +59,36 @@ pub struct RuleSelection {
#[derive(Debug, Default)]
pub struct Configuration {
pub rule_selections: Vec<RuleSelection>,
pub per_file_ignores: Option<Vec<PerFileIgnore>>,
pub allowed_confusables: Option<Vec<char>>,
pub builtins: Option<Vec<String>>,
// Global options
pub cache_dir: Option<PathBuf>,
pub dummy_variable_rgx: Option<Regex>,
pub exclude: Option<Vec<FilePattern>>,
pub extend: Option<PathBuf>,
pub extend_exclude: Vec<FilePattern>,
pub extend_include: Vec<FilePattern>,
pub extend_per_file_ignores: Vec<PerFileIgnore>,
pub external: Option<Vec<String>>,
pub fix: Option<bool>,
pub fix_only: Option<bool>,
pub force_exclude: Option<bool>,
pub output_format: Option<SerializationFormat>,
pub ignore_init_module_imports: Option<bool>,
pub include: Option<Vec<FilePattern>>,
pub line_length: Option<LineLength>,
pub logger_objects: Option<Vec<String>>,
pub namespace_packages: Option<Vec<PathBuf>>,
pub preview: Option<PreviewMode>,
pub required_version: Option<Version>,
pub respect_gitignore: Option<bool>,
pub show_fixes: Option<bool>,
pub show_source: Option<bool>,
pub src: Option<Vec<PathBuf>>,
pub tab_size: Option<TabSize>,
pub target_version: Option<PythonVersion>,
pub task_tags: Option<Vec<String>>,
pub typing_modules: Option<Vec<String>>,
// Plugins
pub flake8_annotations: Option<Flake8AnnotationsOptions>,
pub flake8_bandit: Option<Flake8BanditOptions>,
pub flake8_bugbear: Option<Flake8BugbearOptions>,
pub flake8_builtins: Option<Flake8BuiltinsOptions>,
pub flake8_comprehensions: Option<Flake8ComprehensionsOptions>,
pub flake8_copyright: Option<Flake8CopyrightOptions>,
pub flake8_errmsg: Option<Flake8ErrMsgOptions>,
pub flake8_gettext: Option<Flake8GetTextOptions>,
pub flake8_implicit_str_concat: Option<Flake8ImplicitStrConcatOptions>,
pub flake8_import_conventions: Option<Flake8ImportConventionsOptions>,
pub flake8_pytest_style: Option<Flake8PytestStyleOptions>,
pub flake8_quotes: Option<Flake8QuotesOptions>,
pub flake8_self: Option<Flake8SelfOptions>,
pub flake8_tidy_imports: Option<Flake8TidyImportsOptions>,
pub flake8_type_checking: Option<Flake8TypeCheckingOptions>,
pub flake8_unused_arguments: Option<Flake8UnusedArgumentsOptions>,
pub isort: Option<IsortOptions>,
pub mccabe: Option<McCabeOptions>,
pub pep8_naming: Option<Pep8NamingOptions>,
pub pycodestyle: Option<PycodestyleOptions>,
pub pydocstyle: Option<PydocstyleOptions>,
pub pyflakes: Option<PyflakesOptions>,
pub pylint: Option<PylintOptions>,
pub pyupgrade: Option<PyUpgradeOptions>,
// File resolver options
pub exclude: Option<Vec<FilePattern>>,
pub extend_exclude: Vec<FilePattern>,
pub extend_include: Vec<FilePattern>,
pub force_exclude: Option<bool>,
pub include: Option<Vec<FilePattern>>,
pub respect_gitignore: Option<bool>,
// Generic python options settings
pub builtins: Option<Vec<String>>,
pub namespace_packages: Option<Vec<PathBuf>>,
pub src: Option<Vec<PathBuf>>,
pub target_version: Option<PythonVersion>,
// Global formatting options
pub line_length: Option<LineLength>,
pub tab_size: Option<TabSize>,
pub lint: LintConfiguration,
pub format: FormatConfiguration,
}
@ -133,7 +105,6 @@ impl Configuration {
}
let target_version = self.target_version.unwrap_or_default();
let rules = self.as_rule_table();
let preview = self.preview.unwrap_or_default();
let format = self.format;
@ -157,6 +128,8 @@ impl Configuration {
.unwrap_or(format_defaults.magic_trailing_comma),
};
let lint = self.lint;
Ok(Settings {
cache_dir: self
.cache_dir
@ -183,135 +156,135 @@ impl Configuration {
},
linter: LinterSettings {
rules: lint.as_rule_table(preview),
target_version,
project_root: project_root.to_path_buf(),
rules,
allowed_confusables: self
allowed_confusables: lint
.allowed_confusables
.map(FxHashSet::from_iter)
.unwrap_or_default(),
builtins: self.builtins.unwrap_or_default(),
dummy_variable_rgx: self
dummy_variable_rgx: lint
.dummy_variable_rgx
.unwrap_or_else(|| DUMMY_VARIABLE_RGX.clone()),
external: FxHashSet::from_iter(self.external.unwrap_or_default()),
ignore_init_module_imports: self.ignore_init_module_imports.unwrap_or_default(),
external: FxHashSet::from_iter(lint.external.unwrap_or_default()),
ignore_init_module_imports: lint.ignore_init_module_imports.unwrap_or_default(),
line_length: self.line_length.unwrap_or_default(),
tab_size: self.tab_size.unwrap_or_default(),
namespace_packages: self.namespace_packages.unwrap_or_default(),
per_file_ignores: resolve_per_file_ignores(
self.per_file_ignores
lint.per_file_ignores
.unwrap_or_default()
.into_iter()
.chain(self.extend_per_file_ignores)
.chain(lint.extend_per_file_ignores)
.collect(),
)?,
src: self.src.unwrap_or_else(|| vec![project_root.to_path_buf()]),
task_tags: self
task_tags: lint
.task_tags
.unwrap_or_else(|| TASK_TAGS.iter().map(ToString::to_string).collect()),
logger_objects: self.logger_objects.unwrap_or_default(),
logger_objects: lint.logger_objects.unwrap_or_default(),
preview,
typing_modules: self.typing_modules.unwrap_or_default(),
typing_modules: lint.typing_modules.unwrap_or_default(),
// Plugins
flake8_annotations: self
flake8_annotations: lint
.flake8_annotations
.map(Flake8AnnotationsOptions::into_settings)
.unwrap_or_default(),
flake8_bandit: self
flake8_bandit: lint
.flake8_bandit
.map(Flake8BanditOptions::into_settings)
.unwrap_or_default(),
flake8_bugbear: self
flake8_bugbear: lint
.flake8_bugbear
.map(Flake8BugbearOptions::into_settings)
.unwrap_or_default(),
flake8_builtins: self
flake8_builtins: lint
.flake8_builtins
.map(Flake8BuiltinsOptions::into_settings)
.unwrap_or_default(),
flake8_comprehensions: self
flake8_comprehensions: lint
.flake8_comprehensions
.map(Flake8ComprehensionsOptions::into_settings)
.unwrap_or_default(),
flake8_copyright: self
flake8_copyright: lint
.flake8_copyright
.map(Flake8CopyrightOptions::try_into_settings)
.transpose()?
.unwrap_or_default(),
flake8_errmsg: self
flake8_errmsg: lint
.flake8_errmsg
.map(Flake8ErrMsgOptions::into_settings)
.unwrap_or_default(),
flake8_implicit_str_concat: self
flake8_implicit_str_concat: lint
.flake8_implicit_str_concat
.map(Flake8ImplicitStrConcatOptions::into_settings)
.unwrap_or_default(),
flake8_import_conventions: self
flake8_import_conventions: lint
.flake8_import_conventions
.map(Flake8ImportConventionsOptions::into_settings)
.unwrap_or_default(),
flake8_pytest_style: self
flake8_pytest_style: lint
.flake8_pytest_style
.map(Flake8PytestStyleOptions::try_into_settings)
.transpose()?
.unwrap_or_default(),
flake8_quotes: self
flake8_quotes: lint
.flake8_quotes
.map(Flake8QuotesOptions::into_settings)
.unwrap_or_default(),
flake8_self: self
flake8_self: lint
.flake8_self
.map(Flake8SelfOptions::into_settings)
.unwrap_or_default(),
flake8_tidy_imports: self
flake8_tidy_imports: lint
.flake8_tidy_imports
.map(Flake8TidyImportsOptions::into_settings)
.unwrap_or_default(),
flake8_type_checking: self
flake8_type_checking: lint
.flake8_type_checking
.map(Flake8TypeCheckingOptions::into_settings)
.unwrap_or_default(),
flake8_unused_arguments: self
flake8_unused_arguments: lint
.flake8_unused_arguments
.map(Flake8UnusedArgumentsOptions::into_settings)
.unwrap_or_default(),
flake8_gettext: self
flake8_gettext: lint
.flake8_gettext
.map(Flake8GetTextOptions::into_settings)
.unwrap_or_default(),
isort: self
isort: lint
.isort
.map(IsortOptions::try_into_settings)
.transpose()?
.unwrap_or_default(),
mccabe: self
mccabe: lint
.mccabe
.map(McCabeOptions::into_settings)
.unwrap_or_default(),
pep8_naming: self
pep8_naming: lint
.pep8_naming
.map(Pep8NamingOptions::try_into_settings)
.transpose()?
.unwrap_or_default(),
pycodestyle: self
pycodestyle: lint
.pycodestyle
.map(PycodestyleOptions::into_settings)
.unwrap_or_default(),
pydocstyle: self
pydocstyle: lint
.pydocstyle
.map(PydocstyleOptions::into_settings)
.unwrap_or_default(),
pyflakes: self
pyflakes: lint
.pyflakes
.map(PyflakesOptions::into_settings)
.unwrap_or_default(),
pylint: self
pylint: lint
.pylint
.map(PylintOptions::into_settings)
.unwrap_or_default(),
pyupgrade: self
pyupgrade: lint
.pyupgrade
.map(PyUpgradeOptions::into_settings)
.unwrap_or_default(),
@ -322,26 +295,13 @@ impl Configuration {
}
pub fn from_options(options: Options, project_root: &Path) -> Result<Self> {
let lint = if let Some(lint) = options.lint {
lint.combine(options.lint_top_level)
} else {
options.lint_top_level
};
Ok(Self {
rule_selections: vec![RuleSelection {
select: options.select,
ignore: options
.ignore
.into_iter()
.flatten()
.chain(options.extend_ignore.into_iter().flatten())
.collect(),
extend_select: options.extend_select.unwrap_or_default(),
fixable: options.fixable,
unfixable: options
.unfixable
.into_iter()
.flatten()
.chain(options.extend_unfixable.into_iter().flatten())
.collect(),
extend_fixable: options.extend_fixable.unwrap_or_default(),
}],
allowed_confusables: options.allowed_confusables,
builtins: options.builtins,
cache_dir: options
.cache_dir
@ -351,11 +311,7 @@ impl Configuration {
})
.transpose()
.map_err(|e| anyhow!("Invalid `cache-dir` value: {e}"))?,
dummy_variable_rgx: options
.dummy_variable_rgx
.map(|pattern| Regex::new(&pattern))
.transpose()
.map_err(|e| anyhow!("Invalid `dummy-variable-rgx` value: {e}"))?,
exclude: options.exclude.map(|paths| {
paths
.into_iter()
@ -397,6 +353,159 @@ impl Configuration {
.collect()
})
.unwrap_or_default(),
include: options.include.map(|paths| {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
}),
fix: options.fix,
fix_only: options.fix_only,
output_format: options.output_format.or_else(|| {
options
.format
.as_ref()
.and_then(FormatOrOutputFormat::as_output_format)
}),
force_exclude: options.force_exclude,
line_length: options.line_length,
tab_size: options.tab_size,
namespace_packages: options
.namespace_packages
.map(|namespace_package| resolve_src(&namespace_package, project_root))
.transpose()?,
preview: options.preview.map(PreviewMode::from),
required_version: options.required_version,
respect_gitignore: options.respect_gitignore,
show_source: options.show_source,
show_fixes: options.show_fixes,
src: options
.src
.map(|src| resolve_src(&src, project_root))
.transpose()?,
target_version: options.target_version,
lint: LintConfiguration::from_options(lint, project_root)?,
format: if let Some(FormatOrOutputFormat::Format(format)) = options.format {
FormatConfiguration::from_options(format)?
} else {
FormatConfiguration::default()
},
})
}
#[must_use]
pub fn combine(self, config: Self) -> Self {
Self {
builtins: self.builtins.or(config.builtins),
cache_dir: self.cache_dir.or(config.cache_dir),
exclude: self.exclude.or(config.exclude),
extend: self.extend.or(config.extend),
extend_exclude: config
.extend_exclude
.into_iter()
.chain(self.extend_exclude)
.collect(),
extend_include: config
.extend_include
.into_iter()
.chain(self.extend_include)
.collect(),
include: self.include.or(config.include),
fix: self.fix.or(config.fix),
fix_only: self.fix_only.or(config.fix_only),
output_format: self.output_format.or(config.output_format),
force_exclude: self.force_exclude.or(config.force_exclude),
line_length: self.line_length.or(config.line_length),
tab_size: self.tab_size.or(config.tab_size),
namespace_packages: self.namespace_packages.or(config.namespace_packages),
required_version: self.required_version.or(config.required_version),
respect_gitignore: self.respect_gitignore.or(config.respect_gitignore),
show_source: self.show_source.or(config.show_source),
show_fixes: self.show_fixes.or(config.show_fixes),
src: self.src.or(config.src),
target_version: self.target_version.or(config.target_version),
preview: self.preview.or(config.preview),
lint: self.lint.combine(config.lint),
format: self.format.combine(config.format),
}
}
}
#[derive(Debug, Default)]
pub struct LintConfiguration {
// Rule selection
pub extend_per_file_ignores: Vec<PerFileIgnore>,
pub per_file_ignores: Option<Vec<PerFileIgnore>>,
pub rule_selections: Vec<RuleSelection>,
// Global lint settings
pub allowed_confusables: Option<Vec<char>>,
pub dummy_variable_rgx: Option<Regex>,
pub external: Option<Vec<String>>,
pub ignore_init_module_imports: Option<bool>,
pub logger_objects: Option<Vec<String>>,
pub task_tags: Option<Vec<String>>,
pub typing_modules: Option<Vec<String>>,
// Plugins
pub flake8_annotations: Option<Flake8AnnotationsOptions>,
pub flake8_bandit: Option<Flake8BanditOptions>,
pub flake8_bugbear: Option<Flake8BugbearOptions>,
pub flake8_builtins: Option<Flake8BuiltinsOptions>,
pub flake8_comprehensions: Option<Flake8ComprehensionsOptions>,
pub flake8_copyright: Option<Flake8CopyrightOptions>,
pub flake8_errmsg: Option<Flake8ErrMsgOptions>,
pub flake8_gettext: Option<Flake8GetTextOptions>,
pub flake8_implicit_str_concat: Option<Flake8ImplicitStrConcatOptions>,
pub flake8_import_conventions: Option<Flake8ImportConventionsOptions>,
pub flake8_pytest_style: Option<Flake8PytestStyleOptions>,
pub flake8_quotes: Option<Flake8QuotesOptions>,
pub flake8_self: Option<Flake8SelfOptions>,
pub flake8_tidy_imports: Option<Flake8TidyImportsOptions>,
pub flake8_type_checking: Option<Flake8TypeCheckingOptions>,
pub flake8_unused_arguments: Option<Flake8UnusedArgumentsOptions>,
pub isort: Option<IsortOptions>,
pub mccabe: Option<McCabeOptions>,
pub pep8_naming: Option<Pep8NamingOptions>,
pub pycodestyle: Option<PycodestyleOptions>,
pub pydocstyle: Option<PydocstyleOptions>,
pub pyflakes: Option<PyflakesOptions>,
pub pylint: Option<PylintOptions>,
pub pyupgrade: Option<PyUpgradeOptions>,
}
impl LintConfiguration {
fn from_options(options: LintOptions, project_root: &Path) -> Result<Self> {
Ok(LintConfiguration {
rule_selections: vec![RuleSelection {
select: options.select,
ignore: options
.ignore
.into_iter()
.flatten()
.chain(options.extend_ignore.into_iter().flatten())
.collect(),
extend_select: options.extend_select.unwrap_or_default(),
fixable: options.fixable,
unfixable: options
.unfixable
.into_iter()
.flatten()
.chain(options.extend_unfixable.into_iter().flatten())
.collect(),
extend_fixable: options.extend_fixable.unwrap_or_default(),
}],
allowed_confusables: options.allowed_confusables,
dummy_variable_rgx: options
.dummy_variable_rgx
.map(|pattern| Regex::new(&pattern))
.transpose()
.map_err(|e| anyhow!("Invalid `dummy-variable-rgx` value: {e}"))?,
extend_per_file_ignores: options
.extend_per_file_ignores
.map(|per_file_ignores| {
@ -409,32 +518,7 @@ impl Configuration {
})
.unwrap_or_default(),
external: options.external,
fix: options.fix,
fix_only: options.fix_only,
output_format: options.output_format.or_else(|| {
options
.format
.as_ref()
.and_then(FormatOrOutputFormat::as_output_format)
}),
force_exclude: options.force_exclude,
ignore_init_module_imports: options.ignore_init_module_imports,
include: options.include.map(|paths| {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
}),
line_length: options.line_length,
tab_size: options.tab_size,
namespace_packages: options
.namespace_packages
.map(|namespace_package| resolve_src(&namespace_package, project_root))
.transpose()?,
preview: options.preview.map(PreviewMode::from),
per_file_ignores: options.per_file_ignores.map(|per_file_ignores| {
per_file_ignores
.into_iter()
@ -443,15 +527,6 @@ impl Configuration {
})
.collect()
}),
required_version: options.required_version,
respect_gitignore: options.respect_gitignore,
show_source: options.show_source,
show_fixes: options.show_fixes,
src: options
.src
.map(|src| resolve_src(&src, project_root))
.transpose()?,
target_version: options.target_version,
task_tags: options.task_tags,
logger_objects: options.logger_objects,
typing_modules: options.typing_modules,
@ -480,18 +555,10 @@ impl Configuration {
pyflakes: options.pyflakes,
pylint: options.pylint,
pyupgrade: options.pyupgrade,
format: if let Some(FormatOrOutputFormat::Format(format)) = options.format {
FormatConfiguration::from_options(format)?
} else {
FormatConfiguration::default()
},
})
}
pub fn as_rule_table(&self) -> RuleTable {
let preview = self.preview.unwrap_or_default();
fn as_rule_table(&self, preview: PreviewMode) -> RuleTable {
// The select_set keeps track of which rules have been selected.
let mut select_set: RuleSet = PREFIXES
.iter()
@ -731,47 +798,18 @@ impl Configuration {
.chain(self.rule_selections)
.collect(),
allowed_confusables: self.allowed_confusables.or(config.allowed_confusables),
builtins: self.builtins.or(config.builtins),
cache_dir: self.cache_dir.or(config.cache_dir),
dummy_variable_rgx: self.dummy_variable_rgx.or(config.dummy_variable_rgx),
exclude: self.exclude.or(config.exclude),
extend: self.extend.or(config.extend),
extend_exclude: config
.extend_exclude
.into_iter()
.chain(self.extend_exclude)
.collect(),
extend_include: config
.extend_include
.into_iter()
.chain(self.extend_include)
.collect(),
extend_per_file_ignores: config
.extend_per_file_ignores
.into_iter()
.chain(self.extend_per_file_ignores)
.collect(),
external: self.external.or(config.external),
fix: self.fix.or(config.fix),
fix_only: self.fix_only.or(config.fix_only),
output_format: self.output_format.or(config.output_format),
force_exclude: self.force_exclude.or(config.force_exclude),
include: self.include.or(config.include),
ignore_init_module_imports: self
.ignore_init_module_imports
.or(config.ignore_init_module_imports),
line_length: self.line_length.or(config.line_length),
logger_objects: self.logger_objects.or(config.logger_objects),
tab_size: self.tab_size.or(config.tab_size),
namespace_packages: self.namespace_packages.or(config.namespace_packages),
per_file_ignores: self.per_file_ignores.or(config.per_file_ignores),
required_version: self.required_version.or(config.required_version),
respect_gitignore: self.respect_gitignore.or(config.respect_gitignore),
show_source: self.show_source.or(config.show_source),
show_fixes: self.show_fixes.or(config.show_fixes),
src: self.src.or(config.src),
target_version: self.target_version.or(config.target_version),
preview: self.preview.or(config.preview),
task_tags: self.task_tags.or(config.task_tags),
typing_modules: self.typing_modules.or(config.typing_modules),
// Plugins
@ -809,8 +847,6 @@ impl Configuration {
pyflakes: self.pyflakes.combine(config.pyflakes),
pylint: self.pylint.combine(config.pylint),
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
format: self.format.combine(config.format),
}
}
}
@ -858,7 +894,6 @@ impl FormatConfiguration {
}
}
}
pub(crate) trait CombinePluginOptions {
#[must_use]
fn combine(self, other: Self) -> Self;
@ -902,7 +937,7 @@ mod tests {
use ruff_linter::settings::types::PreviewMode;
use ruff_linter::RuleSelector;
use crate::configuration::{Configuration, RuleSelection};
use crate::configuration::{LintConfiguration, RuleSelection};
const NURSERY_RULES: &[Rule] = &[
Rule::MissingCopyrightNotice,
@ -966,12 +1001,11 @@ mod tests {
selections: impl IntoIterator<Item = RuleSelection>,
preview: Option<PreviewMode>,
) -> RuleSet {
Configuration {
LintConfiguration {
rule_selections: selections.into_iter().collect(),
preview,
..Configuration::default()
..LintConfiguration::default()
}
.as_rule_table()
.as_rule_table(preview.unwrap_or_default())
.iter_enabled()
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is off-by-default
.filter(|rule| rule.noqa_code() != "RUF014")

View file

@ -36,30 +36,6 @@ use crate::settings::LineEnding;
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Options {
/// 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<Vec<char>>,
/// 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<Vec<String>>,
/// A path to the cache directory.
///
/// By default, Ruff stores cache results in a `.ruff_cache` directory in
@ -77,19 +53,98 @@ pub struct Options {
)]
pub cache_dir: Option<String>,
/// 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_`.
/// 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#""^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$""#,
value_type = "re.Pattern",
default = r#"None"#,
value_type = "str",
example = r#"
# Only ignore variables named "_".
dummy-variable-rgx = "^_$"
# Extend the `pyproject.toml` file in the parent directory.
extend = "../pyproject.toml"
# But use a different line length.
line-length = 100
"#
)]
pub dummy_variable_rgx: Option<String>,
pub extend: Option<String>,
/// The style in which violation messages should be formatted: `"text"`
/// (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#""text""#,
value_type = r#""text" | "json" | "junit" | "github" | "gitlab" | "pylint" | "azure""#,
example = r#"
# Group violations by containing file.
output-format = "grouped"
"#
)]
pub output_format: Option<SerializationFormat>,
/// Enable autofix behavior by-default when running `ruff` (overridden
/// by the `--fix` and `--no-fix` command-line flags).
#[option(default = "false", value_type = "bool", example = "fix = true")]
pub fix: Option<bool>,
/// Like `fix`, but disables reporting on leftover violation. Implies `fix`.
#[option(default = "false", value_type = "bool", example = "fix-only = true")]
pub fix_only: Option<bool>,
/// 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
"#
)]
pub show_source: Option<bool>,
/// Whether to show an enumeration of all autofixed 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<bool>,
/// Require a specific version of Ruff to be running (useful for unifying
/// results across many environments, e.g., with a `pyproject.toml`
/// file).
#[option(
default = "None",
value_type = "str",
example = r#"
required-version = "0.0.193"
"#
)]
pub required_version: Option<Version>,
/// 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<bool>,
// File resolver options
/// A list of file patterns to exclude from linting.
///
/// Exclusions are based on globs, and can be either:
@ -115,25 +170,6 @@ pub struct Options {
)]
pub exclude: Option<Vec<String>>,
/// 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#"None"#,
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<String>,
/// A list of file patterns to omit from linting, in addition to those
/// specified by `exclude`.
///
@ -175,6 +211,218 @@ pub struct Options {
)]
pub extend_include: Option<Vec<String>>,
/// 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<bool>,
/// 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<Vec<String>>,
/// 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<bool>,
// 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<Vec<String>>,
/// Mark the specified directories as namespace packages. For the purpose of
/// module resolution, Ruff will treat those directories 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<Vec<String>>,
/// 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 omitted, and Ruff is configured via a `pyproject.toml` file, the
/// target version will be inferred from its `project.requires-python`
/// field (e.g., `requires-python = ">=3.8"`). If Ruff is configured via
/// `ruff.toml` or `.ruff.toml`, no such inference will be performed.
#[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<PythonVersion>,
/// 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<Vec<String>>,
// Global Formatting options
/// The line length to use when enforcing long-lines violations (like
/// `E501`). Must be greater than `0` and less than or equal to `320`.
#[option(
default = "88",
value_type = "int",
example = r#"
# Allow lines to be as long as 120 characters.
line-length = 120
"#
)]
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))]
pub line_length: Option<LineLength>,
/// The tabulation size to calculate line length.
#[option(
default = "4",
value_type = "int",
example = r#"
tab-size = 8
"#
)]
pub tab_size: Option<TabSize>,
pub lint: Option<LintOptions>,
/// The lint sections specified at the top level.
#[serde(flatten)]
pub lint_top_level: LintOptions,
/// Options to configure the code formatting.
///
/// Previously:
/// The style in which violation messages should be formatted: `"text"`
/// (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).
///
/// This option has been **deprecated** in favor of `output-format`
/// to avoid ambiguity with Ruff's upcoming formatter.
#[option_group]
pub format: Option<FormatOrOutputFormat>,
}
/// Experimental section to configure Ruff's linting. This new section will eventually
/// replace the top-level linting options.
///
/// Options specified in the `lint` section take precedence over the top-level settings.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Debug, PartialEq, Eq, Default, ConfigurationOptions, CombineOptions, Serialize, Deserialize,
)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct LintOptions {
/// 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<Vec<char>>,
/// 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<String>,
/// A list of rule codes or prefixes to ignore, in addition to those
/// specified by `ignore`.
///
@ -238,15 +486,6 @@ pub struct Options {
)]
pub external: Option<Vec<String>>,
/// Enable autofix behavior by-default when running `ruff` (overridden
/// by the `--fix` and `--no-fix` command-line flags).
#[option(default = "false", value_type = "bool", example = "fix = true")]
pub fix: Option<bool>,
/// Like `fix`, but disables reporting on leftover violation. Implies `fix`.
#[option(default = "false", value_type = "bool", example = "fix-only = true")]
pub fix_only: Option<bool>,
/// A list of rule codes or prefixes to consider autofixable. By default,
/// all rules are considered autofixable.
#[option(
@ -259,47 +498,6 @@ pub struct Options {
)]
pub fixable: Option<Vec<RuleSelector>>,
/// The style in which violation messages should be formatted: `"text"`
/// (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#""text""#,
value_type = r#""text" | "json" | "junit" | "github" | "gitlab" | "pylint" | "azure""#,
example = r#"
# Group violations by containing file.
output-format = "grouped"
"#
)]
pub output_format: Option<SerializationFormat>,
#[option(
default = r#"false"#,
value_type = "bool",
example = r#"
force-exclude = true
"#
)]
/// 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<bool>,
/// A list of rule codes or prefixes to ignore. Prefixes can specify exact
/// rules (like `F841`), entire categories (like `F`), or anything in
/// between.
@ -330,46 +528,6 @@ pub struct Options {
)]
pub ignore_init_module_imports: Option<bool>,
/// 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<Vec<String>>,
/// The line length to use when enforcing long-lines violations (like
/// `E501`). Must be greater than `0` and less than or equal to `320`.
#[option(
default = "88",
value_type = "int",
example = r#"
# Allow lines to be as long as 120 characters.
line-length = 120
"#
)]
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))]
pub line_length: Option<LineLength>,
/// The tabulation size to calculate line length.
#[option(
default = "4",
value_type = "int",
example = r#"
tab-size = 8
"#
)]
pub tab_size: Option<TabSize>,
/// A list of objects that should be treated equivalently to a
/// `logging.Logger` object.
///
@ -395,30 +553,6 @@ pub struct Options {
)]
pub logger_objects: Option<Vec<String>>,
/// Require a specific version of Ruff to be running (useful for unifying
/// results across many environments, e.g., with a `pyproject.toml`
/// file).
#[option(
default = "None",
value_type = "str",
example = r#"
required-version = "0.0.193"
"#
)]
pub required_version: Option<Version>,
/// 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<bool>,
/// A list of rule codes or prefixes to enable. Prefixes can specify exact
/// rules (like `F841`), entire categories (like `F`), or anything in
/// between.
@ -436,113 +570,6 @@ pub struct Options {
)]
pub select: Option<Vec<RuleSelector>>,
/// 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
"#
)]
pub show_source: Option<bool>,
/// Whether to show an enumeration of all autofixed 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<bool>,
/// 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<Vec<String>>,
/// Mark the specified directories as namespace packages. For the purpose of
/// module resolution, Ruff will treat those directories 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<Vec<String>>,
/// 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 omitted, and Ruff is configured via a `pyproject.toml` file, the
/// target version will be inferred from its `project.requires-python`
/// field (e.g., `requires-python = ">=3.8"`). If Ruff is configured via
/// `ruff.toml` or `.ruff.toml`, no such inference will be performed.
#[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<PythonVersion>,
/// 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<bool>,
/// A list of task tags to recognize (e.g., "TODO", "FIXME", "XXX").
///
/// Comments starting with these tags will be ignored by commented-out code
@ -551,7 +578,9 @@ pub struct Options {
#[option(
default = r#"["TODO", "FIXME", "XXX"]"#,
value_type = "list[str]",
example = r#"task-tags = ["HACK"]"#
example = r#"
task-tags = ["HACK"]
"#
)]
pub task_tags: Option<Vec<String>>,
@ -677,20 +706,6 @@ pub struct Options {
#[option_group]
pub pyupgrade: Option<PyUpgradeOptions>,
/// Options to configure the code formatting.
///
/// Previously:
/// The style in which violation messages should be formatted: `"text"`
/// (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).
///
/// This option has been **deprecated** in favor of `output-format`
/// to avoid ambiguity with Ruff's upcoming formatter.
#[option_group]
pub format: Option<FormatOrOutputFormat>,
// 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.

View file

@ -161,7 +161,7 @@ mod tests {
use ruff_linter::line_width::LineLength;
use ruff_linter::settings::types::PatternPrefixPair;
use crate::options::Options;
use crate::options::{LintOptions, Options};
use crate::pyproject::{find_settings_toml, parse_pyproject_toml, Pyproject, Tools};
use crate::tests::test_resource_path;
@ -236,7 +236,10 @@ select = ["E501"]
pyproject.tool,
Some(Tools {
ruff: Some(Options {
select: Some(vec![codes::Pycodestyle::E501.into()]),
lint_top_level: LintOptions {
select: Some(vec![codes::Pycodestyle::E501.into()]),
..LintOptions::default()
},
..Options::default()
})
})
@ -254,8 +257,11 @@ ignore = ["E501"]
pyproject.tool,
Some(Tools {
ruff: Some(Options {
extend_select: Some(vec![codes::Ruff::_100.into()]),
ignore: Some(vec![codes::Pycodestyle::E501.into()]),
lint_top_level: LintOptions {
extend_select: Some(vec![codes::Ruff::_100.into()]),
ignore: Some(vec![codes::Pycodestyle::E501.into()]),
..LintOptions::default()
},
..Options::default()
})
})
@ -308,10 +314,14 @@ other-attribute = 1
"migrations".to_string(),
"with_excluded_file/other_excluded_file.py".to_string(),
]),
per_file_ignores: Some(FxHashMap::from_iter([(
"__init__.py".to_string(),
vec![codes::Pyflakes::_401.into()]
)])),
lint_top_level: LintOptions {
per_file_ignores: Some(FxHashMap::from_iter([(
"__init__.py".to_string(),
vec![codes::Pyflakes::_401.into()]
)])),
..LintOptions::default()
},
..Options::default()
}
);