mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
Update rule selection to respect preview mode (#7195)
## Summary <!-- What's the purpose of the change? What does it do, and why? --> Extends work in #7046 (some relevant discussion there) Changes: - All nursery rules are now referred to as preview rules - Documentation for the nursery is updated to describe preview - Adds a "PREVIEW" selector for preview rules - This is primarily to allow `--preview --ignore PREVIEW --extend-select FOO001,BAR200` - Using `--preview` enables preview rules that match selectors Notable decisions: - Preview rules are not selectable by their rule code without enabling preview - Retains the "NURSERY" selector for backwards compatibility - Nursery rules are selectable by their rule code for backwards compatiblity Additional work: - Selection of preview rules without the "--preview" flag should display a warning - Use of deprecated nursery selection behavior should display a warning - Nursery selection should be removed after some time ## Test Plan <!-- How was it tested? --> Manual confirmation (i.e. we don't have an preview rules yet just nursery rules so I added a preview rule for manual testing) New unit tests --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
7c9bbcf4e2
commit
6566d00295
15 changed files with 607 additions and 233 deletions
|
@ -440,14 +440,17 @@ 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()
|
||||
.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();
|
||||
|
||||
// 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 +485,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 +495,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 +507,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 +517,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);
|
||||
}
|
||||
}
|
||||
|
@ -761,26 +764,122 @@ pub fn resolve_src(src: &[String], project_root: &Path) -> Result<Vec<PathBuf>>
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::configuration::{Configuration, RuleSelection};
|
||||
use ruff::codes::Pycodestyle;
|
||||
use ruff::registry::{Rule, RuleSet};
|
||||
use ruff::codes::{Flake8Copyright, Pycodestyle};
|
||||
use ruff::registry::{Linter, Rule, RuleSet};
|
||||
use ruff::settings::types::PreviewMode;
|
||||
use ruff::RuleSelector;
|
||||
|
||||
const NURSERY_RULES: &[Rule] = &[
|
||||
Rule::MissingCopyrightNotice,
|
||||
Rule::IndentationWithInvalidMultiple,
|
||||
Rule::NoIndentedBlock,
|
||||
Rule::UnexpectedIndentation,
|
||||
Rule::IndentationWithInvalidMultipleComment,
|
||||
Rule::NoIndentedBlockComment,
|
||||
Rule::UnexpectedIndentationComment,
|
||||
Rule::OverIndented,
|
||||
Rule::WhitespaceAfterOpenBracket,
|
||||
Rule::WhitespaceBeforeCloseBracket,
|
||||
Rule::WhitespaceBeforePunctuation,
|
||||
Rule::WhitespaceBeforeParameters,
|
||||
Rule::MultipleSpacesBeforeOperator,
|
||||
Rule::MultipleSpacesAfterOperator,
|
||||
Rule::TabBeforeOperator,
|
||||
Rule::TabAfterOperator,
|
||||
Rule::MissingWhitespaceAroundOperator,
|
||||
Rule::MissingWhitespaceAroundArithmeticOperator,
|
||||
Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
|
||||
Rule::MissingWhitespaceAroundModuloOperator,
|
||||
Rule::MissingWhitespace,
|
||||
Rule::MultipleSpacesAfterComma,
|
||||
Rule::TabAfterComma,
|
||||
Rule::UnexpectedSpacesAroundKeywordParameterEquals,
|
||||
Rule::MissingWhitespaceAroundParameterEquals,
|
||||
Rule::TooFewSpacesBeforeInlineComment,
|
||||
Rule::NoSpaceAfterInlineComment,
|
||||
Rule::NoSpaceAfterBlockComment,
|
||||
Rule::MultipleLeadingHashesForBlockComment,
|
||||
Rule::MultipleSpacesAfterKeyword,
|
||||
Rule::MultipleSpacesBeforeKeyword,
|
||||
Rule::TabAfterKeyword,
|
||||
Rule::TabBeforeKeyword,
|
||||
Rule::MissingWhitespaceAfterKeyword,
|
||||
Rule::CompareToEmptyString,
|
||||
Rule::NoSelfUse,
|
||||
Rule::EqWithoutHash,
|
||||
Rule::BadDunderMethodName,
|
||||
Rule::RepeatedAppend,
|
||||
Rule::DeleteFullSlice,
|
||||
Rule::CheckAndRemoveFromSet,
|
||||
Rule::QuadraticListSummation,
|
||||
];
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn resolve_rules(selections: impl IntoIterator<Item = RuleSelection>) -> RuleSet {
|
||||
fn resolve_rules(
|
||||
selections: impl IntoIterator<Item = RuleSelection>,
|
||||
preview: Option<PreviewMode>,
|
||||
) -> RuleSet {
|
||||
Configuration {
|
||||
rule_selections: selections.into_iter().collect(),
|
||||
preview,
|
||||
..Configuration::default()
|
||||
}
|
||||
.as_rule_table()
|
||||
.iter_enabled()
|
||||
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is off-by-default
|
||||
.filter(|rule| rule.noqa_code() != "RUF014")
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_codes() {
|
||||
let actual = resolve_rules([RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
..RuleSelection::default()
|
||||
}]);
|
||||
fn select_linter() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Linter::Pycodestyle.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
None,
|
||||
);
|
||||
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::MixedSpacesAndTabs,
|
||||
Rule::MultipleImportsOnOneLine,
|
||||
Rule::ModuleImportNotAtTopOfFile,
|
||||
Rule::LineTooLong,
|
||||
Rule::MultipleStatementsOnOneLineColon,
|
||||
Rule::MultipleStatementsOnOneLineSemicolon,
|
||||
Rule::UselessSemicolon,
|
||||
Rule::NoneComparison,
|
||||
Rule::TrueFalseComparison,
|
||||
Rule::NotInTest,
|
||||
Rule::NotIsTest,
|
||||
Rule::TypeComparison,
|
||||
Rule::BareExcept,
|
||||
Rule::LambdaAssignment,
|
||||
Rule::AmbiguousVariableName,
|
||||
Rule::AmbiguousClassName,
|
||||
Rule::AmbiguousFunctionName,
|
||||
Rule::IOError,
|
||||
Rule::SyntaxError,
|
||||
Rule::TabIndentation,
|
||||
Rule::TrailingWhitespace,
|
||||
Rule::MissingNewlineAtEndOfFile,
|
||||
Rule::BlankLineWithWhitespace,
|
||||
Rule::DocLineTooLong,
|
||||
Rule::InvalidEscapeSequence,
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_one_char_prefix() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
None,
|
||||
);
|
||||
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::TrailingWhitespace,
|
||||
|
@ -791,19 +890,31 @@ mod tests {
|
|||
Rule::TabIndentation,
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
let actual = resolve_rules([RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W6.into()]),
|
||||
..RuleSelection::default()
|
||||
}]);
|
||||
#[test]
|
||||
fn select_two_char_prefix() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W6.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::InvalidEscapeSequence);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
let actual = resolve_rules([RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
}]);
|
||||
#[test]
|
||||
fn select_prefix_ignore_code() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::TrailingWhitespace,
|
||||
Rule::BlankLineWithWhitespace,
|
||||
|
@ -812,73 +923,100 @@ mod tests {
|
|||
Rule::TabIndentation,
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
let actual = resolve_rules([RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W292.into()]),
|
||||
ignore: vec![Pycodestyle::W.into()],
|
||||
..RuleSelection::default()
|
||||
}]);
|
||||
let expected = RuleSet::from_rule(Rule::MissingNewlineAtEndOfFile);
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules([RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W605.into()]),
|
||||
ignore: vec![Pycodestyle::W605.into()],
|
||||
..RuleSelection::default()
|
||||
}]);
|
||||
let expected = RuleSet::empty();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules([
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
extend_select: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
]);
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::TrailingWhitespace,
|
||||
Rule::MissingNewlineAtEndOfFile,
|
||||
Rule::BlankLineWithWhitespace,
|
||||
Rule::DocLineTooLong,
|
||||
Rule::InvalidEscapeSequence,
|
||||
Rule::TabIndentation,
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules([
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
extend_select: vec![Pycodestyle::W292.into()],
|
||||
#[test]
|
||||
fn select_code_ignore_prefix() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W292.into()]),
|
||||
ignore: vec![Pycodestyle::W.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
]);
|
||||
}],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::MissingNewlineAtEndOfFile);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carry_over_ignore() {
|
||||
let actual = resolve_rules([
|
||||
RuleSelection {
|
||||
select: Some(vec![]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
fn select_code_ignore_code() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W605.into()]),
|
||||
ignore: vec![Pycodestyle::W605.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
..RuleSelection::default()
|
||||
},
|
||||
}],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::empty();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_prefix_ignore_code_then_extend_select_code() {
|
||||
let actual = resolve_rules(
|
||||
[
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
extend_select: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::TrailingWhitespace,
|
||||
Rule::MissingNewlineAtEndOfFile,
|
||||
Rule::BlankLineWithWhitespace,
|
||||
Rule::DocLineTooLong,
|
||||
Rule::InvalidEscapeSequence,
|
||||
Rule::TabIndentation,
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_prefix_ignore_code_then_extend_select_code_ignore_prefix() {
|
||||
let actual = resolve_rules(
|
||||
[
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
extend_select: vec![Pycodestyle::W292.into()],
|
||||
ignore: vec![Pycodestyle::W.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::MissingNewlineAtEndOfFile);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_code_then_select_prefix() {
|
||||
let actual = resolve_rules(
|
||||
[
|
||||
RuleSelection {
|
||||
select: Some(vec![]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
..RuleSelection::default()
|
||||
},
|
||||
],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::TrailingWhitespace,
|
||||
Rule::BlankLineWithWhitespace,
|
||||
|
@ -887,19 +1025,25 @@ mod tests {
|
|||
Rule::TabIndentation,
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
let actual = resolve_rules([
|
||||
RuleSelection {
|
||||
select: Some(vec![]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W505.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
]);
|
||||
#[test]
|
||||
fn ignore_code_then_select_prefix_ignore_code() {
|
||||
let actual = resolve_rules(
|
||||
[
|
||||
RuleSelection {
|
||||
select: Some(vec![]),
|
||||
ignore: vec![Pycodestyle::W292.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
RuleSelection {
|
||||
select: Some(vec![Pycodestyle::W.into()]),
|
||||
ignore: vec![Pycodestyle::W505.into()],
|
||||
..RuleSelection::default()
|
||||
},
|
||||
],
|
||||
None,
|
||||
);
|
||||
let expected = RuleSet::from_rules(&[
|
||||
Rule::TrailingWhitespace,
|
||||
Rule::BlankLineWithWhitespace,
|
||||
|
@ -908,4 +1052,124 @@ mod tests {
|
|||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_linter_preview() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Linter::Flake8Copyright.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
let expected = RuleSet::empty();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Linter::Flake8Copyright.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::MissingCopyrightNotice);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_prefix_preview() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Flake8Copyright::_0.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
let expected = RuleSet::empty();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Flake8Copyright::_0.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::MissingCopyrightNotice);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_preview() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::Preview]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
let expected = RuleSet::empty();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::Preview]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
let expected = RuleSet::from_rules(NURSERY_RULES);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_select_code() {
|
||||
// Backwards compatible behavior allows selection of nursery rules with their exact code
|
||||
// when preview is disabled
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Flake8Copyright::_001.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::MissingCopyrightNotice);
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![Flake8Copyright::_001.into()]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
let expected = RuleSet::from_rule(Rule::MissingCopyrightNotice);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn select_nursery() {
|
||||
// Backwards compatible behavior allows selection of nursery rules with the nursery selector
|
||||
// when preview is disabled
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::Nursery]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
let expected = RuleSet::from_rules(NURSERY_RULES);
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::Nursery]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
let expected = RuleSet::from_rules(NURSERY_RULES);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue