def test_match_unparse(): if sys.version_info < (3, 10): pytest.skip('Match statement not in python < 3.10') source = ''' match a: case (0 as a) as b: pass match a: case _:pass match a: case 0|(0|0): pass case (0|0)|0: pass case 0|0|0: pass match (lambda: a)(): case [action, obj]:pass match a:= h: case [action, obj]:pass case {**rest}: pass ''' expected_ast = ast.parse(source) actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast))
def test_remove(): if sys.version_info < (3, 6): pytest.skip('annotations unavailable in python < 3.6') source = ''' class Dummy(NermedTupel): myfield: int mysecondfile: str class Dummy(typing.NermedTupel): myfield: int mysecondfile: str ''' expected = ''' class Dummy(NermedTupel): myfield: 0 mysecondfile: 0 class Dummy(typing.NermedTupel): myfield: 0 mysecondfile: 0 ''' expected_ast = ast.parse(expected) actual_ast = remove_annotations(source) compare_ast(expected_ast, actual_ast)
def is_correct_ast(self, code): try: c = ast.parse(code, 'FString candidate', mode='eval') compare_ast(self.node, c.body) return True except Exception as e: return False
def test_pep(): if sys.version_info < (3, 8): pytest.skip('No Assignment expressions in python < 3.8') source = ''' def name(p1, p2, /, p_or_kw, *, kw): pass def name(p1, p2=None, /, p_or_kw=None, *, kw): pass def name(p1, p2=None, /, *, kw): pass def name(p1, p2=None, /): pass def name(p1, p2, /, p_or_kw): pass def name(p1, p2, /): pass def name(p_or_kw, *, kw): pass def name(*, kw): pass def standard_arg(arg): print(arg) def pos_only_arg(arg, /): print(arg) def kwd_only_arg(*, arg): print(arg) def combined_example(pos_only, /, standard, *, kwd_only): print(pos_only, standard, kwd_only) ''' expected_ast = ast.parse(source) actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast))
def test_hoist_from_setcomp(): if sys.version_info < (2, 7): pytest.skip('No SetComp in python < 2.7') source = ''' class a: def a(): for i in {a for a in 'Hello' + 'Hello' + 'World'}: pass return 'World' def c(): return 'World' ''' expected = ''' A = 'World' class a: def a(): B = 'Hello' for i in {a for a in B + B + A}: pass return A def c(): return A ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_remove_literal_num(): source = '213' expected = '' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def unparse(module): """ Turn a module AST into python code This returns an exact representation of the given module, such that it can be parsed back into the same AST. :param module: The module to turn into python code :type: module: :class:`ast.Module` :rtype: str """ assert isinstance(module, ast.Module) printer = ModulePrinter() printer(module) try: minified_module = ast.parse(printer.code, 'python_minifier.unparse output') except SyntaxError as syntax_error: raise UnstableMinification(syntax_error, '', printer.code) try: compare_ast(module, minified_module) except CompareError as compare_error: raise UnstableMinification(compare_error, '', printer.code) return printer.code
def test_remove_pass_empty_module(): source = 'pass' expected = '' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_complex(): source = ''' "module docstring" a = 'hello' def t(): "function docstring" a = 2 0 2 'sadfsaf' def g(): "just a docstring" ''' expected = ''' a = 'hello' def t(): a=2 def g(): 0 ''' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_remove_if_line(): source = '''if True: pass''' expected = '''if True: 0''' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_hoist_multiple_small(): source = ''' A = '.' B = '.' C = '.' D = '.' E = '.' F = '.' G = '.' ''' expected = ''' H = '.' A = H B = H C = H D = H E = H F = H G = H ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_remove_literal_str(): source = '"hello"' expected = '' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_hoist_from_listcomp(): source = ''' class a: def a(): for i in [a for a in 'Hello']: pass for i in [a for a in 'World']: pass return 'World' def c(): return 'Hello' ''' expected = ''' A = 'Hello' class a: def a(): B = 'World' for i in [a for a in A]: pass for i in [a for a in B]: pass return B def c(): return A ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_hoist_from_generator(): source = ''' class a: def a(): for i in (a for a in 'Hello'): pass for i in (a for a in 'World'): pass return 'World' def c(): return 'Hello' ''' expected = ''' A = 'Hello' class a: def a(): B = 'World' for i in (a for a in A): pass for i in (a for a in B): pass return B def c(): return A ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_import(): source = '''import builtins import collections''' expected = 'import builtins, collections' expected_ast = ast.parse(expected) actual_ast = combine_imports(ast.parse(source)) compare_ast(expected_ast, actual_ast)
def test_remove_from_class_empty(): source = '''class A: pass ''' expected = 'class A:0' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_pep(): if sys.version_info < (3, 9): pytest.skip('Decorator expression not allowed in python <3.9') source = """ buttons = [QPushButton(f'Button {i}') for i in range(10)] # Do stuff with the list of buttons... @buttons[0].clicked.connect def spam(): ... @buttons[1].clicked.connect def eggs(): ... # Do stuff with the list of buttons... @(f, g) def a(): pass @(f, g) class A:pass @lambda func: (lambda *p: func(*p).u()) def g(n): pass @s := lambda func: (lambda *p: func(*p).u()) def g(name): pass @s def r(n, t): pass @lambda f: lambda *p: f or f(*p).u() def g(name): pass @lambda f: lambda *p: \ [_ for _ in [ \ f(*p), ] if _][0] def c(): pass @lambda f: lambda *p: \ list(filter(lambda _: _,[ (a := t()) and False, f(*p), (b := t()) and False, ]))[0] def c(): pass """ expected_ast = ast.parse(source) actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast))
def test_AnnAssign_novalue(): if sys.version_info < (3, 6): pytest.skip('Variable annotation unsupported in python < 3.6') source = '''a :str ''' expected = '''a:0''' expected_ast = ast.parse(expected) actual_ast = remove_annotations(source) compare_ast(expected_ast, actual_ast)
def test_remove_pass_module(): source = '''import collections pass a = 1 pass''' expected = '''import collections a=1''' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_nohoist_single_usage(): source = ''' a = 'Hello' ''' expected = ''' a = 'Hello' ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_import_from(): source = '''from builtins import dir from builtins import help import collections from collections import abc''' expected = '''from builtins import dir, help import collections from collections import abc''' expected_ast = ast.parse(expected) actual_ast = combine_imports(ast.parse(source)) compare_ast(expected_ast, actual_ast)
def test_AsyncFunctionDef(): if sys.version_info < (3, 6): pytest.skip('Async function unsupported in python < 3.5') source = '''async def test(a: str, b: int=1, *c: hello, **aws: crap) -> None: pass''' expected = '''async def test(a, b=1, *c, **aws): pass''' expected_ast = ast.parse(expected) actual_ast = remove_annotations(source) compare_ast(expected_ast, actual_ast)
def test_await_fstring(): if sys.version_info < (3, 7): pytest.skip( 'Await in f-string expressions not allowed in python < 3.7') source = ''' async def a(): return 'hello' async def b(): return f'{await b()}' ''' expected_ast = ast.parse(source) actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast))
def test_remove_suite(): source = '''if True: pass a=1 pass return None''' expected = '''if True: a=1 return None''' expected_ast = ast.parse(expected) actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast)
def test_import_as(): source = '''import builtins import collections as c import functools as f import datetime pass''' expected = '''import builtins, collections as c, functools as f, datetime pass''' expected_ast = ast.parse(expected) actual_ast = combine_imports(ast.parse(source)) compare_ast(expected_ast, actual_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))
def test_no_hoist_multiple_small(): source = ''' A = '' B = '' ''' expected = ''' A = '' B = '' ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_import_in_function(): source = '''def test(): import collection as c import builtins return None ''' expected = '''def test(): import collection as c, builtins return None ''' expected_ast = ast.parse(expected) actual_ast = combine_imports(ast.parse(source)) compare_ast(expected_ast, actual_ast)
def test_hoist_multiple_usage(): source = ''' A = 'Hello' B = 'Hello' ''' expected = ''' C = 'Hello' A = C B = C ''' expected_ast = ast.parse(expected) actual_ast = hoist(source) compare_ast(expected_ast, actual_ast)
def test_dict_expanson(): if sys.version_info < (3, 5): pytest.skip('dict expansion not allowed in python < 3.5') source = [ r'{**a>>9}', r'{**(a or b)}', r'{**(a and b)}', r'{**a+b}', r'{**(lambda a:a)}', r'{**(a<b)}', r'{**(yield a())}', r'{**(a if a else a)}' ] for expression in source: expected_ast = ast.parse(expression) minified = unparse(expected_ast) compare_ast(expected_ast, ast.parse(minified)) assert expression == minified