11"""A linter that checks test docstrings for the arrange/act/assert structure."""
22
3+ from __future__ import annotations
4+
35import argparse
46import ast
57import re
8+ import sys
69from functools import wraps
710from pathlib import Path
8- from typing import Callable , Iterable , List , NamedTuple , Optional , Tuple , Type
11+ from typing import Callable , Iterator , NamedTuple
12+
13+ # Can't cover both paths of a conditional import
14+ if sys .version_info < (3 , 10 ):
15+ from typing_extensions import ParamSpec # pragma: nocover
16+ else :
17+ from typing import ParamSpec # pragma: nocover
918
1019from flake8 .options .manager import OptionManager
1120
@@ -71,12 +80,15 @@ class Section(NamedTuple):
7180 index_ : int
7281 name : str
7382 description : str
74- next_section_name : Optional [str ]
83+ next_section_name : str | None
84+
85+
86+ AIMPPParamSpec = ParamSpec ("AIMPPParamSpec" )
7587
7688
7789def _append_invalid_msg_prefix_postfix (
78- func : Callable [..., Optional [ str ] ]
79- ) -> Callable [..., Optional [ str ] ]:
90+ func : Callable [AIMPPParamSpec , str | None ]
91+ ) -> Callable [AIMPPParamSpec , str | None ]:
8092 """Add the code prefix and invalid message postfix to the return value.
8193
8294 Args:
@@ -87,7 +99,7 @@ def _append_invalid_msg_prefix_postfix(
8799 """
88100
89101 @wraps (func )
90- def wrapper (* args , ** kwargs ) :
102+ def wrapper (* args : AIMPPParamSpec . args , ** kwargs : AIMPPParamSpec . kwargs ) -> str | None :
91103 """Wrap the function."""
92104 if (return_value := func (* args , ** kwargs )) is None :
93105 return None
@@ -98,7 +110,7 @@ def wrapper(*args, **kwargs):
98110
99111def _section_start_problem_message (
100112 line : str , section : Section , col_offset : int , section_prefix : str
101- ) -> Optional [ str ] :
113+ ) -> str | None :
102114 """Check the first line of a section.
103115
104116 Args:
@@ -136,7 +148,7 @@ def _section_start_problem_message(
136148 return None
137149
138150
139- def _next_section_start (line : str , next_section_name : Optional [ str ] , section_prefix : str ) -> bool :
151+ def _next_section_start (line : str , next_section_name : str | None , section_prefix : str ) -> bool :
140152 """Detect whether the line is the start of the next section.
141153
142154 The next section is defined to be either that the line starts with the next section name after
@@ -165,11 +177,11 @@ def _next_section_start(line: str, next_section_name: Optional[str], section_pre
165177
166178def _remaining_description_problem_message (
167179 section : Section ,
168- docstring_lines : List [str ],
180+ docstring_lines : list [str ],
169181 section_prefix : str ,
170182 description_prefix : str ,
171183 indent_size : int ,
172- ) -> Tuple [ Optional [ str ] , int ]:
184+ ) -> tuple [ str | None , int ]:
173185 """Check the remaining description of a section after the first line.
174186
175187 Args:
@@ -213,7 +225,7 @@ def _remaining_description_problem_message(
213225@_append_invalid_msg_prefix_postfix
214226def _docstring_problem_message (
215227 docstring : str , col_offset : int , docs_pattern : DocsPattern , indent_size : int
216- ) -> Optional [ str ] :
228+ ) -> str | None :
217229 """Get the problem message for a docstring.
218230
219231 Args:
@@ -296,7 +308,7 @@ class Visitor(ast.NodeVisitor):
296308 problems: All the problems that were encountered.
297309 """
298310
299- problems : List [Problem ]
311+ problems : list [Problem ]
300312 _test_docs_pattern : DocsPattern
301313 _test_function_pattern : str
302314 _indent_size : int
@@ -456,7 +468,7 @@ def parse_options(cls, options: argparse.Namespace) -> None: # pragma: nocover
456468 getattr (options , _cli_arg_name_to_attr (INDENT_SIZE_ARN_NAME ), None ) or cls ._indent_size
457469 )
458470
459- def run (self ) -> Iterable [ Tuple [int , int , str , Type ["Plugin" ]]]:
471+ def run (self ) -> Iterator [ tuple [int , int , str , type ["Plugin" ]]]:
460472 """Lint a file.
461473
462474 Yields:
0 commit comments