python-minifier 2.11.2__py2-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_minifier/__init__.py +269 -0
- python_minifier/__init__.pyi +37 -0
- python_minifier/__main__.py +332 -0
- python_minifier/ast_compare.py +104 -0
- python_minifier/ast_compat.py +40 -0
- python_minifier/ast_printer.py +130 -0
- python_minifier/expression_printer.py +753 -0
- python_minifier/f_string.py +446 -0
- python_minifier/ministring.py +179 -0
- python_minifier/module_printer.py +862 -0
- python_minifier/py.typed +0 -0
- python_minifier/rename/__init__.py +6 -0
- python_minifier/rename/bind_names.py +192 -0
- python_minifier/rename/binding.py +495 -0
- python_minifier/rename/mapper.py +175 -0
- python_minifier/rename/name_generator.py +51 -0
- python_minifier/rename/rename_literals.py +249 -0
- python_minifier/rename/renamer.py +230 -0
- python_minifier/rename/resolve_names.py +106 -0
- python_minifier/rename/util.py +203 -0
- python_minifier/token_printer.py +299 -0
- python_minifier/transforms/__init__.py +0 -0
- python_minifier/transforms/combine_imports.py +76 -0
- python_minifier/transforms/constant_folding.py +114 -0
- python_minifier/transforms/remove_annotations.py +130 -0
- python_minifier/transforms/remove_annotations_options.py +35 -0
- python_minifier/transforms/remove_annotations_options.pyi +17 -0
- python_minifier/transforms/remove_asserts.py +26 -0
- python_minifier/transforms/remove_debug.py +53 -0
- python_minifier/transforms/remove_exception_brackets.py +124 -0
- python_minifier/transforms/remove_explicit_return_none.py +38 -0
- python_minifier/transforms/remove_literal_statements.py +61 -0
- python_minifier/transforms/remove_object_base.py +24 -0
- python_minifier/transforms/remove_pass.py +26 -0
- python_minifier/transforms/remove_posargs.py +13 -0
- python_minifier/transforms/suite_transformer.py +196 -0
- python_minifier/util.py +48 -0
- python_minifier-2.11.2.dist-info/LICENSE +21 -0
- python_minifier-2.11.2.dist-info/METADATA +177 -0
- python_minifier-2.11.2.dist-info/RECORD +44 -0
- python_minifier-2.11.2.dist-info/WHEEL +5 -0
- python_minifier-2.11.2.dist-info/entry_points.txt +3 -0
- python_minifier-2.11.2.dist-info/top_level.txt +1 -0
- python_minifier-2.11.2.dist-info/zip-safe +1 -0
python_minifier/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from python_minifier.rename.bind_names import bind_names
|
|
2
|
+
from python_minifier.rename.mapper import add_namespace
|
|
3
|
+
from python_minifier.rename.rename_literals import rename_literals
|
|
4
|
+
from python_minifier.rename.renamer import rename
|
|
5
|
+
from python_minifier.rename.resolve_names import resolve_names
|
|
6
|
+
from python_minifier.rename.util import allow_rename_locals, allow_rename_globals
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
|
|
3
|
+
from python_minifier.rename.binding import NameBinding
|
|
4
|
+
from python_minifier.rename.util import arg_rename_in_place, get_global_namespace, get_nonlocal_namespace, builtins
|
|
5
|
+
from python_minifier.transforms.suite_transformer import NodeVisitor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NameBinder(NodeVisitor):
|
|
9
|
+
"""
|
|
10
|
+
Create a NameBinding for each name that is bound
|
|
11
|
+
|
|
12
|
+
The NameBinding is added to the bindings dictionary in the namespace node the name is local to.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __call__(self, module):
|
|
16
|
+
assert isinstance(module, ast.Module)
|
|
17
|
+
module.tainted = False
|
|
18
|
+
module.preserved = set()
|
|
19
|
+
return self.visit(module)
|
|
20
|
+
|
|
21
|
+
def get_binding(self, name, namespace):
|
|
22
|
+
if name in namespace.global_names and not isinstance(namespace, ast.Module):
|
|
23
|
+
return self.get_binding(name, get_global_namespace(namespace))
|
|
24
|
+
|
|
25
|
+
# nonlocal names should not create a binding in any context
|
|
26
|
+
assert name not in namespace.nonlocal_names
|
|
27
|
+
|
|
28
|
+
for binding in namespace.bindings:
|
|
29
|
+
if binding.name == name:
|
|
30
|
+
break
|
|
31
|
+
else: # weeee!
|
|
32
|
+
binding = NameBinding(name)
|
|
33
|
+
namespace.bindings.append(binding)
|
|
34
|
+
|
|
35
|
+
if name in dir(builtins):
|
|
36
|
+
binding.disallow_rename()
|
|
37
|
+
|
|
38
|
+
if name in namespace.nonlocal_names and isinstance(namespace, ast.Module):
|
|
39
|
+
# This is actually a syntax error - but we want the same syntax error after minifying!
|
|
40
|
+
binding.disallow_rename()
|
|
41
|
+
|
|
42
|
+
if isinstance(namespace, ast.ClassDef):
|
|
43
|
+
# This name will become an attribute of the class, so it can't be renamed
|
|
44
|
+
binding.disallow_rename()
|
|
45
|
+
|
|
46
|
+
return binding
|
|
47
|
+
|
|
48
|
+
def visit_Name(self, node):
|
|
49
|
+
if node.id in node.namespace.nonlocal_names:
|
|
50
|
+
# A nonlocal name does not create a binding.
|
|
51
|
+
# We will resolve the binding later
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if isinstance(node.ctx, (ast.Store, ast.Del)):
|
|
55
|
+
self.get_binding(node.id, node.namespace).add_reference(node)
|
|
56
|
+
|
|
57
|
+
if isinstance(node.ctx, ast.Param):
|
|
58
|
+
binding = self.get_binding(node.id, node.namespace)
|
|
59
|
+
|
|
60
|
+
if arg_rename_in_place(node):
|
|
61
|
+
binding.add_reference(node)
|
|
62
|
+
else:
|
|
63
|
+
binding.add_reference(node, reserved=node.id)
|
|
64
|
+
|
|
65
|
+
if isinstance(node.namespace, ast.Lambda):
|
|
66
|
+
# Lambda function arguments can't be renamed without breaking keyword arguments
|
|
67
|
+
binding.disallow_rename()
|
|
68
|
+
|
|
69
|
+
def visit_ClassDef(self, node):
|
|
70
|
+
if node.name not in node.namespace.nonlocal_names:
|
|
71
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
72
|
+
self.generic_visit(node)
|
|
73
|
+
|
|
74
|
+
def visit_FunctionDef(self, node):
|
|
75
|
+
if node.name not in node.namespace.nonlocal_names:
|
|
76
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
77
|
+
self.generic_visit(node)
|
|
78
|
+
|
|
79
|
+
def visit_AsyncFunctionDef(self, node):
|
|
80
|
+
self.visit_FunctionDef(node)
|
|
81
|
+
|
|
82
|
+
def visit_alias(self, node):
|
|
83
|
+
if node.name == '*':
|
|
84
|
+
get_global_namespace(node).tainted = True
|
|
85
|
+
|
|
86
|
+
root_module = node.name.split('.')[0]
|
|
87
|
+
|
|
88
|
+
if root_module == 'timeit':
|
|
89
|
+
get_global_namespace(node).tainted = True
|
|
90
|
+
|
|
91
|
+
if node.asname is not None:
|
|
92
|
+
if node.asname not in node.namespace.nonlocal_names:
|
|
93
|
+
self.get_binding(node.asname, node.namespace).add_reference(node)
|
|
94
|
+
else:
|
|
95
|
+
# This binds the root module only for a dotted import
|
|
96
|
+
|
|
97
|
+
if root_module not in node.namespace.nonlocal_names:
|
|
98
|
+
binding = self.get_binding(root_module, node.namespace)
|
|
99
|
+
binding.add_reference(node)
|
|
100
|
+
|
|
101
|
+
if '.' in node.name:
|
|
102
|
+
binding.disallow_rename()
|
|
103
|
+
|
|
104
|
+
def visit_arguments(self, node):
|
|
105
|
+
# varargs, kwarg can't be nonlocal
|
|
106
|
+
if isinstance(node.vararg, str):
|
|
107
|
+
binding = self.get_binding(node.vararg, node.namespace)
|
|
108
|
+
binding.add_reference(node)
|
|
109
|
+
|
|
110
|
+
if isinstance(node.kwarg, str):
|
|
111
|
+
binding = self.get_binding(node.kwarg, node.namespace)
|
|
112
|
+
binding.add_reference(node)
|
|
113
|
+
|
|
114
|
+
self.generic_visit(node)
|
|
115
|
+
|
|
116
|
+
def visit_arg(self, node):
|
|
117
|
+
# Args can't be nonlocal
|
|
118
|
+
binding = self.get_binding(node.arg, node.namespace)
|
|
119
|
+
|
|
120
|
+
if arg_rename_in_place(node):
|
|
121
|
+
binding.add_reference(node)
|
|
122
|
+
else:
|
|
123
|
+
binding.add_reference(node, reserved=node.arg)
|
|
124
|
+
|
|
125
|
+
if isinstance(node.namespace, ast.Lambda):
|
|
126
|
+
# Lambda function arguments can't be renamed without breaking keyword arguments
|
|
127
|
+
binding.disallow_rename()
|
|
128
|
+
|
|
129
|
+
self.generic_visit(node)
|
|
130
|
+
|
|
131
|
+
def visit_ExceptHandler(self, node):
|
|
132
|
+
if node.name is not None:
|
|
133
|
+
if isinstance(node.name, str) and node.name not in node.namespace.nonlocal_names:
|
|
134
|
+
# python 3
|
|
135
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
136
|
+
else:
|
|
137
|
+
# In python 2 the name is a Name node,
|
|
138
|
+
# which will be visited by generic_visit
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
self.generic_visit(node)
|
|
142
|
+
|
|
143
|
+
def visit_Global(self, node):
|
|
144
|
+
for name in node.names:
|
|
145
|
+
self.get_binding(name, node.namespace).add_reference(node)
|
|
146
|
+
|
|
147
|
+
def visit_MatchAs(self, node):
|
|
148
|
+
if node.name is not None and node.name not in node.namespace.nonlocal_names:
|
|
149
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
150
|
+
|
|
151
|
+
self.generic_visit(node)
|
|
152
|
+
|
|
153
|
+
def visit_MatchStar(self, node):
|
|
154
|
+
if node.name is not None and node.name not in node.namespace.nonlocal_names:
|
|
155
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
156
|
+
|
|
157
|
+
self.generic_visit(node)
|
|
158
|
+
|
|
159
|
+
def visit_MatchMapping(self, node):
|
|
160
|
+
if node.rest is not None and node.rest not in node.namespace.nonlocal_names:
|
|
161
|
+
self.get_binding(node.rest, node.namespace).add_reference(node)
|
|
162
|
+
|
|
163
|
+
self.generic_visit(node)
|
|
164
|
+
|
|
165
|
+
def visit_TypeVar(self, node):
|
|
166
|
+
if node.name not in node.namespace.nonlocal_names:
|
|
167
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
168
|
+
|
|
169
|
+
get_global_namespace(node.namespace).preserved.add(node.name)
|
|
170
|
+
|
|
171
|
+
def visit_TypeVarTuple(self, node):
|
|
172
|
+
if node.name not in node.namespace.nonlocal_names:
|
|
173
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
174
|
+
|
|
175
|
+
get_global_namespace(node.namespace).preserved.add(node.name)
|
|
176
|
+
|
|
177
|
+
def visit_ParamSpec(self, node):
|
|
178
|
+
if node.name not in node.namespace.nonlocal_names:
|
|
179
|
+
self.get_binding(node.name, node.namespace).add_reference(node)
|
|
180
|
+
|
|
181
|
+
get_global_namespace(node.namespace).preserved.add(node.name)
|
|
182
|
+
|
|
183
|
+
def bind_names(module):
|
|
184
|
+
"""
|
|
185
|
+
Bind names to their local namespace
|
|
186
|
+
|
|
187
|
+
:param module: The module to bind names in
|
|
188
|
+
:type: :class:`ast.Module`
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
NameBinder()(module)
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
|
|
3
|
+
from python_minifier.rename.util import arg_rename_in_place, insert
|
|
4
|
+
from python_minifier.util import is_ast_node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Binding(object):
|
|
8
|
+
"""
|
|
9
|
+
Represents the binding of a name
|
|
10
|
+
|
|
11
|
+
:param name: A name for this binding
|
|
12
|
+
:type name: str or None
|
|
13
|
+
:param bool allow_rename: If this binding may be renamed
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, name=None, allow_rename=True):
|
|
18
|
+
self._references = []
|
|
19
|
+
|
|
20
|
+
self._allow_rename = allow_rename
|
|
21
|
+
|
|
22
|
+
self._name = name
|
|
23
|
+
self._reserved = None
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
return self.__class__.__name__ + '()'
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def name(self):
|
|
30
|
+
"""
|
|
31
|
+
The name for this binding
|
|
32
|
+
|
|
33
|
+
This may be changed using the rename() method.
|
|
34
|
+
If this binding doesn't currently have a name, this returns None.
|
|
35
|
+
|
|
36
|
+
:rtype: str or None
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
return self._name
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def allow_rename(self):
|
|
44
|
+
"""
|
|
45
|
+
Is it allowed to rename this binding
|
|
46
|
+
|
|
47
|
+
:rtype: bool
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
return self._allow_rename
|
|
52
|
+
|
|
53
|
+
def disallow_rename(self):
|
|
54
|
+
"""
|
|
55
|
+
Prevent this binding from being renamed
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
self._allow_rename = False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def reserved(self):
|
|
62
|
+
"""
|
|
63
|
+
A reserved name for this binding
|
|
64
|
+
|
|
65
|
+
This may be a name which this binding reserves in it's reservation scope,
|
|
66
|
+
regardless of if it is renamed.
|
|
67
|
+
|
|
68
|
+
:rtype: str or None
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
return self._reserved
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def references(self):
|
|
76
|
+
"""
|
|
77
|
+
The ast Nodes that reference this binding
|
|
78
|
+
|
|
79
|
+
:rtype: list[ast.AST]
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
return self._references
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def name_references(self):
|
|
87
|
+
"""
|
|
88
|
+
The number of times the name is used
|
|
89
|
+
"""
|
|
90
|
+
return len(self._references)
|
|
91
|
+
|
|
92
|
+
def additional_byte_cost(self):
|
|
93
|
+
"""
|
|
94
|
+
How many additional bytes would be used, if this was renamed
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
arg_rename = False
|
|
98
|
+
additional_bytes = 0
|
|
99
|
+
|
|
100
|
+
for node in self._references:
|
|
101
|
+
if isinstance(node, ast.Name):
|
|
102
|
+
if isinstance(node.ctx, (ast.Load, ast.Store, ast.Del)):
|
|
103
|
+
pass
|
|
104
|
+
else:
|
|
105
|
+
# Python 2 Param context
|
|
106
|
+
if not arg_rename_in_place(node):
|
|
107
|
+
arg_rename = True
|
|
108
|
+
elif is_ast_node(node, (ast.ClassDef, ast.FunctionDef, 'AsyncFunctionDef')):
|
|
109
|
+
pass
|
|
110
|
+
elif isinstance(node, ast.ExceptHandler):
|
|
111
|
+
pass
|
|
112
|
+
elif is_ast_node(node, (ast.Global, 'Nonlocal')):
|
|
113
|
+
pass
|
|
114
|
+
elif isinstance(node, ast.alias):
|
|
115
|
+
if node.asname is None:
|
|
116
|
+
additional_bytes += 4 # ' as '
|
|
117
|
+
elif isinstance(node, ast.arguments):
|
|
118
|
+
if node.vararg == self._name:
|
|
119
|
+
pass
|
|
120
|
+
if node.kwarg == self._name:
|
|
121
|
+
pass
|
|
122
|
+
elif is_ast_node(node, 'arg'):
|
|
123
|
+
if not arg_rename_in_place(node):
|
|
124
|
+
arg_rename = True
|
|
125
|
+
|
|
126
|
+
elif is_ast_node(node, 'MatchAs'):
|
|
127
|
+
if node.name is None:
|
|
128
|
+
additional_bytes += 4 # ' as '
|
|
129
|
+
elif is_ast_node(node, 'MatchStar'):
|
|
130
|
+
pass
|
|
131
|
+
elif is_ast_node(node, 'MatchMapping'):
|
|
132
|
+
pass
|
|
133
|
+
elif is_ast_node(node, 'TypeVar'):
|
|
134
|
+
pass
|
|
135
|
+
elif is_ast_node(node, 'TypeVarTuple'):
|
|
136
|
+
pass
|
|
137
|
+
elif is_ast_node(node, 'ParamSpec'):
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
else:
|
|
141
|
+
raise AssertionError('Unknown reference node')
|
|
142
|
+
|
|
143
|
+
return additional_bytes + (2 if arg_rename else 0)
|
|
144
|
+
|
|
145
|
+
def old_mention_count(self):
|
|
146
|
+
"""
|
|
147
|
+
The number of times the old name would be mentioned in the source code, if this binding was renamed
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
arg_rename = False
|
|
151
|
+
mentions = 0
|
|
152
|
+
|
|
153
|
+
for node in self._references:
|
|
154
|
+
if isinstance(node, ast.Name):
|
|
155
|
+
if isinstance(node.ctx, (ast.Load, ast.Store, ast.Del)):
|
|
156
|
+
pass
|
|
157
|
+
else:
|
|
158
|
+
# Python 2 Param context
|
|
159
|
+
if not arg_rename_in_place(node):
|
|
160
|
+
mentions += 1
|
|
161
|
+
arg_rename = True
|
|
162
|
+
|
|
163
|
+
elif is_ast_node(node, (ast.ClassDef, ast.FunctionDef, 'AsyncFunctionDef')):
|
|
164
|
+
pass
|
|
165
|
+
elif isinstance(node, ast.ExceptHandler):
|
|
166
|
+
pass
|
|
167
|
+
elif is_ast_node(node, (ast.Global, 'Nonlocal')):
|
|
168
|
+
pass
|
|
169
|
+
elif isinstance(node, ast.alias):
|
|
170
|
+
if node.asname is None:
|
|
171
|
+
# import foo -> import foo as bar
|
|
172
|
+
mentions += 1
|
|
173
|
+
elif isinstance(node, ast.arguments):
|
|
174
|
+
pass
|
|
175
|
+
elif is_ast_node(node, 'arg'):
|
|
176
|
+
if not arg_rename_in_place(node):
|
|
177
|
+
mentions += 1
|
|
178
|
+
arg_rename = True
|
|
179
|
+
|
|
180
|
+
elif is_ast_node(node, 'MatchAs'):
|
|
181
|
+
pass
|
|
182
|
+
elif is_ast_node(node, 'MatchStar'):
|
|
183
|
+
pass
|
|
184
|
+
elif is_ast_node(node, 'MatchMapping'):
|
|
185
|
+
pass
|
|
186
|
+
elif is_ast_node(node, 'TypeVar'):
|
|
187
|
+
pass
|
|
188
|
+
elif is_ast_node(node, 'TypeVarTuple'):
|
|
189
|
+
pass
|
|
190
|
+
elif is_ast_node(node, 'ParamSpec'):
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
raise AssertionError('Unknown reference node')
|
|
195
|
+
|
|
196
|
+
return mentions + (1 if arg_rename else 0)
|
|
197
|
+
|
|
198
|
+
def new_mention_count(self):
|
|
199
|
+
"""
|
|
200
|
+
The number of times a new name would be mentioned in the source code
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
arg_rename = False
|
|
204
|
+
mentions = 0
|
|
205
|
+
|
|
206
|
+
for node in self._references:
|
|
207
|
+
if isinstance(node, ast.Name):
|
|
208
|
+
if isinstance(node.ctx, (ast.Load, ast.Store, ast.Del)):
|
|
209
|
+
mentions += 1
|
|
210
|
+
else:
|
|
211
|
+
# Python 2 Param context
|
|
212
|
+
arg_rename = True
|
|
213
|
+
elif is_ast_node(node, (ast.ClassDef, ast.FunctionDef, 'AsyncFunctionDef')):
|
|
214
|
+
mentions += 1
|
|
215
|
+
elif isinstance(node, ast.ExceptHandler):
|
|
216
|
+
mentions += 1
|
|
217
|
+
elif is_ast_node(node, (ast.Global, 'Nonlocal')):
|
|
218
|
+
mentions += len([n for n in node.names if n == self._name])
|
|
219
|
+
elif isinstance(node, ast.alias):
|
|
220
|
+
mentions += 1
|
|
221
|
+
elif isinstance(node, ast.arguments):
|
|
222
|
+
if node.vararg == self._name:
|
|
223
|
+
mentions += 1
|
|
224
|
+
if node.kwarg == self._name:
|
|
225
|
+
mentions += 1
|
|
226
|
+
elif is_ast_node(node, 'arg'):
|
|
227
|
+
arg_rename = True
|
|
228
|
+
|
|
229
|
+
elif is_ast_node(node, 'MatchAs'):
|
|
230
|
+
mentions += 1
|
|
231
|
+
elif is_ast_node(node, 'MatchStar'):
|
|
232
|
+
mentions += 1
|
|
233
|
+
elif is_ast_node(node, 'MatchMapping'):
|
|
234
|
+
mentions += 1
|
|
235
|
+
elif is_ast_node(node, 'TypeVar'):
|
|
236
|
+
mentions += 1
|
|
237
|
+
elif is_ast_node(node, 'TypeVarTuple'):
|
|
238
|
+
mentions += 1
|
|
239
|
+
elif is_ast_node(node, 'ParamSpec'):
|
|
240
|
+
mentions += 1
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
raise AssertionError('Unknown reference node')
|
|
244
|
+
|
|
245
|
+
return mentions + (1 if arg_rename else 0)
|
|
246
|
+
|
|
247
|
+
def add_reference(self, node, allow_rename=True, reserved=None):
|
|
248
|
+
"""
|
|
249
|
+
Add a new reference to this binding
|
|
250
|
+
|
|
251
|
+
:param node: The node that references this binding
|
|
252
|
+
:type node: :class:`ast.AST`
|
|
253
|
+
:param bool allow_rename: If this binding may be renamed
|
|
254
|
+
:param str reserved: A name used by the node, even if the binding is renamed.
|
|
255
|
+
:param int rename_cost: Additional cost of renaming the reference, in bytes
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
self.references.append(node)
|
|
260
|
+
|
|
261
|
+
if allow_rename is False:
|
|
262
|
+
self.disallow_rename()
|
|
263
|
+
|
|
264
|
+
if reserved is not None:
|
|
265
|
+
self._reserved = reserved
|
|
266
|
+
|
|
267
|
+
def should_rename(self, new_name):
|
|
268
|
+
"""
|
|
269
|
+
Is it space efficient to rename this binding
|
|
270
|
+
|
|
271
|
+
:param str new_name: The candidate name
|
|
272
|
+
:rtype: bool
|
|
273
|
+
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
raise NotImplementedError()
|
|
277
|
+
|
|
278
|
+
def rename(self, new_name):
|
|
279
|
+
"""
|
|
280
|
+
Rename this binding and all nodes that reference it
|
|
281
|
+
|
|
282
|
+
:param str new_name: The new name to use
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
raise NotImplementedError()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class NameBinding(Binding):
|
|
290
|
+
"""
|
|
291
|
+
Represents the binding of a defined name
|
|
292
|
+
|
|
293
|
+
A NameBinding will be attached to the local namespace that defines it.
|
|
294
|
+
|
|
295
|
+
:param str name: The original bound name
|
|
296
|
+
:param bool allow_rename: If this binding may be renamed
|
|
297
|
+
:param int rename_cost: The cost of renaming this binding in bytes
|
|
298
|
+
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
def __init__(self, name, *args, **kwargs):
|
|
302
|
+
super(NameBinding, self).__init__(name, *args, **kwargs)
|
|
303
|
+
|
|
304
|
+
if name.startswith('__') and name.endswith('__'):
|
|
305
|
+
# System defined name
|
|
306
|
+
self.disallow_rename()
|
|
307
|
+
|
|
308
|
+
def __repr__(self):
|
|
309
|
+
return self.__class__.__name__ + '(name=%r, allow_rename=%r) <references=%r>' % (self._name, self._allow_rename, len(self._references))
|
|
310
|
+
|
|
311
|
+
def should_rename(self, new_name):
|
|
312
|
+
"""
|
|
313
|
+
Is it space efficient to rename this binding
|
|
314
|
+
|
|
315
|
+
:param str new_name: The candidate name
|
|
316
|
+
:rtype: bool
|
|
317
|
+
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
current_cost = len(self.references) * len(self._name)
|
|
321
|
+
|
|
322
|
+
old_mentions = self.old_mention_count()
|
|
323
|
+
new_mentions = self.new_mention_count()
|
|
324
|
+
additional_bytes = self.additional_byte_cost()
|
|
325
|
+
rename_cost = (old_mentions * len(self._name)) + (new_mentions * len(new_name)) + additional_bytes
|
|
326
|
+
|
|
327
|
+
return rename_cost <= current_cost
|
|
328
|
+
|
|
329
|
+
def disallow_rename(self):
|
|
330
|
+
"""
|
|
331
|
+
Prevent this binding from being renamed
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
super(NameBinding, self).disallow_rename()
|
|
335
|
+
self._reserved = self._name
|
|
336
|
+
|
|
337
|
+
def rename(self, new_name):
|
|
338
|
+
"""
|
|
339
|
+
Rename this binding and all nodes that reference it
|
|
340
|
+
|
|
341
|
+
:param str new_name: The new name to use
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
func_namespace_binding = None
|
|
346
|
+
|
|
347
|
+
for node in self.references:
|
|
348
|
+
|
|
349
|
+
if isinstance(node, ast.Name):
|
|
350
|
+
|
|
351
|
+
if isinstance(node.ctx, (ast.Load, ast.Store, ast.Del)):
|
|
352
|
+
node.id = new_name
|
|
353
|
+
else:
|
|
354
|
+
# Python 2 Param context
|
|
355
|
+
|
|
356
|
+
if arg_rename_in_place(node):
|
|
357
|
+
node.id = new_name
|
|
358
|
+
|
|
359
|
+
else:
|
|
360
|
+
if func_namespace_binding is None:
|
|
361
|
+
func_namespace_binding = node.namespace
|
|
362
|
+
else:
|
|
363
|
+
assert func_namespace_binding is node.namespace
|
|
364
|
+
|
|
365
|
+
elif is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef')):
|
|
366
|
+
node.name = new_name
|
|
367
|
+
elif isinstance(node, ast.ClassDef):
|
|
368
|
+
node.name = new_name
|
|
369
|
+
elif isinstance(node, ast.alias):
|
|
370
|
+
if new_name == node.name:
|
|
371
|
+
node.asname = None
|
|
372
|
+
else:
|
|
373
|
+
node.asname = new_name
|
|
374
|
+
elif is_ast_node(node, 'arg'):
|
|
375
|
+
|
|
376
|
+
if arg_rename_in_place(node):
|
|
377
|
+
node.arg = new_name
|
|
378
|
+
|
|
379
|
+
else:
|
|
380
|
+
if func_namespace_binding is None:
|
|
381
|
+
func_namespace_binding = node.namespace
|
|
382
|
+
else:
|
|
383
|
+
assert func_namespace_binding is node.namespace
|
|
384
|
+
|
|
385
|
+
elif isinstance(node, ast.ExceptHandler):
|
|
386
|
+
node.name = new_name
|
|
387
|
+
elif is_ast_node(node, (ast.Global, 'Nonlocal')):
|
|
388
|
+
node.names = [new_name if n == self._name else n for n in node.names]
|
|
389
|
+
elif isinstance(node, ast.arguments):
|
|
390
|
+
|
|
391
|
+
rename_vararg = (node.vararg == self._name) and not getattr(node, 'vararg_renamed', False)
|
|
392
|
+
rename_kwarg = (node.kwarg == self._name) and not getattr(node, 'kwarg_renamed', False)
|
|
393
|
+
|
|
394
|
+
if rename_vararg:
|
|
395
|
+
node.vararg = new_name
|
|
396
|
+
node.vararg_renamed = True
|
|
397
|
+
if rename_kwarg:
|
|
398
|
+
node.kwarg = new_name
|
|
399
|
+
node.kwarg_renamed = True
|
|
400
|
+
|
|
401
|
+
elif is_ast_node(node, 'MatchAs'):
|
|
402
|
+
node.name = new_name
|
|
403
|
+
elif is_ast_node(node, 'MatchStar'):
|
|
404
|
+
node.name = new_name
|
|
405
|
+
elif is_ast_node(node, 'MatchMapping'):
|
|
406
|
+
node.rest = new_name
|
|
407
|
+
elif is_ast_node(node, 'TypeVar'):
|
|
408
|
+
node.name = new_name
|
|
409
|
+
elif is_ast_node(node, 'TypeVarTuple'):
|
|
410
|
+
node.name = new_name
|
|
411
|
+
elif is_ast_node(node, 'ParamSpec'):
|
|
412
|
+
node.name = new_name
|
|
413
|
+
|
|
414
|
+
if func_namespace_binding is not None:
|
|
415
|
+
func_namespace_binding.body = list(
|
|
416
|
+
insert(
|
|
417
|
+
func_namespace_binding.body,
|
|
418
|
+
ast.Assign(
|
|
419
|
+
targets=[ast.Name(id=new_name, ctx=ast.Store())],
|
|
420
|
+
value=ast.Name(id=self._name, ctx=ast.Load()),
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
self._name = new_name
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class BuiltinBinding(NameBinding):
|
|
429
|
+
"""
|
|
430
|
+
Represents the usage of a builtin
|
|
431
|
+
|
|
432
|
+
:param str name: The name of the builtin
|
|
433
|
+
:param namespace: The module the builtin is used in
|
|
434
|
+
:type namespace: :class:`ast.Module`
|
|
435
|
+
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
def __init__(self, name, namespace, *args, **kwargs):
|
|
439
|
+
super(BuiltinBinding, self).__init__(name, *args, **kwargs)
|
|
440
|
+
self.namespace = namespace
|
|
441
|
+
|
|
442
|
+
# These builtins actually act like keywords, so should not be changed
|
|
443
|
+
if name == 'super':
|
|
444
|
+
# If we replace 'super' with another name the compiler will neglect to create the
|
|
445
|
+
# __class__ implicit closure reference, breaking the zero argument super() call.
|
|
446
|
+
self.disallow_rename()
|
|
447
|
+
elif name == 'object':
|
|
448
|
+
# Classes must inherit from object to become a new-style class in python2
|
|
449
|
+
self.disallow_rename()
|
|
450
|
+
|
|
451
|
+
def new_mention_count(self):
|
|
452
|
+
# All mentions must be Names, which would be replaced
|
|
453
|
+
# Plus an Assign with the new name
|
|
454
|
+
return len(self.references) + 1
|
|
455
|
+
|
|
456
|
+
def old_mention_count(self):
|
|
457
|
+
# The old name would be mentioned in the Assign
|
|
458
|
+
return 1
|
|
459
|
+
|
|
460
|
+
def additional_byte_cost(self):
|
|
461
|
+
return 2 # '=' + '\n'
|
|
462
|
+
|
|
463
|
+
def rename(self, new_name):
|
|
464
|
+
builtin = self._name
|
|
465
|
+
super(BuiltinBinding, self).rename(new_name)
|
|
466
|
+
self.namespace.body = list(
|
|
467
|
+
insert(
|
|
468
|
+
self.namespace.body,
|
|
469
|
+
ast.Assign(
|
|
470
|
+
targets=[ast.Name(id=new_name, ctx=ast.Store())], value=ast.Name(id=builtin, ctx=ast.Load())
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
def is_redefined(self):
|
|
476
|
+
"""
|
|
477
|
+
Do one of the references to this builtin name redefine it?
|
|
478
|
+
|
|
479
|
+
Could some references actually not be references to the builtin?
|
|
480
|
+
|
|
481
|
+
This can happen with code like:
|
|
482
|
+
|
|
483
|
+
class MyClass:
|
|
484
|
+
IndexError = IndexError
|
|
485
|
+
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
for node in self.references:
|
|
489
|
+
if not isinstance(node, ast.Name):
|
|
490
|
+
return True
|
|
491
|
+
|
|
492
|
+
if not isinstance(node.ctx, ast.Load):
|
|
493
|
+
return True
|
|
494
|
+
|
|
495
|
+
return False
|