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:
Zanie Blue 2023-09-11 12:28:39 -05:00 committed by GitHub
parent 7c9bbcf4e2
commit 6566d00295
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 607 additions and 233 deletions

View file

@ -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);
}
}