Implement F541 (#12)

This commit is contained in:
Charlie Marsh 2022-08-16 10:47:13 -04:00 committed by GitHub
parent 3b1b53dacf
commit ddd554f9de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 7 deletions

View file

@ -0,0 +1,9 @@
a = "abc"
b = f"ghi{'jkl'}"
c = f"def"
d = f"def" + "ghi"
e = (
f"def" +
"ghi"
)

View file

@ -1,9 +1,10 @@
use std::collections::HashSet; use std::collections::HashSet;
use rustpython_parser::ast::{Arg, Arguments, ExprKind, Stmt, StmtKind, Suite}; use rustpython_parser::ast::{Arg, Arguments, Expr, ExprKind, Stmt, StmtKind, Suite};
use crate::checks::{Check, CheckKind}; use crate::checks::{Check, CheckKind};
use crate::visitor::{walk_arguments, walk_stmt, Visitor}; use crate::visitor;
use crate::visitor::Visitor;
#[derive(Default)] #[derive(Default)]
struct Checker { struct Checker {
@ -34,7 +35,23 @@ impl Visitor for Checker {
_ => {} _ => {}
} }
walk_stmt(self, stmt); visitor::walk_stmt(self, stmt);
}
fn visit_expr(&mut self, expr: &Expr) {
if let ExprKind::JoinedStr { values } = &expr.node {
if !values
.iter()
.any(|value| matches!(value.node, ExprKind::FormattedValue { .. }))
{
self.checks.push(Check {
kind: CheckKind::FStringMissingPlaceholders,
location: expr.location,
});
}
}
visitor::walk_expr(self, expr);
} }
fn visit_arguments(&mut self, arguments: &Arguments) { fn visit_arguments(&mut self, arguments: &Arguments) {
@ -66,7 +83,7 @@ impl Visitor for Checker {
idents.insert(ident.clone()); idents.insert(ident.clone());
} }
walk_arguments(self, arguments); visitor::walk_arguments(self, arguments);
} }
} }

View file

@ -4,8 +4,9 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind { pub enum CheckKind {
DuplicateArgumentName, DuplicateArgumentName,
ImportStarUsage, FStringMissingPlaceholders,
IfTuple, IfTuple,
ImportStarUsage,
LineTooLong, LineTooLong,
} }
@ -14,6 +15,7 @@ impl CheckKind {
pub fn code(&self) -> &'static str { pub fn code(&self) -> &'static str {
match self { match self {
CheckKind::DuplicateArgumentName => "F831", CheckKind::DuplicateArgumentName => "F831",
CheckKind::FStringMissingPlaceholders => "F541",
CheckKind::IfTuple => "F634", CheckKind::IfTuple => "F634",
CheckKind::ImportStarUsage => "F403", CheckKind::ImportStarUsage => "F403",
CheckKind::LineTooLong => "E501", CheckKind::LineTooLong => "E501",
@ -24,6 +26,7 @@ impl CheckKind {
pub fn body(&self) -> &'static str { pub fn body(&self) -> &'static str {
match self { match self {
CheckKind::DuplicateArgumentName => "Duplicate argument name in function definition", CheckKind::DuplicateArgumentName => "Duplicate argument name in function definition",
CheckKind::FStringMissingPlaceholders => "f-string without any placeholders",
CheckKind::IfTuple => "If test is a tuple, which is always `True`", CheckKind::IfTuple => "If test is a tuple, which is always `True`",
CheckKind::ImportStarUsage => "Unable to detect undefined names", CheckKind::ImportStarUsage => "Unable to detect undefined names",
CheckKind::LineTooLong => "Line too long", CheckKind::LineTooLong => "Line too long",

View file

@ -44,7 +44,9 @@ mod tests {
use rustpython_parser::ast::Location; use rustpython_parser::ast::Location;
use crate::cache; use crate::cache;
use crate::checks::CheckKind::{DuplicateArgumentName, IfTuple, ImportStarUsage, LineTooLong}; use crate::checks::CheckKind::{
DuplicateArgumentName, FStringMissingPlaceholders, IfTuple, ImportStarUsage, LineTooLong,
};
use crate::linter::check_path; use crate::linter::check_path;
use crate::message::Message; use crate::message::Message;
@ -79,6 +81,37 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn f_string_missing_placeholders() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/f_string_missing_placeholders.py"),
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: FStringMissingPlaceholders,
location: Location::new(4, 7),
filename: "./resources/test/src/f_string_missing_placeholders.py".to_string(),
},
Message {
kind: FStringMissingPlaceholders,
location: Location::new(5, 7),
filename: "./resources/test/src/f_string_missing_placeholders.py".to_string(),
},
Message {
kind: FStringMissingPlaceholders,
location: Location::new(7, 7),
filename: "./resources/test/src/f_string_missing_placeholders.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 1..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test] #[test]
fn if_tuple() -> Result<()> { fn if_tuple() -> Result<()> {
let actual = check_path( let actual = check_path(

View file

@ -295,7 +295,7 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) { pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
match &expr.node { match &expr.node {
ExprKind::BoolOp { op, values } => { ExprKind::BoolOp { op, values } => {
visitor.visit_boolop(op); visitor.visit_boolop(op);