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.
Files changed (44) hide show
  1. python_minifier/__init__.py +269 -0
  2. python_minifier/__init__.pyi +37 -0
  3. python_minifier/__main__.py +332 -0
  4. python_minifier/ast_compare.py +104 -0
  5. python_minifier/ast_compat.py +40 -0
  6. python_minifier/ast_printer.py +130 -0
  7. python_minifier/expression_printer.py +753 -0
  8. python_minifier/f_string.py +446 -0
  9. python_minifier/ministring.py +179 -0
  10. python_minifier/module_printer.py +862 -0
  11. python_minifier/py.typed +0 -0
  12. python_minifier/rename/__init__.py +6 -0
  13. python_minifier/rename/bind_names.py +192 -0
  14. python_minifier/rename/binding.py +495 -0
  15. python_minifier/rename/mapper.py +175 -0
  16. python_minifier/rename/name_generator.py +51 -0
  17. python_minifier/rename/rename_literals.py +249 -0
  18. python_minifier/rename/renamer.py +230 -0
  19. python_minifier/rename/resolve_names.py +106 -0
  20. python_minifier/rename/util.py +203 -0
  21. python_minifier/token_printer.py +299 -0
  22. python_minifier/transforms/__init__.py +0 -0
  23. python_minifier/transforms/combine_imports.py +76 -0
  24. python_minifier/transforms/constant_folding.py +114 -0
  25. python_minifier/transforms/remove_annotations.py +130 -0
  26. python_minifier/transforms/remove_annotations_options.py +35 -0
  27. python_minifier/transforms/remove_annotations_options.pyi +17 -0
  28. python_minifier/transforms/remove_asserts.py +26 -0
  29. python_minifier/transforms/remove_debug.py +53 -0
  30. python_minifier/transforms/remove_exception_brackets.py +124 -0
  31. python_minifier/transforms/remove_explicit_return_none.py +38 -0
  32. python_minifier/transforms/remove_literal_statements.py +61 -0
  33. python_minifier/transforms/remove_object_base.py +24 -0
  34. python_minifier/transforms/remove_pass.py +26 -0
  35. python_minifier/transforms/remove_posargs.py +13 -0
  36. python_minifier/transforms/suite_transformer.py +196 -0
  37. python_minifier/util.py +48 -0
  38. python_minifier-2.11.2.dist-info/LICENSE +21 -0
  39. python_minifier-2.11.2.dist-info/METADATA +177 -0
  40. python_minifier-2.11.2.dist-info/RECORD +44 -0
  41. python_minifier-2.11.2.dist-info/WHEEL +5 -0
  42. python_minifier-2.11.2.dist-info/entry_points.txt +3 -0
  43. python_minifier-2.11.2.dist-info/top_level.txt +1 -0
  44. 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