Implement flake8-annotations (#625)

This commit is contained in:
Charlie Marsh 2022-11-06 17:25:49 -05:00 committed by GitHub
parent 050f34dd25
commit 99d9aa61bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1987 additions and 213 deletions

View file

@ -52,8 +52,9 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
8. [flake8-builtins](#flake8-builtins) 8. [flake8-builtins](#flake8-builtins)
9. [flake8-print](#flake8-print) 9. [flake8-print](#flake8-print)
10. [flake8-quotes](#flake8-quotes) 10. [flake8-quotes](#flake8-quotes)
11. [Ruff-specific rules](#ruff-specific-rules) 11. [flake8-annotations](#flake8-annotations)
12. [Meta rules](#meta-rules) 12. [Ruff-specific rules](#ruff-specific-rules)
13. [Meta rules](#meta-rules)
5. [Editor Integrations](#editor-integrations) 5. [Editor Integrations](#editor-integrations)
6. [FAQ](#faq) 6. [FAQ](#faq)
7. [Development](#development) 7. [Development](#development)
@ -523,6 +524,23 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | | | Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | | | Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
### flake8-annotations
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*args` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**kwargs` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `self` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `cls` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod | |
### Ruff-specific rules ### Ruff-specific rules
| Code | Name | Message | Fix | | Code | Name | Message | Fix |
@ -605,6 +623,7 @@ including:
- [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34)
@ -627,6 +646,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use ruff::flake8_quotes::settings::Quote; use ruff::flake8_quotes::settings::Quote;
use ruff::settings::options::Options; use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject; use ruff::settings::pyproject::Pyproject;
use ruff::{flake8_quotes, pep8_naming}; use ruff::{flake8_annotations, flake8_quotes, pep8_naming};
use crate::plugin::Plugin; use crate::plugin::Plugin;
use crate::{parser, plugin}; use crate::{parser, plugin};
@ -39,6 +39,7 @@ pub fn convert(
// Parse each supported option. // Parse each supported option.
let mut options: Options = Default::default(); let mut options: Options = Default::default();
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default(); let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default(); let mut pep8_naming: pep8_naming::settings::Options = Default::default();
for (key, value) in flake8 { for (key, value) in flake8 {
@ -78,6 +79,25 @@ pub fn convert(
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"), Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
} }
} }
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.suppress_none_returning = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"suppress-dummy-args" | "suppress_dummy_args" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.suppress_dummy_args = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"mypy-init-return" | "mypy_init_return" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.mypy_init_return = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-quotes // flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() { "quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single), "'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
@ -94,10 +114,9 @@ pub fn convert(
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single), "\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
_ => eprintln!("Unexpected '{key}' value: {value}"), _ => eprintln!("Unexpected '{key}' value: {value}"),
}, },
"avoid-escape" | "avoid_escape" => match value.trim() { "avoid-escape" | "avoid_escape" => match parser::parse_bool(value.as_ref()) {
"true" => flake8_quotes.avoid_escape = Some(true), Ok(bool) => flake8_quotes.avoid_escape = Some(bool),
"false" => flake8_quotes.avoid_escape = Some(false), Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
_ => eprintln!("Unexpected '{key}' value: {value}"),
}, },
// pep8-naming // pep8-naming
"ignore-names" | "ignore_names" => { "ignore-names" | "ignore_names" => {
@ -166,6 +185,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}); });
@ -195,6 +215,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}); });
@ -224,6 +245,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}); });
@ -253,6 +275,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}); });
@ -282,6 +305,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options { flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single), inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None, multiline_quotes: None,
@ -354,6 +378,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}); });
@ -384,6 +409,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options { flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single), inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None, multiline_quotes: None,

View file

@ -37,6 +37,15 @@ pub fn parse_strings(value: &str) -> Vec<String> {
.collect() .collect()
} }
/// Parse a boolean.
pub fn parse_bool(value: &str) -> Result<bool> {
match value.trim() {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(anyhow::anyhow!("Unexpected boolean value: {value}")),
}
}
#[derive(Debug)] #[derive(Debug)]
struct Token { struct Token {
token_name: TokenType, token_name: TokenType,

View file

@ -12,6 +12,7 @@ pub enum Plugin {
Flake8Docstrings, Flake8Docstrings,
Flake8Print, Flake8Print,
Flake8Quotes, Flake8Quotes,
Flake8Annotations,
PEP8Naming, PEP8Naming,
Pyupgrade, Pyupgrade,
} }
@ -27,6 +28,7 @@ impl FromStr for Plugin {
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings), "flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-print" => Ok(Plugin::Flake8Print), "flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes), "flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"pep8-naming" => Ok(Plugin::PEP8Naming), "pep8-naming" => Ok(Plugin::PEP8Naming),
"pyupgrade" => Ok(Plugin::Pyupgrade), "pyupgrade" => Ok(Plugin::Pyupgrade),
_ => Err(anyhow!("Unknown plugin: {}", string)), _ => Err(anyhow!("Unknown plugin: {}", string)),
@ -58,6 +60,7 @@ impl Plugin {
} }
Plugin::Flake8Print => vec![CheckCodePrefix::T], Plugin::Flake8Print => vec![CheckCodePrefix::T],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q], Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::PEP8Naming => vec![CheckCodePrefix::N], Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::U], Plugin::Pyupgrade => vec![CheckCodePrefix::U],
} }
@ -262,6 +265,31 @@ pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
"avoid-escape" | "avoid_escape" => { "avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes); plugins.insert(Plugin::Flake8Quotes);
} }
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
plugins.insert(Plugin::Flake8Annotations);
}
"suppress-dummy-args" | "suppress_dummy_args" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-untyped-defs" | "allow_untyped_defs" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-untyped-nested" | "allow_untyped_nested" => {
plugins.insert(Plugin::Flake8Annotations);
}
"mypy-init-return" | "mypy_init_return" => {
plugins.insert(Plugin::Flake8Annotations);
}
"dispatch-decorators" | "dispatch_decorators" => {
plugins.insert(Plugin::Flake8Annotations);
}
"overload-decorators" | "overload_decorators" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// pep8-naming // pep8-naming
"ignore-names" | "ignore_names" => { "ignore-names" | "ignore_names" => {
plugins.insert(Plugin::PEP8Naming); plugins.insert(Plugin::PEP8Naming);

5
foo.py Normal file
View file

@ -0,0 +1,5 @@
def f():
class A:
return 1
f()

View file

@ -0,0 +1,33 @@
# Error
def foo(a, b):
pass
# Error
def foo(a: int, b):
pass
# Error
def foo(a: int, b) -> int:
pass
# Error
def foo(a: int, b: int):
pass
# Error
def foo():
pass
# OK
def foo(a: int, b: int) -> int:
pass
# OK
def foo() -> int:
pass

View file

@ -0,0 +1,41 @@
"""Test case expected to be run with `mypy_init_return = True`."""
# Error
class Foo:
def __init__(self):
...
# Error
class Foo:
def __init__(self, foo):
...
# OK
class Foo:
def __init__(self, foo) -> None:
...
# OK
class Foo:
def __init__(self) -> None:
...
# OK
class Foo:
def __init__(self, foo: int):
...
# OK
class Foo:
def __init__(self, foo: int) -> None:
...
# Error
def __init__(self, foo: int):
...

View file

@ -0,0 +1,26 @@
"""Test case expected to be run with `suppress_dummy_args = True`."""
# OK
def foo(_) -> None:
...
# OK
def foo(*_) -> None:
...
# OK
def foo(**_) -> None:
...
# OK
def foo(a: int, _) -> None:
...
# OK
def foo() -> None:
def bar(_) -> None:
...

View file

@ -0,0 +1,55 @@
"""Test case expected to be run with `suppress_none_returning = True`."""
# OK
def foo():
a = 2 + 2
# OK
def foo():
return
# OK
def foo():
return None
# OK
def foo():
a = 2 + 2
if a == 4:
return
else:
return
# OK
def foo():
a = 2 + 2
if a == 4:
return None
else:
return
# OK
def foo():
def bar() -> bool:
return True
bar()
# Error
def foo():
return True
# Error
def foo():
a = 2 + 2
if a == 4:
return True
else:
return

View file

@ -32,8 +32,8 @@ use crate::settings::Settings;
use crate::source_code_locator::SourceCodeLocator; use crate::source_code_locator::SourceCodeLocator;
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope}; use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
use crate::{ use crate::{
docstrings, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print, pep8_naming, docstrings, flake8_annotations, flake8_bugbear, flake8_builtins, flake8_comprehensions,
pycodestyle, pydocstyle, pyflakes, pyupgrade, flake8_print, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade,
}; };
const GLOBAL_SCOPE_INDEX: usize = 0; const GLOBAL_SCOPE_INDEX: usize = 0;
@ -57,8 +57,8 @@ pub struct Checker<'a> {
pub(crate) locator: &'a SourceCodeLocator<'a>, pub(crate) locator: &'a SourceCodeLocator<'a>,
// Computed checks. // Computed checks.
checks: Vec<Check>, checks: Vec<Check>,
// Docstring tracking. // Function and class definition tracking (e.g., for docstring enforcement).
docstrings: Vec<(Definition<'a>, Visibility)>, definitions: Vec<(Definition<'a>, Visibility)>,
// Edit tracking. // Edit tracking.
// TODO(charlie): Instead of exposing deletions, wrap in a public API. // TODO(charlie): Instead of exposing deletions, wrap in a public API.
pub(crate) deletions: BTreeSet<usize>, pub(crate) deletions: BTreeSet<usize>,
@ -100,7 +100,7 @@ impl<'a> Checker<'a> {
path, path,
locator, locator,
checks: Default::default(), checks: Default::default(),
docstrings: Default::default(), definitions: Default::default(),
deletions: Default::default(), deletions: Default::default(),
parents: Default::default(), parents: Default::default(),
parent_stack: Default::default(), parent_stack: Default::default(),
@ -834,7 +834,8 @@ where
&Documentable::Function, &Documentable::Function,
); );
let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Function); let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Function);
self.docstrings.push((definition, scope.visibility.clone())); self.definitions
.push((definition, scope.visibility.clone()));
self.visible_scope = scope; self.visible_scope = scope;
self.deferred_functions.push(( self.deferred_functions.push((
@ -852,7 +853,8 @@ where
&Documentable::Class, &Documentable::Class,
); );
let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Class); let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Class);
self.docstrings.push((definition, scope.visibility.clone())); self.definitions
.push((definition, scope.visibility.clone()));
self.visible_scope = scope; self.visible_scope = scope;
for stmt in body { for stmt in body {
@ -2105,7 +2107,7 @@ impl<'a> Checker<'a> {
'b: 'a, 'b: 'a,
{ {
let docstring = docstrings::extraction::docstring_from(python_ast); let docstring = docstrings::extraction::docstring_from(python_ast);
self.docstrings.push(( self.definitions.push((
Definition { Definition {
kind: if self.path.ends_with("__init__.py") { kind: if self.path.ends_with("__init__.py") {
DefinitionKind::Package DefinitionKind::Package
@ -2354,68 +2356,86 @@ impl<'a> Checker<'a> {
} }
} }
fn check_docstrings(&mut self) { fn check_definitions(&mut self) {
while let Some((docstring, visibility)) = self.docstrings.pop() { while let Some((definition, visibility)) = self.definitions.pop() {
if !pydocstyle::plugins::not_empty(self, &docstring) { // flake8-annotations
if self.settings.enabled.contains(&CheckCode::ANN001)
|| self.settings.enabled.contains(&CheckCode::ANN002)
|| self.settings.enabled.contains(&CheckCode::ANN003)
|| self.settings.enabled.contains(&CheckCode::ANN201)
|| self.settings.enabled.contains(&CheckCode::ANN202)
|| self.settings.enabled.contains(&CheckCode::ANN001)
|| self.settings.enabled.contains(&CheckCode::ANN002)
|| self.settings.enabled.contains(&CheckCode::ANN003)
|| self.settings.enabled.contains(&CheckCode::ANN101)
|| self.settings.enabled.contains(&CheckCode::ANN102)
|| self.settings.enabled.contains(&CheckCode::ANN201)
|| self.settings.enabled.contains(&CheckCode::ANN202)
{
flake8_annotations::plugins::definition(self, &definition, &visibility);
}
// pydocstyle
if !pydocstyle::plugins::not_empty(self, &definition) {
continue; continue;
} }
if !pydocstyle::plugins::not_missing(self, &docstring, &visibility) { if !pydocstyle::plugins::not_missing(self, &definition, &visibility) {
continue; continue;
} }
if self.settings.enabled.contains(&CheckCode::D200) { if self.settings.enabled.contains(&CheckCode::D200) {
pydocstyle::plugins::one_liner(self, &docstring); pydocstyle::plugins::one_liner(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D201) if self.settings.enabled.contains(&CheckCode::D201)
|| self.settings.enabled.contains(&CheckCode::D202) || self.settings.enabled.contains(&CheckCode::D202)
{ {
pydocstyle::plugins::blank_before_after_function(self, &docstring); pydocstyle::plugins::blank_before_after_function(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D203) if self.settings.enabled.contains(&CheckCode::D203)
|| self.settings.enabled.contains(&CheckCode::D204) || self.settings.enabled.contains(&CheckCode::D204)
|| self.settings.enabled.contains(&CheckCode::D211) || self.settings.enabled.contains(&CheckCode::D211)
{ {
pydocstyle::plugins::blank_before_after_class(self, &docstring); pydocstyle::plugins::blank_before_after_class(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D205) { if self.settings.enabled.contains(&CheckCode::D205) {
pydocstyle::plugins::blank_after_summary(self, &docstring); pydocstyle::plugins::blank_after_summary(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D206) if self.settings.enabled.contains(&CheckCode::D206)
|| self.settings.enabled.contains(&CheckCode::D207) || self.settings.enabled.contains(&CheckCode::D207)
|| self.settings.enabled.contains(&CheckCode::D208) || self.settings.enabled.contains(&CheckCode::D208)
{ {
pydocstyle::plugins::indent(self, &docstring); pydocstyle::plugins::indent(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D209) { if self.settings.enabled.contains(&CheckCode::D209) {
pydocstyle::plugins::newline_after_last_paragraph(self, &docstring); pydocstyle::plugins::newline_after_last_paragraph(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D210) { if self.settings.enabled.contains(&CheckCode::D210) {
pydocstyle::plugins::no_surrounding_whitespace(self, &docstring); pydocstyle::plugins::no_surrounding_whitespace(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D212) if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D213) || self.settings.enabled.contains(&CheckCode::D213)
{ {
pydocstyle::plugins::multi_line_summary_start(self, &docstring); pydocstyle::plugins::multi_line_summary_start(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D300) { if self.settings.enabled.contains(&CheckCode::D300) {
pydocstyle::plugins::triple_quotes(self, &docstring); pydocstyle::plugins::triple_quotes(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D400) { if self.settings.enabled.contains(&CheckCode::D400) {
pydocstyle::plugins::ends_with_period(self, &docstring); pydocstyle::plugins::ends_with_period(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D402) { if self.settings.enabled.contains(&CheckCode::D402) {
pydocstyle::plugins::no_signature(self, &docstring); pydocstyle::plugins::no_signature(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D403) { if self.settings.enabled.contains(&CheckCode::D403) {
pydocstyle::plugins::capitalized(self, &docstring); pydocstyle::plugins::capitalized(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D404) { if self.settings.enabled.contains(&CheckCode::D404) {
pydocstyle::plugins::starts_with_this(self, &docstring); pydocstyle::plugins::starts_with_this(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D415) { if self.settings.enabled.contains(&CheckCode::D415) {
pydocstyle::plugins::ends_with_punctuation(self, &docstring); pydocstyle::plugins::ends_with_punctuation(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D418) { if self.settings.enabled.contains(&CheckCode::D418) {
pydocstyle::plugins::if_needed(self, &docstring); pydocstyle::plugins::if_needed(self, &definition);
} }
if self.settings.enabled.contains(&CheckCode::D212) if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D214) || self.settings.enabled.contains(&CheckCode::D214)
@ -2433,7 +2453,7 @@ impl<'a> Checker<'a> {
|| self.settings.enabled.contains(&CheckCode::D416) || self.settings.enabled.contains(&CheckCode::D416)
|| self.settings.enabled.contains(&CheckCode::D417) || self.settings.enabled.contains(&CheckCode::D417)
{ {
pydocstyle::plugins::sections(self, &docstring); pydocstyle::plugins::sections(self, &definition);
} }
} }
} }
@ -2512,7 +2532,7 @@ pub fn check_ast(
checker.check_dead_scopes(); checker.check_dead_scopes();
// Check docstrings. // Check docstrings.
checker.check_docstrings(); checker.check_definitions();
checker.checks checker.checks
} }

View file

@ -115,6 +115,17 @@ pub enum CheckCode {
Q001, Q001,
Q002, Q002,
Q003, Q003,
// flake8-annotations
ANN001,
ANN002,
ANN003,
ANN101,
ANN102,
ANN201,
ANN202,
ANN204,
ANN205,
ANN206,
// pyupgrade // pyupgrade
U001, U001,
U002, U002,
@ -205,6 +216,7 @@ pub enum CheckCategory {
Flake8Builtins, Flake8Builtins,
Flake8Print, Flake8Print,
Flake8Quotes, Flake8Quotes,
Flake8Annotations,
Ruff, Ruff,
Meta, Meta,
} }
@ -219,6 +231,7 @@ impl CheckCategory {
CheckCategory::Flake8Comprehensions => "flake8-comprehensions", CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Print => "flake8-print", CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes", CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Pyupgrade => "pyupgrade", CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle", CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::PEP8Naming => "pep8-naming", CheckCategory::PEP8Naming => "pep8-naming",
@ -242,6 +255,9 @@ impl CheckCategory {
} }
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"), CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"), CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"), CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"), CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"), CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
@ -357,6 +373,17 @@ pub enum CheckKind {
BadQuotesMultilineString(Quote), BadQuotesMultilineString(Quote),
BadQuotesDocstring(Quote), BadQuotesDocstring(Quote),
AvoidQuoteEscape, AvoidQuoteEscape,
// flake8-annotations
MissingTypeFunctionArgument,
MissingTypeArgs,
MissingTypeKwargs,
MissingTypeSelf,
MissingTypeCls,
MissingReturnTypePublicFunction,
MissingReturnTypePrivateFunction,
MissingReturnTypeMagicMethod,
MissingReturnTypeStaticMethod,
MissingReturnTypeClassMethod,
// pyupgrade // pyupgrade
TypeOfPrimitive(Primitive), TypeOfPrimitive(Primitive),
UnnecessaryAbspath, UnnecessaryAbspath,
@ -565,6 +592,17 @@ impl CheckCode {
CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double), CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double),
CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double), CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double),
CheckCode::Q003 => CheckKind::AvoidQuoteEscape, CheckCode::Q003 => CheckKind::AvoidQuoteEscape,
// flake8-annotations
CheckCode::ANN001 => CheckKind::MissingTypeFunctionArgument,
CheckCode::ANN002 => CheckKind::MissingTypeArgs,
CheckCode::ANN003 => CheckKind::MissingTypeKwargs,
CheckCode::ANN101 => CheckKind::MissingTypeSelf,
CheckCode::ANN102 => CheckKind::MissingTypeCls,
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction,
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction,
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod,
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod,
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod,
// pyupgrade // pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType, CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath, CheckCode::U002 => CheckKind::UnnecessaryAbspath,
@ -747,6 +785,16 @@ impl CheckCode {
CheckCode::Q001 => CheckCategory::Flake8Quotes, CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes, CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes, CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::U001 => CheckCategory::Pyupgrade, CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U002 => CheckCategory::Pyupgrade, CheckCode::U002 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade, CheckCode::U003 => CheckCategory::Pyupgrade,
@ -915,6 +963,17 @@ impl CheckKind {
CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001, CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001,
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002, CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
CheckKind::AvoidQuoteEscape => &CheckCode::Q003, CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
// flake8-annotations
CheckKind::MissingTypeFunctionArgument => &CheckCode::ANN001,
CheckKind::MissingTypeArgs => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs => &CheckCode::ANN003,
CheckKind::MissingTypeSelf => &CheckCode::ANN101,
CheckKind::MissingTypeCls => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction => &CheckCode::ANN202,
CheckKind::MissingReturnTypeMagicMethod => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod => &CheckCode::ANN206,
// pyupgrade // pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003, CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002, CheckKind::UnnecessaryAbspath => &CheckCode::U002,
@ -1300,6 +1359,33 @@ impl CheckKind {
CheckKind::AvoidQuoteEscape => { CheckKind::AvoidQuoteEscape => {
"Change outer quotes to avoid escaping inner quotes".to_string() "Change outer quotes to avoid escaping inner quotes".to_string()
} }
// flake8-annotations
CheckKind::MissingTypeFunctionArgument => {
"Missing type annotation for function argument".to_string()
}
CheckKind::MissingTypeArgs => "Missing type annotation for `*args`".to_string(),
CheckKind::MissingTypeKwargs => "Missing type annotation for `**kwargs`".to_string(),
CheckKind::MissingTypeSelf => {
"Missing type annotation for `self` in method".to_string()
}
CheckKind::MissingTypeCls => {
"Missing type annotation for `cls` in classmethod".to_string()
}
CheckKind::MissingReturnTypePublicFunction => {
"Missing return type annotation for public function".to_string()
}
CheckKind::MissingReturnTypePrivateFunction => {
"Missing return type annotation for private function".to_string()
}
CheckKind::MissingReturnTypeMagicMethod => {
"Missing return type annotation for magic method".to_string()
}
CheckKind::MissingReturnTypeStaticMethod => {
"Missing return type annotation for staticmethod".to_string()
}
CheckKind::MissingReturnTypeClassMethod => {
"Missing return type annotation for classmethod".to_string()
}
// pyupgrade // pyupgrade
CheckKind::TypeOfPrimitive(primitive) => { CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin()) format!("Use `{}` instead of `type(...)`", primitive.builtin())

View file

@ -13,6 +13,23 @@ pub enum CheckCodePrefix {
A001, A001,
A002, A002,
A003, A003,
ANN,
ANN0,
ANN00,
ANN001,
ANN002,
ANN003,
ANN1,
ANN10,
ANN101,
ANN102,
ANN2,
ANN20,
ANN201,
ANN202,
ANN204,
ANN205,
ANN206,
B, B,
B0, B0,
B00, B00,
@ -257,6 +274,46 @@ impl CheckCodePrefix {
CheckCodePrefix::A001 => vec![CheckCode::A001], CheckCodePrefix::A001 => vec![CheckCode::A001],
CheckCodePrefix::A002 => vec![CheckCode::A002], CheckCodePrefix::A002 => vec![CheckCode::A002],
CheckCodePrefix::A003 => vec![CheckCode::A003], CheckCodePrefix::A003 => vec![CheckCode::A003],
CheckCodePrefix::ANN => vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN0 => vec![CheckCode::ANN001, CheckCode::ANN002, CheckCode::ANN003],
CheckCodePrefix::ANN00 => vec![CheckCode::ANN001, CheckCode::ANN002, CheckCode::ANN003],
CheckCodePrefix::ANN001 => vec![CheckCode::ANN001],
CheckCodePrefix::ANN002 => vec![CheckCode::ANN002],
CheckCodePrefix::ANN003 => vec![CheckCode::ANN003],
CheckCodePrefix::ANN1 => vec![CheckCode::ANN101, CheckCode::ANN102],
CheckCodePrefix::ANN10 => vec![CheckCode::ANN101, CheckCode::ANN102],
CheckCodePrefix::ANN101 => vec![CheckCode::ANN101],
CheckCodePrefix::ANN102 => vec![CheckCode::ANN102],
CheckCodePrefix::ANN2 => vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN20 => vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN201 => vec![CheckCode::ANN201],
CheckCodePrefix::ANN202 => vec![CheckCode::ANN202],
CheckCodePrefix::ANN204 => vec![CheckCode::ANN204],
CheckCodePrefix::ANN205 => vec![CheckCode::ANN205],
CheckCodePrefix::ANN206 => vec![CheckCode::ANN206],
CheckCodePrefix::B => vec![ CheckCodePrefix::B => vec![
CheckCode::B002, CheckCode::B002,
CheckCode::B003, CheckCode::B003,
@ -931,6 +988,23 @@ impl CheckCodePrefix {
CheckCodePrefix::A001 => PrefixSpecificity::Explicit, CheckCodePrefix::A001 => PrefixSpecificity::Explicit,
CheckCodePrefix::A002 => PrefixSpecificity::Explicit, CheckCodePrefix::A002 => PrefixSpecificity::Explicit,
CheckCodePrefix::A003 => PrefixSpecificity::Explicit, CheckCodePrefix::A003 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN => PrefixSpecificity::Category,
CheckCodePrefix::ANN0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN00 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN001 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN002 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN003 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN10 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN101 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN102 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN20 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN201 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN202 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN204 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN205 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN206 => PrefixSpecificity::Explicit,
CheckCodePrefix::B => PrefixSpecificity::Category, CheckCodePrefix::B => PrefixSpecificity::Category,
CheckCodePrefix::B0 => PrefixSpecificity::Hundreds, CheckCodePrefix::B0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::B00 => PrefixSpecificity::Tens, CheckCodePrefix::B00 => PrefixSpecificity::Tens,

View file

@ -0,0 +1,131 @@
pub mod plugins;
pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use rustpython_parser::lexer::LexResult;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::linter::tokenize;
use crate::{flake8_annotations, fs, linter, noqa, Settings, SourceCodeLocator};
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
let contents = fs::read_file(path)?;
let tokens: Vec<LexResult> = tokenize(&contents);
let locator = SourceCodeLocator::new(&contents);
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
linter::check_path(
path,
&contents,
tokens,
&locator,
&noqa_line_for,
settings,
autofix,
)
}
#[test]
fn defaults() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_annotations/annotation_presence.py"),
&Settings {
..Settings::for_rules(vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn suppress_dummy_args() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_annotations/suppress_dummy_args.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: true,
suppress_none_returning: false,
},
..Settings::for_rules(vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn mypy_init_return() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_annotations/mypy_init_return.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: true,
suppress_dummy_args: false,
suppress_none_returning: false,
},
..Settings::for_rules(vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn suppress_none_returning() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_annotations/suppress_none_returning.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: false,
suppress_none_returning: true,
},
..Settings::for_rules(vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View file

@ -0,0 +1,326 @@
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::{CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::visibility::Visibility;
use crate::{visibility, Check};
#[derive(Default)]
struct ReturnStatementVisitor<'a> {
returns: Vec<&'a Option<Box<Expr>>>,
}
impl<'a, 'b> Visitor<'b> for ReturnStatementVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
}
StmtKind::Return { value } => self.returns.push(value),
_ => visitor::walk_stmt(self, stmt),
}
}
}
fn is_none_returning(stmt: &Stmt) -> bool {
let mut visitor: ReturnStatementVisitor = Default::default();
for stmt in match_body(stmt) {
visitor.visit_stmt(stmt);
}
for expr in visitor.returns.into_iter().flatten() {
if !matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
return false;
}
}
true
}
fn match_args(stmt: &Stmt) -> &Arguments {
match &stmt.node {
StmtKind::FunctionDef { args, .. } | StmtKind::AsyncFunctionDef { args, .. } => args,
_ => panic!("Found non-FunctionDef in match_args"),
}
}
fn match_body(stmt: &Stmt) -> &Vec<Stmt> {
match &stmt.node {
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => body,
_ => panic!("Found non-FunctionDef in match_body"),
}
}
fn match_returns(stmt: &Stmt) -> &Option<Box<Expr>> {
match &stmt.node {
StmtKind::FunctionDef { returns, .. } | StmtKind::AsyncFunctionDef { returns, .. } => {
returns
}
_ => panic!("Found non-FunctionDef in match_returns"),
}
}
/// Generate flake8-annotation checks for a given `Definition`.
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
match &definition.kind {
DefinitionKind::Module => {}
DefinitionKind::Package => {}
DefinitionKind::Class(_) => {}
DefinitionKind::NestedClass(_) => {}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
let args = match_args(stmt);
let returns = match_returns(stmt);
// ANN001
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
{
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN001) {
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument,
Range::from_located(arg),
));
}
}
}
}
// ANN002
if let Some(arg) = &args.vararg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN002) {
checker.add_check(Check::new(
CheckKind::MissingTypeArgs,
Range::from_located(arg),
));
}
}
}
}
// ANN003
if let Some(arg) = &args.kwarg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN003) {
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs,
Range::from_located(arg),
));
}
}
}
}
// ANN201, ANN202
if returns.is_none() {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(stmt)
{
return;
}
match visibility {
Visibility::Public => {
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction,
Range::from_located(stmt),
));
}
}
Visibility::Private => {
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction,
Range::from_located(stmt),
));
}
}
}
}
}
DefinitionKind::Method(stmt) => {
let args = match_args(stmt);
let returns = match_returns(stmt);
let mut has_any_typed_arg = false;
// ANN001
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(!visibility::is_staticmethod(stmt)),
)
{
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN001) {
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument,
Range::from_located(arg),
));
}
}
} else {
has_any_typed_arg = true;
}
}
// ANN002
if let Some(arg) = &args.vararg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN002) {
checker.add_check(Check::new(
CheckKind::MissingTypeArgs,
Range::from_located(arg),
));
}
}
} else {
has_any_typed_arg = true;
}
}
// ANN003
if let Some(arg) = &args.kwarg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN003) {
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs,
Range::from_located(arg),
));
}
}
} else {
has_any_typed_arg = true;
}
}
// ANN101, ANN102
if !visibility::is_staticmethod(stmt) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN101) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls,
Range::from_located(arg),
));
}
} else {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
checker.add_check(Check::new(
CheckKind::MissingTypeSelf,
Range::from_located(arg),
));
}
}
}
}
}
// ANN201, ANN202
if returns.is_none() {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(stmt)
{
return;
}
if visibility::is_classmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN206) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod,
Range::from_located(stmt),
));
}
} else if visibility::is_staticmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod,
Range::from_located(stmt),
));
}
} else if visibility::is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod,
Range::from_located(stmt),
));
}
} else if visibility::is_init(stmt) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
if checker.settings.enabled.contains(&CheckCode::ANN204) {
if !(checker.settings.flake8_annotations.mypy_init_return
&& has_any_typed_arg)
{
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod,
Range::from_located(stmt),
));
}
}
} else {
match visibility {
Visibility::Public => {
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction,
Range::from_located(stmt),
));
}
}
Visibility::Private => {
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction,
Range::from_located(stmt),
));
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,36 @@
//! Settings for the `flake-annotations` plugin.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
/// Allow omission of a return type hint for `__init__` if at least one
/// argument is annotated.
pub mypy_init_return: Option<bool>,
/// Suppress ANN000-level errors for dummy arguments, like `_`.
pub suppress_dummy_args: Option<bool>,
/// Suppress ANN200-level errors for functions that meet one of the
/// following criteria:
/// - Contain no `return` statement
/// - Explicit `return` statement(s) all return `None` (explicitly or
/// implicitly).
pub suppress_none_returning: Option<bool>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub mypy_init_return: bool,
pub suppress_dummy_args: bool,
pub suppress_none_returning: bool,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or_default(),
suppress_none_returning: options.suppress_none_returning.unwrap_or_default(),
}
}
}

View file

@ -0,0 +1,69 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind: MissingReturnTypePublicFunction
location:
row: 2
column: 0
end_location:
row: 7
column: 0
fix: ~
- kind: MissingTypeFunctionArgument
location:
row: 2
column: 8
end_location:
row: 2
column: 9
fix: ~
- kind: MissingTypeFunctionArgument
location:
row: 2
column: 11
end_location:
row: 2
column: 12
fix: ~
- kind: MissingReturnTypePublicFunction
location:
row: 7
column: 0
end_location:
row: 12
column: 0
fix: ~
- kind: MissingTypeFunctionArgument
location:
row: 7
column: 16
end_location:
row: 7
column: 17
fix: ~
- kind: MissingTypeFunctionArgument
location:
row: 12
column: 16
end_location:
row: 12
column: 17
fix: ~
- kind: MissingReturnTypePublicFunction
location:
row: 17
column: 0
end_location:
row: 22
column: 0
fix: ~
- kind: MissingReturnTypePublicFunction
location:
row: 22
column: 0
end_location:
row: 27
column: 0
fix: ~

View file

@ -0,0 +1,29 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind: MissingReturnTypeMagicMethod
location:
row: 5
column: 4
end_location:
row: 10
column: 0
fix: ~
- kind: MissingReturnTypeMagicMethod
location:
row: 11
column: 4
end_location:
row: 16
column: 0
fix: ~
- kind: MissingReturnTypePrivateFunction
location:
row: 40
column: 0
end_location:
row: 42
column: 0
fix: ~

View file

@ -0,0 +1,6 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
[]

View file

@ -0,0 +1,21 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind: MissingReturnTypePublicFunction
location:
row: 45
column: 0
end_location:
row: 50
column: 0
fix: ~
- kind: MissingReturnTypePublicFunction
location:
row: 50
column: 0
end_location:
row: 56
column: 0
fix: ~

View file

@ -135,172 +135,3 @@ pub fn quotes(
None None
} }
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use rustpython_parser::lexer::LexResult;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::flake8_quotes::settings::Quote;
use crate::linter::tokenize;
use crate::{flake8_quotes, fs, linter, noqa, Settings, SourceCodeLocator};
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
let contents = fs::read_file(path)?;
let tokens: Vec<LexResult> = tokenize(&contents);
let locator = SourceCodeLocator::new(&contents);
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
linter::check_path(
path,
&contents,
tokens,
&locator,
&noqa_line_for,
settings,
autofix,
)
}
#[test_case(Path::new("doubles.py"))]
#[test_case(Path::new("doubles_escaped.py"))]
#[test_case(Path::new("doubles_multiline_string.py"))]
#[test_case(Path::new("doubles_noqa.py"))]
#[test_case(Path::new("doubles_wrapped.py"))]
fn doubles(path: &Path) -> Result<()> {
let snapshot = format!("doubles_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("singles.py"))]
#[test_case(Path::new("singles_escaped.py"))]
#[test_case(Path::new("singles_multiline_string.py"))]
#[test_case(Path::new("singles_noqa.py"))]
#[test_case(Path::new("singles_wrapped.py"))]
fn singles(path: &Path) -> Result<()> {
let snapshot = format!("singles_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn double_docstring(path: &Path) -> Result<()> {
let snapshot = format!("double_docstring_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn single_docstring(path: &Path) -> Result<()> {
let snapshot = format!("single_docstring_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View file

@ -1,2 +1,171 @@
pub mod checks; pub mod checks;
pub mod settings; pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use rustpython_parser::lexer::LexResult;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::flake8_quotes::settings::Quote;
use crate::linter::tokenize;
use crate::{flake8_quotes, fs, linter, noqa, Settings, SourceCodeLocator};
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
let contents = fs::read_file(path)?;
let tokens: Vec<LexResult> = tokenize(&contents);
let locator = SourceCodeLocator::new(&contents);
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
linter::check_path(
path,
&contents,
tokens,
&locator,
&noqa_line_for,
settings,
autofix,
)
}
#[test_case(Path::new("doubles.py"))]
#[test_case(Path::new("doubles_escaped.py"))]
#[test_case(Path::new("doubles_multiline_string.py"))]
#[test_case(Path::new("doubles_noqa.py"))]
#[test_case(Path::new("doubles_wrapped.py"))]
fn doubles(path: &Path) -> Result<()> {
let snapshot = format!("doubles_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("singles.py"))]
#[test_case(Path::new("singles_escaped.py"))]
#[test_case(Path::new("singles_multiline_string.py"))]
#[test_case(Path::new("singles_noqa.py"))]
#[test_case(Path::new("singles_wrapped.py"))]
fn singles(path: &Path) -> Result<()> {
let snapshot = format!("singles_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn double_docstring(path: &Path) -> Result<()> {
let snapshot = format!("double_docstring_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn single_docstring(path: &Path) -> Result<()> {
let snapshot = format!("single_docstring_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View file

@ -0,0 +1,50 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 5
column: 0
end_location:
row: 7
column: 3
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 16
column: 4
end_location:
row: 18
column: 7
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 21
column: 20
end_location:
row: 22
column: 37
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 30
column: 8
end_location:
row: 32
column: 11
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 35
column: 12
end_location:
row: 37
column: 15
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 3
column: 4
end_location:
row: 3
column: 27
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 5
column: 22
end_location:
row: 5
column: 43
fix: ~

View file

@ -0,0 +1,50 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 3
column: 4
end_location:
row: 3
column: 26
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 11
column: 4
end_location:
row: 11
column: 26
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 15
column: 38
end_location:
row: 17
column: 3
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 17
column: 4
end_location:
row: 17
column: 19
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 21
column: 4
end_location:
row: 21
column: 27
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 4
column: 0
end_location:
row: 6
column: 3
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 9
column: 0
end_location:
row: 11
column: 3
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 2
column: 0
end_location:
row: 2
column: 31
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 6
column: 0
end_location:
row: 6
column: 31
fix: ~

View file

@ -0,0 +1,32 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: double
location:
row: 1
column: 0
end_location:
row: 3
column: 3
fix: ~
- kind:
BadQuotesDocstring: double
location:
row: 14
column: 4
end_location:
row: 16
column: 7
fix: ~
- kind:
BadQuotesDocstring: double
location:
row: 26
column: 8
end_location:
row: 28
column: 11
fix: ~

View file

@ -0,0 +1,32 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: double
location:
row: 2
column: 4
end_location:
row: 2
column: 53
fix: ~
- kind:
BadQuotesDocstring: double
location:
row: 6
column: 8
end_location:
row: 6
column: 57
fix: ~
- kind:
BadQuotesDocstring: double
location:
row: 9
column: 28
end_location:
row: 9
column: 52
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: double
location:
row: 2
column: 4
end_location:
row: 2
column: 56
fix: ~
- kind:
BadQuotesDocstring: double
location:
row: 8
column: 4
end_location:
row: 10
column: 7
fix: ~

View file

@ -0,0 +1,14 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: double
location:
row: 1
column: 0
end_location:
row: 3
column: 3
fix: ~

View file

@ -0,0 +1,14 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: double
location:
row: 1
column: 0
end_location:
row: 1
column: 49
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesInlineString: single
location:
row: 1
column: 24
end_location:
row: 1
column: 45
fix: ~
- kind:
BadQuotesInlineString: single
location:
row: 2
column: 24
end_location:
row: 2
column: 46
fix: ~

View file

@ -0,0 +1,13 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind: AvoidQuoteEscape
location:
row: 1
column: 25
end_location:
row: 1
column: 47
fix: ~

View file

@ -0,0 +1,14 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 1
column: 4
end_location:
row: 3
column: 12
fix: ~

View file

@ -0,0 +1,6 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
[]

View file

@ -0,0 +1,6 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
[]

View file

@ -0,0 +1,32 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: single
location:
row: 1
column: 0
end_location:
row: 3
column: 3
fix: ~
- kind:
BadQuotesDocstring: single
location:
row: 12
column: 4
end_location:
row: 14
column: 7
fix: ~
- kind:
BadQuotesDocstring: single
location:
row: 24
column: 8
end_location:
row: 26
column: 11
fix: ~

View file

@ -0,0 +1,32 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: single
location:
row: 2
column: 4
end_location:
row: 2
column: 53
fix: ~
- kind:
BadQuotesDocstring: single
location:
row: 6
column: 8
end_location:
row: 6
column: 57
fix: ~
- kind:
BadQuotesDocstring: single
location:
row: 9
column: 28
end_location:
row: 9
column: 52
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: single
location:
row: 2
column: 4
end_location:
row: 2
column: 56
fix: ~
- kind:
BadQuotesDocstring: single
location:
row: 8
column: 4
end_location:
row: 10
column: 7
fix: ~

View file

@ -0,0 +1,14 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: single
location:
row: 1
column: 0
end_location:
row: 3
column: 3
fix: ~

View file

@ -0,0 +1,14 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesDocstring: single
location:
row: 1
column: 0
end_location:
row: 1
column: 49
fix: ~

View file

@ -0,0 +1,59 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: double
location:
row: 5
column: 0
end_location:
row: 7
column: 3
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 11
column: 20
end_location:
row: 13
column: 3
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 18
column: 4
end_location:
row: 20
column: 7
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 23
column: 20
end_location:
row: 24
column: 37
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 32
column: 8
end_location:
row: 34
column: 11
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 37
column: 12
end_location:
row: 39
column: 15
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: double
location:
row: 3
column: 4
end_location:
row: 3
column: 27
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 5
column: 22
end_location:
row: 5
column: 43
fix: ~

View file

@ -0,0 +1,50 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: double
location:
row: 3
column: 4
end_location:
row: 3
column: 26
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 11
column: 4
end_location:
row: 11
column: 26
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 15
column: 38
end_location:
row: 17
column: 3
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 17
column: 4
end_location:
row: 17
column: 19
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 21
column: 4
end_location:
row: 21
column: 27
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: double
location:
row: 4
column: 0
end_location:
row: 6
column: 3
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 9
column: 0
end_location:
row: 11
column: 3
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: double
location:
row: 2
column: 0
end_location:
row: 2
column: 31
fix: ~
- kind:
BadQuotesMultilineString: double
location:
row: 6
column: 0
end_location:
row: 6
column: 31
fix: ~

View file

@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesInlineString: double
location:
row: 1
column: 24
end_location:
row: 1
column: 45
fix: ~
- kind:
BadQuotesInlineString: double
location:
row: 2
column: 24
end_location:
row: 2
column: 46
fix: ~

View file

@ -0,0 +1,13 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind: AvoidQuoteEscape
location:
row: 1
column: 25
end_location:
row: 1
column: 47
fix: ~

View file

@ -0,0 +1,14 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: double
location:
row: 1
column: 4
end_location:
row: 3
column: 12
fix: ~

View file

@ -0,0 +1,6 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
[]

View file

@ -0,0 +1,6 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
[]

View file

@ -25,6 +25,7 @@ pub mod cli;
pub mod code_gen; pub mod code_gen;
mod cst; mod cst;
mod docstrings; mod docstrings;
pub mod flake8_annotations;
mod flake8_bugbear; mod flake8_bugbear;
mod flake8_builtins; mod flake8_builtins;
mod flake8_comprehensions; mod flake8_comprehensions;

View file

@ -13,7 +13,7 @@ use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::docstrings::helpers; use crate::docstrings::helpers;
use crate::docstrings::sections::{section_contexts, SectionContext}; use crate::docstrings::sections::{section_contexts, SectionContext};
use crate::docstrings::styles::SectionStyle; use crate::docstrings::styles::SectionStyle;
use crate::visibility::{is_init, is_magic, is_overload, is_static, Visibility}; use crate::visibility::{is_init, is_magic, is_overload, is_staticmethod, Visibility};
/// D100, D101, D102, D103, D104, D105, D106, D107 /// D100, D101, D102, D103, D104, D105, D106, D107
pub fn not_missing( pub fn not_missing(
@ -1308,7 +1308,8 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
.skip( .skip(
// If this is a non-static method, skip `cls` or `self`. // If this is a non-static method, skip `cls` or `self`.
usize::from( usize::from(
matches!(definition.kind, DefinitionKind::Method(_)) && !is_static(parent), matches!(definition.kind, DefinitionKind::Method(_))
&& !is_staticmethod(parent),
), ),
) )
.collect(); .collect();

View file

@ -12,7 +12,7 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
use crate::settings::pyproject::load_options; use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PythonVersion}; use crate::settings::types::{FilePattern, PythonVersion};
use crate::{flake8_quotes, pep8_naming}; use crate::{flake8_annotations, flake8_quotes, pep8_naming};
#[derive(Debug)] #[derive(Debug)]
pub struct Configuration { pub struct Configuration {
@ -27,6 +27,7 @@ pub struct Configuration {
pub select: Vec<CheckCodePrefix>, pub select: Vec<CheckCodePrefix>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
// Plugins // Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings, pub pep8_naming: pep8_naming::settings::Settings,
} }
@ -95,6 +96,10 @@ impl Configuration {
line_length: options.line_length.unwrap_or(88), line_length: options.line_length.unwrap_or(88),
per_file_ignores: options.per_file_ignores.unwrap_or_default(), per_file_ignores: options.per_file_ignores.unwrap_or_default(),
// Plugins // Plugins
flake8_annotations: options
.flake8_annotations
.map(flake8_annotations::settings::Settings::from_options)
.unwrap_or_default(),
flake8_quotes: options flake8_quotes: options
.flake8_quotes .flake8_quotes
.map(flake8_quotes::settings::Settings::from_options) .map(flake8_quotes::settings::Settings::from_options)

View file

@ -11,7 +11,7 @@ use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity}; use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::settings::configuration::Configuration; use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion}; use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{flake8_quotes, pep8_naming}; use crate::{flake8_annotations, flake8_quotes, pep8_naming};
pub mod configuration; pub mod configuration;
pub mod options; pub mod options;
@ -29,6 +29,7 @@ pub struct Settings {
pub per_file_ignores: Vec<PerFileIgnore>, pub per_file_ignores: Vec<PerFileIgnore>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
// Plugins // Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings, pub pep8_naming: pep8_naming::settings::Settings,
} }
@ -45,6 +46,7 @@ impl Settings {
), ),
exclude: config.exclude, exclude: config.exclude,
extend_exclude: config.extend_exclude, extend_exclude: config.extend_exclude,
flake8_annotations: config.flake8_annotations,
flake8_quotes: config.flake8_quotes, flake8_quotes: config.flake8_quotes,
line_length: config.line_length, line_length: config.line_length,
pep8_naming: config.pep8_naming, pep8_naming: config.pep8_naming,
@ -62,6 +64,7 @@ impl Settings {
line_length: 88, line_length: 88,
per_file_ignores: Default::default(), per_file_ignores: Default::default(),
target_version: PythonVersion::Py310, target_version: PythonVersion::Py310,
flake8_annotations: Default::default(),
flake8_quotes: Default::default(), flake8_quotes: Default::default(),
pep8_naming: Default::default(), pep8_naming: Default::default(),
} }
@ -76,6 +79,7 @@ impl Settings {
line_length: 88, line_length: 88,
per_file_ignores: Default::default(), per_file_ignores: Default::default(),
target_version: PythonVersion::Py310, target_version: PythonVersion::Py310,
flake8_annotations: Default::default(),
flake8_quotes: Default::default(), flake8_quotes: Default::default(),
pep8_naming: Default::default(), pep8_naming: Default::default(),
} }
@ -95,6 +99,7 @@ impl Hash for Settings {
} }
self.target_version.hash(state); self.target_version.hash(state);
// Add plugin properties in alphabetical order. // Add plugin properties in alphabetical order.
self.flake8_annotations.hash(state);
self.flake8_quotes.hash(state); self.flake8_quotes.hash(state);
self.pep8_naming.hash(state); self.pep8_naming.hash(state);
} }

View file

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
use crate::{flake8_quotes, pep8_naming}; use crate::{flake8_annotations, flake8_quotes, pep8_naming};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
@ -22,6 +22,7 @@ pub struct Options {
pub dummy_variable_rgx: Option<String>, pub dummy_variable_rgx: Option<String>,
pub target_version: Option<PythonVersion>, pub target_version: Option<PythonVersion>,
// Plugins // Plugins
pub flake8_annotations: Option<flake8_annotations::settings::Options>,
pub flake8_quotes: Option<flake8_quotes::settings::Options>, pub flake8_quotes: Option<flake8_quotes::settings::Options>,
pub pep8_naming: Option<pep8_naming::settings::Options>, pub pep8_naming: Option<pep8_naming::settings::Options>,
} }

View file

@ -143,6 +143,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}) })
@ -170,6 +171,7 @@ line-length = 79
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}) })
@ -197,6 +199,7 @@ exclude = ["foo.py"]
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}) })
@ -224,6 +227,7 @@ select = ["E501"]
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}) })
@ -252,6 +256,7 @@ ignore = ["E501"]
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: None, flake8_quotes: None,
pep8_naming: None, pep8_naming: None,
}) })
@ -326,6 +331,7 @@ other-attribute = 1
),])), ),])),
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options { flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(Quote::Single), inline_quotes: Some(Quote::Single),
multiline_quotes: Some(Quote::Double), multiline_quotes: Some(Quote::Double),

View file

@ -7,7 +7,7 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::{FilePattern, PythonVersion}; use crate::settings::types::{FilePattern, PythonVersion};
use crate::{flake8_quotes, pep8_naming, Configuration}; use crate::{flake8_annotations, flake8_quotes, pep8_naming, Configuration};
/// Struct to render user-facing exclusion patterns. /// Struct to render user-facing exclusion patterns.
#[derive(Debug)] #[derive(Debug)]
@ -46,6 +46,7 @@ pub struct UserConfiguration {
pub select: Vec<CheckCodePrefix>, pub select: Vec<CheckCodePrefix>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
// Plugins // Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings, pub pep8_naming: pep8_naming::settings::Settings,
// Non-settings exposed to the user // Non-settings exposed to the user
@ -78,6 +79,7 @@ impl UserConfiguration {
per_file_ignores: configuration.per_file_ignores, per_file_ignores: configuration.per_file_ignores,
select: configuration.select, select: configuration.select,
target_version: configuration.target_version, target_version: configuration.target_version,
flake8_annotations: configuration.flake8_annotations,
flake8_quotes: configuration.flake8_quotes, flake8_quotes: configuration.flake8_quotes,
pep8_naming: configuration.pep8_naming, pep8_naming: configuration.pep8_naming,
project_root, project_root,

View file

@ -28,13 +28,24 @@ pub struct VisibleScope {
} }
/// Returns `true` if a function is a "static method". /// Returns `true` if a function is a "static method".
pub fn is_static(stmt: &Stmt) -> bool { pub fn is_staticmethod(stmt: &Stmt) -> bool {
match &stmt.node { match &stmt.node {
StmtKind::FunctionDef { decorator_list, .. } StmtKind::FunctionDef { decorator_list, .. }
| StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list | StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list
.iter() .iter()
.any(|expr| match_name_or_attr(expr, "staticmethod")), .any(|expr| match_name_or_attr(expr, "staticmethod")),
_ => panic!("Found non-FunctionDef in is_overload"), _ => panic!("Found non-FunctionDef in is_staticmethod"),
}
}
/// Returns `true` if a function is a "class method".
pub fn is_classmethod(stmt: &Stmt) -> bool {
match &stmt.node {
StmtKind::FunctionDef { decorator_list, .. }
| StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list
.iter()
.any(|expr| match_name_or_attr(expr, "classmethod")),
_ => panic!("Found non-FunctionDef in is_classmethod"),
} }
} }