Add documentation for mccabe, isort, and flake8-annotations (#2691)

This commit is contained in:
Charlie Marsh 2023-02-09 11:56:18 -05:00 committed by GitHub
parent 8a98cfc4b8
commit 7d5fb0de8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 713 additions and 23 deletions

View file

@ -16,6 +16,25 @@ use crate::visibility;
use crate::visibility::Visibility;
define_violation!(
/// ### What it does
/// Checks that function arguments have type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the types of function arguments. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any provided arguments match expectation.
///
/// ### Example
/// ```python
/// def foo(x):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// def foo(x: int):
/// ...
/// ```
pub struct MissingTypeFunctionArgument {
pub name: String,
}
@ -29,6 +48,25 @@ impl Violation for MissingTypeFunctionArgument {
}
define_violation!(
/// ### What it does
/// Checks that function `*args` arguments have type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the types of function arguments. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any provided arguments match expectation.
///
/// ### Example
/// ```python
/// def foo(*args):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// def foo(*args: int):
/// ...
/// ```
pub struct MissingTypeArgs {
pub name: String,
}
@ -42,6 +80,25 @@ impl Violation for MissingTypeArgs {
}
define_violation!(
/// ### What it does
/// Checks that function `**kwargs` arguments have type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the types of function arguments. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any provided arguments match expectation.
///
/// ### Example
/// ```python
/// def foo(**kwargs):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// def foo(**kwargs: int):
/// ...
/// ```
pub struct MissingTypeKwargs {
pub name: String,
}
@ -55,6 +112,30 @@ impl Violation for MissingTypeKwargs {
}
define_violation!(
/// ### What it does
/// Checks that instance method `self` arguments have type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the types of function arguments. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any provided arguments match expectation.
///
/// Note that many type checkers will infer the type of `self` automatically, so this
/// annotation is not strictly necessary.
///
/// ### Example
/// ```python
/// class Foo:
/// def bar(self):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// def bar(self: "Foo"):
/// ...
/// ```
pub struct MissingTypeSelf {
pub name: String,
}
@ -68,6 +149,32 @@ impl Violation for MissingTypeSelf {
}
define_violation!(
/// ### What it does
/// Checks that class method `cls` arguments have type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the types of function arguments. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any provided arguments match expectation.
///
/// Note that many type checkers will infer the type of `cls` automatically, so this
/// annotation is not strictly necessary.
///
/// ### Example
/// ```python
/// class Foo:
/// @classmethod
/// def bar(cls):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// @classmethod
/// def bar(cls: Type["Foo"]):
/// ...
/// ```
pub struct MissingTypeCls {
pub name: String,
}
@ -81,6 +188,25 @@ impl Violation for MissingTypeCls {
}
define_violation!(
/// ### What it does
/// Checks that public functions and methods have return type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the return types of functions. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any returned values, and the types expected by callers, match expectation.
///
/// ### Example
/// ```python
/// def add(a, b):
/// return a + b
/// ```
///
/// Use instead:
/// ```python
/// def add(a: int, b: int) -> int:
/// return a + b
/// ```
pub struct MissingReturnTypePublicFunction {
pub name: String,
}
@ -94,6 +220,25 @@ impl Violation for MissingReturnTypePublicFunction {
}
define_violation!(
/// ### What it does
/// Checks that private functions and methods have return type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the return types of functions. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any returned values, and the types expected by callers, match expectation.
///
/// ### Example
/// ```python
/// def _add(a, b):
/// return a + b
/// ```
///
/// Use instead:
/// ```python
/// def _add(a: int, b: int) -> int:
/// return a + b
/// ```
pub struct MissingReturnTypePrivateFunction {
pub name: String,
}
@ -107,6 +252,38 @@ impl Violation for MissingReturnTypePrivateFunction {
}
define_violation!(
/// ### What it does
/// Checks that "special" methods, like `__init__`, `__new__`, and `__call__`, have
/// return type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the return types of functions. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any returned values, and the types expected by callers, match expectation.
///
/// Note that type checkers often allow you to omit the return type annotation for
/// `__init__` methods, as long as at least one argument has a type annotation. To
/// opt-in to this behavior, use the `mypy-init-return` setting in your `pyproject.toml`
/// or `ruff.toml` file:
///
/// ```toml
/// [tool.ruff.flake8-annotations]
/// mypy-init-return = true
/// ```
///
/// ### Example
/// ```python
/// class Foo:
/// def __init__(self, x: int):
/// self.x = x
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// def __init__(self, x: int) -> None:
/// self.x = x
/// ```
pub struct MissingReturnTypeSpecialMethod {
pub name: String,
}
@ -124,6 +301,29 @@ impl AlwaysAutofixableViolation for MissingReturnTypeSpecialMethod {
}
define_violation!(
/// ### What it does
/// Checks that static methods have return type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the return types of functions. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any returned values, and the types expected by callers, match expectation.
///
/// ### Example
/// ```python
/// class Foo:
/// @staticmethod
/// def bar():
/// return 1
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// @staticmethod
/// def bar() -> int:
/// return 1
/// ```
pub struct MissingReturnTypeStaticMethod {
pub name: String,
}
@ -137,6 +337,29 @@ impl Violation for MissingReturnTypeStaticMethod {
}
define_violation!(
/// ### What it does
/// Checks that class methods have return type annotations.
///
/// ### Why is this bad?
/// Type annotations are a good way to document the return types of functions. They also
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
/// any returned values, and the types expected by callers, match expectation.
///
/// ### Example
/// ```python
/// class Foo:
/// @classmethod
/// def bar(cls):
/// return 1
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// @classmethod
/// def bar(cls) -> int:
/// return 1
/// ```
pub struct MissingReturnTypeClassMethod {
pub name: String,
}
@ -150,6 +373,25 @@ impl Violation for MissingReturnTypeClassMethod {
}
define_violation!(
/// ### What it does
/// Checks that an expression is annotated with a more specific type than `Any`.
///
/// ### Why is this bad?
/// `Any` is a type that can be anything, and it is the default type for
/// unannotated expressions. It is better to be explicit about the type of an
/// expression, and to use `Any` only when it is really needed.
///
/// ### Example
/// ```python
/// def foo(x: Any):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// def foo(x: int):
/// ...
/// ```
pub struct DynamicallyTypedExpression {
pub name: String,
}

View file

@ -15,6 +15,26 @@ use crate::source_code::{Locator, Stylist};
use crate::violation::AlwaysAutofixableViolation;
define_violation!(
/// ### What it does
/// Adds any required imports, as specified by the user, to the top of the file.
///
/// ### Why is this bad?
/// In some projects, certain imports are required to be present in all files. For
/// example, some projects assume that `from __future__ import annotations` is enabled,
/// and thus require that import to be present in all files. Omitting a "required" import
/// (as specified by the user) can cause errors or unexpected behavior.
///
/// ### Example
/// ```python
/// import typing
/// ```
///
/// Use instead:
/// ```python
/// from __future__ import annotations
///
/// import typing
/// ```
pub struct MissingRequiredImport(pub String);
);
impl AlwaysAutofixableViolation for MissingRequiredImport {

View file

@ -19,6 +19,24 @@ use crate::source_code::{Indexer, Locator, Stylist};
use crate::violation::AlwaysAutofixableViolation;
define_violation!(
/// ### What it does
/// De-duplicates, groups, and sorts imports based on the provided `isort` settings.
///
/// ### Why is this bad?
/// Consistency is good. Use a common convention for imports to make your code
/// more readable and idiomatic.
///
/// ### Example
/// ```python
/// import pandas
/// import numpy as np
/// ```
///
/// Use instead:
/// ```python
/// import numpy as np
/// import pandas
/// ```
pub struct UnsortedImports;
);
impl AlwaysAutofixableViolation for UnsortedImports {

View file

@ -7,6 +7,43 @@ use crate::source_code::Locator;
use crate::violation::Violation;
define_violation!(
/// ### What it does
/// Checks for functions with a high `McCabe` complexity.
///
/// The `McCabe` complexity of a function is a measure of the complexity of the
/// control flow graph of the function. It is calculated by adding one to the
/// number of decision points in the function. A decision point is a place in
/// the code where the program has a choice of two or more paths to follow.
///
/// ### Why is this bad?
/// Functions with a high complexity are hard to understand and maintain.
///
/// ### Example
/// ```python
/// def foo(a, b, c):
/// if a:
/// if b:
/// if c:
/// return 1
/// else:
/// return 2
/// else:
/// return 3
/// else:
/// return 4
/// ```
///
/// Use instead:
/// ```python
/// def foo(a, b, c):
/// if not a:
/// return 4
/// if not b:
/// return 3
/// if not c:
/// return 2
/// return 1
/// ```
pub struct FunctionIsTooComplex {
pub name: String,
pub complexity: usize,

View file

@ -30,20 +30,13 @@ impl Parse for LintMeta {
fn parse(input: ParseStream) -> Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let mut in_code = false;
let mut explanation = String::new();
for attr in &attrs {
if let Some(lit) = parse_attr(["doc"], attr) {
let value = lit.value();
let line = value.strip_prefix(' ').unwrap_or(&value);
if line.starts_with("```") {
explanation += line;
explanation.push('\n');
in_code = !in_code;
} else if !(in_code && line.starts_with("# ")) {
explanation += line;
explanation.push('\n');
}
explanation.push_str(line);
explanation.push('\n');
} else {
return Err(Error::new_spanned(attr, "unexpected attribute"));
}