From 91283e53e2845fd9b57e33dd28517505787dd95c Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 14 Sep 2023 12:48:10 -0500 Subject: [PATCH] Add `explicit-preview-rules` to toggle explicit selection of preview rules --- crates/flake8_to_ruff/src/plugin.rs | 4 +-- crates/ruff/src/rule_selector.rs | 17 ++++++++--- crates/ruff/src/settings/defaults.rs | 5 ++-- crates/ruff/src/settings/mod.rs | 1 + crates/ruff_workspace/src/configuration.rs | 33 ++++++++++++++-------- crates/ruff_workspace/src/options.rs | 18 ++++++++++-- ruff.schema.json | 7 +++++ 7 files changed, 62 insertions(+), 23 deletions(-) diff --git a/crates/flake8_to_ruff/src/plugin.rs b/crates/flake8_to_ruff/src/plugin.rs index 4c43dbabfe1da..22a9072498378 100644 --- a/crates/flake8_to_ruff/src/plugin.rs +++ b/crates/flake8_to_ruff/src/plugin.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use anyhow::anyhow; use ruff::registry::Linter; -use ruff::settings::types::PreviewMode; +use ruff::rule_selector::PreviewOptions; use ruff::RuleSelector; #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] @@ -332,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet) -> Vec .filter(|plugin| { for selector in selectors { if selector - .rules(PreviewMode::Disabled) + .rules(&PreviewOptions::default()) .any(|rule| Linter::from(plugin).rules().any(|r| r == rule)) { return true; diff --git a/crates/ruff/src/rule_selector.rs b/crates/ruff/src/rule_selector.rs index fc3e3611af51d..6460e7b3cea8a 100644 --- a/crates/ruff/src/rule_selector.rs +++ b/crates/ruff/src/rule_selector.rs @@ -198,16 +198,19 @@ impl RuleSelector { } } - /// Returns rules matching the selector, taking into account whether preview mode is enabled. - pub fn rules(&self, preview: PreviewMode) -> impl Iterator + '_ { + /// Returns rules matching the selector, taking into account preview options enabled. + pub fn rules<'a, 'b>(&'a self, preview: &'b PreviewOptions) -> impl Iterator + 'a { + let preview_enabled = preview.mode.is_enabled(); + let preview_require_explicit = preview.require_explicit; #[allow(deprecated)] self.all_rules().filter(move |rule| { // Always include rules that are not in preview or the nursery !(rule.is_preview() || rule.is_nursery()) // Backwards compatibility allows selection of nursery rules by exact code or dedicated group || ((matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery()) - // Enabling preview includes all preview or nursery rules - || preview.is_enabled() + // Enabling preview includes all preview or nursery rules unless explicit selection + // is turned on + || (preview_enabled && (matches!(self, RuleSelector::Rule { .. }) || !preview_require_explicit)) }) } } @@ -232,6 +235,12 @@ impl Iterator for RuleSelectorIter { } } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PreviewOptions { + pub mode: PreviewMode, + pub require_explicit: bool, +} + #[cfg(feature = "schemars")] mod schema { use itertools::Itertools; diff --git a/crates/ruff/src/settings/defaults.rs b/crates/ruff/src/settings/defaults.rs index 1ed635e290235..ba057cb0f9008 100644 --- a/crates/ruff/src/settings/defaults.rs +++ b/crates/ruff/src/settings/defaults.rs @@ -9,7 +9,7 @@ use super::Settings; use crate::codes::{self, RuleCodePrefix}; use crate::line_width::{LineLength, TabSize}; use crate::registry::Linter; -use crate::rule_selector::RuleSelector; +use crate::rule_selector::{PreviewOptions, RuleSelector}; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, @@ -75,7 +75,7 @@ impl Default for Settings { Self { rules: PREFIXES .iter() - .flat_map(|selector| selector.rules(PreviewMode::default())) + .flat_map(|selector| selector.rules(&PreviewOptions::default())) .collect(), allowed_confusables: FxHashSet::from_iter([]), builtins: vec![], @@ -91,6 +91,7 @@ impl Default for Settings { logger_objects: vec![], namespace_packages: vec![], preview: PreviewMode::default(), + explicit_preview_rules: false, per_file_ignores: vec![], project_root: path_dedot::CWD.clone(), respect_gitignore: true, diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index ea5d38aa98830..cdef15dc02641 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -57,6 +57,7 @@ pub struct Settings { pub target_version: PythonVersion, pub preview: PreviewMode, + pub explicit_preview_rules: bool, // Resolver settings pub exclude: FilePatternSet, diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 8e98012327659..518a09a880d43 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -20,7 +20,7 @@ use regex::Regex; use ruff::line_width::{LineLength, TabSize}; use ruff::registry::RuleNamespace; use ruff::registry::{Rule, RuleSet, INCOMPATIBLE_CODES}; -use ruff::rule_selector::Specificity; +use ruff::rule_selector::{PreviewOptions, Specificity}; use ruff::settings::rule_table::RuleTable; use ruff::settings::types::{ FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, @@ -69,6 +69,7 @@ pub struct Configuration { pub logger_objects: Option>, pub namespace_packages: Option>, pub preview: Option, + pub explicit_preview_rules: Option, pub required_version: Option, pub respect_gitignore: Option, pub show_fixes: Option, @@ -177,6 +178,7 @@ impl Configuration { }), logger_objects: self.logger_objects.unwrap_or_default(), preview: self.preview.unwrap_or_default(), + explicit_preview_rules: self.explicit_preview_rules.unwrap_or_default(), typing_modules: self.typing_modules.unwrap_or_default(), // Plugins flake8_annotations: self @@ -391,6 +393,7 @@ impl Configuration { .map(|namespace_package| resolve_src(&namespace_package, project_root)) .transpose()?, preview: options.preview.map(PreviewMode::from), + explicit_preview_rules: options.explicit_preview_rules, per_file_ignores: options.per_file_ignores.map(|per_file_ignores| { per_file_ignores .into_iter() @@ -440,16 +443,19 @@ impl Configuration { } pub fn as_rule_table(&self) -> RuleTable { - let preview = self.preview.unwrap_or_default(); + let preview = PreviewOptions { + mode: self.preview.unwrap_or_default(), + require_explicit: self.explicit_preview_rules.unwrap_or_default(), + }; // The select_set keeps track of which rules have been selected. let mut select_set: RuleSet = defaults::PREFIXES .iter() - .flat_map(|selector| selector.rules(preview)) + .flat_map(|selector| selector.rules(&preview)) .collect(); // The fixable set keeps track of which rules are fixable. - let mut fixable_set: RuleSet = RuleSelector::All.rules(preview).collect(); + let mut fixable_set: RuleSet = RuleSelector::All.rules(&preview).collect(); // Ignores normally only subtract from the current set of selected // rules. By that logic the ignore in `select = [], ignore = ["E501"]` @@ -488,7 +494,7 @@ impl Configuration { .chain(selection.extend_select.iter()) .filter(|s| s.specificity() == spec) { - for rule in selector.rules(preview) { + for rule in selector.rules(&preview) { select_map_updates.insert(rule, true); } } @@ -498,7 +504,7 @@ impl Configuration { .chain(carriedover_ignores.into_iter().flatten()) .filter(|s| s.specificity() == spec) { - for rule in selector.rules(preview) { + for rule in selector.rules(&preview) { select_map_updates.insert(rule, false); } } @@ -510,7 +516,7 @@ impl Configuration { .chain(selection.extend_fixable.iter()) .filter(|s| s.specificity() == spec) { - for rule in selector.rules(preview) { + for rule in selector.rules(&preview) { fixable_map_updates.insert(rule, true); } } @@ -520,7 +526,7 @@ impl Configuration { .chain(carriedover_unfixables.into_iter().flatten()) .filter(|s| s.specificity() == spec) { - for rule in selector.rules(preview) { + for rule in selector.rules(&preview) { fixable_map_updates.insert(rule, false); } } @@ -587,16 +593,16 @@ impl Configuration { { #[allow(deprecated)] if matches!(selector, RuleSelector::Nursery) { - let suggestion = if preview.is_disabled() { + let suggestion = if preview.mode.is_disabled() { " Use the `--preview` flag instead." } else { // We have no suggested alternative since there is intentionally no "PREVIEW" selector "" }; warn_user_once!("The `NURSERY` selector has been deprecated.{suggestion}"); - } + }; - if preview.is_disabled() { + if preview.mode.is_disabled() { if let RuleSelector::Rule { prefix, .. } = selector { if prefix.rules().any(|rule| rule.is_nursery()) { deprecated_nursery_selectors.insert(selector); @@ -604,7 +610,7 @@ impl Configuration { } // Check if the selector is empty because preview mode is disabled - if selector.rules(PreviewMode::Disabled).next().is_none() { + if selector.rules(&PreviewOptions::default()).next().is_none() { ignored_preview_selectors.insert(selector); } } @@ -722,6 +728,9 @@ impl Configuration { src: self.src.or(config.src), target_version: self.target_version.or(config.target_version), preview: self.preview.or(config.preview), + explicit_preview_rules: self + .explicit_preview_rules + .or(config.explicit_preview_rules), task_tags: self.task_tags.or(config.task_tags), typing_modules: self.typing_modules.or(config.typing_modules), // Plugins diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 3c8cc9e227494..2c496993f8a7e 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -494,15 +494,27 @@ pub struct Options { /// use unstable rules and fixes. pub preview: Option, #[option( - default = r#"["TODO", "FIXME", "XXX"]"#, - value_type = "list[str]", - example = r#"task-tags = ["HACK"]"# + default = "false", + value_type = "bool", + example = r#" + # Require explicit selection of preview rules + explicit-preview-rules = true + "# )] + /// Whether to require exact codes to select preview rules. When enabled, + /// preview rules will be be selected by prefixes — the full code of each + /// preview rule will be required to enable the rule. + 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>, #[option( default = r#"[]"#, diff --git a/ruff.schema.json b/ruff.schema.json index 4402f3c7975e2..1dbc00ef9d61c 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -49,6 +49,13 @@ "type": "string" } }, + "explicit-preview-rules": { + "description": "Whether to require exact codes to select preview rules. When enabled, preview rules will be be selected by prefixes — the full code of each preview rule will be required to enable the rule.", + "type": [ + "boolean", + "null" + ] + }, "extend": { "description": "A path to a local `pyproject.toml` file to merge into this configuration. User home directory and environment variables will be expanded.\n\nTo 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.", "type": [