Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d30ed2e

Browse files
Skip -> None on abstract and stub methods (#34)
1 parent d309e44 commit d30ed2e

3 files changed

Lines changed: 75 additions & 15 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ Things to add:
8080
Unreleased
8181

8282
- Add `--pyanalyze-report`
83+
- Do not add `None` return types to methods marked with `@abstractmethod` and
84+
to methods in stub files
8385

8486
21.12.0 (December 21, 2021)
8587

autotyping/autotyping.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import argparse
22
from dataclasses import dataclass, field
3+
import enum
34
import json
45
from typing import Dict, List, Optional, Sequence, Set, Tuple, Type
56
from typing_extensions import TypedDict
@@ -35,6 +36,11 @@ class PyanalyzeSuggestion(TypedDict):
3536
imports: List[str]
3637

3738

39+
class DecoratorKind(enum.Enum):
40+
asynq = 1
41+
abstractmethod = 2
42+
43+
3844
@dataclass
3945
class State:
4046
annotate_optionals: List[NamedParam]
@@ -85,6 +91,8 @@ class AutotypeCommand(VisitorBasedCodemodCommand):
8591
DESCRIPTION: str = "Automatically adds simple type annotations."
8692
METADATA_DEPENDENCIES = (PositionProvider,)
8793

94+
state: State
95+
8896
@staticmethod
8997
def add_args(arg_parser: argparse.ArgumentParser) -> None:
9098
arg_parser.add_argument(
@@ -246,6 +254,10 @@ def __init__(
246254
only_without_imports=only_without_imports,
247255
)
248256

257+
def is_stub(self) -> bool:
258+
filename = self.context.filename
259+
return filename is not None and filename.endswith(".pyi")
260+
249261
def visit_FunctionDef(self, node: libcst.FunctionDef) -> None:
250262
self.state.seen_return_statement.append(False)
251263
self.state.seen_raise_statement.append(False)
@@ -277,7 +289,9 @@ def leave_Lambda(
277289
def leave_FunctionDef(
278290
self, original_node: libcst.FunctionDef, updated_node: libcst.FunctionDef
279291
) -> libcst.CSTNode:
280-
is_asynq = any(is_asynq_decorator(dec) for dec in original_node.decorators)
292+
kinds = {get_decorator_kind(decorator) for decorator in updated_node.decorators}
293+
is_asynq = DecoratorKind.asynq in kinds
294+
is_abstractmethod = DecoratorKind.abstractmethod in kinds
281295
seen_return = self.state.seen_return_statement.pop()
282296
seen_raise = self.state.seen_raise_statement.pop()
283297
seen_yield = self.state.seen_yield.pop()
@@ -338,6 +352,8 @@ def leave_FunctionDef(
338352
and not seen_raise
339353
and not seen_return
340354
and (is_asynq or not seen_yield)
355+
and not is_abstractmethod
356+
and not self.is_stub()
341357
):
342358
return updated_node.with_changes(
343359
returns=libcst.Annotation(annotation=libcst.Name(value="None"))
@@ -555,16 +571,26 @@ def type_of_expression(expr: libcst.BaseExpression) -> Optional[Type[object]]:
555571
return None
556572

557573

558-
def is_asynq_decorator(dec: libcst.Decorator) -> bool:
559-
"""Is this @asynq()?"""
560-
if not isinstance(dec.decorator, libcst.Call):
561-
return False
562-
call = dec.decorator
563-
if not isinstance(call.func, libcst.Name):
564-
return False
565-
if call.func.value != "asynq":
566-
return False
567-
if call.args:
568-
# @asynq() with custom arguments may do something unexpected
569-
return False
570-
return True
574+
def get_decorator_kind(dec: libcst.Decorator) -> Optional[DecoratorKind]:
575+
"""Is this @asynq() or @abstractmethod?"""
576+
if isinstance(dec.decorator, libcst.Call):
577+
call = dec.decorator
578+
if not isinstance(call.func, libcst.Name):
579+
return None
580+
if call.func.value != "asynq":
581+
return None
582+
if call.args:
583+
# @asynq() with custom arguments may do something unexpected
584+
return None
585+
return DecoratorKind.asynq
586+
elif isinstance(dec.decorator, libcst.Name):
587+
if dec.decorator.value == "abstractmethod":
588+
return DecoratorKind.abstractmethod
589+
elif isinstance(dec.decorator, libcst.Attribute):
590+
if (
591+
dec.decorator.attr.value == "abstractmethod"
592+
and isinstance(dec.decorator.value, libcst.Name)
593+
and dec.decorator.value.value == "abc"
594+
):
595+
return DecoratorKind.abstractmethod
596+
return None

tests/test_codemod.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from libcst.codemod import CodemodTest
1+
from libcst.codemod import CodemodTest, CodemodContext
22
from autotyping.autotyping import AutotypeCommand
33

44

@@ -28,6 +28,14 @@ def bar():
2828
2929
def baz():
3030
return
31+
32+
@abstractmethod
33+
def very_abstract():
34+
pass
35+
36+
@abc.abstractmethod
37+
def very_abstract_without_import_from():
38+
pass
3139
"""
3240
after = """
3341
def foo() -> None:
@@ -38,9 +46,33 @@ def bar():
3846
3947
def baz() -> None:
4048
return
49+
50+
@abstractmethod
51+
def very_abstract():
52+
pass
53+
54+
@abc.abstractmethod
55+
def very_abstract_without_import_from():
56+
pass
4157
"""
4258
self.assertCodemod(before, after, none_return=True)
4359

60+
def test_none_return_stub(self) -> None:
61+
before = """
62+
def foo():
63+
pass
64+
"""
65+
after = """
66+
def foo():
67+
pass
68+
"""
69+
self.assertCodemod(
70+
before,
71+
after,
72+
none_return=True,
73+
context_override=CodemodContext(filename="stub.pyi"),
74+
)
75+
4476
def test_scalar_return(self) -> None:
4577
before = """
4678
def foo():

0 commit comments

Comments
 (0)