python-minifier
Advanced tools
| """ | ||
| The is a backwards compatible shim for the ast module. | ||
| This is the best way to make the ast module work the same in both python 2 and 3. | ||
| This is essentially what the ast module was doing until 3.12, when it started throwing | ||
| deprecation warnings. | ||
| """ | ||
| from ast import * | ||
| # Ideally we don't import anything else | ||
| if 'TypeAlias' in globals(): | ||
| # Add n and s properties to Constant so it can stand in for Num, Str and Bytes | ||
| Constant.n = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment] | ||
| Constant.s = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment] | ||
| # These classes are redefined from the ones in ast that complain about deprecation | ||
| # They will continue to work once they are removed from ast | ||
| class Str(Constant): # type: ignore[no-redef] | ||
| def __new__(cls, s, *args, **kwargs): | ||
| return Constant(value=s, *args, **kwargs) | ||
| class Bytes(Constant): # type: ignore[no-redef] | ||
| def __new__(cls, s, *args, **kwargs): | ||
| return Constant(value=s, *args, **kwargs) | ||
| class Num(Constant): # type: ignore[no-redef] | ||
| def __new__(cls, n, *args, **kwargs): | ||
| return Constant(value=n, *args, **kwargs) | ||
| class NameConstant(Constant): # type: ignore[no-redef] | ||
| def __new__(cls, *args, **kwargs): | ||
| return Constant(*args, **kwargs) | ||
| class Ellipsis(Constant): # type: ignore[no-redef] | ||
| def __new__(cls, *args, **kwargs): | ||
| return Constant(value=literal_eval('...'), *args, **kwargs) |
| """ | ||
| Print a representation of an AST | ||
| This prints a human readable representation of the nodes in the AST. | ||
| The goal is to make it easy to see what the AST looks like, and to | ||
| make it easy to compare two ASTs. | ||
| This is not intended to be a complete representation of the AST, some | ||
| fields or field names may be omitted for clarity. It should still be precise and unambiguous. | ||
| """ | ||
| import python_minifier.ast_compat as ast | ||
| from python_minifier.util import is_ast_node | ||
| INDENT = ' ' | ||
| # The field name that can be omitted for each node | ||
| # Either it's the only field or would otherwise be obvious | ||
| default_fields = { | ||
| 'Constant': 'value', | ||
| 'Num': 'n', | ||
| 'Str': 's', | ||
| 'Bytes': 's', | ||
| 'NameConstant': 'value', | ||
| 'FormattedValue': 'value', | ||
| 'JoinedStr': 'values', | ||
| 'List': 'elts', | ||
| 'Tuple': 'elts', | ||
| 'Set': 'elts', | ||
| 'Name': 'id', | ||
| 'Expr': 'value', | ||
| 'UnaryOp': 'op', | ||
| 'BinOp': 'op', | ||
| 'BoolOp': 'op', | ||
| 'Call': 'func', | ||
| 'Index': 'value', | ||
| 'ExtSlice': 'dims', | ||
| 'Assert': 'test', | ||
| 'Delete': 'targets', | ||
| 'Import': 'names', | ||
| 'If': 'test', | ||
| 'While': 'test', | ||
| 'Try': 'handlers', | ||
| 'TryExcept': 'handlers', | ||
| 'With': 'items', | ||
| 'withitem': 'context_expr', | ||
| 'FunctionDef': 'name', | ||
| 'arg': 'arg', | ||
| 'Return': 'value', | ||
| 'Yield': 'value', | ||
| 'YieldFrom': 'value', | ||
| 'Global': 'names', | ||
| 'Nonlocal': 'names', | ||
| 'ClassDef': 'name', | ||
| 'AsyncFunctionDef': 'name', | ||
| 'Await': 'value', | ||
| 'AsyncWith': 'items', | ||
| 'Raise': 'exc', | ||
| 'Subscript': 'value', | ||
| 'Attribute': 'value', | ||
| 'AugAssign': 'op', | ||
| } | ||
| def is_literal(node, field): | ||
| if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value': | ||
| return True | ||
| if is_ast_node(node, ast.Num) and field == 'n': | ||
| return True | ||
| if is_ast_node(node, ast.Str) and field == 's': | ||
| return True | ||
| if is_ast_node(node, 'Bytes') and field == 's': | ||
| return True | ||
| if is_ast_node(node, 'NameConstant') and field == 'value': | ||
| return True | ||
| return False | ||
| def print_ast(node): | ||
| if not isinstance(node, ast.AST): | ||
| return repr(node) | ||
| s = '' | ||
| node_name = node.__class__.__name__ | ||
| s += node_name | ||
| s += '(' | ||
| first = True | ||
| for field, value in ast.iter_fields(node): | ||
| if not value and not is_literal(node, field): | ||
| # Don't bother printing fields that are empty, except for literals | ||
| continue | ||
| if field == 'ctx': | ||
| # Don't print the ctx, it's always apparent from context | ||
| continue | ||
| if first: | ||
| first = False | ||
| else: | ||
| s += ', ' | ||
| if default_fields.get(node_name) != field: | ||
| s += field + '=' | ||
| if isinstance(value, ast.AST): | ||
| s += print_ast(value) | ||
| elif isinstance(value, list): | ||
| s += '[' | ||
| first_list = True | ||
| for item in value: | ||
| if first_list: | ||
| first_list = False | ||
| else: | ||
| s += ',' | ||
| for line in print_ast(item).splitlines(): | ||
| s += '\n' + INDENT + line | ||
| s += '\n]' | ||
| else: | ||
| s += repr(value) | ||
| s += ')' | ||
| return s |
| import python_minifier.ast_compat as ast | ||
| import math | ||
| import sys | ||
| from python_minifier.ast_compare import compare_ast | ||
| from python_minifier.expression_printer import ExpressionPrinter | ||
| from python_minifier.transforms.suite_transformer import SuiteTransformer | ||
| from python_minifier.util import is_ast_node | ||
| class FoldConstants(SuiteTransformer): | ||
| """ | ||
| Fold Constants if it would reduce the size of the source | ||
| """ | ||
| def __init__(self): | ||
| super(FoldConstants, self).__init__() | ||
| def visit_BinOp(self, node): | ||
| node.left = self.visit(node.left) | ||
| node.right = self.visit(node.right) | ||
| # Check this is a constant expression that could be folded | ||
| # 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 | ||
| if not is_ast_node(node.left, (ast.Num, 'NameConstant')): | ||
| return node | ||
| if not is_ast_node(node.right, (ast.Num, 'NameConstant')): | ||
| return node | ||
| if isinstance(node.op, ast.Div): | ||
| # Folding div is subtle, since it can have different results in Python 2 and Python 3 | ||
| # Do this once target version options have been implemented | ||
| return node | ||
| if isinstance(node.op, ast.Pow): | ||
| # This can be folded, but it is unlikely to reduce the size of the source | ||
| # It can also be slow to evaluate | ||
| return node | ||
| # Evaluate the expression | ||
| try: | ||
| original_expression = unparse_expression(node) | ||
| original_value = safe_eval(original_expression) | ||
| except Exception: | ||
| return node | ||
| # Choose the best representation of the value | ||
| if isinstance(original_value, float) and math.isnan(original_value): | ||
| # There is no nan literal. | ||
| # we could use float('nan'), but that complicates folding as it's not a Constant | ||
| return node | ||
| elif isinstance(original_value, bool): | ||
| new_node = ast.NameConstant(value=original_value) | ||
| elif isinstance(original_value, (int, float, complex)): | ||
| try: | ||
| if repr(original_value).startswith('-') and not sys.version_info < (3, 0): | ||
| # Represent negative numbers as a USub UnaryOp, so that the ast roundtrip is correct | ||
| new_node = ast.UnaryOp(op=ast.USub(), operand=ast.Num(n=-original_value)) | ||
| else: | ||
| new_node = ast.Num(n=original_value) | ||
| except Exception: | ||
| # repr(value) failed, most likely due to some limit | ||
| return node | ||
| else: | ||
| return node | ||
| # Evaluate the new value representation | ||
| try: | ||
| folded_expression = unparse_expression(new_node) | ||
| folded_value = safe_eval(folded_expression) | ||
| except Exception as e: | ||
| # This can happen if the value is too large to be represented as a literal | ||
| # or if the value is unparsed as nan, inf or -inf - which are not valid python literals | ||
| return node | ||
| if len(folded_expression) >= len(original_expression): | ||
| # Result is not shorter than original expression | ||
| return node | ||
| # Check the folded expression parses back to the same AST | ||
| try: | ||
| folded_ast = ast.parse(folded_expression, 'folded expression', mode='eval') | ||
| compare_ast(new_node, folded_ast.body) | ||
| except Exception: | ||
| # This can happen if the printed value doesn't parse back to the same AST | ||
| # e.g. complex numbers can be parsed as BinOp | ||
| return node | ||
| # Check the folded value is the same as the original value | ||
| if not equal_value_and_type(folded_value, original_value): | ||
| return node | ||
| # New representation is shorter and has the same value, so use it | ||
| return self.add_child(new_node, node.parent, node.namespace) | ||
| def equal_value_and_type(a, b): | ||
| if type(a) != type(b): | ||
| return False | ||
| if isinstance(a, float) and math.isnan(a) and not math.isnan(b): | ||
| return False | ||
| return a == b | ||
| def safe_eval(expression): | ||
| globals = {} | ||
| locals = {} | ||
| # This will return the value, or could raise an exception | ||
| return eval(expression, globals, locals) | ||
| def unparse_expression(node): | ||
| expression_printer = ExpressionPrinter() | ||
| return expression_printer(node) |
| class RemoveAnnotationsOptions(object): | ||
| """ | ||
| Options for the RemoveAnnotations transform | ||
| This can be passed to the minify function as the remove_annotations argument | ||
| :param remove_variable_annotations: Remove variable annotations | ||
| :type remove_variable_annotations: bool | ||
| :param remove_return_annotations: Remove return annotations | ||
| :type remove_return_annotations: bool | ||
| :param remove_argument_annotations: Remove argument annotations | ||
| :type remove_argument_annotations: bool | ||
| :param remove_class_attribute_annotations: Remove class attribute annotations | ||
| :type remove_class_attribute_annotations: bool | ||
| """ | ||
| remove_variable_annotations = True | ||
| remove_return_annotations = True | ||
| remove_argument_annotations = True | ||
| remove_class_attribute_annotations = False | ||
| def __init__(self, remove_variable_annotations=True, remove_return_annotations=True, remove_argument_annotations=True, remove_class_attribute_annotations=False): | ||
| self.remove_variable_annotations = remove_variable_annotations | ||
| self.remove_return_annotations = remove_return_annotations | ||
| self.remove_argument_annotations = remove_argument_annotations | ||
| self.remove_class_attribute_annotations = remove_class_attribute_annotations | ||
| def __repr__(self): | ||
| 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) | ||
| def __nonzero__(self): | ||
| return any((self.remove_variable_annotations, self.remove_return_annotations, self.remove_argument_annotations, self.remove_class_attribute_annotations)) | ||
| def __bool__(self): | ||
| return self.__nonzero__() |
| class RemoveAnnotationsOptions: | ||
| remove_variable_annotations: bool | ||
| remove_return_annotations: bool | ||
| remove_argument_annotations: bool | ||
| remove_class_attribute_annotations: bool | ||
| def __init__(self, | ||
| remove_variable_annotations: bool = ..., | ||
| remove_return_annotations: bool = ..., | ||
| remove_argument_annotations: bool = ..., | ||
| remove_class_attribute_annotations: bool = ...): | ||
| ... | ||
| def __repr__(self) -> str: ... | ||
| def __nonzero__(self) -> bool: ... | ||
| def __bool__(self) -> bool: ... |
| """ | ||
| Remove Call nodes that are only used to raise exceptions with no arguments | ||
| If a Raise statement is used on a Name and the name refers to an exception, it is automatically instantiated with no arguments | ||
| We can remove any Call nodes that are only used to raise exceptions with no arguments and let the Raise statement do the instantiation. | ||
| When printed, this essentially removes the brackets from the exception name. | ||
| We can't generally know if a name refers to an exception, so we only do this for builtin exceptions | ||
| """ | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
| from python_minifier.rename.binding import BuiltinBinding | ||
| # These are always exceptions, in every version of python | ||
| builtin_exceptions = [ | ||
| 'SyntaxError', 'Exception', 'ValueError', 'BaseException', 'MemoryError', 'RuntimeError', 'DeprecationWarning', 'UnicodeEncodeError', 'KeyError', 'LookupError', 'TypeError', 'BufferError', | ||
| 'ImportError', 'OSError', 'StopIteration', 'ArithmeticError', 'UserWarning', 'PendingDeprecationWarning', 'RuntimeWarning', 'IndentationError', 'UnicodeTranslateError', 'UnboundLocalError', | ||
| 'AttributeError', 'EOFError', 'UnicodeWarning', 'BytesWarning', 'NameError', 'IndexError', 'TabError', 'SystemError', 'OverflowError', 'FutureWarning', 'SystemExit', 'Warning', | ||
| 'FloatingPointError', 'ReferenceError', 'UnicodeError', 'AssertionError', 'SyntaxWarning', 'UnicodeDecodeError', 'GeneratorExit', 'ImportWarning', 'KeyboardInterrupt', 'ZeroDivisionError', | ||
| 'NotImplementedError' | ||
| ] | ||
| # These are exceptions only in python 2.7 | ||
| builtin_exceptions_2_7 = [ | ||
| 'IOError', | ||
| 'StandardError', | ||
| 'EnvironmentError', | ||
| 'VMSError', | ||
| 'WindowsError' | ||
| ] | ||
| # These are exceptions in 3.3+ | ||
| builtin_exceptions_3_3 = [ | ||
| 'ChildProcessError', | ||
| 'ConnectionError', | ||
| 'BrokenPipeError', | ||
| 'ConnectionAbortedError', | ||
| 'ConnectionRefusedError', | ||
| 'ConnectionResetError', | ||
| 'FileExistsError', | ||
| 'FileNotFoundError', | ||
| 'InterruptedError', | ||
| 'IsADirectoryError', | ||
| 'NotADirectoryError', | ||
| 'PermissionError', | ||
| 'ProcessLookupError', | ||
| 'TimeoutError', | ||
| 'ResourceWarning', | ||
| ] | ||
| # These are exceptions in 3.5+ | ||
| builtin_exceptions_3_5 = [ | ||
| 'StopAsyncIteration', | ||
| 'RecursionError', | ||
| ] | ||
| # These are exceptions in 3.6+ | ||
| builtin_exceptions_3_6 = [ | ||
| 'ModuleNotFoundError' | ||
| ] | ||
| # These are exceptions in 3.10+ | ||
| builtin_exceptions_3_10 = [ | ||
| 'EncodingWarning' | ||
| ] | ||
| # These are exceptions in 3.11+ | ||
| builtin_exceptions_3_11 = [ | ||
| 'BaseExceptionGroup', | ||
| 'ExceptionGroup', | ||
| 'BaseExceptionGroup', | ||
| ] | ||
| def _remove_empty_call(binding): | ||
| assert isinstance(binding, BuiltinBinding) | ||
| for name_node in binding.references: | ||
| # For this to be a builtin, all references must be name nodes as it is not defined anywhere | ||
| assert isinstance(name_node, ast.Name) and isinstance(name_node.ctx, ast.Load) | ||
| if not isinstance(name_node.parent, ast.Call): | ||
| # This is not a call | ||
| continue | ||
| call_node = name_node.parent | ||
| if not isinstance(call_node.parent, ast.Raise): | ||
| # This is not a raise statement | ||
| continue | ||
| raise_node = call_node.parent | ||
| if len(call_node.args) > 0 or len(call_node.keywords) > 0: | ||
| # This is a call with arguments | ||
| continue | ||
| # This is an instance of the exception being called with no arguments | ||
| # let's replace it with just the name, cutting out the Call node | ||
| if raise_node.exc is call_node: | ||
| raise_node.exc = name_node | ||
| elif raise_node.cause is call_node: | ||
| raise_node.cause = name_node | ||
| name_node.parent = raise_node | ||
| def remove_no_arg_exception_call(module): | ||
| assert isinstance(module, ast.Module) | ||
| if sys.version_info < (3, 0): | ||
| return module | ||
| for binding in module.bindings: | ||
| if not isinstance(binding, BuiltinBinding): | ||
| continue | ||
| if binding.is_redefined(): | ||
| continue | ||
| if binding.name in builtin_exceptions: | ||
| # We can remove any calls to builtin exceptions | ||
| _remove_empty_call(binding) | ||
| return module |
| import sys | ||
| import pytest | ||
| from helpers import assert_namespace_tree | ||
| def test_module_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| name_in_module = True | ||
| name_in_module = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - BuiltinBinding(name='True', allow_rename=True) <references=2> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_lambda_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| name_in_module = 0 | ||
| b = lambda arg, *args, **kwargs: arg + 1 | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='b', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=1> | ||
| + Lambda | ||
| - NameBinding(name='arg', allow_rename=False) <references=2> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_function_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| name_in_module = True | ||
| def func(arg, *args, **kwargs): | ||
| name_in_func = True | ||
| print(name_in_module) | ||
| def inner_func(): | ||
| print(name_in_module) | ||
| name_in_inner_func = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - BuiltinBinding(name='True', allow_rename=True) <references=3> | ||
| - NameBinding(name='func', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=3> | ||
| + Function func | ||
| - NameBinding(name='arg', allow_rename=True) <references=1> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='inner_func', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_func', allow_rename=True) <references=1> | ||
| + Function inner_func | ||
| - NameBinding(name='name_in_inner_func', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # region generator namespace | ||
| def test_generator_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| a = (x for x in range(10)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_generator_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = (x for x in f for x in x) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_generator_namespace_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = (c for line in file for c in line) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_generator(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = (c for c in (line for line in file)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_generator_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = '' | ||
| a = (x for x in (x for x in x)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region setcomp | ||
| def test_setcomp_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| a = {x for x in range(10)} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_setcomp_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = {x for x in f for x in x} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_setcomp_namespace_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c for line in file for c in line} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_setcomp(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c for c in {line for line in file}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_setcomp_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = '' | ||
| a = {x for x in {x for x in x}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region listcomp | ||
| def test_listcomp_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| a = [x for x in range(10)] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_listcomp_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = [x for x in f for x in x] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=5> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_listcomp_namespace_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = [c for line in file for c in line] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=3> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_listcomp(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a =[c for c in [line for line in file]] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=3> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_listcomp_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = '' | ||
| a =[x for x in [x for x in x]] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=6> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region dictcomp | ||
| def test_dictcomp_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| a = {x: x for x in range(10)} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_dictcomp_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = {x: x for x, x in f for x, x in x} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=7> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_dictcomp_namespace_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c: c for line, line in file for c, c in line} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='c', allow_rename=True) <references=4> | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_dictcomp(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c: c for c, c in {line: line for line in file}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=False) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='c', allow_rename=True) <references=4> | ||
| + DictComp | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_dictcomp_2(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| x = {} | ||
| a = {x:x for x, x in {x: x for x in x}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| def test_class_namespace(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| OhALongName = 'Hello' | ||
| OhALongName = 'Hello' | ||
| MyOtherName = 'World' | ||
| def func(): | ||
| class C: | ||
| OhALongName = ' World' | ||
| MyOtherName = OhALongName | ||
| ClassName = 0 | ||
| func() | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyOtherName', allow_rename=True) <references=1> | ||
| - NameBinding(name='OhALongName', allow_rename=False) <references=4> | ||
| - NameBinding(name='func', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='C', allow_rename=True) <references=1> | ||
| + Class C | ||
| - nonlocal OhALongName | ||
| - NameBinding(name='ClassName', allow_rename=False) <references=1> | ||
| - NameBinding(name='MyOtherName', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_name_rebinding(): | ||
| if sys.version_info >= (3, 0): | ||
| pytest.skip('Test is for python 2 only') | ||
| source = ''' | ||
| OhALongName = 'Hello' | ||
| OhALongName = 'Hello' | ||
| MyOtherName = 'World' | ||
| def func(): | ||
| class C: | ||
| OhALongName = OhALongName + ' World' | ||
| MyOtherName = OhALongName | ||
| func() | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyOtherName', allow_rename=True) <references=1> | ||
| - NameBinding(name='OhALongName', allow_rename=False) <references=5> | ||
| - NameBinding(name='func', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='C', allow_rename=True) <references=1> | ||
| + Class C | ||
| - nonlocal OhALongName | ||
| - NameBinding(name='MyOtherName', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) |
| import sys | ||
| import pytest | ||
| from helpers import assert_namespace_tree | ||
| def test_module_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| name_in_module = True | ||
| name_in_module = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - BuiltinBinding(name='True', allow_rename=True) <references=2> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_lambda_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| name_in_module = 0 | ||
| b = lambda arg, *args, **kwargs: arg + 1 | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='b', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=1> | ||
| + Lambda | ||
| - NameBinding(name='arg', allow_rename=False) <references=2> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_function_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| name_in_module = True | ||
| def func(arg, *args, **kwargs): | ||
| name_in_func = True | ||
| print(name_in_module) | ||
| def inner_func(): | ||
| print(name_in_module) | ||
| name_in_inner_func = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - BuiltinBinding(name='True', allow_rename=True) <references=3> | ||
| - NameBinding(name='func', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=3> | ||
| - BuiltinBinding(name='print', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='arg', allow_rename=True) <references=1> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='inner_func', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_func', allow_rename=True) <references=1> | ||
| + Function inner_func | ||
| - NameBinding(name='name_in_inner_func', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # region generator namespace | ||
| def test_generator_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| a = (x for x in range(10)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_generator_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = (x for x in f for x in x) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_generator_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = (c for line in file for c in line) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_generator(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = (c for c in (line for line in file)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_generator_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = '' | ||
| a = (x for x in (x for x in x)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region setcomp | ||
| def test_setcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| a = {x for x in range(10)} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_setcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = {x for x in f for x in x} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_setcomp_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c for line in file for c in line} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_setcomp(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c for c in {line for line in file}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_setcomp_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = '' | ||
| a = {x for x in {x for x in x}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region listcomp | ||
| def test_listcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| a = [x for x in range(10)] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_listcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = [x for x in f for x in x] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_listcomp_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = [c for line in file for c in line] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_listcomp(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a =[c for c in [line for line in file]] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + ListComp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_listcomp_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = '' | ||
| a =[x for x in [x for x in x]] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region dictcomp | ||
| def test_dictcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| a = {x: x for x in range(10)} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_dictcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = {x: x for x, x in f for x, x in x} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=7> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_dictcomp_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c: c for line, line in file for c, c in line} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='c', allow_rename=True) <references=4> | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_dictcomp(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c: c for c, c in {line: line for line in file}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='c', allow_rename=True) <references=4> | ||
| + DictComp | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_dictcomp_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| x = {} | ||
| a = {x:x for x, x in {x: x for x in x}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| def test_class_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| OhALongName = 'Hello' | ||
| OhALongName = 'Hello' | ||
| MyOtherName = 'World' | ||
| def func(): | ||
| class C: | ||
| OhALongName = ' World' | ||
| MyOtherName = OhALongName | ||
| ClassName = 0 | ||
| func() | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyOtherName', allow_rename=True) <references=1> | ||
| - NameBinding(name='OhALongName', allow_rename=False) <references=4> | ||
| - NameBinding(name='func', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='C', allow_rename=True) <references=1> | ||
| + Class C | ||
| - nonlocal OhALongName | ||
| - NameBinding(name='ClassName', allow_rename=False) <references=1> | ||
| - NameBinding(name='MyOtherName', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_name_rebinding(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
| source = ''' | ||
| OhALongName = 'Hello' | ||
| OhALongName = 'Hello' | ||
| MyOtherName = 'World' | ||
| def func(): | ||
| class C: | ||
| OhALongName = OhALongName + ' World' | ||
| MyOtherName = OhALongName | ||
| func() | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyOtherName', allow_rename=True) <references=1> | ||
| - NameBinding(name='OhALongName', allow_rename=False) <references=5> | ||
| - NameBinding(name='func', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='C', allow_rename=True) <references=1> | ||
| + Class C | ||
| - nonlocal OhALongName | ||
| - NameBinding(name='MyOtherName', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) |
| import sys | ||
| import pytest | ||
| from helpers import assert_namespace_tree | ||
| def test_module_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| name_in_module = True | ||
| name_in_module = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_lambda_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| name_in_module = True | ||
| b = lambda arg, *args, **kwargs: arg + 1 | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='b', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=1> | ||
| + Lambda | ||
| - NameBinding(name='arg', allow_rename=False) <references=2> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_function_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| name_in_module = True | ||
| def func(arg, *args, **kwargs): | ||
| name_in_func = True | ||
| print(name_in_module) | ||
| def inner_func(): | ||
| print(name_in_module) | ||
| name_in_inner_func = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='func', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=3> | ||
| - BuiltinBinding(name='print', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='arg', allow_rename=True) <references=1> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='inner_func', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_func', allow_rename=True) <references=1> | ||
| + Function inner_func | ||
| - NameBinding(name='name_in_inner_func', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_async_function_namespace(): | ||
| if sys.version_info < (3, 5): | ||
| pytest.skip('No async functions in python < 3.5') | ||
| source = ''' | ||
| name_in_module = True | ||
| async def func(arg, *args, **kwargs): | ||
| name_in_func = True | ||
| print(name_in_module) | ||
| async def inner_func(): | ||
| print(name_in_module) | ||
| name_in_inner_func = True | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='func', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_module', allow_rename=True) <references=3> | ||
| - BuiltinBinding(name='print', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='arg', allow_rename=True) <references=1> | ||
| - NameBinding(name='args', allow_rename=True) <references=1> | ||
| - NameBinding(name='inner_func', allow_rename=True) <references=1> | ||
| - NameBinding(name='kwargs', allow_rename=True) <references=1> | ||
| - NameBinding(name='name_in_func', allow_rename=True) <references=1> | ||
| + Function inner_func | ||
| - NameBinding(name='name_in_inner_func', allow_rename=True) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # region generator namespace | ||
| def test_generator_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| a = (x for x in range(10)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_generator_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = (x for x in f for x in x) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_generator_namespace_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = (c for line in file for c in line) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_generator(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = (c for c in (line for line in file)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + GeneratorExp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_generator_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = '' | ||
| a = (x for x in (x for x in x)) | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + GeneratorExp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region setcomp | ||
| def test_setcomp_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| a = {x for x in range(10)} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_setcomp_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = {x for x in f for x in x} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_setcomp_namespace_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c for line in file for c in line} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_setcomp(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c for c in {line for line in file}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + SetComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_setcomp_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = '' | ||
| a = {x for x in {x for x in x}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + SetComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region listcomp | ||
| def test_listcomp_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| a = [x for x in range(10)] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_listcomp_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = [x for x in f for x in x] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_listcomp_namespace_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = [c for line in file for c in line] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_listcomp(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a =[c for c in [line for line in file]] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + ListComp | ||
| - NameBinding(name='c', allow_rename=True) <references=2> | ||
| + ListComp | ||
| - NameBinding(name='line', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_listcomp_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = '' | ||
| a =[x for x in [x for x in x]] | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + ListComp | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| # region dictcomp | ||
| def test_dictcomp_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| a = {x: x for x in range(10)} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='range', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_dictcomp_namespace(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = [] | ||
| f = [] | ||
| a = {x: x for x, x in f for x, x in x} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='f', allow_rename=True) <references=2> | ||
| - NameBinding(name='x', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=7> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_multi_dictcomp_namespace_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c: c for line, line in file for c, c in line} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='c', allow_rename=True) <references=4> | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_dictcomp(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| c = '' | ||
| line = '' | ||
| file = [] | ||
| a = {c: c for c, c in {line: line for line in file}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='c', allow_rename=True) <references=1> | ||
| - NameBinding(name='file', allow_rename=True) <references=2> | ||
| - NameBinding(name='line', allow_rename=True) <references=1> | ||
| + DictComp | ||
| - NameBinding(name='c', allow_rename=True) <references=4> | ||
| + DictComp | ||
| - NameBinding(name='line', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_nested_dictcomp_2(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| x = {} | ||
| a = {x:x for x, x in {x: x for x in x}} | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='a', allow_rename=True) <references=1> | ||
| - NameBinding(name='x', allow_rename=True) <references=2> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=4> | ||
| + DictComp | ||
| - NameBinding(name='x', allow_rename=True) <references=3> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| # endregion | ||
| def test_class_namespace(): | ||
| if sys.version_info < (3, 6): | ||
| pytest.skip('Annotations are not available in python < 3.6') | ||
| source = ''' | ||
| OhALongName = 'Hello' | ||
| OhALongName = 'Hello' | ||
| MyOtherName = 'World' | ||
| def func(): | ||
| class C: | ||
| OhALongName = ' World' | ||
| MyOtherName = OhALongName | ||
| ClassName: int | ||
| func() | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyOtherName', allow_rename=True) <references=1> | ||
| - NameBinding(name='OhALongName', allow_rename=False) <references=4> | ||
| - NameBinding(name='func', allow_rename=True) <references=2> | ||
| - BuiltinBinding(name='int', allow_rename=True) <references=1> | ||
| + Function func | ||
| - NameBinding(name='C', allow_rename=True) <references=1> | ||
| + Class C | ||
| - nonlocal OhALongName | ||
| - nonlocal int | ||
| - NameBinding(name='ClassName', allow_rename=False) <references=1> | ||
| - NameBinding(name='MyOtherName', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_name_rebinding(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('Test requires python 3.4 or later') | ||
| source = ''' | ||
| OhALongName = 'Hello' | ||
| OhALongName = 'Hello' | ||
| MyOtherName = 'World' | ||
| def func(): | ||
| class C: | ||
| OhALongName = OhALongName + ' World' | ||
| MyOtherName = OhALongName | ||
| func() | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyOtherName', allow_rename=True) <references=1> | ||
| - NameBinding(name='OhALongName', allow_rename=False) <references=5> | ||
| - NameBinding(name='func', allow_rename=True) <references=2> | ||
| + Function func | ||
| - NameBinding(name='C', allow_rename=True) <references=1> | ||
| + Class C | ||
| - nonlocal OhALongName | ||
| - NameBinding(name='MyOtherName', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_name_allow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=True) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_name_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=1> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_builtin_name_allow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = int | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='int', allow_rename=True) <references=1> | ||
| + Class LazyList | ||
| - nonlocal int | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_builtin_name_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = int | ||
| int = 1 | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - BuiltinBinding(name='int', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal int | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_non_builtin_name_disallow_rename(): | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('NameConstants are different in python < 3.4') | ||
| source = ''' | ||
| int = None | ||
| class LazyList: | ||
| MyAttribute = int | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='int', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal int | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_name_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| MyNonLocal = 2 | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_name_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| MyNonLocal = 2 | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_import_as_disallow_rename(): | ||
| """import os as MyNonLocal""" | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| import os as MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_import_as_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| import os as MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_import_from_as_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| from os import Hello as MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_import_from_as_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| from os import Hello as MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_import_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| import MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_import_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| import MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_import_from_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| from Hello import MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_import_from_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| from Hello import MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_dotted_import_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| import MyNonLocal.Hello | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_dotted_import_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| import MyNonLocal.Hello | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_func_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| def MyNonLocal(): pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| + Function MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_func_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| def MyNonLocal(): pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| + Function MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_async_func_disallow_rename(): | ||
| if sys.version_info < (3, 5): | ||
| pytest.skip('No async functions in python < 3.5') | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| async def MyNonLocal(): pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| + Function MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_async_func_disallow_rename(): | ||
| if sys.version_info < (3, 5): | ||
| pytest.skip('No async functions in python < 3.5') | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| async def MyNonLocal(): pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| + Function MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_classdef_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| class MyNonLocal(): pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| + Class MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_classdef_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| class MyNonLocal(): pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| + Class MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_except_disallow_rename(): | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| try: | ||
| pass | ||
| except Exception as MyNonLocal: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - BuiltinBinding(name='Exception', allow_rename=True) <references=1> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal Exception | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_except_disallow_rename(): | ||
| source = ''' | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| try: | ||
| pass | ||
| except Exception as MyNonLocal: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - BuiltinBinding(name='Exception', allow_rename=True) <references=1> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal Exception | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_match_disallow_rename(): | ||
| if sys.version_info < (3, 10): | ||
| pytest.skip('Match statement not in python < 3.10') | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| Blah = "Hello" | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| match Blah: | ||
| case MyNonLocal: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='Blah', allow_rename=True) <references=2> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal Blah | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_match_disallow_rename(): | ||
| if sys.version_info < (3, 10): | ||
| pytest.skip('Match statement not in python < 3.10') | ||
| source = ''' | ||
| Blah = "Hello" | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| match Blah: | ||
| case MyNonLocal: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='Blah', allow_rename=True) <references=2> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal Blah | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_match_star_disallow_rename(): | ||
| if sys.version_info < (3, 10): | ||
| pytest.skip('Match statement not in python < 3.10') | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| Blah = "Hello" | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| match Blah: | ||
| case [*MyNonLocal]: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='Blah', allow_rename=True) <references=2> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal Blah | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_match_star_disallow_rename(): | ||
| if sys.version_info < (3, 10): | ||
| pytest.skip('Match statement not in python < 3.10') | ||
| source = ''' | ||
| Blah = "Hello" | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| match Blah: | ||
| case [*MyNonLocal]: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='Blah', allow_rename=True) <references=2> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal Blah | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_match_mapping_disallow_rename(): | ||
| if sys.version_info < (3, 10): | ||
| pytest.skip('Match statement not in python < 3.10') | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| Blah = "Hello" | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| match Blah: | ||
| case {**MyNonLocal}: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='Blah', allow_rename=True) <references=2> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=3> | ||
| + Class LazyList | ||
| - nonlocal Blah | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_match_mapping_disallow_rename(): | ||
| if sys.version_info < (3, 10): | ||
| pytest.skip('Match statement not in python < 3.10') | ||
| source = ''' | ||
| Blah = "Hello" | ||
| class LazyList: | ||
| MyAttribute = MyNonLocal | ||
| match Blah: | ||
| case {**MyNonLocal}: | ||
| pass | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='Blah', allow_rename=True) <references=2> | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal Blah | ||
| - nonlocal MyNonLocal | ||
| - NameBinding(name='MyAttribute', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_nonlocal_disallow_rename(): | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('nonlocal in class is invalid in Python 2') | ||
| source = ''' | ||
| MyNonLocal = 1 | ||
| class LazyList: | ||
| nonlocal MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=2> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_class_namespace_undefined_nonlocal_disallow_rename(): | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('nonlocal in class is invalid in Python 2') | ||
| source = ''' | ||
| class LazyList: | ||
| nonlocal MyNonLocal | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='LazyList', allow_rename=True) <references=1> | ||
| - NameBinding(name='MyNonLocal', allow_rename=False) <references=1> | ||
| + Class LazyList | ||
| - nonlocal MyNonLocal | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) |
| import ast | ||
| import sys | ||
| import pytest | ||
| from python_minifier import add_namespace | ||
| from python_minifier.transforms.constant_folding import FoldConstants | ||
| from python_minifier.ast_compare import compare_ast | ||
| def fold_constants(source): | ||
| module = ast.parse(source) | ||
| add_namespace(module) | ||
| FoldConstants()(module) | ||
| return module | ||
| def run_test(source, expected): | ||
| try: | ||
| expected_ast = ast.parse(expected) | ||
| except SyntaxError: | ||
| pytest.skip('Syntax not supported in this version of python') | ||
| actual_ast = fold_constants(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| @pytest.mark.parametrize('source,expected', [ | ||
| ('True | True', 'True'), | ||
| ('True | False', 'True'), | ||
| ('False | True', 'True'), | ||
| ('False | False', 'False'), | ||
| ('True & True', 'True'), | ||
| ('True & False', 'False'), | ||
| ('False & True', 'False'), | ||
| ('False & False', 'False'), | ||
| ('True ^ True', 'False'), | ||
| ('True ^ False', 'True'), | ||
| ('False ^ True', 'True'), | ||
| ('False ^ False', 'False'), | ||
| ('(True | True) | True', 'True'), | ||
| ('(True | True) | False', 'True'), | ||
| ('(True | False) | True', 'True'), | ||
| ('(True | False) | False', 'True'), | ||
| ('(False | True) | True', 'True'), | ||
| ('(False | True) | False', 'True'), | ||
| ('(False | False) | True', 'True'), | ||
| ('(False | False) | False', 'False'), | ||
| ('(True | True) & True', 'True'), | ||
| ('(True | True) & False', 'False'), | ||
| ('(True | False) & True', 'True'), | ||
| ('(True | False) & False', 'False'), | ||
| ('(False | True) & True', 'True'), | ||
| ('(False | True) & False', 'False'), | ||
| ('(False | False) & True', 'False'), | ||
| ('(False | False) & False', 'False'), | ||
| ('(True | True) ^ True', 'False'), | ||
| ('(True | True) ^ False', 'True'), | ||
| ('(True | False) ^ True', 'False'), | ||
| ('(True | False) ^ False', 'True'), | ||
| ('(False | True) ^ True', 'False'), | ||
| ('(False | True) ^ False', 'True'), | ||
| ('(False | False) ^ True', 'True'), | ||
| ('(False | False) ^ False', 'False'), | ||
| ('True | (True | True)', 'True'), | ||
| ('True | (True | False)', 'True'), | ||
| ('True | (False | True)', 'True'), | ||
| ('True | (False | False)', 'True'), | ||
| ('False | (True | True)', 'True'), | ||
| ('False | (True | False)', 'True'), | ||
| ('False | (False | True)', 'True'), | ||
| ('False | (False | False)', 'False'), | ||
| ]) | ||
| def test_bool(source, expected): | ||
| """ | ||
| Test BinOp with bool operands | ||
| This is mainly testing we fold the constants correctly | ||
| """ | ||
| if sys.version_info < (3, 4): | ||
| pytest.skip('NameConstant not in python < 3.4') | ||
| run_test(source, expected) | ||
| @pytest.mark.parametrize('source,expected', [ | ||
| ('10 + 10', '20'), | ||
| ('10 + 0', '10'), | ||
| ('0 + 10', '10'), | ||
| ('10 + 10 + 5', '25'), | ||
| ('10 - 5 + 5', '10'), | ||
| ('10 * 10', '100'), | ||
| ('10 * 10 * 10', '1000'), | ||
| ('(10 * 10) // 10', '10'), | ||
| ('(2 * 10) // (2+2)', '5'), | ||
| ('8>>2', '2'), | ||
| ('8<<2', '32'), | ||
| ('0xff^0x0f', '0xf0'), | ||
| ('0xf0&0xff', '0xf0'), | ||
| ('0xf0|0x0f', '0xff'), | ||
| ('10%2', '0'), | ||
| ('10%3', '1'), | ||
| ('10-100', '-90') | ||
| ]) | ||
| def test_int(source, expected): | ||
| """ | ||
| Test BinOp with integer operands we can fold | ||
| """ | ||
| run_test(source, expected) | ||
| @pytest.mark.parametrize('source,expected', [ | ||
| ('10/10', '10/10'), | ||
| ('5+5/10', '5+5/10'), | ||
| ('2*5/10', '10/10'), | ||
| ('2/5*10', '2/5*10'), | ||
| ('2**5', '2**5'), | ||
| ('5@6', '5@6'), | ||
| ]) | ||
| def test_int_not_eval(source, expected): | ||
| """ | ||
| Test BinOp with operations we don't want to fold | ||
| """ | ||
| run_test(source, expected) | ||
| @pytest.mark.parametrize('source,expected', [ | ||
| ('"Hello" + "World"', '"Hello" + "World"'), | ||
| ('"Hello" * 5', '"Hello" * 5'), | ||
| ('b"Hello" + b"World"', 'b"Hello" + b"World"'), | ||
| ('b"Hello" * 5', 'b"Hello" * 5'), | ||
| ]) | ||
| def test_not_eval(source, expected): | ||
| """ | ||
| Test BinOps we don't want to fold | ||
| """ | ||
| run_test(source, expected) |
| import ast | ||
| import sys | ||
| import pytest | ||
| from python_minifier import unparse | ||
| from python_minifier.ast_compare import compare_ast | ||
| @pytest.mark.parametrize('statement', [ | ||
| 'f"{1=!r:.4}"', | ||
| 'f"{1=:.4}"', | ||
| 'f"{1=!s:.4}"', | ||
| 'f"{1=:.4}"', | ||
| 'f"{1}"', | ||
| 'f"{1=}"', | ||
| 'f"{1=!s}"', | ||
| 'f"{1=!a}"' | ||
| ]) | ||
| def test_fstring_statement(statement): | ||
| if sys.version_info < (3, 8): | ||
| pytest.skip('f-string debug specifier added in python 3.8') | ||
| assert unparse(ast.parse(statement)) == statement | ||
| def test_pep0701(): | ||
| if sys.version_info < (3, 12): | ||
| pytest.skip('f-string syntax is bonkers before python 3.12') | ||
| statement = 'f"{f"{f"{f"{"hello"}"}"}"}"' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = 'f"This is the playlist: {", ".join([])}"' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = 'f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = """ | ||
| f"This is the playlist: {", ".join([ | ||
| 'Take me back to Eden', # My, my, those eyes like fire | ||
| 'Alkaline', # Not acid nor alkaline | ||
| 'Ascensionism' # Take to the broken skies at last | ||
| ])}" | ||
| """ | ||
| assert unparse(ast.parse(statement)) == 'f"This is the playlist: {", ".join(["Take me back to Eden","Alkaline","Ascensionism"])}"' | ||
| #statement = '''print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")''' | ||
| #assert unparse(ast.parse(statement)) == statement | ||
| statement = '''f"Magic wand: {bag["wand"]}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = """ | ||
| f'''A complex trick: { | ||
| bag['bag'] # recursive bags! | ||
| }''' | ||
| """ | ||
| assert unparse(ast.parse(statement)) == 'f"A complex trick: {bag["bag"]}"' | ||
| statement = '''f"These are the things: {", ".join(things)}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = '''f"{source.removesuffix(".py")}.c: $(srcdir)/{source}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = '''f"{f"{f"infinite"}"}"+' '+f"{f"nesting!!!"}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = '''f"{"\\n".join(a)}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = '''f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| statement = '''f"{"":*^{1:{1}}}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| #statement = '''f"{"":*^{1:{1:{1}}}}"''' | ||
| #assert unparse(ast.parse(statement)) == statement | ||
| # SyntaxError: f-string: expressions nested too deeply | ||
| statement = '''f"___{ | ||
| x | ||
| }___"''' | ||
| assert unparse(ast.parse(statement)) == '''f"___{x}___"''' | ||
| statement = '''f"Useless use of lambdas: {(lambda x:x*2)}"''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
| def test_fstring_empty_str(): | ||
| if sys.version_info < (3, 6): | ||
| pytest.skip('f-string expressions not allowed in python < 3.6') | ||
| source = r''' | ||
| f"""\ | ||
| {fg_br}""" | ||
| ''' | ||
| print(source) | ||
| expected_ast = ast.parse(source) | ||
| actual_ast = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(actual_ast)) |
| import ast | ||
| import sys | ||
| import pytest | ||
| from python_minifier import unparse | ||
| from python_minifier.ast_compare import compare_ast | ||
| def test_type_statement(): | ||
| if sys.version_info < (3, 12): | ||
| pytest.skip('Improved generic syntax python < 3.12') | ||
| source = ''' | ||
| type Point = tuple[float, float] | ||
| type ListOrSet[T] = list[T] | set[T] | ||
| ''' | ||
| expected_ast = ast.parse(source) | ||
| actual_ast = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(actual_ast)) | ||
| def test_function_generic(): | ||
| if sys.version_info < (3, 12): | ||
| pytest.skip('Improved generic syntax python < 3.12') | ||
| source = ''' | ||
| def a(): ... | ||
| def func[T](a: T, b: T) -> T: | ||
| ... | ||
| ''' | ||
| expected_ast = ast.parse(source) | ||
| actual_ast = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(actual_ast)) | ||
| def test_class_generic(): | ||
| if sys.version_info < (3, 12): | ||
| pytest.skip('Improved generic syntax python < 3.12') | ||
| source = ''' | ||
| class A: | ||
| ... | ||
| class B[S]: | ||
| ... | ||
| class C[T: str]: | ||
| ... | ||
| ''' | ||
| expected_ast = ast.parse(source) | ||
| actual_ast = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(actual_ast)) | ||
| def test_pep695_unparse(): | ||
| if sys.version_info < (3, 12): | ||
| pytest.skip('Improved generic syntax python < 3.12') | ||
| source = ''' | ||
| class ClassA[T: str]: | ||
| def method1(self) -> T: | ||
| ... | ||
| def func[T](a: T, b: T) -> T: | ||
| ... | ||
| type ListOrSet[T] = list[T] | set[T] | ||
| # This generic class is parameterized by a TypeVar T, a | ||
| # TypeVarTuple Ts, and a ParamSpec P. | ||
| class ChildClass[T, *Ts, **P]: ... | ||
| class ClassA[S, T](Protocol): ... # OK | ||
| class ClassB[S, T](Protocol[S, T]): ... # Recommended type checker error | ||
| class ClassA[T: str]: ... | ||
| class ClassA[T: dict[str, int]]: ... # OK | ||
| class ClassB[T: "ForwardReference"]: ... # OK | ||
| class ClassC[V]: | ||
| class ClassD[T: dict[str, V]]: ... # Type checker error: generic type | ||
| class ClassE[T: [str, int]]: ... # Type checker error: illegal expression form | ||
| class ClassA[AnyStr: (str, bytes)]: ... # OK | ||
| class ClassB[T: ("ForwardReference", bytes)]: ... # OK | ||
| class ClassC[T: ()]: ... # Type checker error: two or more types required | ||
| class ClassD[T: (str, )]: ... # Type checker error: two or more types required | ||
| t1 = (bytes, str) | ||
| class ClassE[T: t1]: ... # Type checker error: literal tuple expression required | ||
| class ClassF[T: (3, bytes)]: ... # Type checker error: invalid expression form | ||
| class ClassG[T: (list[S], str)]: ... # Type checker error: generic type | ||
| # A non-generic type alias | ||
| type IntOrStr = int | str | ||
| # A generic type alias | ||
| type ListOrSet[T] = list[T] | set[T] | ||
| # A type alias that includes a forward reference | ||
| type AnimalOrVegetable = Animal | "Vegetable" | ||
| # A generic self-referential type alias | ||
| type RecursiveList[T] = T | list[RecursiveList[T]] | ||
| T = TypeVar("T") | ||
| type MyList = list[T] # Type checker error: traditional type variable usage | ||
| # The following generates no compiler error, but a type checker | ||
| # should generate an error because an upper bound type must be concrete, | ||
| # and ``Sequence[S]`` is generic. Future extensions to the type system may | ||
| # eliminate this limitation. | ||
| class ClassA[S, T: Sequence[S]]: ... | ||
| # The following generates no compiler error, because the bound for ``S`` | ||
| # is lazily evaluated. However, type checkers should generate an error. | ||
| class ClassB[S: Sequence[T], T]: ... | ||
| class ClassA[T](BaseClass[T], param = Foo[T]): ... # OK | ||
| print(T) # Runtime error: 'T' is not defined | ||
| @dec(Foo[T]) # Runtime error: 'T' is not defined | ||
| class ClassA[T]: ... | ||
| def func1[T](a: T) -> T: ... # OK | ||
| print(T) # Runtime error: 'T' is not defined | ||
| def func2[T](a = list[T]): ... # Runtime error: 'T' is not defined | ||
| @dec(list[T]) # Runtime error: 'T' is not defined | ||
| def func3[T](): ... | ||
| type Alias1[K, V] = Mapping[K, V] | Sequence[K] | ||
| S = 0 | ||
| def outer1[S](): | ||
| S = 1 | ||
| T = 1 | ||
| def outer2[T](): | ||
| def inner1(): | ||
| nonlocal S # OK because it binds variable S from outer1 | ||
| #nonlocal T # Syntax error: nonlocal binding not allowed for type parameter | ||
| def inner2(): | ||
| global S # OK because it binds variable S from global scope | ||
| class Outer: | ||
| class Private: | ||
| pass | ||
| # If the type parameter scope was like a traditional scope, | ||
| # the base class 'Private' would not be accessible here. | ||
| class Inner[T](Private, Sequence[T]): | ||
| pass | ||
| # Likewise, 'Inner' would not be available in these type annotations. | ||
| def method1[T](self, a: Inner[T]) -> Inner[T]: | ||
| return a | ||
| T = 0 | ||
| @decorator(T) # Argument expression `T` evaluates to 0 | ||
| class ClassA[T](Sequence[T]): | ||
| T = 1 | ||
| # All methods below should result in a type checker error | ||
| # "type parameter 'T' already in use" because they are using the | ||
| # type parameter 'T', which is already in use by the outer scope | ||
| # 'ClassA'. | ||
| def method1[T](self): | ||
| ... | ||
| def method2[T](self, x = T): # Parameter 'x' gets default value of 1 | ||
| ... | ||
| def method3[T](self, x: T): # Parameter 'x' has type T (scoped to method3) | ||
| ... | ||
| T = 0 | ||
| # T refers to the global variable | ||
| print(T) # Prints 0 | ||
| class Outer[T]: | ||
| T = 1 | ||
| # T refers to the local variable scoped to class 'Outer' | ||
| print(T) # Prints 1 | ||
| class Inner1: | ||
| T = 2 | ||
| # T refers to the local type variable within 'Inner1' | ||
| print(T) # Prints 2 | ||
| def inner_method(self): | ||
| # T refers to the type parameter scoped to class 'Outer'; | ||
| # If 'Outer' did not use the new type parameter syntax, | ||
| # this would instead refer to the global variable 'T' | ||
| print(T) # Prints 'T' | ||
| def outer_method(self): | ||
| T = 3 | ||
| # T refers to the local variable within 'outer_method' | ||
| print(T) # Prints 3 | ||
| def inner_func(): | ||
| # T refers to the variable captured from 'outer_method' | ||
| print(T) # Prints 3 | ||
| class ClassA[T1, T2, T3](list[T1]): | ||
| def method1(self, a: T2) -> None: | ||
| ... | ||
| def method2(self) -> T3: | ||
| ... | ||
| upper = ClassA[object, Dummy, Dummy] | ||
| lower = ClassA[T1, Dummy, Dummy] | ||
| upper = ClassA[Dummy, object, Dummy] | ||
| lower = ClassA[Dummy, T2, Dummy] | ||
| upper = ClassA[Dummy, Dummy, object] | ||
| lower = ClassA[Dummy, Dummy, T3] | ||
| T1 = TypeVar("T1", infer_variance=True) # Inferred variance | ||
| T2 = TypeVar("T2") # Invariant | ||
| T3 = TypeVar("T3", covariant=True) # Covariant | ||
| # A type checker should infer the variance for T1 but use the | ||
| # specified variance for T2 and T3. | ||
| class ClassA(Generic[T1, T2, T3]): ... | ||
| K = TypeVar("K") | ||
| class ClassA[V](dict[K, V]): ... # Type checker error | ||
| class ClassB[K, V](dict[K, V]): ... # OK | ||
| class ClassC[V]: | ||
| # The use of K and V for "method1" is OK because it uses the | ||
| # "traditional" generic function mechanism where type parameters | ||
| # are implicit. In this case V comes from an outer scope (ClassC) | ||
| # and K is introduced implicitly as a type parameter for "method1". | ||
| def method1(self, a: V, b: K) -> V | K: ... | ||
| # The use of M and K are not allowed for "method2". A type checker | ||
| # should generate an error in this case because this method uses the | ||
| # new syntax for type parameters, and all type parameters associated | ||
| # with the method must be explicitly declared. In this case, ``K`` | ||
| # is not declared by "method2", nor is it supplied by a new-style | ||
| # type parameter defined in an outer scope. | ||
| def method2[M](self, a: M, b: K) -> M | K: ... | ||
| ''' | ||
| expected_ast = ast.parse(source) | ||
| unparse(expected_ast) | ||
| import sys | ||
| import pytest | ||
| import python_minifier.ast_compat as ast | ||
| from python_minifier.util import is_ast_node | ||
| def test_type_nodes(): | ||
| assert is_ast_node(ast.Str('a'), ast.Str) | ||
| if hasattr(ast, 'Bytes'): | ||
| assert is_ast_node(ast.Bytes(b'a'), ast.Bytes) | ||
| assert is_ast_node(ast.Num(1), ast.Num) | ||
| assert is_ast_node(ast.Num(0), ast.Num) | ||
| if hasattr(ast, 'NameConstant'): | ||
| assert is_ast_node(ast.NameConstant(True), ast.NameConstant) | ||
| assert is_ast_node(ast.NameConstant(False), ast.NameConstant) | ||
| assert is_ast_node(ast.NameConstant(None), ast.NameConstant) | ||
| else: | ||
| assert is_ast_node(ast.Name(id='True', ctx=ast.Load()), ast.Name) | ||
| assert is_ast_node(ast.Name(id='False', ctx=ast.Load()), ast.Name) | ||
| assert is_ast_node(ast.Name(id='None', ctx=ast.Load()), ast.Name) | ||
| assert is_ast_node(ast.Ellipsis(), ast.Ellipsis) | ||
| def test_constant_nodes(): | ||
| # only test on python 3.8+ | ||
| if sys.version_info < (3, 8): | ||
| pytest.skip('Constant not available') | ||
| assert is_ast_node(ast.Constant('a'), ast.Str) | ||
| assert is_ast_node(ast.Constant(b'a'), ast.Bytes) | ||
| assert is_ast_node(ast.Constant(1), ast.Num) | ||
| assert is_ast_node(ast.Constant(0), ast.Num) | ||
| assert is_ast_node(ast.Constant(True), ast.NameConstant) | ||
| assert is_ast_node(ast.Constant(False), ast.NameConstant) | ||
| assert is_ast_node(ast.Constant(None), ast.NameConstant) | ||
| assert is_ast_node(ast.Constant(ast.literal_eval('...')), ast.Ellipsis) |
| import ast | ||
| import sys | ||
| import pytest | ||
| from python_minifier.rename import add_namespace, bind_names, resolve_names | ||
| from python_minifier.ast_compare import compare_ast | ||
| from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call | ||
| def remove_brackets(source): | ||
| module = ast.parse(source, 'remove_brackets') | ||
| add_namespace(module) | ||
| bind_names(module) | ||
| resolve_names(module) | ||
| return remove_no_arg_exception_call(module) | ||
| def test_exception_brackets(): | ||
| """This is a buitin so remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = 'def a(): raise Exception()' | ||
| expected = 'def a(): raise Exception' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_zero_division_error_brackets(): | ||
| """This is a buitin so remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = 'def a(): raise ZeroDivisionError()' | ||
| expected = 'def a(): raise ZeroDivisionError' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_builtin_with_arg(): | ||
| """This has an arg so dont' remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = 'def a(): raise Exception(1)' | ||
| expected = 'def a(): raise Exception(1)' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_one_division_error_brackets(): | ||
| """This is not a builtin so don't remove the brackets even though it's not defined in the module""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = 'def a(): raise OneDivisionError()' | ||
| expected = source | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_redefined(): | ||
| """This is usually a builtin, but don't remove brackets if it's been redefined""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = ''' | ||
| def a(): | ||
| raise ZeroDivisionError() | ||
| def b(): | ||
| ZeroDivisionError = blag | ||
| raise ZeroDivisionError() | ||
| ''' | ||
| expected = ''' | ||
| def a(): | ||
| raise ZeroDivisionError | ||
| def b(): | ||
| ZeroDivisionError = blag | ||
| raise ZeroDivisionError() | ||
| ''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_raise_from(): | ||
| """This is a builtin so remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('raise from not supported in this version of python') | ||
| source = 'def a(): raise Exception() from Exception()' | ||
| expected = 'def a(): raise Exception from Exception' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_raise_from_only(): | ||
| """This is a builtin so remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('raise from not supported in this version of python') | ||
| source = 'def a(): raise Hello() from Exception()' | ||
| expected = 'def a(): raise Hello() from Exception' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_raise_from_arg(): | ||
| """This is a builtin so remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('raise from not supported in this version of python') | ||
| source = 'def a(): raise Hello() from Exception(1)' | ||
| expected = 'def a(): raise Hello() from Exception(1)' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_raise_builtin_in_class(): | ||
| """This is a builtin so remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = ''' | ||
| class A: | ||
| raise IndexError() | ||
| ''' | ||
| expected = ''' | ||
| class A: | ||
| raise IndexError | ||
| ''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_raise_redefined_builtin_in_class(): | ||
| """This was a builtin at some point, but it was redefined so don't remove the brackets""" | ||
| if sys.version_info < (3, 0): | ||
| pytest.skip('transform does not work in this version of python') | ||
| source = ''' | ||
| class A: | ||
| if random.choice([True, False]): | ||
| IndexError = IndexError | ||
| raise IndexError() | ||
| ''' | ||
| expected = ''' | ||
| class A: | ||
| if random.choice([True, False]): | ||
| IndexError = IndexError | ||
| raise IndexError() | ||
| ''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_brackets(source) | ||
| compare_ast(expected_ast, actual_ast) |
+5
-4
| Metadata-Version: 2.1 | ||
| Name: python_minifier | ||
| Version: 2.9.0 | ||
| Version: 2.10.0 | ||
| Summary: Transform Python source code into it's most compact representation | ||
@@ -25,2 +25,3 @@ Home-page: https://github.com/dflook/python-minifier | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 2 | ||
@@ -32,3 +33,3 @@ Classifier: Programming Language :: Python :: 2.7 | ||
| Classifier: Topic :: Software Development | ||
| Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12 | ||
| Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13 | ||
| Description-Content-Type: text/markdown | ||
@@ -43,3 +44,3 @@ License-File: LICENSE | ||
| python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6. | ||
| python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6. | ||
@@ -147,3 +148,3 @@ * [PyPI](https://pypi.org/project/python-minifier/) | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11. | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12. | ||
@@ -150,0 +151,0 @@ ## Usage |
+2
-2
@@ -7,3 +7,3 @@ # Python Minifier | ||
| python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6. | ||
| python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6. | ||
@@ -111,3 +111,3 @@ * [PyPI](https://pypi.org/project/python-minifier/) | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11. | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12. | ||
@@ -114,0 +114,0 @@ ## Usage |
+4
-4
| import os.path | ||
| import sys | ||
@@ -26,8 +25,8 @@ from setuptools import setup, find_packages | ||
| packages=find_packages('src'), | ||
| package_data={"python_minifier": ["py.typed", "__init__.pyi"]}, | ||
| package_data={"python_minifier": ["py.typed", "*.pyi", "rename/*.pyi", "transforms/*.pyi"]}, | ||
| long_description=long_desc, | ||
| long_description_content_type='text/markdown', | ||
| python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12', | ||
| version='2.9.0', | ||
| python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13', | ||
| version='2.10.0', | ||
@@ -48,2 +47,3 @@ classifiers=[ | ||
| 'Programming Language :: Python :: 3.11', | ||
| 'Programming Language :: Python :: 3.12', | ||
| 'Programming Language :: Python :: 2', | ||
@@ -50,0 +50,0 @@ 'Programming Language :: Python :: 2.7', |
| Metadata-Version: 2.1 | ||
| Name: python-minifier | ||
| Version: 2.9.0 | ||
| Name: python_minifier | ||
| Version: 2.10.0 | ||
| Summary: Transform Python source code into it's most compact representation | ||
@@ -25,2 +25,3 @@ Home-page: https://github.com/dflook/python-minifier | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 2 | ||
@@ -32,3 +33,3 @@ Classifier: Programming Language :: Python :: 2.7 | ||
| Classifier: Topic :: Software Development | ||
| Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12 | ||
| Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13 | ||
| Description-Content-Type: text/markdown | ||
@@ -43,3 +44,3 @@ License-File: LICENSE | ||
| python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6. | ||
| python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6. | ||
@@ -147,3 +148,3 @@ * [PyPI](https://pypi.org/project/python-minifier/) | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11. | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12. | ||
@@ -150,0 +151,0 @@ ## Usage |
@@ -8,2 +8,4 @@ LICENSE | ||
| src/python_minifier/ast_compare.py | ||
| src/python_minifier/ast_compat.py | ||
| src/python_minifier/ast_printer.py | ||
| src/python_minifier/expression_printer.py | ||
@@ -33,5 +35,9 @@ src/python_minifier/f_string.py | ||
| src/python_minifier/transforms/combine_imports.py | ||
| src/python_minifier/transforms/constant_folding.py | ||
| src/python_minifier/transforms/remove_annotations.py | ||
| src/python_minifier/transforms/remove_annotations_options.py | ||
| src/python_minifier/transforms/remove_annotations_options.pyi | ||
| src/python_minifier/transforms/remove_asserts.py | ||
| src/python_minifier/transforms/remove_debug.py | ||
| src/python_minifier/transforms/remove_exception_brackets.py | ||
| src/python_minifier/transforms/remove_explicit_return_none.py | ||
@@ -45,2 +51,5 @@ src/python_minifier/transforms/remove_literal_statements.py | ||
| test/test_await_fstring.py | ||
| test/test_bind_names.py | ||
| test/test_bind_names_python2.py | ||
| test/test_bind_names_python33.py | ||
| test/test_combine_imports.py | ||
@@ -50,5 +59,8 @@ test/test_comprehension_rename.py | ||
| test/test_dict_expansion.py | ||
| test/test_empty_fstring.py | ||
| test/test_folding.py | ||
| test/test_fstring.py | ||
| test/test_generic_types.py | ||
| test/test_hoist_literals.py | ||
| test/test_import.py | ||
| test/test_is_ast_node.py | ||
| test/test_iterable_unpacking.py | ||
@@ -64,2 +76,3 @@ test/test_match.py | ||
| test/test_remove_debug.py | ||
| test/test_remove_exception_brackets.py | ||
| test/test_remove_explicit_return_none.py | ||
@@ -66,0 +79,0 @@ test/test_remove_literal_statements.py |
@@ -7,3 +7,3 @@ """ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import re | ||
@@ -24,6 +24,9 @@ | ||
| from python_minifier.transforms.combine_imports import CombineImports | ||
| from python_minifier.transforms.constant_folding import FoldConstants | ||
| from python_minifier.transforms.remove_annotations import RemoveAnnotations | ||
| from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions | ||
| from python_minifier.transforms.remove_asserts import RemoveAsserts | ||
| from python_minifier.transforms.remove_debug import RemoveDebug | ||
| from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone | ||
| from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call | ||
| from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements | ||
@@ -57,3 +60,3 @@ from python_minifier.transforms.remove_object_base import RemoveObject | ||
| filename=None, | ||
| remove_annotations=True, | ||
| remove_annotations=RemoveAnnotationsOptions(), | ||
| remove_pass=True, | ||
@@ -73,2 +76,4 @@ remove_literal_statements=False, | ||
| remove_explicit_return_none=True, | ||
| remove_builtin_exception_brackets=True, | ||
| constant_folding=True | ||
| ): | ||
@@ -87,3 +92,4 @@ """ | ||
| :param bool remove_annotations: If type annotations should be removed where possible | ||
| :param remove_annotations: Configures the removal of type annotations. True removes all annotations, False removes none. RemoveAnnotationsOptions can be used to configure the removal of specific annotations. | ||
| :type remove_annotations: bool or RemoveAnnotationsOptions | ||
| :param bool remove_pass: If Pass statements should be removed where possible | ||
@@ -105,2 +111,4 @@ :param bool remove_literal_statements: If statements consisting of a single literal should be removed, including docstrings | ||
| :param bool remove_explicit_return_none: If explicit return None statements should be replaced with a bare return | ||
| :param bool remove_builtin_exception_brackets: If brackets should be removed when raising exceptions with no arguments | ||
| :param bool constant_folding: If literal expressions should be evaluated | ||
@@ -124,5 +132,17 @@ :rtype: str | ||
| if remove_annotations: | ||
| module = RemoveAnnotations()(module) | ||
| if isinstance(remove_annotations, bool): | ||
| remove_annotations_options = RemoveAnnotationsOptions( | ||
| remove_variable_annotations=remove_annotations, | ||
| remove_return_annotations=remove_annotations, | ||
| remove_argument_annotations=remove_annotations, | ||
| remove_class_attribute_annotations=remove_annotations, | ||
| ) | ||
| elif isinstance(remove_annotations, RemoveAnnotationsOptions): | ||
| remove_annotations_options = remove_annotations | ||
| else: | ||
| raise TypeError('remove_annotations must be a bool or RemoveAnnotationsOptions') | ||
| if remove_annotations_options: | ||
| module = RemoveAnnotations(remove_annotations_options)(module) | ||
| if remove_pass: | ||
@@ -143,5 +163,11 @@ module = RemovePass()(module) | ||
| if constant_folding: | ||
| module = FoldConstants()(module) | ||
| bind_names(module) | ||
| resolve_names(module) | ||
| if remove_builtin_exception_brackets and not module.tainted: | ||
| remove_no_arg_exception_call(module) | ||
| if module.tainted: | ||
@@ -151,2 +177,14 @@ rename_globals = False | ||
| if preserve_locals is None: | ||
| preserve_locals = [] | ||
| elif isinstance(preserve_locals, str): | ||
| preserve_locals = [preserve_locals] | ||
| if preserve_globals is None: | ||
| preserve_globals = [] | ||
| elif isinstance(preserve_globals, str): | ||
| preserve_globals = [preserve_globals] | ||
| preserve_locals.extend(module.preserved) | ||
| preserve_globals.extend(module.preserved) | ||
| allow_rename_locals(module, rename_locals, preserve_locals) | ||
@@ -153,0 +191,0 @@ allow_rename_globals(module, rename_globals, preserve_globals) |
| import ast | ||
| from typing import List, Text, AnyStr, Optional, Any | ||
| from typing import List, Text, AnyStr, Optional, Any, Union | ||
| from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions | ||
@@ -11,3 +12,3 @@ class UnstableMinification(RuntimeError): | ||
| filename: Optional[str] = ..., | ||
| remove_annotations: bool = ..., | ||
| remove_annotations: Union[bool, RemoveAnnotationsOptions] = ..., | ||
| remove_pass: bool = ..., | ||
@@ -26,3 +27,5 @@ remove_literal_statements: bool = ..., | ||
| remove_debug: bool = ..., | ||
| remove_explicit_return_none: bool = ... | ||
| remove_explicit_return_none: bool = ..., | ||
| remove_builtin_exception_brackets: bool = ..., | ||
| constant_folding: bool = ... | ||
| ) -> Text: ... | ||
@@ -29,0 +32,0 @@ |
| from __future__ import print_function | ||
| import argparse | ||
| import os | ||
| import sys | ||
| import os | ||
| import argparse | ||
| from pkg_resources import get_distribution, DistributionNotFound | ||
| from python_minifier import minify | ||
| from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions | ||
| try: | ||
| version = get_distribution('python_minifier').version | ||
| except DistributionNotFound: | ||
| version = '0.0.0' | ||
| if sys.version_info >= (3, 8): | ||
| from importlib import metadata | ||
| try: | ||
| version = metadata.version('python-minifier') | ||
| except metadata.PackageNotFoundError: | ||
| version = '0.0.0' | ||
| else: | ||
| from pkg_resources import get_distribution, DistributionNotFound | ||
| try: | ||
| version = get_distribution('python_minifier').version | ||
| except DistributionNotFound: | ||
| version = '0.0.0' | ||
@@ -71,2 +78,3 @@ | ||
| def parse_args(): | ||
@@ -118,8 +126,2 @@ parser = argparse.ArgumentParser(prog='pyminify', description='Minify Python source code', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=main.__doc__) | ||
| minification_options.add_argument( | ||
| '--no-remove-annotations', | ||
| action='store_false', | ||
| help='Disable removing function and variable annotations', | ||
| dest='remove_annotations', | ||
| ) | ||
| minification_options.add_argument( | ||
| '--no-hoist-literals', | ||
@@ -194,2 +196,47 @@ action='store_false', | ||
| ) | ||
| minification_options.add_argument( | ||
| '--no-remove-builtin-exception-brackets', | ||
| action='store_false', | ||
| help='Disable removing brackets when raising builtin exceptions with no arguments', | ||
| dest='remove_exception_brackets', | ||
| ) | ||
| minification_options.add_argument( | ||
| '--no-constant-folding', | ||
| action='store_false', | ||
| help='Disable evaluating literal expressions', | ||
| dest='constant_folding', | ||
| ) | ||
| annotation_options = parser.add_argument_group('remove annotations options', 'Options that affect how annotations are removed') | ||
| annotation_options.add_argument( | ||
| '--no-remove-annotations', | ||
| action='store_false', | ||
| help='Disable removing all annotations', | ||
| dest='remove_annotations', | ||
| ) | ||
| annotation_options.add_argument( | ||
| '--no-remove-variable-annotations', | ||
| action='store_false', | ||
| help='Disable removing variable annotations', | ||
| dest='remove_variable_annotations', | ||
| ) | ||
| annotation_options.add_argument( | ||
| '--no-remove-return-annotations', | ||
| action='store_false', | ||
| help='Disable removing function return annotations', | ||
| dest='remove_return_annotations', | ||
| ) | ||
| annotation_options.add_argument( | ||
| '--no-remove-argument-annotations', | ||
| action='store_false', | ||
| help='Disable removing function argument annotations', | ||
| dest='remove_argument_annotations', | ||
| ) | ||
| annotation_options.add_argument( | ||
| '--remove-class-attribute-annotations', | ||
| action='store_true', | ||
| help='Enable removing class attribute annotations', | ||
| dest='remove_class_attribute_annotations', | ||
| ) | ||
| parser.add_argument('--version', '-v', action='version', version=version) | ||
@@ -213,4 +260,9 @@ | ||
| if args.remove_class_attribute_annotations and not args.remove_annotations: | ||
| sys.stderr.write('error: --remove-class-attribute-annotations would do nothing when used with --no-remove-annotations\n') | ||
| sys.exit(1) | ||
| return args | ||
| def source_modules(args): | ||
@@ -230,2 +282,3 @@ | ||
| def do_minify(source, filename, minification_args): | ||
@@ -245,2 +298,17 @@ | ||
| if minification_args.remove_annotations is False: | ||
| remove_annotations = RemoveAnnotationsOptions( | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=False, | ||
| ) | ||
| else: | ||
| remove_annotations = RemoveAnnotationsOptions( | ||
| remove_variable_annotations=minification_args.remove_variable_annotations, | ||
| remove_return_annotations=minification_args.remove_return_annotations, | ||
| remove_argument_annotations=minification_args.remove_argument_annotations, | ||
| remove_class_attribute_annotations=minification_args.remove_class_attribute_annotations, | ||
| ) | ||
| return minify( | ||
@@ -251,3 +319,3 @@ source, | ||
| remove_pass=minification_args.remove_pass, | ||
| remove_annotations=minification_args.remove_annotations, | ||
| remove_annotations=remove_annotations, | ||
| remove_literal_statements=minification_args.remove_literal_statements, | ||
@@ -265,2 +333,4 @@ hoist_literals=minification_args.hoist_literals, | ||
| remove_explicit_return_none=minification_args.remove_explicit_return_none, | ||
| remove_builtin_exception_brackets=minification_args.remove_exception_brackets, | ||
| constant_folding=minification_args.constant_folding | ||
| ) | ||
@@ -267,0 +337,0 @@ |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -3,0 +3,0 @@ from python_minifier.util import is_ast_node |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
@@ -72,3 +72,3 @@ | ||
| # Make sure the Num get the precedence of the USub operator in this case. | ||
| if sys.version_info < (3, 0) and isinstance(node, ast.Num): | ||
| if sys.version_info < (3, 0) and is_ast_node(node, ast.Num): | ||
| if str(node.n)[0] == '-': | ||
@@ -212,3 +212,3 @@ return self.precedences['USub'] | ||
| if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Num): | ||
| if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and is_ast_node(node.operand, ast.Num): | ||
| # For: -(1), which is parsed as a UnaryOp(USub, Num(1)). | ||
@@ -433,3 +433,3 @@ # Without this special case it would be printed as -1 | ||
| if (value_precedence != 0 and (attr_precedence > value_precedence)) or isinstance(node.value, ast.Num): | ||
| if (value_precedence != 0 and (attr_precedence > value_precedence)) or is_ast_node(node.value, ast.Num): | ||
| self.printer.delimiter('(') | ||
@@ -468,3 +468,3 @@ self._expression(node.value) | ||
| self.visit_ExtSlice(node.slice) | ||
| elif isinstance(node.slice, ast.Ellipsis): | ||
| elif is_ast_node(node.slice, ast.Ellipsis): | ||
| self.visit_Ellipsis(node) | ||
@@ -742,4 +742,9 @@ elif sys.version_info >= (3, 9) and isinstance(node.slice, ast.Tuple): | ||
| self.printer.fstring(str(python_minifier.f_string.OuterFString(node))) | ||
| if sys.version_info < (3, 12): | ||
| pep701 = False | ||
| else: | ||
| pep701 = True | ||
| self.printer.fstring(str(python_minifier.f_string.OuterFString(node, pep701=pep701))) | ||
| def visit_NamedExpr(self, node): | ||
@@ -746,0 +751,0 @@ self._expression(node.target) |
@@ -9,3 +9,3 @@ """ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import copy | ||
@@ -28,3 +28,3 @@ import re | ||
| def __init__(self, node, allowed_quotes): | ||
| def __init__(self, node, allowed_quotes, pep701): | ||
| assert isinstance(node, ast.JoinedStr) | ||
@@ -34,2 +34,3 @@ | ||
| self.allowed_quotes = allowed_quotes | ||
| self.pep701 = pep701 | ||
@@ -59,3 +60,3 @@ def is_correct_ast(self, code): | ||
| if value_node.format_spec is not None: | ||
| conversion_candidates = [c + ':' + fs for c in conversion_candidates for fs in FormatSpec(value_node.format_spec, self.allowed_quotes).candidates()] | ||
| conversion_candidates = [c + ':' + fs for c in conversion_candidates for fs in FormatSpec(value_node.format_spec, self.allowed_quotes, self.pep701).candidates()] | ||
@@ -71,3 +72,6 @@ return [x + '}' for x in conversion_candidates] | ||
| nested_allowed = copy.copy(self.allowed_quotes) | ||
| nested_allowed.remove(quote) | ||
| if not self.pep701: | ||
| nested_allowed.remove(quote) | ||
| for v in self.node.values: | ||
@@ -94,3 +98,3 @@ if is_ast_node(v, ast.Str): | ||
| candidates = [ | ||
| x + y for x in candidates for y in FormattedValue(v, nested_allowed).get_candidates() | ||
| x + y for x in candidates for y in FormattedValue(v, nested_allowed, self.pep701).get_candidates() | ||
| ] + completed | ||
@@ -120,5 +124,5 @@ debug_specifier_candidates = [] | ||
| def __init__(self, node): | ||
| def __init__(self, node, pep701=False): | ||
| assert isinstance(node, ast.JoinedStr) | ||
| super(OuterFString, self).__init__(node, ['"', "'", '"""', "'''"]) | ||
| super(OuterFString, self).__init__(node, ['"', "'", '"""', "'''"], pep701=pep701) | ||
@@ -161,3 +165,3 @@ def __str__(self): | ||
| def __init__(self, node, allowed_quotes): | ||
| def __init__(self, node, allowed_quotes, pep701): | ||
| super(FormattedValue, self).__init__() | ||
@@ -168,2 +172,3 @@ | ||
| self.allowed_quotes = allowed_quotes | ||
| self.pep701 = pep701 | ||
| self.candidates = [''] | ||
@@ -189,3 +194,3 @@ | ||
| self.printer.delimiter(':') | ||
| self._append(FormatSpec(self.node.format_spec, self.allowed_quotes).candidates()) | ||
| self._append(FormatSpec(self.node.format_spec, self.allowed_quotes, pep701=self.pep701).candidates()) | ||
@@ -219,3 +224,3 @@ self.printer.delimiter('}') | ||
| def visit_Str(self, node): | ||
| self.printer.append(str(Str(node.s, self.allowed_quotes)), TokenTypes.NonNumberLiteral) | ||
| self.printer.append(str(Str(node.s, self.allowed_quotes, self.pep701)), TokenTypes.NonNumberLiteral) | ||
@@ -229,4 +234,9 @@ def visit_Bytes(self, node): | ||
| self.printer.delimiter(' ') | ||
| self._append(FString(node, allowed_quotes=self.allowed_quotes).candidates()) | ||
| self._append(FString(node, allowed_quotes=self.allowed_quotes, pep701=self.pep701).candidates()) | ||
| def visit_Lambda(self, node): | ||
| self.printer.delimiter('(') | ||
| super().visit_Lambda(node) | ||
| self.printer.delimiter(')') | ||
| def _finalize(self): | ||
@@ -245,10 +255,11 @@ self.candidates = [x + str(self.printer) for x in self.candidates] | ||
| May use any of the allowed quotes, no backslashes! | ||
| May use any of the allowed quotes. In Python <3.12, backslashes are not allowed. | ||
| """ | ||
| def __init__(self, s, allowed_quotes): | ||
| def __init__(self, s, allowed_quotes, pep701=False): | ||
| self._s = s | ||
| self.allowed_quotes = allowed_quotes | ||
| self.current_quote = None | ||
| self.pep701 = pep701 | ||
@@ -259,3 +270,3 @@ def _can_quote(self, c): | ||
| if (c == '\n' or c == '\r') and len(self.current_quote) == 1: | ||
| if (c == '\n' or c == '\r') and len(self.current_quote) == 1 and not self.pep701: | ||
| return False | ||
@@ -270,3 +281,3 @@ | ||
| for quote in self.allowed_quotes: | ||
| if c == '\n' or c == '\r': | ||
| if not self.pep701 and (c == '\n' or c == '\r'): | ||
| if len(quote) == 3: | ||
@@ -292,4 +303,12 @@ return quote | ||
| l += self.current_quote | ||
| l += c | ||
| if c == '\n': | ||
| l += '\\n' | ||
| elif c == '\r': | ||
| l += '\\r' | ||
| elif c == '\\': | ||
| l += '\\\\' | ||
| else: | ||
| l += c | ||
| if l: | ||
@@ -303,6 +322,6 @@ l += self.current_quote | ||
| if '\0' in self._s or '\\' in self._s: | ||
| raise ValueError('Impossible to represent a %r character in f-string expression part') | ||
| if '\0' in self._s or ('\\' in self._s and not self.pep701): | ||
| raise ValueError('Impossible to represent a character in f-string expression part') | ||
| if '\n' in self._s or '\r' in self._s: | ||
| if not self.pep701 and ('\n' in self._s or '\r' in self._s): | ||
| if '"""' not in self.allowed_quotes and "'''" not in self.allowed_quotes: | ||
@@ -339,3 +358,3 @@ raise ValueError( | ||
| def __init__(self, node, allowed_quotes): | ||
| def __init__(self, node, allowed_quotes, pep701): | ||
| assert isinstance(node, ast.JoinedStr) | ||
@@ -345,2 +364,3 @@ | ||
| self.allowed_quotes = allowed_quotes | ||
| self.pep701 = pep701 | ||
@@ -355,3 +375,3 @@ def candidates(self): | ||
| candidates = [ | ||
| x + y for x in candidates for y in FormattedValue(v, self.allowed_quotes).get_candidates() | ||
| x + y for x in candidates for y in FormattedValue(v, self.allowed_quotes, self.pep701).get_candidates() | ||
| ] | ||
@@ -358,0 +378,0 @@ else: |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
@@ -443,3 +443,3 @@ | ||
| def visit_With(self, node, is_async=False): | ||
| assert isinstance(node, ast.With) or (hasattr(ast, 'AsyncWith') and isinstance(node, ast.AsyncWith)) | ||
| assert is_ast_node(node, (ast.With, 'AsyncWith')) | ||
@@ -473,3 +473,3 @@ self.printer.newline() | ||
| def visit_withitem(self, node): | ||
| assert (hasattr(ast, 'withitem') and isinstance(node, ast.withitem)) or isinstance(node, ast.With) | ||
| assert is_ast_node(node, ('withitem', ast.With)) | ||
@@ -483,5 +483,3 @@ self._expression(node.context_expr) | ||
| def visit_FunctionDef(self, node, is_async=False): | ||
| assert isinstance(node, ast.FunctionDef) or ( | ||
| hasattr(ast, 'AsyncFunctionDef') and isinstance(node, ast.AsyncFunctionDef) | ||
| ) | ||
| assert is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef')) | ||
@@ -500,2 +498,11 @@ self.printer.newline() | ||
| self.printer.identifier(node.name) | ||
| if hasattr(node, 'type_params') and node.type_params: | ||
| self.printer.delimiter('[') | ||
| delimiter = Delimiter(self.printer) | ||
| for type_param in node.type_params: | ||
| delimiter.new_item() | ||
| self.visit(type_param) | ||
| self.printer.delimiter(']') | ||
| self.printer.delimiter('(') | ||
@@ -530,2 +537,10 @@ self.visit_arguments(node.args) | ||
| if hasattr(node, 'type_params') and node.type_params: | ||
| self.printer.delimiter('[') | ||
| delimiter = Delimiter(self.printer) | ||
| for type_param in node.type_params: | ||
| delimiter.new_item() | ||
| self.visit(type_param) | ||
| self.printer.delimiter(']') | ||
| with Delimiter(self.printer, add_parens=True) as delimiter: | ||
@@ -720,2 +735,42 @@ | ||
| # region Generic Types | ||
| def visit_TypeAlias(self, node): | ||
| assert isinstance(node, ast.TypeAlias) | ||
| self.printer.keyword('type') | ||
| self.visit_Name(node.name) | ||
| if hasattr(node, 'type_params') and node.type_params: | ||
| self.printer.delimiter('[') | ||
| delimiter = Delimiter(self.printer) | ||
| for param in node.type_params: | ||
| delimiter.new_item() | ||
| self.visit(param) | ||
| self.printer.delimiter(']') | ||
| self.printer.delimiter('=') | ||
| self._expression(node.value) | ||
| self.printer.end_statement() | ||
| def visit_TypeVar(self, node): | ||
| assert isinstance(node, ast.TypeVar) | ||
| assert isinstance(node.name, str) | ||
| self.printer.identifier(node.name) | ||
| if node.bound: | ||
| self.printer.delimiter(':') | ||
| self._expression(node.bound) | ||
| def visit_TypeVarTuple(self, node): | ||
| assert isinstance(node, ast.TypeVarTuple) | ||
| self.printer.operator('*') | ||
| self.printer.identifier(node.name) | ||
| def visit_ParamSpec(self, node): | ||
| assert isinstance(node, ast.ParamSpec) | ||
| self.printer.operator('*') | ||
| self.printer.operator('*') | ||
| self.printer.identifier(node.name) | ||
| # endregion | ||
| def visit_Module(self, node): | ||
@@ -795,3 +850,4 @@ if hasattr(node, 'docstring') and node.docstring is not None: | ||
| 'Match': self.visit_Match, | ||
| 'match_case': self.visit_match_case | ||
| 'match_case': self.visit_match_case, | ||
| 'TypeAlias': self.visit_TypeAlias | ||
| } | ||
@@ -798,0 +854,0 @@ |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -18,2 +18,3 @@ from python_minifier.rename.binding import NameBinding | ||
| module.tainted = False | ||
| module.preserved = set() | ||
| return self.visit(module) | ||
@@ -28,7 +29,2 @@ | ||
| if isinstance(namespace, ast.ClassDef): | ||
| binding = self.get_binding(name, get_nonlocal_namespace(namespace)) | ||
| binding.disallow_rename() | ||
| return binding | ||
| for binding in namespace.bindings: | ||
@@ -48,2 +44,6 @@ if binding.name == name: | ||
| if isinstance(namespace, ast.ClassDef): | ||
| # This name will become an attribute of the class, so it can't be renamed | ||
| binding.disallow_rename() | ||
| return binding | ||
@@ -168,2 +168,20 @@ | ||
| def visit_TypeVar(self, node): | ||
| if node.name not in node.namespace.nonlocal_names: | ||
| self.get_binding(node.name, node.namespace).add_reference(node) | ||
| get_global_namespace(node.namespace).preserved.add(node.name) | ||
| def visit_TypeVarTuple(self, node): | ||
| if node.name not in node.namespace.nonlocal_names: | ||
| self.get_binding(node.name, node.namespace).add_reference(node) | ||
| get_global_namespace(node.namespace).preserved.add(node.name) | ||
| def visit_ParamSpec(self, node): | ||
| if node.name not in node.namespace.nonlocal_names: | ||
| self.get_binding(node.name, node.namespace).add_reference(node) | ||
| get_global_namespace(node.namespace).preserved.add(node.name) | ||
| def bind_names(module): | ||
@@ -170,0 +188,0 @@ """ |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -14,3 +14,2 @@ from python_minifier.rename.util import arg_rename_in_place, insert | ||
| :param bool allow_rename: If this binding may be renamed | ||
| :param int rename_cost: The cost of renaming this binding in bytes, NOT including the difference in name lengths | ||
@@ -135,2 +134,8 @@ """ | ||
| pass | ||
| elif is_ast_node(node, 'TypeVar'): | ||
| pass | ||
| elif is_ast_node(node, 'TypeVarTuple'): | ||
| pass | ||
| elif is_ast_node(node, 'ParamSpec'): | ||
| pass | ||
@@ -183,2 +188,8 @@ else: | ||
| pass | ||
| elif is_ast_node(node, 'TypeVar'): | ||
| pass | ||
| elif is_ast_node(node, 'TypeVarTuple'): | ||
| pass | ||
| elif is_ast_node(node, 'ParamSpec'): | ||
| pass | ||
@@ -227,2 +238,8 @@ else: | ||
| mentions += 1 | ||
| elif is_ast_node(node, 'TypeVar'): | ||
| mentions += 1 | ||
| elif is_ast_node(node, 'TypeVarTuple'): | ||
| mentions += 1 | ||
| elif is_ast_node(node, 'ParamSpec'): | ||
| mentions += 1 | ||
@@ -296,3 +313,3 @@ else: | ||
| def __repr__(self): | ||
| return self.__class__.__name__ + '(name=%r) <references=%r>' % (self._name, len(self._references)) | ||
| return self.__class__.__name__ + '(name=%r, allow_rename=%r) <references=%r>' % (self._name, self._allow_rename, len(self._references)) | ||
@@ -395,2 +412,8 @@ def should_rename(self, new_name): | ||
| node.rest = new_name | ||
| elif is_ast_node(node, 'TypeVar'): | ||
| node.name = new_name | ||
| elif is_ast_node(node, 'TypeVarTuple'): | ||
| node.name = new_name | ||
| elif is_ast_node(node, 'ParamSpec'): | ||
| node.name = new_name | ||
@@ -457,1 +480,23 @@ if func_namespace_binding is not None: | ||
| ) | ||
| def is_redefined(self): | ||
| """ | ||
| Do one of the references to this builtin name redefine it? | ||
| Could some references actually not be references to the builtin? | ||
| This can happen with code like: | ||
| class MyClass: | ||
| IndexError = IndexError | ||
| """ | ||
| for node in self.references: | ||
| if not isinstance(node, ast.Name): | ||
| return True | ||
| if not isinstance(node.ctx, ast.Load): | ||
| return True | ||
| return False |
@@ -5,3 +5,3 @@ """ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -65,2 +65,6 @@ from python_minifier.rename.util import is_namespace | ||
| if hasattr(functiondef, 'type_params') and functiondef.type_params is not None: | ||
| for node in functiondef.type_params: | ||
| add_parent(node, parent=functiondef, namespace=functiondef.namespace) | ||
| if hasattr(functiondef, 'returns') and functiondef.returns is not None: | ||
@@ -94,2 +98,6 @@ add_parent(functiondef.returns, parent=functiondef, namespace=functiondef.namespace) | ||
| if hasattr(classdef, 'type_params') and classdef.type_params is not None: | ||
| for node in classdef.type_params: | ||
| add_parent(node, parent=classdef, namespace=classdef.namespace) | ||
| def add_parent_to_comprehension(node, namespace): | ||
@@ -158,2 +166,9 @@ assert is_ast_node(node, (ast.GeneratorExp, 'SetComp', 'DictComp', 'ListComp')) | ||
| if isinstance(node, ast.Name): | ||
| if isinstance(namespace, ast.ClassDef): | ||
| if isinstance(node.ctx, ast.Load): | ||
| namespace.nonlocal_names.add(node.id) | ||
| elif isinstance(node.ctx, ast.Store) and isinstance(node.parent, ast.AugAssign): | ||
| namespace.nonlocal_names.add(node.id) | ||
| for child in ast.iter_child_nodes(node): | ||
@@ -160,0 +175,0 @@ add_parent(child, parent=node, namespace=namespace) |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -116,4 +116,5 @@ from python_minifier.rename.binding import Binding | ||
| def __call__(self, module): | ||
| def __call__(self, module, ignore_slots=True): | ||
| self.module = module | ||
| self._ignore_slots = ignore_slots | ||
| self._hoisted = {} | ||
@@ -234,3 +235,17 @@ self.visit(module) | ||
| def visit_Assign(self, node): | ||
| if not self._ignore_slots: | ||
| return self.generic_visit(node) | ||
| if not is_ast_node(node.namespace, ast.ClassDef): | ||
| return self.generic_visit(node) | ||
| for target in node.targets: | ||
| if is_ast_node(target, ast.Name) and target.id == '__slots__': | ||
| # This is a __slots__ assignment, don't hoist the literals | ||
| return | ||
| return self.generic_visit(node) | ||
| def rename_literals(module): | ||
| HoistLiterals()(module) |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -3,0 +3,0 @@ from python_minifier.rename.binding import NameBinding |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -37,3 +37,11 @@ from python_minifier.rename.binding import BuiltinBinding, NameBinding | ||
| def get_binding_disallow_class_namespace_rename(name, namespace): | ||
| binding = get_binding(name, namespace) | ||
| if isinstance(namespace, ast.ClassDef): | ||
| # This name will become an attribute of a class, so it can't be renamed | ||
| binding.disallow_rename() | ||
| return binding | ||
| def resolve_names(node): | ||
@@ -51,8 +59,16 @@ """ | ||
| elif isinstance(node, ast.Name) and node.id in node.namespace.nonlocal_names: | ||
| get_binding(node.id, node.namespace).add_reference(node) | ||
| binding = get_binding(node.id, node.namespace) | ||
| binding.add_reference(node) | ||
| if isinstance(node.ctx, ast.Store) and isinstance(node.namespace, ast.ClassDef): | ||
| binding.disallow_rename() | ||
| elif isinstance(node, ast.ClassDef) and node.name in node.namespace.nonlocal_names: | ||
| get_binding(node.name, node.namespace).add_reference(node) | ||
| binding = get_binding_disallow_class_namespace_rename(node.name, node.namespace) | ||
| binding.add_reference(node) | ||
| elif is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef')) and node.name in node.namespace.nonlocal_names: | ||
| get_binding(node.name, node.namespace).add_reference(node) | ||
| binding = get_binding_disallow_class_namespace_rename(node.name, node.namespace) | ||
| binding.add_reference(node) | ||
| elif isinstance(node, ast.alias): | ||
@@ -62,3 +78,5 @@ | ||
| if node.asname in node.namespace.nonlocal_names: | ||
| get_binding(node.asname, node.namespace).add_reference(node) | ||
| binding = get_binding_disallow_class_namespace_rename(node.asname, node.namespace) | ||
| binding.add_reference(node) | ||
| else: | ||
@@ -69,3 +87,3 @@ # This binds the root module only for a dotted import | ||
| if root_module in node.namespace.nonlocal_names: | ||
| binding = get_binding(root_module, node.namespace) | ||
| binding = get_binding_disallow_class_namespace_rename(root_module, node.namespace) | ||
| binding.add_reference(node) | ||
@@ -78,11 +96,11 @@ | ||
| if isinstance(node.name, str) and node.name in node.namespace.nonlocal_names: | ||
| get_binding(node.name, node.namespace).add_reference(node) | ||
| get_binding_disallow_class_namespace_rename(node.name, node.namespace).add_reference(node) | ||
| elif is_ast_node(node, 'Nonlocal'): | ||
| for name in node.names: | ||
| get_binding(name, node.namespace).add_reference(node) | ||
| get_binding_disallow_class_namespace_rename(name, node.namespace).add_reference(node) | ||
| elif is_ast_node(node, ('MatchAs', 'MatchStar')) and node.name in node.namespace.nonlocal_names: | ||
| get_binding(node.name, node.namespace).add_reference(node) | ||
| get_binding_disallow_class_namespace_rename(node.name, node.namespace).add_reference(node) | ||
| elif is_ast_node(node, 'MatchMapping') and node.rest in node.namespace.nonlocal_names: | ||
| get_binding(node.rest, node.namespace).add_reference(node) | ||
| get_binding_disallow_class_namespace_rename(node.rest, node.namespace).add_reference(node) | ||
@@ -89,0 +107,0 @@ elif is_ast_node(node, 'Exec'): |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
@@ -26,2 +26,11 @@ | ||
| def iter_child_namespaces(node): | ||
| for child in ast.iter_child_nodes(node): | ||
| if is_namespace(child): | ||
| yield child | ||
| else: | ||
| for c in iter_child_namespaces(child): | ||
| yield c | ||
| def get_global_namespace(node): | ||
@@ -28,0 +37,0 @@ """ |
@@ -120,3 +120,5 @@ """Tools for assembling python code from tokens.""" | ||
| 'or', 'pass', 'raise', 'return', | ||
| 'try', 'while', 'with', 'yield', '_', 'case', 'match', 'print', 'exec' | ||
| 'try', 'while', 'with', 'yield', '_', | ||
| 'case', 'match', 'print', 'exec', | ||
| 'type' | ||
| ] | ||
@@ -129,3 +131,3 @@ | ||
| if kw in ['_', 'case', 'match']: | ||
| if kw in ['_', 'case', 'match', 'type']: | ||
| self.previous_token = TokenTypes.SoftKeyword | ||
@@ -132,0 +134,0 @@ else: |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -17,4 +17,6 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer | ||
| alias = [] | ||
| namespace = None | ||
| for statement in node_list: | ||
| namespace = statement.namespace | ||
| if isinstance(statement, ast.Import): | ||
@@ -24,3 +26,3 @@ alias += statement.names | ||
| if alias: | ||
| yield self.add_child(ast.Import(names=alias), parent=parent) | ||
| yield self.add_child(ast.Import(names=alias), parent=parent, namespace=namespace) | ||
| alias = [] | ||
@@ -31,4 +33,5 @@ | ||
| if alias: | ||
| yield self.add_child(ast.Import(names=alias), parent=parent) | ||
| yield self.add_child(ast.Import(names=alias), parent=parent, namespace=namespace) | ||
| def _combine_import_from(self, node_list, parent): | ||
@@ -61,3 +64,3 @@ | ||
| yield self.add_child( | ||
| ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent | ||
| ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent, namespace=prev_import.namespace | ||
| ) | ||
@@ -70,3 +73,3 @@ alias = [] | ||
| yield self.add_child( | ||
| ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent | ||
| ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent, namespace=prev_import.namespace | ||
| ) | ||
@@ -73,0 +76,0 @@ |
@@ -1,4 +0,5 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
| from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions | ||
| from python_minifier.transforms.suite_transformer import SuiteTransformer | ||
@@ -12,2 +13,7 @@ | ||
| def __init__(self, options): | ||
| assert isinstance(options, RemoveAnnotationsOptions) | ||
| self._options = options | ||
| super(RemoveAnnotations, self).__init__() | ||
| def __call__(self, node): | ||
@@ -23,3 +29,6 @@ if sys.version_info < (3, 0): | ||
| if hasattr(node, 'returns'): | ||
| if hasattr(node, 'type_params') and node.type_params is not None: | ||
| node.type_params = [self.visit(t) for t in node.type_params] | ||
| if hasattr(node, 'returns') and self._options.remove_return_annotations: | ||
| node.returns = None | ||
@@ -42,3 +51,4 @@ | ||
| if hasattr(node, 'varargannotation'): | ||
| node.varargannotation = None | ||
| if self._options.remove_argument_annotations: | ||
| node.varargannotation = None | ||
| else: | ||
@@ -49,3 +59,4 @@ if node.vararg: | ||
| if hasattr(node, 'kwargannotation'): | ||
| node.kwargannotation = None | ||
| if self._options.remove_argument_annotations: | ||
| node.kwargannotation = None | ||
| else: | ||
@@ -58,3 +69,4 @@ if node.kwarg: | ||
| def visit_arg(self, node): | ||
| node.annotation = None | ||
| if self._options.remove_argument_annotations: | ||
| node.annotation = None | ||
| return node | ||
@@ -105,2 +117,10 @@ | ||
| # is this a class attribute or a variable? | ||
| if isinstance(node.parent, ast.ClassDef): | ||
| if not self._options.remove_class_attribute_annotations: | ||
| return node | ||
| else: | ||
| if not self._options.remove_variable_annotations: | ||
| return node | ||
| if is_dataclass_field(node) or is_typing_sensitive(node): | ||
@@ -107,0 +127,0 @@ return node |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -3,0 +3,0 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -3,0 +3,0 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
| import sys | ||
@@ -19,4 +19,7 @@ | ||
| if hasattr(node, 'type_params') and node.type_params is not None: | ||
| node.type_params = [self.visit(t) for t in node.type_params] | ||
| node.body = [self.visit(n) for n in node.body] | ||
| return node |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -3,0 +3,0 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -53,2 +53,5 @@ from python_minifier.rename.mapper import add_parent | ||
| if hasattr(node, 'type_params') and node.type_params is not None: | ||
| node.type_params = [self.visit(t) for t in node.type_params] | ||
| node.body = self.suite(node.body, parent=node) | ||
@@ -55,0 +58,0 @@ node.decorator_list = [self.visit(d) for d in node.decorator_list] |
@@ -1,2 +0,2 @@ | ||
| import ast | ||
| import python_minifier.ast_compat as ast | ||
@@ -35,12 +35,12 @@ def is_ast_node(node, types): | ||
| if hasattr(ast, 'Constant') and isinstance(node, ast.Constant): | ||
| if node.value in [None, True, False]: | ||
| return ast.NameConstant in types | ||
| if type(node.value) in [type(None), type(True), type(False)]: | ||
| return ast.NameConstant in actual_types | ||
| elif isinstance(node.value, (int, float, complex)): | ||
| return ast.Num in types | ||
| return ast.Num in actual_types | ||
| elif isinstance(node.value, str): | ||
| return ast.Str in types | ||
| return ast.Str in actual_types | ||
| elif isinstance(node.value, bytes): | ||
| return ast.Bytes in types | ||
| return ast.Bytes in actual_types | ||
| elif node.value == Ellipsis: | ||
| return ast.Ellipsis in types | ||
| return ast.Ellipsis in actual_types | ||
| else: | ||
@@ -47,0 +47,0 @@ raise RuntimeError('Unknown Constant value %r' % type(node.value)) |
| import ast | ||
| from helpers import print_namespace | ||
| from python_minifier import add_namespace | ||
| from python_minifier.rename import bind_names, resolve_names | ||
| from python_minifier.transforms.combine_imports import CombineImports | ||
@@ -12,2 +14,15 @@ from python_minifier.ast_compare import compare_ast | ||
| def assert_namespace_tree(source, expected_tree): | ||
| tree = ast.parse(source) | ||
| add_namespace(tree) | ||
| CombineImports()(tree) | ||
| bind_names(tree) | ||
| resolve_names(tree) | ||
| actual = print_namespace(tree) | ||
| print(actual) | ||
| assert actual.strip() == expected_tree.strip() | ||
| def test_import(): | ||
@@ -85,1 +100,66 @@ source = '''import builtins | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_import_as_class_namespace(): | ||
| source = ''' | ||
| class MyClass: | ||
| import os as Hello | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyClass', allow_rename=True) <references=1> | ||
| + Class MyClass | ||
| - NameBinding(name='Hello', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_import_class_namespace(): | ||
| source = ''' | ||
| class MyClass: | ||
| import Hello | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyClass', allow_rename=True) <references=1> | ||
| + Class MyClass | ||
| - NameBinding(name='Hello', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_from_import_class_namespace(): | ||
| source = ''' | ||
| class MyClass: | ||
| from hello import Hello | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyClass', allow_rename=True) <references=1> | ||
| + Class MyClass | ||
| - NameBinding(name='Hello', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
| def test_from_import_as_class_namespace(): | ||
| source = ''' | ||
| class MyClass: | ||
| from hello import babadook as Hello | ||
| ''' | ||
| expected_namespaces = ''' | ||
| + Module | ||
| - NameBinding(name='MyClass', allow_rename=True) <references=1> | ||
| + Class MyClass | ||
| - NameBinding(name='Hello', allow_rename=False) <references=1> | ||
| ''' | ||
| assert_namespace_tree(source, expected_namespaces) | ||
@@ -8,2 +8,3 @@ import ast | ||
| from python_minifier.ast_compare import compare_ast | ||
| from python_minifier.ast_printer import print_ast | ||
| from python_minifier.rename import add_namespace, bind_names, resolve_names, rename, rename_literals, allow_rename_locals, allow_rename_globals | ||
@@ -495,1 +496,26 @@ | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_no_hoist_slots(): | ||
| source = ''' | ||
| class SlotsA(object): | ||
| __slots__ = ['aaaaa', 'bbbbb'] | ||
| notslots = ['aaaaa'] | ||
| class SlotsB(object): | ||
| __slots__ = 'aaaaa', 'bbbbb' | ||
| notslots = ['aaaaa'] | ||
| ''' | ||
| expected = ''' | ||
| A = 'aaaaa' | ||
| class SlotsA(object): | ||
| __slots__ = ['aaaaa', 'bbbbb'] | ||
| notslots = [A] | ||
| class SlotsB(object): | ||
| __slots__ = 'aaaaa', 'bbbbb' | ||
| notslots = [A] | ||
| ''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = hoist(source) | ||
| print(print_ast(expected_ast)) | ||
| print(print_ast(actual_ast)) | ||
| compare_ast(expected_ast, actual_ast) |
@@ -5,10 +5,10 @@ import ast | ||
| from python_minifier import add_namespace | ||
| from python_minifier import add_namespace, RemoveAnnotationsOptions | ||
| from python_minifier.transforms.remove_annotations import RemoveAnnotations | ||
| from python_minifier.ast_compare import compare_ast | ||
| def remove_annotations(source): | ||
| def remove_annotations(source, **kwargs): | ||
| module = ast.parse(source) | ||
| add_namespace(module) | ||
| RemoveAnnotations()(module) | ||
| RemoveAnnotations(RemoveAnnotationsOptions(**kwargs))(module) | ||
| return module | ||
@@ -23,9 +23,20 @@ | ||
| b = 2 | ||
| c : int = 3''' | ||
| c : int = 3 | ||
| def a(z: str) -> int: pass | ||
| class A: | ||
| a: int | ||
| ''' | ||
| expected = '''a = 1 | ||
| b = 2 | ||
| c = 3''' | ||
| c = 3 | ||
| def a(z: str) -> int: pass | ||
| class A: | ||
| a: int''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=True, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=False) | ||
| compare_ast(expected_ast, actual_ast) | ||
@@ -37,2 +48,3 @@ | ||
| # args and return are removed | ||
| source = '''def test(a: str, b: int=1, *c: hello, **aws: crap) -> None: | ||
@@ -44,5 +56,39 @@ pass''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=True, | ||
| remove_argument_annotations=True, | ||
| remove_class_attribute_annotations=False) | ||
| compare_ast(expected_ast, actual_ast) | ||
| # args only are removed | ||
| source = '''def test(a: str, b: int=1, *c: hello, **aws: crap) -> None: | ||
| pass''' | ||
| expected = '''def test(a, b=1, *c, **aws) -> None: | ||
| pass''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=True, | ||
| remove_class_attribute_annotations=False) | ||
| compare_ast(expected_ast, actual_ast) | ||
| # return only are removed | ||
| source = '''def test(a: str, b: int=1, *c: hello, **aws: crap) -> None: | ||
| pass''' | ||
| expected = '''def test(a: str, b: int=1, *c: hello, **aws: crap): | ||
| pass''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations( | ||
| source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=True, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=False | ||
| ) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_AsyncFunctionDef(): | ||
@@ -58,3 +104,7 @@ if sys.version_info < (3, 6): | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=True, | ||
| remove_return_annotations=True, | ||
| remove_argument_annotations=True, | ||
| remove_class_attribute_annotations=False) | ||
| compare_ast(expected_ast, actual_ast) | ||
@@ -67,9 +117,46 @@ | ||
| source = '''a :str | ||
| class A: | ||
| a: int | ||
| def c(self, a: int) -> None: pass | ||
| ''' | ||
| expected = '''a:0''' | ||
| expected = '''a:0 | ||
| class A: | ||
| a: int | ||
| def c(self, a: int) -> None: pass | ||
| ''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=True, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=False) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_class_attributes(): | ||
| if sys.version_info < (3, 6): | ||
| pytest.skip('Variable annotation unsupported in python < 3.6') | ||
| source = '''a :str | ||
| class A: | ||
| a: int | ||
| b: int=2 | ||
| def c(self, a: int) -> None: pass | ||
| ''' | ||
| expected = '''a:str | ||
| class A: | ||
| a:0 | ||
| b=2 | ||
| def c(self, a: int) -> None: pass | ||
| ''' | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=True) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_no_remove_dataclass(): | ||
@@ -106,3 +193,7 @@ if sys.version_info < (3, 6): | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=True) | ||
| compare_ast(expected_ast, actual_ast) | ||
@@ -130,3 +221,7 @@ | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=True) | ||
| compare_ast(expected_ast, actual_ast) | ||
@@ -155,3 +250,7 @@ | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=True) | ||
| compare_ast(expected_ast, actual_ast) | ||
@@ -182,3 +281,7 @@ | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=True) | ||
| compare_ast(expected_ast, actual_ast) | ||
@@ -227,3 +330,7 @@ | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_annotations(source) | ||
| actual_ast = remove_annotations(source, | ||
| remove_variable_annotations=False, | ||
| remove_return_annotations=False, | ||
| remove_argument_annotations=False, | ||
| remove_class_attribute_annotations=True) | ||
| compare_ast(expected_ast, actual_ast) |
| import ast | ||
| import sys | ||
| import pytest | ||
| from python_minifier import unparse | ||
| from python_minifier.ast_compare import compare_ast | ||
| def test_fstring_empty_str(): | ||
| if sys.version_info < (3, 6): | ||
| pytest.skip('f-string expressions not allowed in python < 3.6') | ||
| source = r''' | ||
| f"""\ | ||
| {fg_br}""" | ||
| ''' | ||
| print(source) | ||
| expected_ast = ast.parse(source) | ||
| actual_ast = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(actual_ast)) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
385939
49.22%81
19.12%10007
50.96%