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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from python_minifier.ast_compare import compare_ast
|
|
6
|
+
from python_minifier.expression_printer import ExpressionPrinter
|
|
7
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
8
|
+
from python_minifier.util import is_ast_node
|
|
9
|
+
|
|
10
|
+
class FoldConstants(SuiteTransformer):
|
|
11
|
+
"""
|
|
12
|
+
Fold Constants if it would reduce the size of the source
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super(FoldConstants, self).__init__()
|
|
17
|
+
|
|
18
|
+
def visit_BinOp(self, node):
|
|
19
|
+
|
|
20
|
+
node.left = self.visit(node.left)
|
|
21
|
+
node.right = self.visit(node.right)
|
|
22
|
+
|
|
23
|
+
# Check this is a constant expression that could be folded
|
|
24
|
+
# We don't try to fold strings or bytes, since they have probably been arranged this way to make the source shorter and we are unlikely to beat that
|
|
25
|
+
if not is_ast_node(node.left, (ast.Num, 'NameConstant')):
|
|
26
|
+
return node
|
|
27
|
+
if not is_ast_node(node.right, (ast.Num, 'NameConstant')):
|
|
28
|
+
return node
|
|
29
|
+
|
|
30
|
+
if isinstance(node.op, ast.Div):
|
|
31
|
+
# Folding div is subtle, since it can have different results in Python 2 and Python 3
|
|
32
|
+
# Do this once target version options have been implemented
|
|
33
|
+
return node
|
|
34
|
+
|
|
35
|
+
if isinstance(node.op, ast.Pow):
|
|
36
|
+
# This can be folded, but it is unlikely to reduce the size of the source
|
|
37
|
+
# It can also be slow to evaluate
|
|
38
|
+
return node
|
|
39
|
+
|
|
40
|
+
# Evaluate the expression
|
|
41
|
+
try:
|
|
42
|
+
original_expression = unparse_expression(node)
|
|
43
|
+
original_value = safe_eval(original_expression)
|
|
44
|
+
except Exception:
|
|
45
|
+
return node
|
|
46
|
+
|
|
47
|
+
# Choose the best representation of the value
|
|
48
|
+
if isinstance(original_value, float) and math.isnan(original_value):
|
|
49
|
+
# There is no nan literal.
|
|
50
|
+
# we could use float('nan'), but that complicates folding as it's not a Constant
|
|
51
|
+
return node
|
|
52
|
+
elif isinstance(original_value, bool):
|
|
53
|
+
new_node = ast.NameConstant(value=original_value)
|
|
54
|
+
elif isinstance(original_value, (int, float, complex)):
|
|
55
|
+
try:
|
|
56
|
+
if repr(original_value).startswith('-') and not sys.version_info < (3, 0):
|
|
57
|
+
# Represent negative numbers as a USub UnaryOp, so that the ast roundtrip is correct
|
|
58
|
+
new_node = ast.UnaryOp(op=ast.USub(), operand=ast.Num(n=-original_value))
|
|
59
|
+
else:
|
|
60
|
+
new_node = ast.Num(n=original_value)
|
|
61
|
+
except Exception:
|
|
62
|
+
# repr(value) failed, most likely due to some limit
|
|
63
|
+
return node
|
|
64
|
+
else:
|
|
65
|
+
return node
|
|
66
|
+
|
|
67
|
+
# Evaluate the new value representation
|
|
68
|
+
try:
|
|
69
|
+
folded_expression = unparse_expression(new_node)
|
|
70
|
+
folded_value = safe_eval(folded_expression)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
# This can happen if the value is too large to be represented as a literal
|
|
73
|
+
# or if the value is unparsed as nan, inf or -inf - which are not valid python literals
|
|
74
|
+
return node
|
|
75
|
+
|
|
76
|
+
if len(folded_expression) >= len(original_expression):
|
|
77
|
+
# Result is not shorter than original expression
|
|
78
|
+
return node
|
|
79
|
+
|
|
80
|
+
# Check the folded expression parses back to the same AST
|
|
81
|
+
try:
|
|
82
|
+
folded_ast = ast.parse(folded_expression, 'folded expression', mode='eval')
|
|
83
|
+
compare_ast(new_node, folded_ast.body)
|
|
84
|
+
except Exception:
|
|
85
|
+
# This can happen if the printed value doesn't parse back to the same AST
|
|
86
|
+
# e.g. complex numbers can be parsed as BinOp
|
|
87
|
+
return node
|
|
88
|
+
|
|
89
|
+
# Check the folded value is the same as the original value
|
|
90
|
+
if not equal_value_and_type(folded_value, original_value):
|
|
91
|
+
return node
|
|
92
|
+
|
|
93
|
+
# New representation is shorter and has the same value, so use it
|
|
94
|
+
return self.add_child(new_node, node.parent, node.namespace)
|
|
95
|
+
|
|
96
|
+
def equal_value_and_type(a, b):
|
|
97
|
+
if type(a) != type(b):
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
if isinstance(a, float) and math.isnan(a) and not math.isnan(b):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
return a == b
|
|
104
|
+
|
|
105
|
+
def safe_eval(expression):
|
|
106
|
+
globals = {}
|
|
107
|
+
locals = {}
|
|
108
|
+
|
|
109
|
+
# This will return the value, or could raise an exception
|
|
110
|
+
return eval(expression, globals, locals)
|
|
111
|
+
|
|
112
|
+
def unparse_expression(node):
|
|
113
|
+
expression_printer = ExpressionPrinter()
|
|
114
|
+
return expression_printer(node)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
|
|
5
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RemoveAnnotations(SuiteTransformer):
|
|
9
|
+
"""
|
|
10
|
+
Remove type annotations from source
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, options):
|
|
14
|
+
assert isinstance(options, RemoveAnnotationsOptions)
|
|
15
|
+
self._options = options
|
|
16
|
+
super(RemoveAnnotations, self).__init__()
|
|
17
|
+
|
|
18
|
+
def __call__(self, node):
|
|
19
|
+
if sys.version_info < (3, 0):
|
|
20
|
+
return node
|
|
21
|
+
return self.visit(node)
|
|
22
|
+
|
|
23
|
+
def visit_FunctionDef(self, node):
|
|
24
|
+
node.args = self.visit_arguments(node.args)
|
|
25
|
+
node.body = self.suite(node.body, parent=node)
|
|
26
|
+
node.decorator_list = [self.visit(d) for d in node.decorator_list]
|
|
27
|
+
|
|
28
|
+
if hasattr(node, 'type_params') and node.type_params is not None:
|
|
29
|
+
node.type_params = [self.visit(t) for t in node.type_params]
|
|
30
|
+
|
|
31
|
+
if hasattr(node, 'returns') and self._options.remove_return_annotations:
|
|
32
|
+
node.returns = None
|
|
33
|
+
|
|
34
|
+
return node
|
|
35
|
+
|
|
36
|
+
def visit_arguments(self, node):
|
|
37
|
+
assert isinstance(node, ast.arguments)
|
|
38
|
+
|
|
39
|
+
if hasattr(node, 'posonlyargs') and node.posonlyargs:
|
|
40
|
+
node.posonlyargs = [self.visit_arg(a) for a in node.posonlyargs]
|
|
41
|
+
|
|
42
|
+
if node.args:
|
|
43
|
+
node.args = [self.visit_arg(a) for a in node.args]
|
|
44
|
+
|
|
45
|
+
if hasattr(node, 'kwonlyargs') and node.kwonlyargs:
|
|
46
|
+
node.kwonlyargs = [self.visit_arg(a) for a in node.kwonlyargs]
|
|
47
|
+
|
|
48
|
+
if hasattr(node, 'varargannotation'):
|
|
49
|
+
if self._options.remove_argument_annotations:
|
|
50
|
+
node.varargannotation = None
|
|
51
|
+
else:
|
|
52
|
+
if node.vararg:
|
|
53
|
+
node.vararg = self.visit_arg(node.vararg)
|
|
54
|
+
|
|
55
|
+
if hasattr(node, 'kwargannotation'):
|
|
56
|
+
if self._options.remove_argument_annotations:
|
|
57
|
+
node.kwargannotation = None
|
|
58
|
+
else:
|
|
59
|
+
if node.kwarg:
|
|
60
|
+
node.kwarg = self.visit_arg(node.kwarg)
|
|
61
|
+
|
|
62
|
+
return node
|
|
63
|
+
|
|
64
|
+
def visit_arg(self, node):
|
|
65
|
+
if self._options.remove_argument_annotations:
|
|
66
|
+
node.annotation = None
|
|
67
|
+
return node
|
|
68
|
+
|
|
69
|
+
def visit_AnnAssign(self, node):
|
|
70
|
+
def is_dataclass_field(node):
|
|
71
|
+
if sys.version_info < (3, 7):
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if not isinstance(node.parent, ast.ClassDef):
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
if len(node.parent.decorator_list) == 0:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
for node in node.parent.decorator_list:
|
|
81
|
+
if isinstance(node, ast.Name) and node.id == 'dataclass':
|
|
82
|
+
return True
|
|
83
|
+
elif isinstance(node, ast.Attribute) and node.attr == 'dataclass':
|
|
84
|
+
return True
|
|
85
|
+
elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'dataclass':
|
|
86
|
+
return True
|
|
87
|
+
elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr == 'dataclass':
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
def is_typing_sensitive(node):
|
|
93
|
+
if sys.version_info < (3, 5):
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
if not isinstance(node.parent, ast.ClassDef):
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
if len(node.parent.bases) == 0:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
tricky_types = ['NamedTuple', 'TypedDict']
|
|
103
|
+
|
|
104
|
+
for node in node.parent.bases:
|
|
105
|
+
if isinstance(node, ast.Name) and node.id in tricky_types:
|
|
106
|
+
return True
|
|
107
|
+
elif isinstance(node, ast. Attribute) and node.attr in tricky_types:
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
# is this a class attribute or a variable?
|
|
113
|
+
if isinstance(node.parent, ast.ClassDef):
|
|
114
|
+
if not self._options.remove_class_attribute_annotations:
|
|
115
|
+
return node
|
|
116
|
+
else:
|
|
117
|
+
if not self._options.remove_variable_annotations:
|
|
118
|
+
return node
|
|
119
|
+
|
|
120
|
+
if is_dataclass_field(node) or is_typing_sensitive(node):
|
|
121
|
+
return node
|
|
122
|
+
elif node.value:
|
|
123
|
+
return self.add_child(ast.Assign([node.target], node.value), parent=node.parent, namespace=node.namespace)
|
|
124
|
+
else:
|
|
125
|
+
# Valueless annotations cause the interpreter to treat the variable as a local.
|
|
126
|
+
# I don't know of another way to do that without assigning to it, so
|
|
127
|
+
# keep it as an AnnAssign, but replace the annotation with '0'
|
|
128
|
+
|
|
129
|
+
node.annotation = self.add_child(ast.Num(0), parent=node.parent, namespace=node.namespace)
|
|
130
|
+
return node
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class RemoveAnnotationsOptions(object):
|
|
2
|
+
"""
|
|
3
|
+
Options for the RemoveAnnotations transform
|
|
4
|
+
|
|
5
|
+
This can be passed to the minify function as the remove_annotations argument
|
|
6
|
+
|
|
7
|
+
:param remove_variable_annotations: Remove variable annotations
|
|
8
|
+
:type remove_variable_annotations: bool
|
|
9
|
+
:param remove_return_annotations: Remove return annotations
|
|
10
|
+
:type remove_return_annotations: bool
|
|
11
|
+
:param remove_argument_annotations: Remove argument annotations
|
|
12
|
+
:type remove_argument_annotations: bool
|
|
13
|
+
:param remove_class_attribute_annotations: Remove class attribute annotations
|
|
14
|
+
:type remove_class_attribute_annotations: bool
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
remove_variable_annotations = True
|
|
18
|
+
remove_return_annotations = True
|
|
19
|
+
remove_argument_annotations = True
|
|
20
|
+
remove_class_attribute_annotations = False
|
|
21
|
+
|
|
22
|
+
def __init__(self, remove_variable_annotations=True, remove_return_annotations=True, remove_argument_annotations=True, remove_class_attribute_annotations=False):
|
|
23
|
+
self.remove_variable_annotations = remove_variable_annotations
|
|
24
|
+
self.remove_return_annotations = remove_return_annotations
|
|
25
|
+
self.remove_argument_annotations = remove_argument_annotations
|
|
26
|
+
self.remove_class_attribute_annotations = remove_class_attribute_annotations
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
return 'RemoveAnnotationsOptions(remove_variable_annotations=%r, remove_return_annotations=%r, remove_argument_annotations=%r, remove_class_attribute_annotations=%r)' % (self.remove_variable_annotations, self.remove_return_annotations, self.remove_argument_annotations, self.remove_class_attribute_annotations)
|
|
30
|
+
|
|
31
|
+
def __nonzero__(self):
|
|
32
|
+
return any((self.remove_variable_annotations, self.remove_return_annotations, self.remove_argument_annotations, self.remove_class_attribute_annotations))
|
|
33
|
+
|
|
34
|
+
def __bool__(self):
|
|
35
|
+
return self.__nonzero__()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class RemoveAnnotationsOptions:
|
|
2
|
+
|
|
3
|
+
remove_variable_annotations: bool
|
|
4
|
+
remove_return_annotations: bool
|
|
5
|
+
remove_argument_annotations: bool
|
|
6
|
+
remove_class_attribute_annotations: bool
|
|
7
|
+
|
|
8
|
+
def __init__(self,
|
|
9
|
+
remove_variable_annotations: bool = ...,
|
|
10
|
+
remove_return_annotations: bool = ...,
|
|
11
|
+
remove_argument_annotations: bool = ...,
|
|
12
|
+
remove_class_attribute_annotations: bool = ...):
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def __repr__(self) -> str: ...
|
|
16
|
+
def __nonzero__(self) -> bool: ...
|
|
17
|
+
def __bool__(self) -> bool: ...
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
|
|
3
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
4
|
+
from python_minifier.util import is_ast_node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RemoveAsserts(SuiteTransformer):
|
|
8
|
+
"""
|
|
9
|
+
Remove assert statements
|
|
10
|
+
|
|
11
|
+
If a statement is syntactically necessary, use an empty expression instead
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __call__(self, node):
|
|
15
|
+
return self.visit(node)
|
|
16
|
+
|
|
17
|
+
def suite(self, node_list, parent):
|
|
18
|
+
without_assert = [self.visit(a) for a in filter(lambda n: not is_ast_node(n, ast.Assert), node_list)]
|
|
19
|
+
|
|
20
|
+
if len(without_assert) == 0:
|
|
21
|
+
if isinstance(parent, ast.Module):
|
|
22
|
+
return []
|
|
23
|
+
else:
|
|
24
|
+
return [self.add_child(ast.Expr(value=ast.Num(0)), parent=parent)]
|
|
25
|
+
|
|
26
|
+
return without_assert
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
5
|
+
from python_minifier.util import is_ast_node
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RemoveDebug(SuiteTransformer):
|
|
9
|
+
"""
|
|
10
|
+
Remove if statements where the condition tests __debug__ is True
|
|
11
|
+
|
|
12
|
+
If a statement is syntactically necessary, use an empty expression instead
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __call__(self, node):
|
|
16
|
+
return self.visit(node)
|
|
17
|
+
|
|
18
|
+
def constant_value(self, node):
|
|
19
|
+
if sys.version_info < (3, 4):
|
|
20
|
+
return node.id == 'True'
|
|
21
|
+
elif is_ast_node(node, 'NameConstant'):
|
|
22
|
+
return node.value
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
def can_remove(self, node):
|
|
26
|
+
if not isinstance(node, ast.If):
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
if isinstance(node.test, ast.Name) and node.test.id == '__debug__':
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.Is) and self.constant_value(node.test.comparators[0]) is True:
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.IsNot) and self.constant_value(node.test.comparators[0]) is False:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.Eq) and self.constant_value(node.test.comparators[0]) is True:
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def suite(self, node_list, parent):
|
|
44
|
+
|
|
45
|
+
without_debug = [self.visit(a) for a in filter(lambda n: not self.can_remove(n), node_list)]
|
|
46
|
+
|
|
47
|
+
if len(without_debug) == 0:
|
|
48
|
+
if isinstance(parent, ast.Module):
|
|
49
|
+
return []
|
|
50
|
+
else:
|
|
51
|
+
return [self.add_child(ast.Expr(value=ast.Num(0)), parent=parent)]
|
|
52
|
+
|
|
53
|
+
return without_debug
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Remove Call nodes that are only used to raise exceptions with no arguments
|
|
3
|
+
|
|
4
|
+
If a Raise statement is used on a Name and the name refers to an exception, it is automatically instantiated with no arguments
|
|
5
|
+
We can remove any Call nodes that are only used to raise exceptions with no arguments and let the Raise statement do the instantiation.
|
|
6
|
+
When printed, this essentially removes the brackets from the exception name.
|
|
7
|
+
|
|
8
|
+
We can't generally know if a name refers to an exception, so we only do this for builtin exceptions
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import python_minifier.ast_compat as ast
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from python_minifier.rename.binding import BuiltinBinding
|
|
15
|
+
|
|
16
|
+
# These are always exceptions, in every version of python
|
|
17
|
+
builtin_exceptions = [
|
|
18
|
+
'SyntaxError', 'Exception', 'ValueError', 'BaseException', 'MemoryError', 'RuntimeError', 'DeprecationWarning', 'UnicodeEncodeError', 'KeyError', 'LookupError', 'TypeError', 'BufferError',
|
|
19
|
+
'ImportError', 'OSError', 'StopIteration', 'ArithmeticError', 'UserWarning', 'PendingDeprecationWarning', 'RuntimeWarning', 'IndentationError', 'UnicodeTranslateError', 'UnboundLocalError',
|
|
20
|
+
'AttributeError', 'EOFError', 'UnicodeWarning', 'BytesWarning', 'NameError', 'IndexError', 'TabError', 'SystemError', 'OverflowError', 'FutureWarning', 'SystemExit', 'Warning',
|
|
21
|
+
'FloatingPointError', 'ReferenceError', 'UnicodeError', 'AssertionError', 'SyntaxWarning', 'UnicodeDecodeError', 'GeneratorExit', 'ImportWarning', 'KeyboardInterrupt', 'ZeroDivisionError',
|
|
22
|
+
'NotImplementedError'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# These are exceptions only in python 2.7
|
|
26
|
+
builtin_exceptions_2_7 = [
|
|
27
|
+
'IOError',
|
|
28
|
+
'StandardError',
|
|
29
|
+
'EnvironmentError',
|
|
30
|
+
'VMSError',
|
|
31
|
+
'WindowsError'
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# These are exceptions in 3.3+
|
|
35
|
+
builtin_exceptions_3_3 = [
|
|
36
|
+
'ChildProcessError',
|
|
37
|
+
'ConnectionError',
|
|
38
|
+
'BrokenPipeError',
|
|
39
|
+
'ConnectionAbortedError',
|
|
40
|
+
'ConnectionRefusedError',
|
|
41
|
+
'ConnectionResetError',
|
|
42
|
+
'FileExistsError',
|
|
43
|
+
'FileNotFoundError',
|
|
44
|
+
'InterruptedError',
|
|
45
|
+
'IsADirectoryError',
|
|
46
|
+
'NotADirectoryError',
|
|
47
|
+
'PermissionError',
|
|
48
|
+
'ProcessLookupError',
|
|
49
|
+
'TimeoutError',
|
|
50
|
+
'ResourceWarning',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# These are exceptions in 3.5+
|
|
54
|
+
builtin_exceptions_3_5 = [
|
|
55
|
+
'StopAsyncIteration',
|
|
56
|
+
'RecursionError',
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
# These are exceptions in 3.6+
|
|
60
|
+
builtin_exceptions_3_6 = [
|
|
61
|
+
'ModuleNotFoundError'
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
# These are exceptions in 3.10+
|
|
65
|
+
builtin_exceptions_3_10 = [
|
|
66
|
+
'EncodingWarning'
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# These are exceptions in 3.11+
|
|
70
|
+
builtin_exceptions_3_11 = [
|
|
71
|
+
'BaseExceptionGroup',
|
|
72
|
+
'ExceptionGroup',
|
|
73
|
+
'BaseExceptionGroup',
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def _remove_empty_call(binding):
|
|
77
|
+
assert isinstance(binding, BuiltinBinding)
|
|
78
|
+
|
|
79
|
+
for name_node in binding.references:
|
|
80
|
+
# For this to be a builtin, all references must be name nodes as it is not defined anywhere
|
|
81
|
+
assert isinstance(name_node, ast.Name) and isinstance(name_node.ctx, ast.Load)
|
|
82
|
+
|
|
83
|
+
if not isinstance(name_node.parent, ast.Call):
|
|
84
|
+
# This is not a call
|
|
85
|
+
continue
|
|
86
|
+
call_node = name_node.parent
|
|
87
|
+
|
|
88
|
+
if not isinstance(call_node.parent, ast.Raise):
|
|
89
|
+
# This is not a raise statement
|
|
90
|
+
continue
|
|
91
|
+
raise_node = call_node.parent
|
|
92
|
+
|
|
93
|
+
if len(call_node.args) > 0 or len(call_node.keywords) > 0:
|
|
94
|
+
# This is a call with arguments
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# This is an instance of the exception being called with no arguments
|
|
98
|
+
# let's replace it with just the name, cutting out the Call node
|
|
99
|
+
|
|
100
|
+
if raise_node.exc is call_node:
|
|
101
|
+
raise_node.exc = name_node
|
|
102
|
+
elif raise_node.cause is call_node:
|
|
103
|
+
raise_node.cause = name_node
|
|
104
|
+
name_node.parent = raise_node
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def remove_no_arg_exception_call(module):
|
|
108
|
+
assert isinstance(module, ast.Module)
|
|
109
|
+
|
|
110
|
+
if sys.version_info < (3, 0):
|
|
111
|
+
return module
|
|
112
|
+
|
|
113
|
+
for binding in module.bindings:
|
|
114
|
+
if not isinstance(binding, BuiltinBinding):
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
if binding.is_redefined():
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
if binding.name in builtin_exceptions:
|
|
121
|
+
# We can remove any calls to builtin exceptions
|
|
122
|
+
_remove_empty_call(binding)
|
|
123
|
+
|
|
124
|
+
return module
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
5
|
+
from python_minifier.util import is_ast_node
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RemoveExplicitReturnNone(SuiteTransformer):
|
|
9
|
+
def __call__(self, node):
|
|
10
|
+
return self.visit(node)
|
|
11
|
+
|
|
12
|
+
def visit_Return(self, node):
|
|
13
|
+
assert isinstance(node, ast.Return)
|
|
14
|
+
|
|
15
|
+
# Transform `return None` -> `return`
|
|
16
|
+
|
|
17
|
+
if sys.version_info < (3, 4) and isinstance(node.value, ast.Name) and node.value.id == 'None':
|
|
18
|
+
node.value = None
|
|
19
|
+
|
|
20
|
+
elif sys.version_info >= (3, 4) and is_ast_node(node.value, 'NameConstant') and node.value.value is None:
|
|
21
|
+
node.value = None
|
|
22
|
+
|
|
23
|
+
return node
|
|
24
|
+
|
|
25
|
+
def visit_FunctionDef(self, node):
|
|
26
|
+
assert is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef'))
|
|
27
|
+
|
|
28
|
+
node.body = [self.visit(a) for a in node.body]
|
|
29
|
+
|
|
30
|
+
# Remove an explicit valueless `return` from the end of a function
|
|
31
|
+
if len(node.body) > 0 and isinstance(node.body[-1], ast.Return) and node.body[-1].value is None:
|
|
32
|
+
node.body.pop()
|
|
33
|
+
|
|
34
|
+
# Replace empty suites with `0` expression statements
|
|
35
|
+
if len(node.body) == 0:
|
|
36
|
+
node.body = [self.add_child(ast.Expr(value=ast.Num(0)), parent=node)]
|
|
37
|
+
|
|
38
|
+
return node
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
|
|
3
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
4
|
+
from python_minifier.util import is_ast_node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_doc(node):
|
|
8
|
+
|
|
9
|
+
if isinstance(node, ast.Attribute):
|
|
10
|
+
if node.attr == '__doc__':
|
|
11
|
+
raise ValueError('__doc__ found!')
|
|
12
|
+
|
|
13
|
+
for child in ast.iter_child_nodes(node):
|
|
14
|
+
find_doc(child)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _doc_in_module(module):
|
|
18
|
+
try:
|
|
19
|
+
find_doc(module)
|
|
20
|
+
return False
|
|
21
|
+
except:
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RemoveLiteralStatements(SuiteTransformer):
|
|
26
|
+
"""
|
|
27
|
+
Remove literal expressions from the code
|
|
28
|
+
|
|
29
|
+
This includes docstrings
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __call__(self, node):
|
|
33
|
+
if _doc_in_module(node):
|
|
34
|
+
return node
|
|
35
|
+
return self.visit(node)
|
|
36
|
+
|
|
37
|
+
def visit_Module(self, node):
|
|
38
|
+
for binding in node.bindings:
|
|
39
|
+
if binding.name == '__doc__':
|
|
40
|
+
node.body = [self.visit(a) for a in node.body]
|
|
41
|
+
return node
|
|
42
|
+
|
|
43
|
+
node.body = self.suite(node.body, parent=node)
|
|
44
|
+
return node
|
|
45
|
+
|
|
46
|
+
def is_literal_statement(self, node):
|
|
47
|
+
if not isinstance(node, ast.Expr):
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
return is_ast_node(node.value, (ast.Num, ast.Str, 'NameConstant', 'Bytes'))
|
|
51
|
+
|
|
52
|
+
def suite(self, node_list, parent):
|
|
53
|
+
without_literals = [self.visit(n) for n in node_list if not self.is_literal_statement(n)]
|
|
54
|
+
|
|
55
|
+
if len(without_literals) == 0:
|
|
56
|
+
if isinstance(parent, ast.Module):
|
|
57
|
+
return []
|
|
58
|
+
else:
|
|
59
|
+
return [self.add_child(ast.Expr(value=ast.Num(0)), parent=parent)]
|
|
60
|
+
|
|
61
|
+
return without_literals
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RemoveObject(SuiteTransformer):
|
|
8
|
+
def __call__(self, node):
|
|
9
|
+
if sys.version_info < (3, 0):
|
|
10
|
+
return node
|
|
11
|
+
|
|
12
|
+
return self.visit(node)
|
|
13
|
+
|
|
14
|
+
def visit_ClassDef(self, node):
|
|
15
|
+
node.bases = [
|
|
16
|
+
b for b in node.bases if not isinstance(b, ast.Name) or (isinstance(b, ast.Name) and b.id != 'object')
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
if hasattr(node, 'type_params') and node.type_params is not None:
|
|
20
|
+
node.type_params = [self.visit(t) for t in node.type_params]
|
|
21
|
+
|
|
22
|
+
node.body = [self.visit(n) for n in node.body]
|
|
23
|
+
|
|
24
|
+
return node
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
|
|
3
|
+
from python_minifier.transforms.suite_transformer import SuiteTransformer
|
|
4
|
+
from python_minifier.util import is_ast_node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RemovePass(SuiteTransformer):
|
|
8
|
+
"""
|
|
9
|
+
Remove Pass keywords from source
|
|
10
|
+
|
|
11
|
+
If a statement is syntactically necessary, use an empty expression instead
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __call__(self, node):
|
|
15
|
+
return self.visit(node)
|
|
16
|
+
|
|
17
|
+
def suite(self, node_list, parent):
|
|
18
|
+
without_pass = [self.visit(a) for a in filter(lambda n: not is_ast_node(n, ast.Pass), node_list)]
|
|
19
|
+
|
|
20
|
+
if len(without_pass) == 0:
|
|
21
|
+
if isinstance(parent, ast.Module):
|
|
22
|
+
return []
|
|
23
|
+
else:
|
|
24
|
+
return [self.add_child(ast.Expr(value=ast.Num(0)), parent=parent)]
|
|
25
|
+
|
|
26
|
+
return without_pass
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def remove_posargs(node):
|
|
5
|
+
if isinstance(node, ast.arguments):
|
|
6
|
+
if hasattr(node, 'posonlyargs'):
|
|
7
|
+
node.args = node.posonlyargs + node.args
|
|
8
|
+
node.posonlyargs = []
|
|
9
|
+
|
|
10
|
+
for child in ast.iter_child_nodes(node):
|
|
11
|
+
remove_posargs(child)
|
|
12
|
+
|
|
13
|
+
return node
|