[refurb] Implement single-item-membership-test (FURB171) (#7815)

## Summary

Implement
[`no-single-item-in`](https://github.com/dosisod/refurb/blob/master/refurb/checks/iterable/no_single_item_in.py)
as `single-item-membership-test` (`FURB171`).

Uses the helper function `generate_comparison` from the `pycodestyle`
implementations; this function should probably be moved, but I am not
sure where at the moment.

Update: moved it to `ruff_python_ast::helpers`.

Related to #1348.

## Test Plan

`cargo test`
This commit is contained in:
Tom Kuson 2023-10-08 15:08:47 +01:00 committed by GitHub
parent bdd925c0f2
commit 62f1ee08e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 368 additions and 63 deletions

View file

@ -1,14 +1,19 @@
use std::borrow::Cow;
use std::path::Path;
use ruff_python_trivia::CommentRanges;
use ruff_source_file::Locator;
use smallvec::SmallVec;
use ruff_text_size::{Ranged, TextRange};
use crate::call_path::CallPath;
use crate::node::AnyNodeRef;
use crate::parenthesize::parenthesized_range;
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
use crate::{
self as ast, Arguments, Constant, ExceptHandler, Expr, MatchCase, Pattern, Stmt, TypeParam,
self as ast, Arguments, CmpOp, Constant, ExceptHandler, Expr, MatchCase, Pattern, Stmt,
TypeParam,
};
/// Return `true` if the `Stmt` is a compound statement (as opposed to a simple statement).
@ -1129,6 +1134,58 @@ impl Truthiness {
}
}
pub fn generate_comparison(
left: &Expr,
ops: &[CmpOp],
comparators: &[Expr],
parent: AnyNodeRef,
comment_ranges: &CommentRanges,
locator: &Locator,
) -> String {
let start = left.start();
let end = comparators.last().map_or_else(|| left.end(), Ranged::end);
let mut contents = String::with_capacity(usize::from(end - start));
// Add the left side of the comparison.
contents.push_str(
locator.slice(
parenthesized_range(left.into(), parent, comment_ranges, locator.contents())
.unwrap_or(left.range()),
),
);
for (op, comparator) in ops.iter().zip(comparators) {
// Add the operator.
contents.push_str(match op {
CmpOp::Eq => " == ",
CmpOp::NotEq => " != ",
CmpOp::Lt => " < ",
CmpOp::LtE => " <= ",
CmpOp::Gt => " > ",
CmpOp::GtE => " >= ",
CmpOp::In => " in ",
CmpOp::NotIn => " not in ",
CmpOp::Is => " is ",
CmpOp::IsNot => " is not ",
});
// Add the right side of the comparison.
contents.push_str(
locator.slice(
parenthesized_range(
comparator.into(),
parent,
comment_ranges,
locator.contents(),
)
.unwrap_or(comparator.range()),
),
);
}
contents
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;