Implement F404 (#159)

This commit is contained in:
Charlie Marsh 2022-09-11 18:05:00 -04:00 committed by GitHub
parent c4565fe0f5
commit 549732b1da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 21 deletions

View File

@ -124,7 +124,7 @@ ruff's goal is to achieve feature-parity with Flake8 when used (1) without any p
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.) stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
implements 28 rules. (Note that these 28 rules likely cover a disproportionate share of errors: implements 30 rules. (Note that these 30 rules likely cover a disproportionate share of errors:
unused imports, undefined variables, etc.) unused imports, undefined variables, etc.)
Of the unimplemented rules, ruff is missing: Of the unimplemented rules, ruff is missing:
@ -157,6 +157,7 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
| E902 | IOError | No such file or directory: `...` | | E902 | IOError | No such file or directory: `...` |
| F401 | UnusedImport | `...` imported but unused | | F401 | UnusedImport | `...` imported but unused |
| F403 | ImportStarUsage | Unable to detect undefined names | | F403 | ImportStarUsage | Unable to detect undefined names |
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | | F541 | FStringMissingPlaceholders | f-string without any placeholders |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | | F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | | F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |

View File

@ -12,6 +12,7 @@ fn main() {
CheckKind::IOError("...".to_string()), CheckKind::IOError("...".to_string()),
CheckKind::IfTuple, CheckKind::IfTuple,
CheckKind::ImportStarUsage, CheckKind::ImportStarUsage,
CheckKind::LateFutureImport,
CheckKind::LineTooLong, CheckKind::LineTooLong,
CheckKind::ModuleImportNotAtTopOfFile, CheckKind::ModuleImportNotAtTopOfFile,
CheckKind::MultiValueRepeatedKeyLiteral, CheckKind::MultiValueRepeatedKeyLiteral,

7
resources/test/fixtures/F404.py vendored Normal file
View File

@ -0,0 +1,7 @@
from __future__ import print_function
"""Docstring"""
from __future__ import absolute_import
from collections import namedtuple
from __future__ import print_function

View File

@ -13,6 +13,7 @@ select = [
"E902", "E902",
"F401", "F401",
"F403", "F403",
"F404",
"F541", "F541",
"F601", "F601",
"F602", "F602",

View File

@ -44,6 +44,7 @@ struct Checker<'a> {
in_literal: bool, in_literal: bool,
seen_non_import: bool, seen_non_import: bool,
seen_docstring: bool, seen_docstring: bool,
futures_allowed: bool,
} }
impl<'a> Checker<'a> { impl<'a> Checker<'a> {
@ -73,6 +74,7 @@ impl<'a> Checker<'a> {
in_literal: false, in_literal: false,
seen_non_import: false, seen_non_import: false,
seen_docstring: false, seen_docstring: false,
futures_allowed: true,
} }
} }
} }
@ -102,32 +104,57 @@ where
// Track whether we've seen docstrings, non-imports, etc. // Track whether we've seen docstrings, non-imports, etc.
match &stmt.node { match &stmt.node {
StmtKind::Import { .. } => {} StmtKind::ImportFrom { module, .. } => {
StmtKind::ImportFrom { .. } => {} // Allow __future__ imports until we see a non-__future__ import.
StmtKind::Expr { value } => { if self.futures_allowed {
if !self.seen_docstring if let Some(module) = module {
&& stmt.location.column() == 1 if module != "__future__" {
&& !operations::in_nested_block(&self.parent_stack, &self.parents) self.futures_allowed = false;
{ }
if let ExprKind::Constant {
value: Constant::Str(_),
..
} = &value.node
{
self.seen_docstring = true;
} }
} }
}
if !self.seen_non_import StmtKind::Import { .. } => {
&& stmt.location.column() == 1 self.futures_allowed = false;
}
StmtKind::Expr { value } => {
if self.seen_docstring
&& !self.seen_non_import
&& !operations::in_nested_block(&self.parent_stack, &self.parents) && !operations::in_nested_block(&self.parent_stack, &self.parents)
{ {
self.seen_non_import = true; self.seen_non_import = true;
} }
if !self.seen_docstring
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
&& matches!(
&value.node,
ExprKind::Constant {
value: Constant::Str(_),
..
},
)
{
self.seen_docstring = true;
}
// Allow docstrings to interrupt __future__ imports.
if self.futures_allowed
&& !matches!(
&value.node,
ExprKind::Constant {
value: Constant::Str(_),
..
},
)
{
self.futures_allowed = false;
}
} }
_ => { _ => {
self.futures_allowed = false;
if !self.seen_non_import if !self.seen_non_import
&& stmt.location.column() == 1
&& !operations::in_nested_block(&self.parent_stack, &self.parents) && !operations::in_nested_block(&self.parent_stack, &self.parents)
{ {
self.seen_non_import = true; self.seen_non_import = true;
@ -363,6 +390,12 @@ where
location: stmt.location, location: stmt.location,
}, },
); );
if !self.futures_allowed && self.settings.select.contains(&CheckCode::F404)
{
self.checks
.push(Check::new(CheckKind::LateFutureImport, stmt.location));
}
} else if alias.node.name == "*" { } else if alias.node.name == "*" {
self.add_binding( self.add_binding(
name, name,

View File

@ -19,6 +19,7 @@ pub enum CheckCode {
E902, E902,
F401, F401,
F403, F403,
F404,
F541, F541,
F601, F601,
F602, F602,
@ -55,6 +56,7 @@ impl FromStr for CheckCode {
"E902" => Ok(CheckCode::E902), "E902" => Ok(CheckCode::E902),
"F401" => Ok(CheckCode::F401), "F401" => Ok(CheckCode::F401),
"F403" => Ok(CheckCode::F403), "F403" => Ok(CheckCode::F403),
"F404" => Ok(CheckCode::F404),
"F541" => Ok(CheckCode::F541), "F541" => Ok(CheckCode::F541),
"F601" => Ok(CheckCode::F601), "F601" => Ok(CheckCode::F601),
"F602" => Ok(CheckCode::F602), "F602" => Ok(CheckCode::F602),
@ -92,6 +94,7 @@ impl CheckCode {
CheckCode::E902 => "E902", CheckCode::E902 => "E902",
CheckCode::F401 => "F401", CheckCode::F401 => "F401",
CheckCode::F403 => "F403", CheckCode::F403 => "F403",
CheckCode::F404 => "F404",
CheckCode::F541 => "F541", CheckCode::F541 => "F541",
CheckCode::F601 => "F601", CheckCode::F601 => "F601",
CheckCode::F602 => "F602", CheckCode::F602 => "F602",
@ -127,6 +130,7 @@ impl CheckCode {
CheckCode::E902 => &LintSource::FileSystem, CheckCode::E902 => &LintSource::FileSystem,
CheckCode::F401 => &LintSource::AST, CheckCode::F401 => &LintSource::AST,
CheckCode::F403 => &LintSource::AST, CheckCode::F403 => &LintSource::AST,
CheckCode::F404 => &LintSource::AST,
CheckCode::F541 => &LintSource::AST, CheckCode::F541 => &LintSource::AST,
CheckCode::F601 => &LintSource::AST, CheckCode::F601 => &LintSource::AST,
CheckCode::F602 => &LintSource::AST, CheckCode::F602 => &LintSource::AST,
@ -173,6 +177,7 @@ pub enum CheckKind {
IOError(String), IOError(String),
IfTuple, IfTuple,
ImportStarUsage, ImportStarUsage,
LateFutureImport,
LineTooLong, LineTooLong,
ModuleImportNotAtTopOfFile, ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral, MultiValueRepeatedKeyLiteral,
@ -207,6 +212,7 @@ impl CheckKind {
CheckKind::IOError(_) => "IOError", CheckKind::IOError(_) => "IOError",
CheckKind::IfTuple => "IfTuple", CheckKind::IfTuple => "IfTuple",
CheckKind::ImportStarUsage => "ImportStarUsage", CheckKind::ImportStarUsage => "ImportStarUsage",
CheckKind::LateFutureImport => "LateFutureImport",
CheckKind::LineTooLong => "LineTooLong", CheckKind::LineTooLong => "LineTooLong",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda", CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile", CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
@ -243,6 +249,7 @@ impl CheckKind {
CheckKind::IOError(_) => &CheckCode::E902, CheckKind::IOError(_) => &CheckCode::E902,
CheckKind::IfTuple => &CheckCode::F634, CheckKind::IfTuple => &CheckCode::F634,
CheckKind::ImportStarUsage => &CheckCode::F403, CheckKind::ImportStarUsage => &CheckCode::F403,
CheckKind::LateFutureImport => &CheckCode::F404,
CheckKind::LineTooLong => &CheckCode::E501, CheckKind::LineTooLong => &CheckCode::E501,
CheckKind::DoNotAssignLambda => &CheckCode::E731, CheckKind::DoNotAssignLambda => &CheckCode::E731,
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741, CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
@ -288,6 +295,9 @@ impl CheckKind {
} }
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(), CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(), CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
CheckKind::LateFutureImport => {
"from __future__ imports must occur at the beginning of the file".to_string()
}
CheckKind::LineTooLong => "Line too long".to_string(), CheckKind::LineTooLong => "Line too long".to_string(),
CheckKind::DoNotAssignLambda => { CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string() "Do not assign a lambda expression, use a def".to_string()
@ -368,23 +378,24 @@ impl CheckKind {
/// Whether the check kind is (potentially) fixable. /// Whether the check kind is (potentially) fixable.
pub fn fixable(&self) -> bool { pub fn fixable(&self) -> bool {
match self { match self {
CheckKind::AmbiguousVariableName(_) => false,
CheckKind::AssertTuple => false, CheckKind::AssertTuple => false,
CheckKind::DefaultExceptNotLast => false, CheckKind::DefaultExceptNotLast => false,
CheckKind::DoNotAssignLambda => false,
CheckKind::DuplicateArgumentName => false, CheckKind::DuplicateArgumentName => false,
CheckKind::FStringMissingPlaceholders => false, CheckKind::FStringMissingPlaceholders => false,
CheckKind::IOError(_) => false, CheckKind::IOError(_) => false,
CheckKind::IfTuple => false, CheckKind::IfTuple => false,
CheckKind::ImportStarUsage => false, CheckKind::ImportStarUsage => false,
CheckKind::DoNotAssignLambda => false, CheckKind::LateFutureImport => false,
CheckKind::AmbiguousVariableName(_) => false,
CheckKind::LineTooLong => false, CheckKind::LineTooLong => false,
CheckKind::ModuleImportNotAtTopOfFile => false, CheckKind::ModuleImportNotAtTopOfFile => false,
CheckKind::MultiValueRepeatedKeyLiteral => false, CheckKind::MultiValueRepeatedKeyLiteral => false,
CheckKind::MultiValueRepeatedKeyVariable(_) => false, CheckKind::MultiValueRepeatedKeyVariable(_) => false,
CheckKind::NoAssertEquals => true, CheckKind::NoAssertEquals => true,
CheckKind::NoneComparison(_) => false,
CheckKind::NotInTest => false, CheckKind::NotInTest => false,
CheckKind::NotIsTest => false, CheckKind::NotIsTest => false,
CheckKind::NoneComparison(_) => false,
CheckKind::RaiseNotImplemented => false, CheckKind::RaiseNotImplemented => false,
CheckKind::ReturnOutsideFunction => false, CheckKind::ReturnOutsideFunction => false,
CheckKind::TooManyExpressionsInStarredAssignment => false, CheckKind::TooManyExpressionsInStarredAssignment => false,

View File

@ -509,6 +509,32 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn f404() -> Result<()> {
let mut actual = check_path(
Path::new("./resources/test/fixtures/F404.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F404]),
},
&fixer::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::LateFutureImport,
location: Location::new(7, 1),
fix: None,
}];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test] #[test]
fn f541() -> Result<()> { fn f541() -> Result<()> {
let mut actual = check_path( let mut actual = check_path(

View File

@ -270,6 +270,7 @@ other-attribute = 1
CheckCode::E902, CheckCode::E902,
CheckCode::F401, CheckCode::F401,
CheckCode::F403, CheckCode::F403,
CheckCode::F404,
CheckCode::F541, CheckCode::F541,
CheckCode::F601, CheckCode::F601,
CheckCode::F602, CheckCode::F602,