diff --git a/.github/workflows/xtest.yaml b/.github/workflows/xtest.yaml index bebbab5f..1603c9eb 100644 --- a/.github/workflows/xtest.yaml +++ b/.github/workflows/xtest.yaml @@ -17,6 +17,11 @@ jobs: container: image: danielflook/python-minifier-build:${{ matrix.python }}-2024-01-12 steps: + - name: Cleanup + run: | + echo rm -vrf "$HOME/.*" "$HOME/*" "$GITHUB_WORKSPACE/.*" "$GITHUB_WORKSPACE/*" + rm -vrf "$HOME/.*" "$HOME/*" "$GITHUB_WORKSPACE/.*" "$GITHUB_WORKSPACE/*" + - name: Checkout uses: actions/checkout@v3 with: diff --git a/corpus_test/generate_results.py b/corpus_test/generate_results.py index a8c3fdf9..8e15c13c 100644 --- a/corpus_test/generate_results.py +++ b/corpus_test/generate_results.py @@ -1,5 +1,4 @@ import argparse -import datetime import gzip import os import sys @@ -63,6 +62,9 @@ def minify_corpus_entry(corpus_path, corpus_entry): result.time = end_time - start_time result.outcome = 'UnstableMinification' + except AssertionError as assertion_error: + result.outcome = 'Exception: AssertionError' + except Exception as exception: result.outcome = 'Exception: ' + str(exception) diff --git a/src/python_minifier/__init__.py b/src/python_minifier/__init__.py index 79753a73..9863b791 100644 --- a/src/python_minifier/__init__.py +++ b/src/python_minifier/__init__.py @@ -228,7 +228,6 @@ def unparse(module): try: compare_ast(module, minified_module) except CompareError as compare_error: - print(printer.code) raise UnstableMinification(compare_error, '', printer.code) return printer.code diff --git a/src/python_minifier/rename/bind_names.py b/src/python_minifier/rename/bind_names.py index f4808cf7..1a2f586a 100644 --- a/src/python_minifier/rename/bind_names.py +++ b/src/python_minifier/rename/bind_names.py @@ -24,11 +24,6 @@ def get_binding(self, name, namespace): # nonlocal names should not create a binding in any context assert name not in namespace.nonlocal_names - if isinstance(namespace, ast.ClassDef): - binding = self.get_binding(name, get_nonlocal_namespace(namespace)) - binding.disallow_rename() - return binding - for binding in namespace.bindings: if binding.name == name: break @@ -43,6 +38,10 @@ def get_binding(self, name, namespace): # This is actually a syntax error - but we want the same syntax error after minifying! binding.disallow_rename() + 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 def visit_Name(self, node): diff --git a/src/python_minifier/rename/binding.py b/src/python_minifier/rename/binding.py index 49af234a..d18f1703 100644 --- a/src/python_minifier/rename/binding.py +++ b/src/python_minifier/rename/binding.py @@ -11,7 +11,6 @@ class Binding(object): :param name: A name for this binding :type name: str or None :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 """ @@ -289,7 +288,7 @@ def __init__(self, name, *args, **kwargs): self.disallow_rename() def __repr__(self): - return self.__class__.__name__ + '(name=%r) ' % (self._name, len(self._references)) + return self.__class__.__name__ + '(name=%r, allow_rename=%r) ' % (self._name, self._allow_rename, len(self._references)) def should_rename(self, new_name): """ @@ -448,3 +447,25 @@ def rename(self, new_name): ), ) ) + + 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 \ No newline at end of file diff --git a/src/python_minifier/rename/mapper.py b/src/python_minifier/rename/mapper.py index f2a98321..d21a1908 100644 --- a/src/python_minifier/rename/mapper.py +++ b/src/python_minifier/rename/mapper.py @@ -160,6 +160,13 @@ def add_parent(node, parent=None, namespace=None): if is_ast_node(node, 'Nonlocal'): namespace.nonlocal_names.update(node.names) + 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): add_parent(child, parent=node, namespace=namespace) diff --git a/src/python_minifier/rename/resolve_names.py b/src/python_minifier/rename/resolve_names.py index d22a0211..104b5366 100644 --- a/src/python_minifier/rename/resolve_names.py +++ b/src/python_minifier/rename/resolve_names.py @@ -34,6 +34,14 @@ def get_binding(name, namespace): namespace.bindings.append(binding) return binding +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): """ @@ -47,23 +55,33 @@ def resolve_names(node): if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load): get_binding(node.id, node.namespace).add_reference(node) 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): if node.asname is not None: 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: # This binds the root module only for a dotted import root_module = node.name.split('.')[0] 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) if '.' in node.name: @@ -71,15 +89,15 @@ def resolve_names(node): elif isinstance(node, ast.ExceptHandler) and node.name is not None: 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) elif is_ast_node(node, 'Exec'): get_global_namespace(node).tainted = True diff --git a/src/python_minifier/rename/util.py b/src/python_minifier/rename/util.py index b049c2ba..098ec24a 100644 --- a/src/python_minifier/rename/util.py +++ b/src/python_minifier/rename/util.py @@ -23,6 +23,15 @@ def create_is_namespace(): is_namespace = create_is_namespace() +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): """ Return the global namespace for a node diff --git a/src/python_minifier/transforms/combine_imports.py b/src/python_minifier/transforms/combine_imports.py index 5373b2ed..43384ec5 100644 --- a/src/python_minifier/transforms/combine_imports.py +++ b/src/python_minifier/transforms/combine_imports.py @@ -14,19 +14,22 @@ class CombineImports(SuiteTransformer): def _combine_import(self, node_list, parent): alias = [] + namespace = None for statement in node_list: + namespace = statement.namespace if isinstance(statement, ast.Import): alias += statement.names else: 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 = [] yield statement 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): @@ -55,7 +58,7 @@ def combine(statement): else: if 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 ) alias = [] @@ -63,7 +66,7 @@ def combine(statement): if 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 ) def suite(self, node_list, parent): diff --git a/src/python_minifier/transforms/remove_exception_brackets.py b/src/python_minifier/transforms/remove_exception_brackets.py index aac64976..9f98d787 100644 --- a/src/python_minifier/transforms/remove_exception_brackets.py +++ b/src/python_minifier/transforms/remove_exception_brackets.py @@ -77,7 +77,8 @@ def _remove_empty_call(binding): assert isinstance(binding, BuiltinBinding) for name_node in binding.references: - assert isinstance(name_node, ast.Name) # For this to be a builtin, all references must be name nodes as it is not defined anywhere + # 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 @@ -110,7 +111,13 @@ def remove_no_arg_exception_call(module): return module for binding in module.bindings: - if isinstance(binding, BuiltinBinding) and binding.name in builtin_exceptions: + 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) diff --git a/test/helpers.py b/test/helpers.py new file mode 100644 index 00000000..44efa8d2 --- /dev/null +++ b/test/helpers.py @@ -0,0 +1,50 @@ +import ast + +from python_minifier.rename import add_namespace, resolve_names +from python_minifier.rename.bind_names import bind_names +from python_minifier.rename.util import iter_child_namespaces +from python_minifier.util import is_ast_node + + +def assert_namespace_tree(source, expected_tree): + tree = ast.parse(source) + + add_namespace(tree) + bind_names(tree) + resolve_names(tree) + + actual = print_namespace(tree) + + print(actual) + assert actual.strip() == expected_tree.strip() + + +def print_namespace(namespace, indent=''): + s = '' + + if not indent: + s += '\n' + + def namespace_name(node): + if is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef')): + return 'Function ' + node.name + elif isinstance(node, ast.ClassDef): + return 'Class ' + node.name + else: + return namespace.__class__.__name__ + + s += indent + '+ ' + namespace_name(namespace) + '\n' + + for name in sorted(namespace.global_names): + s += indent + ' - global ' + name + '\n' + + for name in sorted(namespace.nonlocal_names): + s += indent + ' - nonlocal ' + name + '\n' + + for binding in sorted(namespace.bindings, key=lambda b: b.name): + s += indent + ' - ' + repr(binding) + '\n' + + for child in iter_child_namespaces(namespace): + s += print_namespace(child, indent=indent + ' ') + + return s diff --git a/test/test_bind_names.py b/test/test_bind_names.py new file mode 100644 index 00000000..42253a6b --- /dev/null +++ b/test/test_bind_names.py @@ -0,0 +1,1354 @@ +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) +''' + + 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) + - NameBinding(name='name_in_module', allow_rename=True) + + Lambda + - NameBinding(name='arg', allow_rename=False) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) +''' + + 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) + - NameBinding(name='name_in_module', allow_rename=True) + - BuiltinBinding(name='print', allow_rename=True) + + Function func + - NameBinding(name='arg', allow_rename=True) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='inner_func', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) + - NameBinding(name='name_in_func', allow_rename=True) + + Function inner_func + - NameBinding(name='name_in_inner_func', allow_rename=True) +''' + + 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) + - NameBinding(name='name_in_module', allow_rename=True) + - BuiltinBinding(name='print', allow_rename=True) + + Function func + - NameBinding(name='arg', allow_rename=True) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='inner_func', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) + - NameBinding(name='name_in_func', allow_rename=True) + + Function inner_func + - NameBinding(name='name_in_inner_func', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + GeneratorExp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + GeneratorExp + - NameBinding(name='c', allow_rename=True) + + GeneratorExp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + SetComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + SetComp + - NameBinding(name='c', allow_rename=True) + + SetComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + ListComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + ListComp + - NameBinding(name='c', allow_rename=True) + + ListComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + DictComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + DictComp + - NameBinding(name='c', allow_rename=True) + + DictComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='OhALongName', allow_rename=False) + - NameBinding(name='func', allow_rename=True) + - BuiltinBinding(name='int', allow_rename=True) + + Function func + - NameBinding(name='C', allow_rename=True) + + Class C + - nonlocal OhALongName + - nonlocal int + - NameBinding(name='ClassName', allow_rename=False) + - NameBinding(name='MyOtherName', allow_rename=False) +''' + + 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) + - NameBinding(name='OhALongName', allow_rename=False) + - NameBinding(name='func', allow_rename=True) + + Function func + - NameBinding(name='C', allow_rename=True) + + Class C + - nonlocal OhALongName + - NameBinding(name='MyOtherName', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=True) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - BuiltinBinding(name='int', allow_rename=True) + + Class LazyList + - nonlocal int + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - BuiltinBinding(name='int', allow_rename=False) + + Class LazyList + - nonlocal int + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='int', allow_rename=False) + + Class LazyList + - nonlocal int + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Exception + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Exception + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Blah + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Blah + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Blah + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Blah + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Blah + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='LazyList', allow_rename=True) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal Blah + - nonlocal MyNonLocal + - NameBinding(name='MyAttribute', allow_rename=False) +''' + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + 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) + - NameBinding(name='MyNonLocal', allow_rename=False) + + Class LazyList + - nonlocal MyNonLocal +''' + + assert_namespace_tree(source, expected_namespaces) diff --git a/test/test_bind_names_python2.py b/test/test_bind_names_python2.py new file mode 100644 index 00000000..3c255316 --- /dev/null +++ b/test/test_bind_names_python2.py @@ -0,0 +1,605 @@ +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) + - NameBinding(name='name_in_module', allow_rename=True) +''' + + 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) + - NameBinding(name='name_in_module', allow_rename=True) + + Lambda + - NameBinding(name='arg', allow_rename=False) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) +''' + + 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) + - NameBinding(name='func', allow_rename=True) + - NameBinding(name='name_in_module', allow_rename=True) + + Function func + - NameBinding(name='arg', allow_rename=True) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='inner_func', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) + - NameBinding(name='name_in_func', allow_rename=True) + + Function inner_func + - NameBinding(name='name_in_inner_func', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) + + GeneratorExp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) + + GeneratorExp + - NameBinding(name='c', allow_rename=True) + + GeneratorExp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) + + SetComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) + + SetComp + - NameBinding(name='c', allow_rename=True) + + SetComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) + + DictComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=False) + - NameBinding(name='line', allow_rename=True) + + DictComp + - NameBinding(name='c', allow_rename=True) + + DictComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='OhALongName', allow_rename=False) + - NameBinding(name='func', allow_rename=True) + + Function func + - NameBinding(name='C', allow_rename=True) + + Class C + - nonlocal OhALongName + - NameBinding(name='ClassName', allow_rename=False) + - NameBinding(name='MyOtherName', allow_rename=False) +''' + + 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) + - NameBinding(name='OhALongName', allow_rename=False) + - NameBinding(name='func', allow_rename=True) + + Function func + - NameBinding(name='C', allow_rename=True) + + Class C + - nonlocal OhALongName + - NameBinding(name='MyOtherName', allow_rename=False) +''' + + assert_namespace_tree(source, expected_namespaces) diff --git a/test/test_bind_names_python33.py b/test/test_bind_names_python33.py new file mode 100644 index 00000000..d0ab6b2b --- /dev/null +++ b/test/test_bind_names_python33.py @@ -0,0 +1,620 @@ +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) + - NameBinding(name='name_in_module', allow_rename=True) +''' + + 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) + - NameBinding(name='name_in_module', allow_rename=True) + + Lambda + - NameBinding(name='arg', allow_rename=False) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) +''' + + 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) + - NameBinding(name='func', allow_rename=True) + - NameBinding(name='name_in_module', allow_rename=True) + - BuiltinBinding(name='print', allow_rename=True) + + Function func + - NameBinding(name='arg', allow_rename=True) + - NameBinding(name='args', allow_rename=True) + - NameBinding(name='inner_func', allow_rename=True) + - NameBinding(name='kwargs', allow_rename=True) + - NameBinding(name='name_in_func', allow_rename=True) + + Function inner_func + - NameBinding(name='name_in_inner_func', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + GeneratorExp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + GeneratorExp + - NameBinding(name='c', allow_rename=True) + + GeneratorExp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) + + GeneratorExp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + SetComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + SetComp + - NameBinding(name='c', allow_rename=True) + + SetComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) + + SetComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + ListComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + ListComp + - NameBinding(name='c', allow_rename=True) + + ListComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) + + ListComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - BuiltinBinding(name='range', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='f', allow_rename=True) + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + DictComp + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='c', allow_rename=True) + - NameBinding(name='file', allow_rename=True) + - NameBinding(name='line', allow_rename=True) + + DictComp + - NameBinding(name='c', allow_rename=True) + + DictComp + - NameBinding(name='line', allow_rename=True) +''' + + 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) + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) + + DictComp + - NameBinding(name='x', allow_rename=True) +''' + + 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) + - NameBinding(name='OhALongName', allow_rename=False) + - NameBinding(name='func', allow_rename=True) + + Function func + - NameBinding(name='C', allow_rename=True) + + Class C + - nonlocal OhALongName + - NameBinding(name='ClassName', allow_rename=False) + - NameBinding(name='MyOtherName', allow_rename=False) +''' + + 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) + - NameBinding(name='OhALongName', allow_rename=False) + - NameBinding(name='func', allow_rename=True) + + Function func + - NameBinding(name='C', allow_rename=True) + + Class C + - nonlocal OhALongName + - NameBinding(name='MyOtherName', allow_rename=False) +''' + + assert_namespace_tree(source, expected_namespaces) diff --git a/test/test_combine_imports.py b/test/test_combine_imports.py index 37dfe26a..ec8933b9 100644 --- a/test/test_combine_imports.py +++ b/test/test_combine_imports.py @@ -1,6 +1,8 @@ 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 from python_minifier.ast_compare import compare_ast @@ -9,6 +11,19 @@ def combine_imports(module): CombineImports()(module) return module +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(): source = '''import builtins import collections''' @@ -81,3 +96,68 @@ def test_import_star(): expected_ast = ast.parse(expected) actual_ast = combine_imports(ast.parse(source)) 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) + + Class MyClass + - NameBinding(name='Hello', allow_rename=False) +''' + + 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) + + Class MyClass + - NameBinding(name='Hello', allow_rename=False) +''' + + 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) + + Class MyClass + - NameBinding(name='Hello', allow_rename=False) +''' + + 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) + + Class MyClass + - NameBinding(name='Hello', allow_rename=False) +''' + + assert_namespace_tree(source, expected_namespaces) + diff --git a/test/test_remove_exception_brackets.py b/test/test_remove_exception_brackets.py index 84e00e43..cff97a5b 100644 --- a/test/test_remove_exception_brackets.py +++ b/test/test_remove_exception_brackets.py @@ -123,3 +123,43 @@ def test_raise_from_arg(): 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) \ No newline at end of file