Compare commits

...

7 Commits

Author SHA1 Message Date
Charlie Marsh
ffbbb8d912 Make preview part of the selector API 2023-09-01 19:47:51 +01:00
Charlie Marsh
b9f0ade746 Enable selection of preview rules 2023-09-01 16:47:59 +01:00
Zanie
dd4bf76adf WIP: Rename rule selector from nursery to preview 2023-08-31 15:06:40 -05:00
Zanie
6761f33600 Fix conditional 2023-08-31 11:00:52 -05:00
Zanie
792b76583b Clarify include_preview_rules conditional 2023-08-31 10:03:13 -05:00
Zanie
3e0d849c24 Update ALL rule selector to include all rules then deselect preview rules 2023-08-31 09:53:54 -05:00
Zanie
8b8fd411e2 Rename a bunch of "nursery" references to "preview" 2023-08-31 09:53:54 -05:00
13 changed files with 212 additions and 173 deletions

View File

@@ -4,6 +4,7 @@ use std::str::FromStr;
use anyhow::anyhow;
use ruff::registry::Linter;
use ruff::settings::types::PreviewMode;
use ruff::RuleSelector;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
@@ -331,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
.filter(|plugin| {
for selector in selectors {
if selector
.into_iter()
.rules(PreviewMode::Disabled)
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
{
return true;

View File

@@ -51,8 +51,8 @@ impl PartialEq<&str> for NoqaCode {
pub enum RuleGroup {
/// The rule has not been assigned to any specific group.
Unspecified,
/// The rule is still under development, and must be enabled explicitly.
Nursery,
/// The rule is unstable, and must be enabled explicitly or by enabling preview.
Preview,
}
#[ruff_macros::map_codes]
@@ -64,39 +64,39 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
Some(match (linter, code) {
// pycodestyle errors
(Pycodestyle, "E101") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MixedSpacesAndTabs),
(Pycodestyle, "E111") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
(Pycodestyle, "E113") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
(Pycodestyle, "E114") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
(Pycodestyle, "E115") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
(Pycodestyle, "E116") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
(Pycodestyle, "E117") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::OverIndented),
(Pycodestyle, "E201") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
(Pycodestyle, "E202") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
(Pycodestyle, "E203") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
(Pycodestyle, "E211") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
(Pycodestyle, "E221") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
(Pycodestyle, "E222") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
(Pycodestyle, "E223") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
(Pycodestyle, "E224") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
(Pycodestyle, "E225") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
(Pycodestyle, "E226") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
(Pycodestyle, "E262") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
(Pycodestyle, "E265") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
(Pycodestyle, "E266") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
(Pycodestyle, "E271") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
(Pycodestyle, "E272") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
(Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
(Pycodestyle, "E111") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
(Pycodestyle, "E112") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
(Pycodestyle, "E113") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
(Pycodestyle, "E114") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
(Pycodestyle, "E115") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
(Pycodestyle, "E116") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
(Pycodestyle, "E117") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::OverIndented),
(Pycodestyle, "E201") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
(Pycodestyle, "E202") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
(Pycodestyle, "E203") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
(Pycodestyle, "E211") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
(Pycodestyle, "E221") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
(Pycodestyle, "E222") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
(Pycodestyle, "E223") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
(Pycodestyle, "E224") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
(Pycodestyle, "E225") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
(Pycodestyle, "E226") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
(Pycodestyle, "E227") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
(Pycodestyle, "E228") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
(Pycodestyle, "E231") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
(Pycodestyle, "E241") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
(Pycodestyle, "E242") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabAfterComma),
(Pycodestyle, "E251") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
(Pycodestyle, "E252") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
(Pycodestyle, "E261") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
(Pycodestyle, "E262") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
(Pycodestyle, "E265") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
(Pycodestyle, "E266") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
(Pycodestyle, "E271") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
(Pycodestyle, "E272") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
(Pycodestyle, "E273") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
(Pycodestyle, "E274") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
(Pycodestyle, "E275") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
(Pycodestyle, "E401") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleImportsOnOneLine),
(Pycodestyle, "E402") => (RuleGroup::Unspecified, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
(Pycodestyle, "E501") => (RuleGroup::Unspecified, rules::pycodestyle::rules::LineTooLong),
@@ -176,7 +176,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots),
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),
(Pylint, "E0101") => (RuleGroup::Unspecified, rules::pylint::rules::ReturnInInit),
@@ -216,7 +216,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
(Pylint, "R6301") => (RuleGroup::Preview, rules::pylint::rules::NoSelfUse),
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
(Pylint, "W0129") => (RuleGroup::Unspecified, rules::pylint::rules::AssertOnStringLiteral),
@@ -228,9 +228,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
(Pylint, "W1641") => (RuleGroup::Preview, rules::pylint::rules::EqWithoutHash),
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
(Pylint, "W3201") => (RuleGroup::Preview, rules::pylint::rules::BadDunderMethodName),
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
// flake8-async
@@ -403,7 +403,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
// flake8-copyright
(Flake8Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
(Flake8Copyright, "001") => (RuleGroup::Preview, rules::flake8_copyright::rules::MissingCopyrightNotice),
// pyupgrade
(Pyupgrade, "001") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UselessMetaclassType),
@@ -815,10 +815,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
#[cfg(feature = "unreachable-code")] // When removing this feature gate, also update rules_selector.rs
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
(Ruff, "014") => (RuleGroup::Preview, rules::ruff::rules::UnreachableCode),
(Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
(Ruff, "017") => (RuleGroup::Preview, rules::ruff::rules::QuadraticListSummation),
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
@@ -866,9 +866,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
// refurb
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
(Refurb, "113") => (RuleGroup::Preview, rules::refurb::rules::RepeatedAppend),
(Refurb, "131") => (RuleGroup::Preview, rules::refurb::rules::DeleteFullSlice),
(Refurb, "132") => (RuleGroup::Preview, rules::refurb::rules::CheckAndRemoveFromSet),
_ => return None,
})

View File

@@ -9,13 +9,14 @@ use crate::codes::RuleCodePrefix;
use crate::codes::RuleIter;
use crate::registry::{Linter, Rule, RuleNamespace};
use crate::rule_redirects::get_redirect;
use crate::settings::types::PreviewMode;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RuleSelector {
/// Select all stable rules.
All,
/// Select all nursery rules.
Nursery,
/// Category to select all preview rules, previously known as the nursery
Preview,
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
/// via a single selector.
C,
@@ -29,6 +30,11 @@ pub enum RuleSelector {
prefix: RuleCodePrefix,
redirected_from: Option<&'static str>,
},
/// Select an individual rule with a given prefix.
Rule {
prefix: RuleCodePrefix,
redirected_from: Option<&'static str>,
},
}
impl From<Linter> for RuleSelector {
@@ -43,7 +49,9 @@ impl FromStr for RuleSelector {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ALL" => Ok(Self::All),
"NURSERY" => Ok(Self::Nursery),
// Legacy support for selecting preview rules as "nursery"
"NURSERY" => Ok(Self::Preview),
"PREVIEW" => Ok(Self::Preview),
"C" => Ok(Self::C),
"T" => Ok(Self::T),
_ => {
@@ -59,16 +67,42 @@ impl FromStr for RuleSelector {
return Ok(Self::Linter(linter));
}
Ok(Self::Prefix {
prefix: RuleCodePrefix::parse(&linter, code)
.map_err(|_| ParseError::Unknown(s.to_string()))?,
redirected_from,
})
// Does the selector select a single rule?
let prefix = RuleCodePrefix::parse(&linter, code)
.map_err(|_| ParseError::Unknown(s.to_string()))?;
if is_single_rule_selector(&prefix) {
Ok(Self::Rule {
prefix,
redirected_from,
})
} else {
Ok(Self::Prefix {
prefix,
redirected_from,
})
}
}
}
}
}
/// Returns `true` if the [`RuleCodePrefix`] matches a single rule exactly
/// (e.g., `E225`, as opposed to `E2`).
fn is_single_rule_selector(prefix: &RuleCodePrefix) -> bool {
let mut rules = prefix.rules();
// The selector must match a single rule.
let Some(rule) = rules.next() else {
return false;
};
if rules.next().is_some() {
return false;
}
// The rule must match the selector exactly.
rule.noqa_code().suffix() == prefix.short_code()
}
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("Unknown rule selector: `{0}`")]
@@ -81,10 +115,10 @@ impl RuleSelector {
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
match self {
RuleSelector::All => ("", "ALL"),
RuleSelector::Nursery => ("", "NURSERY"),
RuleSelector::Preview => ("", "PREVIEW"),
RuleSelector::C => ("", "C"),
RuleSelector::T => ("", "T"),
RuleSelector::Prefix { prefix, .. } => {
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
(prefix.linter().common_prefix(), prefix.short_code())
}
RuleSelector::Linter(l) => (l.common_prefix(), ""),
@@ -135,26 +169,13 @@ impl Visitor<'_> for SelectorVisitor {
}
}
impl From<RuleCodePrefix> for RuleSelector {
fn from(prefix: RuleCodePrefix) -> Self {
Self::Prefix {
prefix,
redirected_from: None,
}
}
}
impl IntoIterator for &RuleSelector {
type IntoIter = RuleSelectorIter;
type Item = Rule;
fn into_iter(self) -> Self::IntoIter {
impl RuleSelector {
/// Return all matching rules, regardless of whether they're in preview.
pub fn all_rules(&self) -> impl Iterator<Item = Rule> + '_ {
match self {
RuleSelector::All => {
RuleSelectorIter::All(Rule::iter().filter(|rule| !rule.is_nursery()))
}
RuleSelector::Nursery => {
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
RuleSelector::Preview => {
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_preview))
}
RuleSelector::C => RuleSelectorIter::Chain(
Linter::Flake8Comprehensions
@@ -167,13 +188,22 @@ impl IntoIterator for &RuleSelector {
.chain(Linter::Flake8Print.rules()),
),
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.rules()),
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.clone().rules()),
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
RuleSelectorIter::Vec(prefix.clone().rules())
}
}
}
/// Returns rules matching the selector, taking into account whether preview mode is enabled.
pub fn rules(&self, preview: PreviewMode) -> impl Iterator<Item = Rule> + '_ {
self.all_rules().filter(move |rule| {
matches!(self, RuleSelector::Rule { .. }) || preview.is_enabled() || !rule.is_preview()
})
}
}
pub enum RuleSelectorIter {
All(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
All(RuleIter),
Nursery(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
Vec(std::vec::IntoIter<Rule>),
@@ -266,18 +296,18 @@ impl RuleSelector {
pub fn specificity(&self) -> Specificity {
match self {
RuleSelector::All => Specificity::All,
RuleSelector::Nursery => Specificity::All,
RuleSelector::Preview => Specificity::All,
RuleSelector::T => Specificity::LinterGroup,
RuleSelector::C => Specificity::LinterGroup,
RuleSelector::Linter(..) => Specificity::Linter,
RuleSelector::Rule { .. } => Specificity::Rule,
RuleSelector::Prefix { prefix, .. } => {
let prefix: &'static str = prefix.short_code();
match prefix.len() {
1 => Specificity::Code1Char,
2 => Specificity::Code2Chars,
3 => Specificity::Code3Chars,
4 => Specificity::Code4Chars,
5 => Specificity::Code5Chars,
1 => Specificity::Prefix1Char,
2 => Specificity::Prefix2Chars,
3 => Specificity::Prefix3Chars,
4 => Specificity::Prefix4Chars,
_ => panic!("RuleSelector::specificity doesn't yet support codes with so many characters"),
}
}
@@ -285,16 +315,24 @@ impl RuleSelector {
}
}
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
pub enum Specificity {
/// The specificity when selecting all rules (e.g., `--select ALL`).
All,
/// The specificity when selecting a linter group (e.g., `--select PL`).
LinterGroup,
/// The specificity when selecting a linter (e.g., `--select PLE` or `--select UP`).
Linter,
Code1Char,
Code2Chars,
Code3Chars,
Code4Chars,
Code5Chars,
/// The specificity when selecting via a rule prefix at one-character depth (e.g., `--select PLE1`).
Prefix1Char,
/// The specificity when selecting via a rule prefix at two-character depth (e.g., `--select PLE12`).
Prefix2Chars,
/// The specificity when selecting via a rule prefix at one-character depth (e.g., `--select PLE120`).
Prefix3Chars,
/// The specificity when selecting via a rule prefix at one-character depth (e.g., `--select PLE120`).
Prefix4Chars,
/// The specificity when selecting an individual rule (e.g., `--select PLE1205`).
Rule,
}
#[cfg(feature = "clap")]

View File

@@ -70,7 +70,10 @@ pub static INCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
impl Default for Settings {
fn default() -> Self {
Self {
rules: PREFIXES.iter().flat_map(IntoIterator::into_iter).collect(),
rules: PREFIXES
.iter()
.flat_map(|selector| selector.rules(PreviewMode::default()))
.collect(),
allowed_confusables: FxHashSet::from_iter([]),
builtins: vec![],
dummy_variable_rgx: DUMMY_VARIABLE_RGX.clone(),

View File

@@ -194,7 +194,7 @@ pub struct PerFileIgnore {
impl PerFileIgnore {
pub fn new(pattern: String, prefixes: &[RuleSelector], project_root: Option<&Path>) -> Self {
let rules: RuleSet = prefixes.iter().flat_map(IntoIterator::into_iter).collect();
let rules: RuleSet = prefixes.iter().flat_map(RuleSelector::all_rules).collect();
let path = Path::new(&pattern);
let absolute = match project_root {
Some(project_root) => fs::normalize_path_to(path, project_root),

View File

@@ -19,7 +19,7 @@ struct Explanation<'a> {
message_formats: &'a [&'a str],
autofix: String,
explanation: Option<&'a str>,
nursery: bool,
preview: bool,
}
impl<'a> Explanation<'a> {
@@ -35,7 +35,7 @@ impl<'a> Explanation<'a> {
message_formats: rule.message_formats(),
autofix,
explanation: rule.explanation(),
nursery: rule.is_nursery(),
preview: rule.is_preview(),
}
}
}
@@ -58,11 +58,10 @@ fn format_rule_text(rule: Rule) -> String {
output.push('\n');
}
if rule.is_nursery() {
if rule.is_preview() {
output.push_str(&format!(
r#"This rule is part of the **nursery**, a collection of newer lints that are
still under development. As such, it must be enabled by explicitly selecting
{}."#,
r#"This rule is in preview and is not stable. It may be enabled by explicitly selecting {}"
" or providing the `--preview` flag."#,
rule.noqa_code()
));
output.push('\n');

View File

@@ -43,11 +43,10 @@ pub(crate) fn main(args: &Args) -> Result<()> {
output.push('\n');
}
if rule.is_nursery() {
if rule.is_preview() {
output.push_str(&format!(
r#"This rule is part of the **nursery**, a collection of newer lints that are
still under development. As such, it must be enabled by explicitly selecting
{}."#,
r#"This rule is in preview and is not stable. It may be enabled by explicitly selecting {}"
" or providing the `--preview` flag."#,
rule.noqa_code()
));
output.push('\n');

View File

@@ -11,7 +11,7 @@ use ruff_diagnostics::AutofixKind;
use ruff_workspace::options::Options;
const FIX_SYMBOL: &str = "🛠";
const NURSERY_SYMBOL: &str = "🌅";
const PREVIEW_SYMBOL: &str = "🌅";
fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>, linter: &Linter) {
table_out.push_str("| Code | Name | Message | |");
@@ -25,12 +25,12 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
}
AutofixKind::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
};
let nursery_token = if rule.is_nursery() {
format!("<span style='opacity: 1'>{NURSERY_SYMBOL}</span>")
let preview_token = if rule.is_preview() {
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
} else {
format!("<span style='opacity: 0.1'>{NURSERY_SYMBOL}</span>")
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")
};
let status_token = format!("{fix_token} {nursery_token}");
let status_token = format!("{fix_token} {preview_token}");
let rule_name = rule.as_ref();
@@ -61,7 +61,7 @@ pub(crate) fn generate() -> String {
table_out.push('\n');
table_out.push_str(&format!(
"The {NURSERY_SYMBOL} emoji indicates that a rule is part of the [\"nursery\"](../faq/#what-is-the-nursery)."
"The {PREVIEW_SYMBOL} emoji indicates that a rule is part of the [\"nursery\"](../faq/#what-is-the-nursery)."
));
table_out.push('\n');
table_out.push('\n');

View File

@@ -8,7 +8,7 @@ use syn::{
Ident, ItemFn, LitStr, Pat, Path, Stmt, Token,
};
use crate::rule_code_prefix::{get_prefix_ident, if_all_same, is_nursery};
use crate::rule_code_prefix::{get_prefix_ident, if_all_same};
/// A rule entry in the big match statement such a
/// `(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),`
@@ -156,7 +156,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
output.extend(quote! {
impl #linter {
pub fn rules(self) -> ::std::vec::IntoIter<Rule> {
pub fn rules(&self) -> ::std::vec::IntoIter<Rule> {
match self { #prefix_into_iter_match_arms }
}
}
@@ -172,7 +172,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
})
}
pub fn rules(self) -> ::std::vec::IntoIter<Rule> {
pub fn rules(&self) -> ::std::vec::IntoIter<Rule> {
match self {
#(RuleCodePrefix::#linter_idents(prefix) => prefix.clone().rules(),)*
}
@@ -195,26 +195,12 @@ fn rules_by_prefix(
// TODO(charlie): Why do we do this here _and_ in `rule_code_prefix::expand`?
let mut rules_by_prefix = BTreeMap::new();
for (code, rule) in rules {
// Nursery rules have to be explicitly selected, so we ignore them when looking at
// prefix-level selectors (e.g., `--select SIM10`), but add the rule itself under
// its fully-qualified code (e.g., `--select SIM101`).
if is_nursery(&rule.group) {
rules_by_prefix.insert(code.clone(), vec![(rule.path.clone(), rule.attrs.clone())]);
continue;
}
for code in rules.keys() {
for i in 1..=code.len() {
let prefix = code[..i].to_string();
let rules: Vec<_> = rules
.iter()
.filter_map(|(code, rule)| {
// Nursery rules have to be explicitly selected, so we ignore them when
// looking at prefixes.
if is_nursery(&rule.group) {
return None;
}
if code.starts_with(&prefix) {
Some((rule.path.clone(), rule.attrs.clone()))
} else {
@@ -311,8 +297,8 @@ See also https://github.com/astral-sh/ruff/issues/2186.
}
}
pub fn is_nursery(&self) -> bool {
matches!(self.group(), RuleGroup::Nursery)
pub fn is_preview(&self) -> bool {
matches!(self.group(), RuleGroup::Preview)
}
}
@@ -336,12 +322,10 @@ fn generate_iter_impl(
let mut linter_rules_match_arms = quote!();
let mut linter_all_rules_match_arms = quote!();
for (linter, map) in linter_to_rules {
let rule_paths = map.values().filter(|rule| !is_nursery(&rule.group)).map(
|Rule { attrs, path, .. }| {
let rule_name = path.segments.last().unwrap();
quote!(#(#attrs)* Rule::#rule_name)
},
);
let rule_paths = map.values().map(|Rule { attrs, path, .. }| {
let rule_name = path.segments.last().unwrap();
quote!(#(#attrs)* Rule::#rule_name)
});
linter_rules_match_arms.extend(quote! {
Linter::#linter => vec![#(#rule_paths,)*].into_iter(),
});

View File

@@ -12,22 +12,14 @@ pub(crate) fn expand<'a>(
let mut prefix_to_codes: BTreeMap<String, BTreeSet<String>> = BTreeMap::default();
let mut code_to_attributes: BTreeMap<String, &[Attribute]> = BTreeMap::default();
for (variant, group, attr) in variants {
for (variant, .., attr) in variants {
let code_str = variant.to_string();
// Nursery rules have to be explicitly selected, so we ignore them when looking at prefixes.
if is_nursery(group) {
for i in 1..=code_str.len() {
let prefix = code_str[..i].to_string();
prefix_to_codes
.entry(code_str.clone())
.entry(prefix)
.or_default()
.insert(code_str.clone());
} else {
for i in 1..=code_str.len() {
let prefix = code_str[..i].to_string();
prefix_to_codes
.entry(prefix)
.or_default()
.insert(code_str.clone());
}
}
code_to_attributes.insert(code_str, attr);
@@ -125,14 +117,3 @@ pub(crate) fn get_prefix_ident(prefix: &str) -> Ident {
};
Ident::new(&prefix, Span::call_site())
}
/// Returns true if the given group is the "nursery" group.
pub(crate) fn is_nursery(group: &Path) -> bool {
let group = group
.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<String>>()
.join("::");
group == "RuleGroup::Nursery"
}

View File

@@ -440,14 +440,18 @@ impl Configuration {
}
pub fn as_rule_table(&self) -> RuleTable {
let preview = self.preview.unwrap_or_default();
// The select_set keeps track of which rules have been selected.
let mut select_set: RuleSet = defaults::PREFIXES.iter().flatten().collect();
// The fixable set keeps track of which rules are fixable.
let mut fixable_set: RuleSet = RuleSelector::All
.into_iter()
.chain(&RuleSelector::Nursery)
let mut select_set: RuleSet = defaults::PREFIXES
.iter()
.map(|selector| selector.rules(preview))
.flatten()
.collect();
// The fixable set keeps track of which rules are fixable.
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"]`
// would be effectless. Instead we carry over the ignores to the next
@@ -482,7 +486,7 @@ impl Configuration {
.chain(selection.extend_select.iter())
.filter(|s| s.specificity() == spec)
{
for rule in selector {
for rule in selector.rules(preview) {
select_map_updates.insert(rule, true);
}
}
@@ -492,7 +496,7 @@ impl Configuration {
.chain(carriedover_ignores.into_iter().flatten())
.filter(|s| s.specificity() == spec)
{
for rule in selector {
for rule in selector.rules(preview) {
select_map_updates.insert(rule, false);
}
}
@@ -504,7 +508,7 @@ impl Configuration {
.chain(selection.extend_fixable.iter())
.filter(|s| s.specificity() == spec)
{
for rule in selector {
for rule in selector.rules(preview) {
fixable_map_updates.insert(rule, true);
}
}
@@ -514,7 +518,7 @@ impl Configuration {
.chain(carriedover_unfixables.into_iter().flatten())
.filter(|s| s.specificity() == spec)
{
for rule in selector {
for rule in selector.rules(preview) {
fixable_map_updates.insert(rule, false);
}
}

View File

@@ -382,13 +382,13 @@ matter how they're provided, which avoids accidental incompatibilities and simpl
By default, no `convention` is set, and so the enabled rules are determined by the `select` setting
alone.
## What is the "nursery"?
## What is preview mode?
The "nursery" is a collection of newer rules that are considered experimental or unstable.
Preview mode enables a collection of newer rules and fixes that are considered experimental or unstable.
If a rule is marked as part of the "nursery", it can only be enabled via direct selection. For
example, consider a hypothetical rule, `HYP001`. If `HYP001` were included in the "nursery", it
could be enabled by adding the following to your `pyproject.toml`:
If a rule is marked as preview, it can only be enabled via direct selection or the `--preview` flag. For
example, consider a hypothetical rule, `HYP001`. If `HYP001` were in preview, it could be enabled by adding the
following to your `pyproject.toml`:
```toml
[tool.ruff]
@@ -409,10 +409,15 @@ Similarly, it would _not_ be enabled via the `ALL` selector:
select = ["ALL"]
```
(The "nursery" terminology comes from [Clippy](https://doc.rust-lang.org/nightly/clippy/), a similar
tool for linting Rust code.)
Unless you also enabled `preview`:
To see which rules are currently in the "nursery", visit the [rules reference](https://beta.ruff.rs/docs/rules/).
```toml
[tool.ruff]
select = ["ALL"]
preview = true
```
To see which rules are currently in preview, visit the [rules reference](https://beta.ruff.rs/docs/rules/).
## How can I tell what settings Ruff is using to check my code?

25
ruff.schema.json generated
View File

@@ -1797,6 +1797,8 @@
"COM818",
"COM819",
"CPY",
"CPY0",
"CPY00",
"CPY001",
"D",
"D1",
@@ -1883,6 +1885,7 @@
"E1",
"E10",
"E101",
"E11",
"E111",
"E112",
"E113",
@@ -1890,10 +1893,14 @@
"E115",
"E116",
"E117",
"E2",
"E20",
"E201",
"E202",
"E203",
"E21",
"E211",
"E22",
"E221",
"E222",
"E223",
@@ -1902,15 +1909,20 @@
"E226",
"E227",
"E228",
"E23",
"E231",
"E24",
"E241",
"E242",
"E25",
"E251",
"E252",
"E26",
"E261",
"E262",
"E265",
"E266",
"E27",
"E271",
"E272",
"E273",
@@ -2051,7 +2063,10 @@
"FLY00",
"FLY002",
"FURB",
"FURB1",
"FURB11",
"FURB113",
"FURB13",
"FURB131",
"FURB132",
"G",
@@ -2196,6 +2211,9 @@
"PLC04",
"PLC041",
"PLC0414",
"PLC1",
"PLC19",
"PLC190",
"PLC1901",
"PLC3",
"PLC30",
@@ -2288,6 +2306,9 @@
"PLR55",
"PLR550",
"PLR5501",
"PLR6",
"PLR63",
"PLR630",
"PLR6301",
"PLW",
"PLW0",
@@ -2315,12 +2336,16 @@
"PLW1509",
"PLW151",
"PLW1510",
"PLW16",
"PLW164",
"PLW1641",
"PLW2",
"PLW29",
"PLW290",
"PLW2901",
"PLW3",
"PLW32",
"PLW320",
"PLW3201",
"PLW33",
"PLW330",