Skip to content

Commit c63884c

Browse files
authored
Release v3.0
2 parents e9e1110 + 8eb49ee commit c63884c

22 files changed

+265
-1123
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 2.9.1
2+
current_version = 3.0.0
33
commit = False
44

55
[bumpversion:file:README.md]

.github/workflows/lint_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
runs-on: ubuntu-latest
3636
strategy:
3737
matrix:
38-
python-version: ["3.7", "3.8", "3.9", "3.10"]
38+
python-version: ["3.8", "3.9", "3.10", "3.11"]
3939
fail-fast: false
4040

4141
steps:

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
# Changelog
22
Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (`<major>`.`<minor>`.`<patch>`)
33

4+
## [v3.0.0]
5+
### Added
6+
* Add `ANN402` for the presence of type comments
7+
### Changed
8+
* Python 3.8.1 is now the minimum supported version
9+
* Flake8 v5.0 is now the minimum supported version
10+
11+
### Removed
12+
* Remove support for [PEP 484-style](https://www.python.org/dev/peps/pep-0484/#type-comments) type comments
13+
* See: https://mail.python.org/archives/list/[email protected]/thread/66JDHQ2I3U3CPUIYA43W7SPEJLLPUETG/
14+
* See: https://github.com/python/mypy/issues/12947
15+
* Remove `ANN301`
16+
417
## [v2.9.1]
518
### Changed
619
* #144 Unpin the version ceiling for `attrs`.

README.md

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# flake8-annotations
2-
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/flake8-annotations/2.9.1?logo=python&logoColor=FFD43B)](https://pypi.org/project/flake8-annotations/)
2+
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/flake8-annotations/3.0.0?logo=python&logoColor=FFD43B)](https://pypi.org/project/flake8-annotations/)
33
[![PyPI](https://img.shields.io/pypi/v/flake8-annotations?logo=Python&logoColor=FFD43B)](https://pypi.org/project/flake8-annotations/)
44
[![PyPI - License](https://img.shields.io/pypi/l/flake8-annotations?color=magenta)](https://github.com/sco1/flake8-annotations/blob/main/LICENSE)
55
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/sco1/flake8-annotations/main.svg)](https://results.pre-commit.ci/latest/github/sco1/flake8-annotations/main)
66
[![Open in Visual Studio Code](https://img.shields.io/badge/Open%20in-VSCode.dev-blue)](https://github.dev/sco1/flake8-annotations)
77

8-
`flake8-annotations` is a plugin for [Flake8](http://flake8.pycqa.org/en/latest/) that detects the absence of [PEP 3107-style](https://www.python.org/dev/peps/pep-3107/) function annotations and [PEP 484-style](https://www.python.org/dev/peps/pep-0484/#type-comments) type comments (see: [Caveats](#Caveats-for-PEP-484-style-Type-Comments)).
8+
`flake8-annotations` is a plugin for [Flake8](http://flake8.pycqa.org/en/latest/) that detects the absence of [PEP 3107-style](https://www.python.org/dev/peps/pep-3107/) function annotations.
99

10-
What this won't do: Check variable annotations (see: [PEP 526](https://www.python.org/dev/peps/pep-0526/)), respect stub files, or replace [mypy](http://mypy-lang.org/).
10+
What this won't do: replace [mypy](http://mypy-lang.org/), check type comments (see: [PEP 484](https://peps.python.org/pep-0484/#type-comments)), check variable annotations (see: [PEP 526](https://www.python.org/dev/peps/pep-0526/)), or respect stub files.
1111

1212
## Installation
1313
Install from PyPi with your favorite `pip` invocation:
@@ -32,7 +32,7 @@ cog.out(
3232
]]] -->
3333
```bash
3434
$ flake8 --version
35-
5.0.4 (flake8-annotations: 2.9.1, mccabe: 0.7.0, pycodestyle: 2.9.1, pyflakes:2.5.0) CPython 3.10.6 on Darwin
35+
6.0.0 (flake8-annotations: 3.0.0, mccabe: 0.7.0, pycodestyle: 2.10.0, pyflakes: 3.0.1) CPython 3.11.0 on Darwin
3636
```
3737
<!-- [[[end]]] -->
3838

@@ -62,17 +62,12 @@ With the exception of `ANN4xx`-level warnings, all warnings are enabled by defau
6262
| `ANN205` | Missing return type annotation for staticmethod |
6363
| `ANN206` | Missing return type annotation for classmethod |
6464

65-
### Type Comments
66-
**Deprecation notice**: Support for type comments will be removed in `3.0`. See [this issue](https://github.com/sco1/flake8-annotations/issues/95) for more information.
67-
| ID | Description |
68-
|----------|-----------------------------------------------------------|
69-
| `ANN301` | PEP 484 disallows both type annotations and type comments |
70-
7165
### Opinionated Warnings
7266
These warnings are disabled by default.
7367
| ID | Description |
7468
|----------|------------------------------------------------------------------------|
7569
| `ANN401` | Dynamically typed expressions (typing.Any) are disallowed.<sup>2</sup> |
70+
| `ANN402` | Type comments are disallowed. |
7671

7772
Use [`extend-select`](https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-extend-ignore) to enable opinionated warnings without overriding other implicit configurations<sup>3</sup>.
7873

@@ -195,54 +190,6 @@ Will not raise linting errors for missing annotations for the arguments & return
195190

196191
Decorator(s) to treat as `typing.overload` may be specified by the [`--overload-decorators`](#--overload-decorators-liststr) configuration option.
197192

198-
## Caveats for PEP 484-style Type Comments
199-
**Deprecation notice**: Support for type comments will be removed in `3.0`. See [this issue](https://github.com/sco1/flake8-annotations/issues/95) for more information.
200-
### Mixing argument-level and function-level type comments
201-
Support is provided for mixing argument-level and function-level type comments.
202-
203-
```py
204-
def foo(
205-
arg1, # type: bool
206-
arg2, # type: bool
207-
): # type: (...) -> bool
208-
pass
209-
```
210-
211-
**Note:** If present, function-level type comments will override any argument-level type comments.
212-
213-
### Partial type comments
214-
Partially type hinted functions are supported for non-static class methods.
215-
216-
For example:
217-
218-
```py
219-
class Foo:
220-
def __init__(self):
221-
# type: () -> None
222-
...
223-
224-
def bar(self, a):
225-
# type: (int) -> int
226-
...
227-
```
228-
Will consider `bar`'s `self` argument as unannotated and use the `int` type hint for `a`.
229-
230-
Partial type comments utilizing ellipses as placeholders is also supported:
231-
232-
```py
233-
def foo(arg1, arg2):
234-
# type: (bool) -> bool
235-
pass
236-
```
237-
Will show `arg2` as missing a type hint.
238-
239-
```py
240-
def foo(arg1, arg2):
241-
# type: (..., bool) -> bool
242-
pass
243-
```
244-
Will show `arg1` as missing a type hint.
245-
246193
## Dynamic Typing Caveats
247194
Support is only provided for the following patterns:
248195
* `from typing import any; foo: Any`

flake8_annotations/__init__.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
1-
import sys
2-
3-
if sys.version_info >= (3, 8):
4-
PY_GTE_38 = True
5-
else:
6-
PY_GTE_38 = False
7-
8-
__version__ = "2.9.1"
1+
__version__ = "3.0.0"

flake8_annotations/ast_walker.py

Lines changed: 5 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,19 @@
11
from __future__ import annotations
22

3+
import ast
34
import typing as t
4-
from itertools import zip_longest
55

66
from attrs import define
77

8-
from flake8_annotations import PY_GTE_38
98
from flake8_annotations.enums import AnnotationType, ClassDecoratorType, FunctionType
109

11-
# Check if we can use the stdlib ast module instead of typed_ast; stdlib ast gains native type
12-
# comment support in Python 3.8
13-
if PY_GTE_38:
14-
import ast
15-
from ast import Ellipsis as ast_Ellipsis
16-
else:
17-
from typed_ast import ast3 as ast # type: ignore[no-redef]
18-
from typed_ast.ast3 import Ellipsis as ast_Ellipsis # type: ignore[assignment]
19-
20-
2110
AST_DECORATOR_NODES = t.Union[ast.Attribute, ast.Call, ast.Name]
2211
AST_DEF_NODES = t.Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef]
2312
AST_FUNCTION_TYPES = t.Union[ast.FunctionDef, ast.AsyncFunctionDef]
2413

2514
# The order of AST_ARG_TYPES must match Python's grammar
2615
# See: https://docs.python.org/3/library/ast.html#abstract-grammar
27-
AST_ARG_TYPES: t.Tuple[str, ...] = ("args", "vararg", "kwonlyargs", "kwarg")
28-
if PY_GTE_38:
29-
# Positional-only args introduced in Python 3.8
30-
# If posonlyargs are present, they will be before other argument types
31-
AST_ARG_TYPES = ("posonlyargs",) + AST_ARG_TYPES
16+
AST_ARG_TYPES: t.Tuple[str, ...] = ("posonlyargs", "args", "vararg", "kwonlyargs", "kwarg")
3217

3318

3419
@define(slots=True)
@@ -40,7 +25,6 @@ class Argument:
4025
col_offset: int
4126
annotation_type: AnnotationType
4227
has_type_annotation: bool = False
43-
has_3107_annotation: bool = False
4428
has_type_comment: bool = False
4529
is_dynamically_typed: bool = False
4630

@@ -59,45 +43,33 @@ def from_arg_node(cls, node: ast.arg, annotation_type_name: str) -> Argument:
5943
annotation_type = AnnotationType[annotation_type_name]
6044
new_arg = cls(node.arg, node.lineno, node.col_offset, annotation_type)
6145

62-
new_arg.has_type_annotation = False
6346
if node.annotation:
6447
new_arg.has_type_annotation = True
65-
new_arg.has_3107_annotation = True
6648

6749
if cls._is_annotated_any(node.annotation):
6850
new_arg.is_dynamically_typed = True
6951

7052
if node.type_comment:
71-
new_arg.has_type_annotation = True
7253
new_arg.has_type_comment = True
7354

74-
if cls._is_annotated_any(node.type_comment):
75-
new_arg.is_dynamically_typed = True
76-
7755
return new_arg
7856

7957
@staticmethod
80-
def _is_annotated_any(arg_expr: t.Union[ast.expr, str]) -> bool:
58+
def _is_annotated_any(arg_expr: ast.expr) -> bool:
8159
"""
8260
Check if the provided expression node is annotated with `typing.Any`.
8361
8462
Support is provided for the following patterns:
8563
* `from typing import Any; foo: Any`
8664
* `import typing; foo: typing.Any`
8765
* `import typing as <alias>; foo: <alias>.Any`
88-
89-
Type comments are also supported. Inline type comments are assumed to be passed here as
90-
`str`, and function-level type comments are assumed to be passed as `ast.expr`.
9166
"""
9267
if isinstance(arg_expr, ast.Name):
9368
if arg_expr.id == "Any":
9469
return True
9570
elif isinstance(arg_expr, ast.Attribute):
9671
if arg_expr.attr == "Any":
9772
return True
98-
elif isinstance(arg_expr, str):
99-
if arg_expr.split(".", maxsplit=1)[-1] == "Any":
100-
return True
10173

10274
return False
10375

@@ -252,19 +224,15 @@ def from_function_node(
252224
return_arg = Argument("return", def_end_lineno, def_end_col_offset, AnnotationType.RETURN)
253225
if node.returns:
254226
return_arg.has_type_annotation = True
255-
return_arg.has_3107_annotation = True
256227
new_function.is_return_annotated = True
257228

258229
if Argument._is_annotated_any(node.returns):
259230
return_arg.is_dynamically_typed = True
260231

261232
new_function.args.append(return_arg)
262233

263-
# Type comments in-line with input arguments are handled by the Argument class
264-
# If a function-level type comment is present, attempt to parse for any missed type hints
265234
if node.type_comment:
266235
new_function.has_type_comment = True
267-
new_function = cls.try_type_comment(new_function, node)
268236

269237
# Check for the presence of non-`None` returns using the special-case return node visitor
270238
return_visitor = ReturnVisitor(node)
@@ -278,10 +246,7 @@ def colon_seeker(node: AST_FUNCTION_TYPES, lines: t.List[str]) -> t.Tuple[int, i
278246
"""
279247
Find the line & column indices of the function definition's closing colon.
280248
281-
Processing paths are Python version-dependent, as there are differences in where the
282-
docstring is placed in the AST:
283-
* Python >= 3.8, docstrings are contained in the body of the function node
284-
* Python < 3.8, docstrings are contained in the function node
249+
For Python >= 3.8, docstrings are contained in the body of the function node.
285250
286251
NOTE: AST's line numbers are 1-indexed, column offsets are 0-indexed. Since `lines` is a
287252
list, it will be 0-indexed.
@@ -290,23 +255,9 @@ def colon_seeker(node: AST_FUNCTION_TYPES, lines: t.List[str]) -> t.Tuple[int, i
290255
if node.lineno == node.body[0].lineno:
291256
return Function._single_line_colon_seeker(node, lines[node.lineno - 1])
292257

293-
# With Python < 3.8, the function node includes the docstring & the body does not, so
294-
# we have rewind through any docstrings, if present, before looking for the def colon
295-
# We should end up with lines[def_end_lineno - 1] having the colon
296-
def_end_lineno = node.body[0].lineno
297-
if not PY_GTE_38:
298-
# If the docstring is on one line then no rewinding is necessary.
299-
n_triple_quotes = lines[def_end_lineno - 1].count('"""')
300-
if n_triple_quotes == 1: # pragma: no branch
301-
# Docstring closure, rewind until the opening is found & take the line prior
302-
while True:
303-
def_end_lineno -= 1
304-
if '"""' in lines[def_end_lineno - 1]: # pragma: no branch
305-
# Docstring has closed
306-
break
307-
308258
# Once we've gotten here, we've found the line where the docstring begins, so we have
309259
# to step up one more line to get to the close of the def
260+
def_end_lineno = node.body[0].lineno
310261
def_end_lineno -= 1
311262

312263
# Use str.rfind() to account for annotations on the same line, definition closure should
@@ -324,82 +275,6 @@ def _single_line_colon_seeker(node: AST_FUNCTION_TYPES, line: str) -> t.Tuple[in
324275

325276
return node.lineno, def_end_col_offset
326277

327-
@staticmethod
328-
def try_type_comment(func_obj: Function, node: AST_FUNCTION_TYPES) -> Function:
329-
"""
330-
Attempt to infer type hints from a function-level type comment.
331-
332-
If a function is type commented it is assumed to have a return annotation, otherwise Python
333-
will fail to parse the hint.
334-
"""
335-
# If we're in this function then the node is guaranteed to have a type comment, so we can
336-
# ignore mypy's complaint about an incompatible type for `node.type_comment`
337-
# Because we're passing in the `func_type` arg, we know that our return is guaranteed to be
338-
# ast.FunctionType
339-
hint_tree: ast.FunctionType = ast.parse(node.type_comment, "<func_type>", "func_type") # type: ignore[assignment, arg-type] # noqa: E501
340-
hint_tree = Function._maybe_inject_class_argument(hint_tree, func_obj)
341-
342-
for arg, hint_comment in zip_longest(func_obj.args, hint_tree.argtypes):
343-
if isinstance(hint_comment, ast_Ellipsis):
344-
continue
345-
346-
if arg and hint_comment:
347-
arg.has_type_annotation = True
348-
arg.has_type_comment = True
349-
350-
if Argument._is_annotated_any(hint_comment):
351-
arg.is_dynamically_typed = True
352-
353-
# Return arg is always last
354-
func_obj.args[-1].has_type_annotation = True
355-
func_obj.args[-1].has_type_comment = True
356-
func_obj.is_return_annotated = True
357-
if Argument._is_annotated_any(hint_tree.returns):
358-
arg.is_dynamically_typed = True
359-
360-
return func_obj
361-
362-
@staticmethod
363-
def _maybe_inject_class_argument(
364-
hint_tree: ast.FunctionType, func_obj: Function
365-
) -> ast.FunctionType:
366-
"""
367-
Inject `self` or `cls` args into a type comment to align with PEP 3107-style annotations.
368-
369-
Because PEP 484 does not describe a method to provide partial function-level type comments,
370-
there is a potential for ambiguity in the context of both class methods and classmethods
371-
when aligning type comments to method arguments.
372-
373-
These two class methods, for example, should lint equivalently:
374-
375-
def bar(self, a):
376-
# type: (int) -> int
377-
...
378-
379-
def bar(self, a: int) -> int
380-
...
381-
382-
When this example type comment is parsed by `ast` and then matched with the method's
383-
arguments, it associates the `int` hint to `self` rather than `a`, so a dummy hint needs to
384-
be provided in situations where `self` or `class` are not hinted in the type comment in
385-
order to achieve equivalent linting results to PEP-3107 style annotations.
386-
387-
A dummy `ast.Ellipses` constant is injected if the following criteria are met:
388-
1. The function node is either a class method or classmethod
389-
2. The number of hinted args is at least 1 less than the number of function args
390-
"""
391-
if not func_obj.is_class_method:
392-
# Short circuit
393-
return hint_tree
394-
395-
if func_obj.class_decorator_type != ClassDecoratorType.STATICMETHOD:
396-
if len(hint_tree.argtypes) < (len(func_obj.args) - 1): # Subtract 1 to skip return arg
397-
# Ignore mypy's objection to this assignment, Ellipsis subclasses expr so I'm not
398-
# sure how to make Mypy happy with this but I think it still makes semantic sense
399-
hint_tree.argtypes = [ast.Ellipsis()] + hint_tree.argtypes
400-
401-
return hint_tree
402-
403278
@staticmethod
404279
def get_function_type(function_name: str) -> FunctionType:
405280
"""

0 commit comments

Comments
 (0)