Skip to content

Commit 75df9db

Browse files
authored
Merge pull request #382 from asottile/split
split pyupgrade into plugins
2 parents 5d27880 + 157a9fb commit 75df9db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+3822
-3041
lines changed

pyupgrade.py

Lines changed: 0 additions & 2829 deletions
This file was deleted.

pyupgrade/__init__.py

Whitespace-only changes.

pyupgrade/__main__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from pyupgrade._main import main
2+
3+
if __name__ == '__main__':
4+
exit(main())

pyupgrade/_ast_helpers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import ast
2+
import warnings
3+
from typing import Container
4+
from typing import Dict
5+
from typing import Set
6+
from typing import Union
7+
8+
from tokenize_rt import Offset
9+
10+
11+
def ast_parse(contents_text: str) -> ast.Module:
12+
# intentionally ignore warnings, we might be fixing warning-ridden syntax
13+
with warnings.catch_warnings():
14+
warnings.simplefilter('ignore')
15+
return ast.parse(contents_text.encode())
16+
17+
18+
def ast_to_offset(node: Union[ast.expr, ast.stmt]) -> Offset:
19+
return Offset(node.lineno, node.col_offset)
20+
21+
22+
def is_name_attr(
23+
node: ast.AST,
24+
imports: Dict[str, Set[str]],
25+
mod: str,
26+
names: Container[str],
27+
) -> bool:
28+
return (
29+
isinstance(node, ast.Name) and
30+
node.id in names and
31+
node.id in imports[mod]
32+
) or (
33+
isinstance(node, ast.Attribute) and
34+
isinstance(node.value, ast.Name) and
35+
node.value.id == mod and
36+
node.attr in names
37+
)
38+
39+
40+
def has_starargs(call: ast.Call) -> bool:
41+
return (
42+
any(k.arg is None for k in call.keywords) or
43+
any(isinstance(a, ast.Starred) for a in call.args)
44+
)

pyupgrade/_data.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import ast
2+
import collections
3+
import pkgutil
4+
from typing import Callable
5+
from typing import Dict
6+
from typing import Iterable
7+
from typing import List
8+
from typing import NamedTuple
9+
from typing import Set
10+
from typing import Tuple
11+
from typing import Type
12+
from typing import TYPE_CHECKING
13+
from typing import TypeVar
14+
15+
from tokenize_rt import Offset
16+
from tokenize_rt import Token
17+
18+
from pyupgrade import _plugins
19+
20+
if TYPE_CHECKING:
21+
from typing import Protocol
22+
else:
23+
Protocol = object
24+
25+
Version = Tuple[int, ...]
26+
27+
28+
class Settings(NamedTuple):
29+
min_version: Version = (2, 7)
30+
keep_percent_format: bool = False
31+
keep_mock: bool = False
32+
33+
34+
class State(NamedTuple):
35+
settings: Settings
36+
from_imports: Dict[str, Set[str]]
37+
in_annotation: bool = False
38+
39+
40+
AST_T = TypeVar('AST_T', bound=ast.AST)
41+
TokenFunc = Callable[[int, List[Token]], None]
42+
ASTFunc = Callable[[State, AST_T, ast.AST], Iterable[Tuple[Offset, TokenFunc]]]
43+
44+
RECORD_FROM_IMPORTS = frozenset((
45+
'__future__',
46+
'functools',
47+
'mmap',
48+
'select',
49+
'six',
50+
'socket',
51+
'sys',
52+
'typing',
53+
))
54+
55+
FUNCS = collections.defaultdict(list)
56+
57+
58+
def register(tp: Type[AST_T]) -> Callable[[ASTFunc[AST_T]], ASTFunc[AST_T]]:
59+
def register_decorator(func: ASTFunc[AST_T]) -> ASTFunc[AST_T]:
60+
FUNCS[tp].append(func)
61+
return func
62+
return register_decorator
63+
64+
65+
class ASTCallbackMapping(Protocol):
66+
def __getitem__(self, tp: Type[AST_T]) -> List[ASTFunc[AST_T]]: ...
67+
68+
69+
def visit(
70+
funcs: ASTCallbackMapping,
71+
tree: ast.Module,
72+
settings: Settings,
73+
) -> Dict[Offset, List[TokenFunc]]:
74+
initial_state = State(
75+
settings=settings,
76+
from_imports=collections.defaultdict(set),
77+
)
78+
79+
nodes: List[Tuple[State, ast.AST, ast.AST]] = [(initial_state, tree, tree)]
80+
81+
ret = collections.defaultdict(list)
82+
while nodes:
83+
state, node, parent = nodes.pop()
84+
85+
tp = type(node)
86+
for ast_func in funcs[tp]:
87+
for offset, token_func in ast_func(state, node, parent):
88+
ret[offset].append(token_func)
89+
90+
if (
91+
isinstance(node, ast.ImportFrom) and
92+
not node.level and
93+
node.module in RECORD_FROM_IMPORTS
94+
):
95+
state.from_imports[node.module].update(
96+
name.name for name in node.names if not name.asname
97+
)
98+
99+
for name in reversed(node._fields):
100+
value = getattr(node, name)
101+
if name in {'annotation', 'returns'}:
102+
next_state = state._replace(in_annotation=True)
103+
else:
104+
next_state = state
105+
106+
if isinstance(value, ast.AST):
107+
nodes.append((next_state, value, node))
108+
elif isinstance(value, list):
109+
for value in reversed(value):
110+
if isinstance(value, ast.AST):
111+
nodes.append((next_state, value, node))
112+
return ret
113+
114+
115+
def _import_plugins() -> None:
116+
# https://github.com/python/mypy/issues/1422
117+
plugins_path: str = _plugins.__path__ # type: ignore
118+
mod_infos = pkgutil.walk_packages(plugins_path, f'{_plugins.__name__}.')
119+
for _, name, _ in mod_infos:
120+
__import__(name, fromlist=['_trash'])
121+
122+
123+
_import_plugins()

0 commit comments

Comments
 (0)