From 6e8059392ea2ca46ccf0f96ed19cd67acb8bc6a9 Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Thu, 10 Oct 2024 13:31:56 +0100 Subject: [PATCH 1/7] Sort imports --- corpus_test/__init__.py | 0 corpus_test/generate_report.py | 3 +- corpus_test/generate_results.py | 8 ++--- hypo_test/__init__.py | 0 hypo_test/expressions.py | 18 ++++++++++- hypo_test/folding.py | 6 ++-- hypo_test/module.py | 14 ++++---- hypo_test/patterns.py | 3 +- hypo_test/test_it.py | 22 +++++++------ src/python_minifier/__init__.py | 16 +++++----- src/python_minifier/__init__.pyi | 3 +- src/python_minifier/__main__.py | 2 +- src/python_minifier/expression_printer.py | 6 ++-- src/python_minifier/f_string.py | 6 ++-- src/python_minifier/module_printer.py | 3 +- src/python_minifier/rename/__init__.py | 2 +- src/python_minifier/rename/bind_names.py | 2 +- src/python_minifier/rename/resolve_names.py | 2 +- src/python_minifier/rename/util.py | 3 +- src/python_minifier/token_printer.py | 1 + .../transforms/constant_folding.py | 4 ++- .../transforms/remove_annotations.py | 3 +- .../transforms/remove_debug.py | 3 +- .../transforms/remove_exception_brackets.py | 3 +- .../transforms/remove_explicit_return_none.py | 3 +- .../transforms/remove_object_base.py | 3 +- src/python_minifier/util.py | 1 + test/test_assignment_expressions.py | 3 ++ test/test_await_fstring.py | 3 ++ test/test_bind_names_python2.py | 1 + test/test_bind_names_python312.py | 1 + test/test_bind_names_python313.py | 1 + test/test_bind_names_python33.py | 1 + test/test_combine_imports.py | 7 ++-- test/test_comprehension_rename.py | 1 + test/test_decorator_expressions.py | 3 ++ test/test_dict_expansion.py | 3 ++ test/test_folding.py | 4 +-- test/test_generic_types.py | 3 ++ test/test_hoist_literals.py | 11 ++++++- test/test_is_ast_node.py | 2 ++ test/test_iterable_unpacking.py | 3 ++ test/test_match.py | 3 ++ test/test_match_rename.py | 32 +++++++++++-------- test/test_name_generator.py | 1 + test/test_nonlocal.py | 1 + test/test_posargs.py | 3 ++ test/test_remove_annotations.py | 7 ++-- test/test_remove_assert.py | 6 ++-- test/test_remove_debug.py | 2 +- test/test_remove_exception_brackets.py | 2 +- test/test_remove_literal_statements.py | 5 +-- test/test_remove_object.py | 5 +-- test/test_remove_pass.py | 6 ++-- test/test_rename_builtins.py | 8 ++--- test/test_rename_locals.py | 5 +-- test/test_slice.py | 1 + test/test_type_param_defaults.py | 2 ++ typing_test/test_badtyping.py | 1 + typing_test/test_typing.py | 3 +- xtest/test_unparse_env.py | 8 +++-- 61 files changed, 191 insertions(+), 97 deletions(-) create mode 100644 corpus_test/__init__.py create mode 100644 hypo_test/__init__.py diff --git a/corpus_test/__init__.py b/corpus_test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/corpus_test/generate_report.py b/corpus_test/generate_report.py index ecb13cc6..7fe22d2f 100644 --- a/corpus_test/generate_report.py +++ b/corpus_test/generate_report.py @@ -1,10 +1,11 @@ import argparse import os import sys + from dataclasses import dataclass, field from typing import Iterable -from result import Result, ResultReader +from .result import Result, ResultReader ENHANCED_REPORT = os.environ.get('ENHANCED_REPORT', True) diff --git a/corpus_test/generate_results.py b/corpus_test/generate_results.py index e7c52c92..21293dcd 100644 --- a/corpus_test/generate_results.py +++ b/corpus_test/generate_results.py @@ -1,16 +1,14 @@ import argparse import datetime import gzip +import logging import os import sys import time - -import logging - - import python_minifier -from result import Result, ResultWriter + +from .result import Result, ResultWriter try: RE = RecursionError diff --git a/hypo_test/__init__.py b/hypo_test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hypo_test/expressions.py b/hypo_test/expressions.py index d6d9a190..fed45b8e 100644 --- a/hypo_test/expressions.py +++ b/hypo_test/expressions.py @@ -5,7 +5,23 @@ import unicodedata from hypothesis import assume -from hypothesis.strategies import integers, lists, binary, sampled_from, recursive, dictionaries, booleans, SearchStrategy, text, composite, one_of, floats, complex_numbers, characters, none +from hypothesis.strategies import ( + SearchStrategy, + binary, + booleans, + characters, + complex_numbers, + composite, + dictionaries, + floats, + integers, + lists, + none, + one_of, + recursive, + sampled_from, + text +) comparison_operators = sampled_from([ ast.Eq(), diff --git a/hypo_test/folding.py b/hypo_test/folding.py index 5900be8a..e881a719 100644 --- a/hypo_test/folding.py +++ b/hypo_test/folding.py @@ -1,8 +1,8 @@ -from hypothesis import assume -from hypothesis.strategies import integers, lists, binary, sampled_from, recursive, dictionaries, booleans, SearchStrategy, text, composite, one_of, floats, complex_numbers, characters, none import ast -from expressions import NameConstant, Num +from hypothesis.strategies import SearchStrategy, composite, lists, recursive, sampled_from + +from .expressions import NameConstant, Num leaves = NameConstant() | Num() diff --git a/hypo_test/module.py b/hypo_test/module.py index 50eabfae..d362da35 100644 --- a/hypo_test/module.py +++ b/hypo_test/module.py @@ -1,10 +1,10 @@ import ast from hypothesis import assume -from hypothesis.strategies import integers, lists, sampled_from, recursive, none, booleans, SearchStrategy, composite, one_of +from hypothesis.strategies import SearchStrategy, booleans, composite, integers, lists, none, one_of, recursive, sampled_from + +from .expressions import Name, arguments, expression, name -from hypo_test.expressions import Name, expression, name -from expressions import arguments @composite def Assign(draw) -> ast.Assign: @@ -193,7 +193,7 @@ def FunctionDef(draw, statements) -> ast.FunctionDef: args = draw(arguments()) body = draw(lists(statements, min_size=1, max_size=3)) decorator_list = draw(lists(Name(), min_size=0, max_size=2)) - type_params = draw(none() | lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) + type_params = draw(lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) returns = draw(none() | expression()) return ast.FunctionDef(n, args, body, decorator_list, returns, type_params=type_params) @@ -203,7 +203,7 @@ def AsyncFunctionDef(draw, statements) -> ast.AsyncFunctionDef: args = draw(arguments()) body = draw(lists(statements, min_size=1, max_size=3)) decorator_list = draw(lists(Name(), min_size=0, max_size=2)) - type_params = draw(none() | lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) + type_params = draw(lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) returns = draw(none() | expression()) return ast.AsyncFunctionDef(n, args, body, decorator_list, returns, type_params=type_params) @@ -230,7 +230,7 @@ def ClassDef(draw, statements) -> ast.ClassDef: keywords=keywords, body=body, decorator_list=decorator_list, - type_params=draw(none() | lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) + type_params=draw(lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) ) if hasattr(ast, 'Print'): @@ -286,7 +286,7 @@ def suite() -> SearchStrategy: ClassDef(statements), Try(statements) ), - max_leaves=150 + max_leaves=100 ) @composite diff --git a/hypo_test/patterns.py b/hypo_test/patterns.py index c1b8f6dc..216bed10 100644 --- a/hypo_test/patterns.py +++ b/hypo_test/patterns.py @@ -3,7 +3,8 @@ import string from hypothesis import assume -from hypothesis.strategies import lists, sampled_from, recursive, text, composite, one_of, none, booleans, integers +from hypothesis.strategies import booleans, composite, integers, lists, none, one_of, recursive, sampled_from, text + @composite def name(draw): diff --git a/hypo_test/test_it.py b/hypo_test/test_it.py index 9327f4b9..9b4a46bb 100644 --- a/hypo_test/test_it.py +++ b/hypo_test/test_it.py @@ -1,19 +1,21 @@ import ast from datetime import timedelta -from hypothesis import given, settings, Verbosity, note, HealthCheck -from folding import FoldableExpression -from patterns import Pattern -from python_minifier import ModulePrinter +from hypothesis import HealthCheck, Verbosity, given, note, settings + from python_minifier.ast_compare import compare_ast from python_minifier.ast_printer import print_ast from python_minifier.expression_printer import ExpressionPrinter -from expressions import Expression -from module import Module, TypeAlias +from python_minifier.module_printer import ModulePrinter from python_minifier.rename.mapper import add_parent from python_minifier.transforms.constant_folding import FoldConstants +from .expressions import Expression +from .folding import FoldableExpression +from .module import Module, TypeAlias +from .patterns import Pattern + @given(node=Expression()) @settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=100, suppress_health_check=[HealthCheck.too_slow]) #verbosity=Verbosity.verbose @@ -28,7 +30,7 @@ def test_expression(node): @given(node=Module()) -@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=100, suppress_health_check=[HealthCheck.too_slow]) #verbosity=Verbosity.verbose +@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=100, suppress_health_check=[HealthCheck.too_slow], verbosity=Verbosity.verbose) def test_module(node): assert isinstance(node, ast.Module) @@ -61,7 +63,7 @@ def test_pattern(node): compare_ast(module, ast.parse(code, 'test_pattern')) @given(node=FoldableExpression()) -@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=100000, suppress_health_check=[HealthCheck.too_slow]) #verbosity=Verbosity.verbose +@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=1000, suppress_health_check=[HealthCheck.too_slow]) #verbosity=Verbosity.verbose def test_folding(node): assert isinstance(node, ast.AST) note(print_ast(node)) @@ -74,7 +76,7 @@ def test_folding(node): constant_folder(node) @given(node=TypeAlias()) -@settings(report_multiple_bugs=False, deadline=timedelta(seconds=2), max_examples=1000, verbosity=Verbosity.verbose) +@settings(report_multiple_bugs=False, deadline=timedelta(seconds=2), max_examples=100, verbosity=Verbosity.verbose) def test_type_alias(node): module = ast.Module( @@ -88,7 +90,7 @@ def test_type_alias(node): compare_ast(module, ast.parse(code, 'test_type_alias')) @given(node=TypeAlias()) -@settings(report_multiple_bugs=False, deadline=timedelta(seconds=2), max_examples=1000, verbosity=Verbosity.verbose) +@settings(report_multiple_bugs=False, deadline=timedelta(seconds=2), max_examples=100, verbosity=Verbosity.verbose) def test_function_type_param(node): module = ast.Module( diff --git a/src/python_minifier/__init__.py b/src/python_minifier/__init__.py index a7ab2b67..39f307c1 100644 --- a/src/python_minifier/__init__.py +++ b/src/python_minifier/__init__.py @@ -4,29 +4,29 @@ """ -import python_minifier.ast_compat as ast import re +import python_minifier.ast_compat as ast + from python_minifier.ast_compare import CompareError, compare_ast from python_minifier.module_printer import ModulePrinter from python_minifier.rename import ( - rename_literals, - bind_names, - resolve_names, - rename, + add_namespace, allow_rename_globals, allow_rename_locals, - add_namespace, + bind_names, + rename, + rename_literals, + resolve_names ) - 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_explicit_return_none import RemoveExplicitReturnNone from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements from python_minifier.transforms.remove_object_base import RemoveObject from python_minifier.transforms.remove_pass import RemovePass diff --git a/src/python_minifier/__init__.pyi b/src/python_minifier/__init__.pyi index a414796d..c44e7370 100644 --- a/src/python_minifier/__init__.pyi +++ b/src/python_minifier/__init__.pyi @@ -1,5 +1,6 @@ import ast -from typing import List, Text, AnyStr, Optional, Any, Union + +from typing import Any, AnyStr, List, Optional, Text, Union from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions diff --git a/src/python_minifier/__main__.py b/src/python_minifier/__main__.py index 056fe5ca..179a4817 100644 --- a/src/python_minifier/__main__.py +++ b/src/python_minifier/__main__.py @@ -14,7 +14,7 @@ except metadata.PackageNotFoundError: version = '0.0.0' else: - from pkg_resources import get_distribution, DistributionNotFound + from pkg_resources import DistributionNotFound, get_distribution try: version = get_distribution('python_minifier').version except DistributionNotFound: diff --git a/src/python_minifier/expression_printer.py b/src/python_minifier/expression_printer.py index 300f399c..394e7201 100644 --- a/src/python_minifier/expression_printer.py +++ b/src/python_minifier/expression_printer.py @@ -1,9 +1,9 @@ -import python_minifier.ast_compat as ast import sys -from python_minifier.util import is_ast_node +import python_minifier.ast_compat as ast -from python_minifier.token_printer import TokenPrinter, Delimiter +from python_minifier.token_printer import Delimiter, TokenPrinter +from python_minifier.util import is_ast_node class ExpressionPrinter(object): diff --git a/src/python_minifier/f_string.py b/src/python_minifier/f_string.py index 332a2546..02bb1a4f 100644 --- a/src/python_minifier/f_string.py +++ b/src/python_minifier/f_string.py @@ -6,13 +6,13 @@ """ -import python_minifier.ast_compat as ast import copy import re +import python_minifier.ast_compat as ast + from python_minifier import UnstableMinification -from python_minifier.ast_compare import CompareError -from python_minifier.ast_compare import compare_ast +from python_minifier.ast_compare import CompareError, compare_ast from python_minifier.expression_printer import ExpressionPrinter from python_minifier.ministring import MiniString from python_minifier.token_printer import TokenTypes diff --git a/src/python_minifier/module_printer.py b/src/python_minifier/module_printer.py index d1a8c6b7..897671f3 100644 --- a/src/python_minifier/module_printer.py +++ b/src/python_minifier/module_printer.py @@ -1,6 +1,7 @@ -import python_minifier.ast_compat as ast import sys +import python_minifier.ast_compat as ast + from .expression_printer import ExpressionPrinter from .token_printer import Delimiter from .util import is_ast_node diff --git a/src/python_minifier/rename/__init__.py b/src/python_minifier/rename/__init__.py index 6d054290..37248864 100644 --- a/src/python_minifier/rename/__init__.py +++ b/src/python_minifier/rename/__init__.py @@ -3,4 +3,4 @@ from python_minifier.rename.rename_literals import rename_literals from python_minifier.rename.renamer import rename from python_minifier.rename.resolve_names import resolve_names -from python_minifier.rename.util import allow_rename_locals, allow_rename_globals +from python_minifier.rename.util import allow_rename_globals, allow_rename_locals diff --git a/src/python_minifier/rename/bind_names.py b/src/python_minifier/rename/bind_names.py index 57597674..e3a6dcd4 100644 --- a/src/python_minifier/rename/bind_names.py +++ b/src/python_minifier/rename/bind_names.py @@ -1,7 +1,7 @@ import python_minifier.ast_compat as ast from python_minifier.rename.binding import NameBinding -from python_minifier.rename.util import arg_rename_in_place, get_global_namespace, get_nonlocal_namespace, builtins +from python_minifier.rename.util import arg_rename_in_place, builtins, get_global_namespace from python_minifier.transforms.suite_transformer import NodeVisitor diff --git a/src/python_minifier/rename/resolve_names.py b/src/python_minifier/rename/resolve_names.py index 104b5366..95a0937a 100644 --- a/src/python_minifier/rename/resolve_names.py +++ b/src/python_minifier/rename/resolve_names.py @@ -1,7 +1,7 @@ import python_minifier.ast_compat as ast from python_minifier.rename.binding import BuiltinBinding, NameBinding -from python_minifier.rename.util import get_global_namespace, get_nonlocal_namespace, builtins +from python_minifier.rename.util import builtins, get_global_namespace, get_nonlocal_namespace from python_minifier.util import is_ast_node diff --git a/src/python_minifier/rename/util.py b/src/python_minifier/rename/util.py index b78735ab..c27f0ad2 100644 --- a/src/python_minifier/rename/util.py +++ b/src/python_minifier/rename/util.py @@ -1,6 +1,7 @@ -import python_minifier.ast_compat as ast import sys +import python_minifier.ast_compat as ast + from python_minifier.util import is_ast_node diff --git a/src/python_minifier/token_printer.py b/src/python_minifier/token_printer.py index e8813fd2..71382c92 100644 --- a/src/python_minifier/token_printer.py +++ b/src/python_minifier/token_printer.py @@ -3,6 +3,7 @@ import re import sys + class TokenTypes(object): NoToken = 0 Identifier = 1 diff --git a/src/python_minifier/transforms/constant_folding.py b/src/python_minifier/transforms/constant_folding.py index bc1a33b5..d6f79ae7 100644 --- a/src/python_minifier/transforms/constant_folding.py +++ b/src/python_minifier/transforms/constant_folding.py @@ -1,12 +1,14 @@ -import python_minifier.ast_compat as ast import math import sys +import python_minifier.ast_compat as ast + 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 diff --git a/src/python_minifier/transforms/remove_annotations.py b/src/python_minifier/transforms/remove_annotations.py index d96a29a9..ae47bc5b 100644 --- a/src/python_minifier/transforms/remove_annotations.py +++ b/src/python_minifier/transforms/remove_annotations.py @@ -1,6 +1,7 @@ -import python_minifier.ast_compat as ast import sys +import python_minifier.ast_compat as ast + from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions from python_minifier.transforms.suite_transformer import SuiteTransformer diff --git a/src/python_minifier/transforms/remove_debug.py b/src/python_minifier/transforms/remove_debug.py index d744c3aa..3cd3580a 100644 --- a/src/python_minifier/transforms/remove_debug.py +++ b/src/python_minifier/transforms/remove_debug.py @@ -1,6 +1,7 @@ -import python_minifier.ast_compat as ast import sys +import python_minifier.ast_compat as ast + from python_minifier.transforms.suite_transformer import SuiteTransformer from python_minifier.util import is_ast_node diff --git a/src/python_minifier/transforms/remove_exception_brackets.py b/src/python_minifier/transforms/remove_exception_brackets.py index 9f98d787..d34a022e 100644 --- a/src/python_minifier/transforms/remove_exception_brackets.py +++ b/src/python_minifier/transforms/remove_exception_brackets.py @@ -8,9 +8,10 @@ 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 +import python_minifier.ast_compat as ast + from python_minifier.rename.binding import BuiltinBinding # These are always exceptions, in every version of python diff --git a/src/python_minifier/transforms/remove_explicit_return_none.py b/src/python_minifier/transforms/remove_explicit_return_none.py index c152ea54..93463971 100644 --- a/src/python_minifier/transforms/remove_explicit_return_none.py +++ b/src/python_minifier/transforms/remove_explicit_return_none.py @@ -1,6 +1,7 @@ -import python_minifier.ast_compat as ast import sys +import python_minifier.ast_compat as ast + from python_minifier.transforms.suite_transformer import SuiteTransformer from python_minifier.util import is_ast_node diff --git a/src/python_minifier/transforms/remove_object_base.py b/src/python_minifier/transforms/remove_object_base.py index 9a34b9c9..395959b1 100644 --- a/src/python_minifier/transforms/remove_object_base.py +++ b/src/python_minifier/transforms/remove_object_base.py @@ -1,6 +1,7 @@ -import python_minifier.ast_compat as ast import sys +import python_minifier.ast_compat as ast + from python_minifier.transforms.suite_transformer import SuiteTransformer diff --git a/src/python_minifier/util.py b/src/python_minifier/util.py index 9e80a73b..d1ea6252 100644 --- a/src/python_minifier/util.py +++ b/src/python_minifier/util.py @@ -1,5 +1,6 @@ import python_minifier.ast_compat as ast + def is_ast_node(node, types): """ Is a node one of the specified node types diff --git a/test/test_assignment_expressions.py b/test/test_assignment_expressions.py index 7f371ec0..c88e0b92 100644 --- a/test/test_assignment_expressions.py +++ b/test/test_assignment_expressions.py @@ -1,9 +1,12 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_pep(): if sys.version_info < (3, 8): pytest.skip('No Assignment expressions in python < 3.8') diff --git a/test/test_await_fstring.py b/test/test_await_fstring.py index f37b0953..c3c52513 100644 --- a/test/test_await_fstring.py +++ b/test/test_await_fstring.py @@ -1,9 +1,12 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_await_fstring(): if sys.version_info < (3, 7): pytest.skip('Await in f-string expressions not allowed in python < 3.7') diff --git a/test/test_bind_names_python2.py b/test/test_bind_names_python2.py index 3c255316..4bf159a4 100644 --- a/test/test_bind_names_python2.py +++ b/test/test_bind_names_python2.py @@ -4,6 +4,7 @@ from helpers import assert_namespace_tree + def test_module_namespace(): if sys.version_info >= (3, 0): pytest.skip('Test is for python 2 only') diff --git a/test/test_bind_names_python312.py b/test/test_bind_names_python312.py index 22302cad..51a91afb 100644 --- a/test/test_bind_names_python312.py +++ b/test/test_bind_names_python312.py @@ -4,6 +4,7 @@ from helpers import assert_namespace_tree + def test_class_typevar_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') diff --git a/test/test_bind_names_python313.py b/test/test_bind_names_python313.py index 6713cfe4..38178d57 100644 --- a/test/test_bind_names_python313.py +++ b/test/test_bind_names_python313.py @@ -4,6 +4,7 @@ from helpers import assert_namespace_tree + def test_class_typevar_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') diff --git a/test/test_bind_names_python33.py b/test/test_bind_names_python33.py index d0ab6b2b..8a8f3dae 100644 --- a/test/test_bind_names_python33.py +++ b/test/test_bind_names_python33.py @@ -4,6 +4,7 @@ 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') diff --git a/test/test_combine_imports.py b/test/test_combine_imports.py index ec8933b9..de28e3e2 100644 --- a/test/test_combine_imports.py +++ b/test/test_combine_imports.py @@ -1,10 +1,11 @@ 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 +from python_minifier.rename import add_namespace, bind_names, resolve_names +from python_minifier.transforms.combine_imports import CombineImports + def combine_imports(module): add_namespace(module) diff --git a/test/test_comprehension_rename.py b/test/test_comprehension_rename.py index c5eecb5b..4991424f 100644 --- a/test/test_comprehension_rename.py +++ b/test/test_comprehension_rename.py @@ -6,6 +6,7 @@ from python_minifier import minify from python_minifier.ast_compare import compare_ast + def test_listcomp_regression_2_7(): if sys.version_info >= (3, 0): pytest.skip('ListComp doesn\'t create a new namespace in python < 3.0') diff --git a/test/test_decorator_expressions.py b/test/test_decorator_expressions.py index e5d2d9ea..8df82d1e 100644 --- a/test/test_decorator_expressions.py +++ b/test/test_decorator_expressions.py @@ -1,9 +1,12 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_pep(): if sys.version_info < (3, 9): pytest.skip('Decorator expression not allowed in python <3.9') diff --git a/test/test_dict_expansion.py b/test/test_dict_expansion.py index 44834ea9..884d135b 100644 --- a/test/test_dict_expansion.py +++ b/test/test_dict_expansion.py @@ -1,9 +1,12 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_dict_expanson(): if sys.version_info < (3, 5): pytest.skip('dict expansion not allowed in python < 3.5') diff --git a/test/test_folding.py b/test/test_folding.py index 40b22240..d9e1b644 100644 --- a/test/test_folding.py +++ b/test/test_folding.py @@ -3,9 +3,9 @@ import pytest -from python_minifier import add_namespace -from python_minifier.transforms.constant_folding import FoldConstants from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace +from python_minifier.transforms.constant_folding import FoldConstants def fold_constants(source): diff --git a/test/test_generic_types.py b/test/test_generic_types.py index 07938798..2fbdd847 100644 --- a/test/test_generic_types.py +++ b/test/test_generic_types.py @@ -1,9 +1,12 @@ 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') diff --git a/test/test_hoist_literals.py b/test/test_hoist_literals.py index 10602efa..46c22d18 100644 --- a/test/test_hoist_literals.py +++ b/test/test_hoist_literals.py @@ -6,7 +6,16 @@ from python_minifier import unparse 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 +from python_minifier.rename import ( + add_namespace, + allow_rename_globals, + allow_rename_locals, + bind_names, + rename, + rename_literals, + resolve_names +) + def hoist(source): module = ast.parse(source) diff --git a/test/test_is_ast_node.py b/test/test_is_ast_node.py index 25d8b253..f92a54d3 100644 --- a/test/test_is_ast_node.py +++ b/test/test_is_ast_node.py @@ -3,8 +3,10 @@ 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) diff --git a/test/test_iterable_unpacking.py b/test/test_iterable_unpacking.py index dd95a2b8..b6a87270 100644 --- a/test/test_iterable_unpacking.py +++ b/test/test_iterable_unpacking.py @@ -1,9 +1,12 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_return(): if sys.version_info < (3, 0): pytest.skip('Iterable unpacking in return not allowed in python < 3.0') diff --git a/test/test_match.py b/test/test_match.py index eb783abd..b5f99579 100644 --- a/test/test_match.py +++ b/test/test_match.py @@ -1,9 +1,12 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_pep635_unparse(): if sys.version_info < (3, 10): pytest.skip('Match statement not in python < 3.10') diff --git a/test/test_match_rename.py b/test/test_match_rename.py index dede5343..207fa56e 100644 --- a/test/test_match_rename.py +++ b/test/test_match_rename.py @@ -3,13 +3,19 @@ import pytest -from python_minifier import ( - add_namespace, bind_names, resolve_names, allow_rename_locals, allow_rename_globals, - compare_ast, rename as do_rename, CompareError, unparse, rename_literals +from python_minifier import unparse +from python_minifier.rename import ( + add_namespace, + allow_rename_globals, + allow_rename_locals, + bind_names, + rename, + rename_literals, + resolve_names ) -def rename(source, locals, globals): +def do_rename(source, locals, globals): # This will raise if the source file can't be parsed module = ast.parse(source, 'test_match_rename') add_namespace(module) @@ -20,7 +26,7 @@ def rename(source, locals, globals): allow_rename_globals(module, globals) rename_literals(module) - do_rename(module) + rename(module) return module @@ -63,7 +69,7 @@ def C(): ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=False, globals=True) + actual_ast = do_rename(source, locals=False, globals=True) assert_code(expected_ast, actual_ast) @@ -102,7 +108,7 @@ def func(expensive_rename): ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=True, globals=False) + actual_ast = do_rename(source, locals=True, globals=False) assert_code(expected_ast, actual_ast) @@ -135,7 +141,7 @@ def func(expensive_rename): ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=True, globals=False) + actual_ast = do_rename(source, locals=True, globals=False) assert_code(expected_ast, actual_ast) @@ -173,7 +179,7 @@ def C(expensive_rename): ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=False, globals=True) + actual_ast = do_rename(source, locals=False, globals=True) assert_code(expected_ast, actual_ast) @@ -206,7 +212,7 @@ def func(expensive_rename): ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=True, globals=False) + actual_ast = do_rename(source, locals=True, globals=False) assert_code(expected_ast, actual_ast) @@ -239,7 +245,7 @@ def B(expensive_rename): ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=False, globals=True) + actual_ast = do_rename(source, locals=False, globals=True) assert_code(expected_ast, actual_ast) @@ -296,7 +302,7 @@ class A: pass ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=True, globals=False) + actual_ast = do_rename(source, locals=True, globals=False) assert_code(expected_ast, actual_ast) @@ -351,5 +357,5 @@ class Local: pass ''' expected_ast = ast.parse(expected) - actual_ast = rename(source, locals=False, globals=True) + actual_ast = do_rename(source, locals=False, globals=True) assert_code(expected_ast, actual_ast) diff --git a/test/test_name_generator.py b/test/test_name_generator.py index 6df0621b..658e832a 100644 --- a/test/test_name_generator.py +++ b/test/test_name_generator.py @@ -2,6 +2,7 @@ from python_minifier.rename.name_generator import name_filter + def test_name_generator(): ng = name_filter() diff --git a/test/test_nonlocal.py b/test/test_nonlocal.py index dd6205e0..7a51e408 100644 --- a/test/test_nonlocal.py +++ b/test/test_nonlocal.py @@ -4,6 +4,7 @@ import python_minifier + def test_nonlocal_name(): if sys.version_info < (3, 0): pytest.skip('No nonlocal in python < 3.0') diff --git a/test/test_posargs.py b/test/test_posargs.py index 6dedd0de..3eb81c0d 100644 --- a/test/test_posargs.py +++ b/test/test_posargs.py @@ -1,10 +1,13 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast from python_minifier.transforms.remove_posargs import remove_posargs + def test_pep(): if sys.version_info < (3, 8): pytest.skip('No Assignment expressions in python < 3.8') diff --git a/test/test_remove_annotations.py b/test/test_remove_annotations.py index ecc27f80..6cabfee7 100644 --- a/test/test_remove_annotations.py +++ b/test/test_remove_annotations.py @@ -1,10 +1,13 @@ import ast import sys + import pytest -from python_minifier import add_namespace, RemoveAnnotationsOptions -from python_minifier.transforms.remove_annotations import RemoveAnnotations +from python_minifier import RemoveAnnotationsOptions from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace +from python_minifier.transforms.remove_annotations import RemoveAnnotations + def remove_annotations(source, **kwargs): module = ast.parse(source) diff --git a/test/test_remove_assert.py b/test/test_remove_assert.py index 5a19f199..b7116162 100644 --- a/test/test_remove_assert.py +++ b/test/test_remove_assert.py @@ -1,7 +1,9 @@ import ast -from python_minifier import add_namespace, bind_names, resolve_names -from python_minifier.transforms.remove_asserts import RemoveAsserts + from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace, bind_names, resolve_names +from python_minifier.transforms.remove_asserts import RemoveAsserts + def remove_asserts(source): module = ast.parse(source, 'remove_asserts') diff --git a/test/test_remove_debug.py b/test/test_remove_debug.py index 34d99570..aa4ce1a7 100644 --- a/test/test_remove_debug.py +++ b/test/test_remove_debug.py @@ -2,8 +2,8 @@ import pytest -from python_minifier import add_namespace, bind_names, resolve_names from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace, bind_names, resolve_names from python_minifier.transforms.remove_debug import RemoveDebug diff --git a/test/test_remove_exception_brackets.py b/test/test_remove_exception_brackets.py index cff97a5b..b7d3d358 100644 --- a/test/test_remove_exception_brackets.py +++ b/test/test_remove_exception_brackets.py @@ -3,8 +3,8 @@ import pytest -from python_minifier.rename import add_namespace, bind_names, resolve_names from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace, bind_names, resolve_names from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call diff --git a/test/test_remove_literal_statements.py b/test/test_remove_literal_statements.py index 1268d410..512dca1a 100644 --- a/test/test_remove_literal_statements.py +++ b/test/test_remove_literal_statements.py @@ -1,8 +1,9 @@ import ast -from python_minifier import add_namespace, bind_names, resolve_names -from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace, bind_names, resolve_names +from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements + def remove_literals(source): module = ast.parse(source, 'test_remove_literal_statements') diff --git a/test/test_remove_object.py b/test/test_remove_object.py index d4b45418..ffd74e93 100644 --- a/test/test_remove_object.py +++ b/test/test_remove_object.py @@ -1,9 +1,10 @@ import ast -import pytest import sys -from python_minifier.transforms.remove_object_base import RemoveObject +import pytest + from python_minifier.ast_compare import compare_ast +from python_minifier.transforms.remove_object_base import RemoveObject def test_remove_object_py3(): diff --git a/test/test_remove_pass.py b/test/test_remove_pass.py index e4861497..4838fee1 100644 --- a/test/test_remove_pass.py +++ b/test/test_remove_pass.py @@ -1,7 +1,9 @@ import ast -from python_minifier import add_namespace, bind_names, resolve_names -from python_minifier.transforms.remove_pass import RemovePass + from python_minifier.ast_compare import compare_ast +from python_minifier.rename import add_namespace, bind_names, resolve_names +from python_minifier.transforms.remove_pass import RemovePass + def remove_literals(source): module = ast.parse(source, 'remove_literals') diff --git a/test/test_rename_builtins.py b/test/test_rename_builtins.py index b2857df7..4fb4f92b 100644 --- a/test/test_rename_builtins.py +++ b/test/test_rename_builtins.py @@ -5,12 +5,10 @@ """ import ast -import sys -import pytest - -from python_minifier import add_namespace, bind_names, resolve_names, allow_rename_locals, allow_rename_globals, \ - compare_ast, rename, CompareError, unparse +from python_minifier import unparse +from python_minifier.ast_compare import CompareError, compare_ast +from python_minifier.rename import add_namespace, allow_rename_globals, allow_rename_locals, bind_names, rename, resolve_names def do_rename(source): diff --git a/test/test_rename_locals.py b/test/test_rename_locals.py index 9bfd8469..d7ce3f66 100644 --- a/test/test_rename_locals.py +++ b/test/test_rename_locals.py @@ -9,8 +9,9 @@ import pytest -from python_minifier import add_namespace, bind_names, resolve_names, allow_rename_locals, allow_rename_globals, \ - compare_ast, rename, CompareError, unparse +from python_minifier import unparse +from python_minifier.ast_compare import CompareError, compare_ast +from python_minifier.rename import add_namespace, allow_rename_globals, allow_rename_locals, bind_names, rename, resolve_names def rename_locals(source): diff --git a/test/test_slice.py b/test/test_slice.py index f2f68f3c..445b83ee 100644 --- a/test/test_slice.py +++ b/test/test_slice.py @@ -3,6 +3,7 @@ from python_minifier import unparse from python_minifier.ast_compare import compare_ast + def test_slice(): """AST for slices was changed in 3.9""" diff --git a/test/test_type_param_defaults.py b/test/test_type_param_defaults.py index f5133abe..4498ccac 100644 --- a/test/test_type_param_defaults.py +++ b/test/test_type_param_defaults.py @@ -1,6 +1,8 @@ import ast import sys + import pytest + from python_minifier import unparse from python_minifier.ast_compare import compare_ast diff --git a/typing_test/test_badtyping.py b/typing_test/test_badtyping.py index 45a3263d..130f18b3 100644 --- a/typing_test/test_badtyping.py +++ b/typing_test/test_badtyping.py @@ -4,6 +4,7 @@ from python_minifier import minify + def test_typing() -> None: minify(456, diff --git a/typing_test/test_typing.py b/typing_test/test_typing.py index 3adf3283..bf18496f 100644 --- a/typing_test/test_typing.py +++ b/typing_test/test_typing.py @@ -4,7 +4,8 @@ import ast -from python_minifier import minify, unparse, awslambda, RemoveAnnotationsOptions +from python_minifier import RemoveAnnotationsOptions, awslambda, minify, unparse + def test_typing() -> None: """ This should have good types """ diff --git a/xtest/test_unparse_env.py b/xtest/test_unparse_env.py index b0ef18d0..8dce3369 100644 --- a/xtest/test_unparse_env.py +++ b/xtest/test_unparse_env.py @@ -1,12 +1,14 @@ +import ast import os import sys -import ast -import pytest import warnings -warnings.filterwarnings("ignore") + +import pytest from python_minifier import minify, unparse +warnings.filterwarnings("ignore") + def gather_files(): print('Interpreter version: ', sys.version_info) print('sys.path: ', sys.path) From 5d92a1aa936a64e5b59ca76947d4105068c023b3 Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Fri, 11 Oct 2024 10:05:27 +0100 Subject: [PATCH 2/7] Format Python code --- corpus_test/generate_report.py | 6 + corpus_test/generate_results.py | 2 +- hypo_test/README.md | 17 +- hypo_test/expressions.py | 157 ++++++++++------ hypo_test/folding.py | 36 ++-- hypo_test/module.py | 120 ++++++++---- hypo_test/patterns.py | 13 +- hypo_test/test_it.py | 25 ++- src/python_minifier/__init__.py | 2 + src/python_minifier/__init__.pyi | 4 + src/python_minifier/__main__.py | 2 + src/python_minifier/ast_compat.py | 5 + src/python_minifier/ast_printer.py | 2 + src/python_minifier/expression_printer.py | 2 +- src/python_minifier/module_printer.py | 3 +- src/python_minifier/rename/bind_names.py | 1 + src/python_minifier/rename/binding.py | 2 +- src/python_minifier/rename/mapper.py | 1 + src/python_minifier/rename/rename_literals.py | 2 + src/python_minifier/rename/resolve_names.py | 2 + src/python_minifier/rename/util.py | 1 + src/python_minifier/token_printer.py | 2 + .../transforms/combine_imports.py | 1 - .../transforms/constant_folding.py | 5 +- .../transforms/remove_annotations.py | 2 +- .../transforms/remove_annotations_options.py | 4 +- .../transforms/remove_annotations_options.pyi | 15 +- .../transforms/remove_exception_brackets.py | 1 + test/test_bind_names.py | 54 ++++++ test/test_bind_names_python312.py | 10 +- test/test_bind_names_python313.py | 10 +- test/test_combine_imports.py | 11 +- test/test_comprehension_rename.py | 4 + test/test_folding.py | 173 ++++++++++-------- test/test_fstring.py | 32 ++-- test/test_generic_types.py | 4 +- test/test_hoist_literals.py | 19 ++ test/test_import.py | 44 ++--- test/test_is_ast_node.py | 1 + test/test_match.py | 2 + test/test_nonlocal.py | 2 + test/test_posargs.py | 3 +- test/test_remove_annotations.py | 141 ++++++++------ test/test_remove_assert.py | 8 + test/test_remove_debug.py | 34 ++-- test/test_remove_exception_brackets.py | 11 +- test/test_remove_literal_statements.py | 3 + test/test_remove_object.py | 1 - test/test_remove_pass.py | 8 + test/test_rename_builtins.py | 2 + test/test_rename_locals.py | 11 ++ test/test_type_param_defaults.py | 6 +- typing_test/test_badtyping.py | 5 +- typing_test/test_typing.py | 40 ++-- xtest/test_regrtest.py | 1 + xtest/test_unparse_env.py | 2 + 56 files changed, 721 insertions(+), 356 deletions(-) diff --git a/corpus_test/generate_report.py b/corpus_test/generate_report.py index 7fe22d2f..7ee51311 100644 --- a/corpus_test/generate_report.py +++ b/corpus_test/generate_report.py @@ -183,6 +183,7 @@ def format_difference(compare: Iterable[Result], base: Iterable[Result]) -> str: else: return s + def report_larger_than_original(results_dir: str, python_versions: str, minifier_sha: str) -> str: yield ''' ## Larger than original @@ -201,6 +202,7 @@ def report_larger_than_original(results_dir: str, python_versions: str, minifier for entry in larger_than_original: yield f'| {entry.corpus_entry} | {entry.original_size} | {entry.minified_size} ({entry.minified_size - entry.original_size:+}) |' + def report_unstable(results_dir: str, python_versions: str, minifier_sha: str) -> str: yield ''' ## Unstable @@ -219,6 +221,7 @@ def report_unstable(results_dir: str, python_versions: str, minifier_sha: str) - for entry in unstable: yield f'| {entry.corpus_entry} | {python_version} | {entry.original_size} |' + def report_exceptions(results_dir: str, python_versions: str, minifier_sha: str) -> str: yield ''' ## Exceptions @@ -243,6 +246,7 @@ def report_exceptions(results_dir: str, python_versions: str, minifier_sha: str) if not exceptions_found: yield ' None | | |' + def report_larger_than_base(results_dir: str, python_versions: str, minifier_sha: str, base_sha: str) -> str: yield ''' ## Top 10 Larger than base @@ -272,6 +276,7 @@ def report_larger_than_base(results_dir: str, python_versions: str, minifier_sha if not there_are_some_larger_than_base: yield '| N/A | N/A | N/A |' + def report_slowest(results_dir: str, python_versions: str, minifier_sha: str) -> str: yield ''' ## Top 10 Slowest @@ -288,6 +293,7 @@ def report_slowest(results_dir: str, python_versions: str, minifier_sha: str) -> for entry in sorted(summary.entries.values(), key=lambda entry: entry.time, reverse=True)[:10]: yield f'| {entry.corpus_entry} | {entry.original_size} | {entry.minified_size} | {entry.time:.3f} |' + def report(results_dir: str, minifier_ref: str, minifier_sha: str, base_ref: str, base_sha: str) -> Iterable[str]: """ Generate a report comparing the results of two versions of python-minifier diff --git a/corpus_test/generate_results.py b/corpus_test/generate_results.py index 21293dcd..63476e76 100644 --- a/corpus_test/generate_results.py +++ b/corpus_test/generate_results.py @@ -34,7 +34,6 @@ def minify_corpus_entry(corpus_path, corpus_entry): with open(os.path.join(corpus_path, corpus_entry), 'rb') as f: source = f.read() - result = Result(corpus_entry, len(source), 0, 0, '') start_time = time.time() @@ -133,6 +132,7 @@ def corpus_test(corpus_path, results_path, sha, regenerate_results): print('Finished') + def bool_parse(value): return value == 'true' diff --git a/hypo_test/README.md b/hypo_test/README.md index 1e17316a..9c6e9cda 100644 --- a/hypo_test/README.md +++ b/hypo_test/README.md @@ -1,16 +1,17 @@ # Hypothesis tests The hypothesis strategies in this directory generate an AST that python can parse. -It does not take care to generate semantically valid programs. +It does not take care to generate semantically valid programs. Failure cases should shrink into valid programs, though. TODO: Assignment targets: (in comprehensions too) + - Tuples, sets?? -Starred -Call arguments -Delete targets -ImportFrom levels -functiondef args -Await -f-strings + Starred + Call arguments + Delete targets + ImportFrom levels + functiondef args + Await + f-strings diff --git a/hypo_test/expressions.py b/hypo_test/expressions.py index fed45b8e..1a37204d 100644 --- a/hypo_test/expressions.py +++ b/hypo_test/expressions.py @@ -23,18 +23,21 @@ text ) -comparison_operators = sampled_from([ - ast.Eq(), - ast.NotEq(), - ast.Lt(), - ast.LtE(), - ast.Gt(), - ast.GtE(), - ast.Is(), - ast.IsNot(), - ast.In(), - ast.NotIn() -]) +comparison_operators = sampled_from( + [ + ast.Eq(), + ast.NotEq(), + ast.Lt(), + ast.LtE(), + ast.Gt(), + ast.GtE(), + ast.Is(), + ast.IsNot(), + ast.In(), + ast.NotIn() + ] +) + # region: Literals @@ -53,38 +56,46 @@ def to_node(n) -> ast.AST: return to_node(draw(integers() | floats(allow_nan=False) | complex_numbers(allow_infinity=True, allow_nan=False))) + @composite def Str(draw) -> ast.Str: return ast.Str(''.join(draw(lists(characters(), min_size=0, max_size=3)))) + @composite def Bytes(draw) -> ast.Bytes: return ast.Bytes(draw(binary(max_size=3))) + @composite def List(draw, expression) -> ast.List: l = draw(lists(expression, min_size=0, max_size=3)) return ast.List(elts=l, ctx=ast.Load()) + @composite def Tuple(draw, expression) -> ast.Tuple: t = draw(lists(expression, min_size=0, max_size=3)) return ast.Tuple(elts=t, ctx=ast.Load()) + @composite def Set(draw, expression) -> ast.Set: s = draw(lists(expression, min_size=1, max_size=3)) return ast.Set(elts=s) + @composite def Dict(draw, expression) -> ast.Dict: d = draw(dictionaries(expression, expression, min_size=0, max_size=3)) return ast.Dict(keys=list(d.keys()), values=list(d.values())) + @composite def NameConstant(draw) -> ast.NameConstant: return ast.NameConstant(draw(sampled_from([None, True, False]))) + # endregion @composite @@ -93,7 +104,13 @@ def name(draw) -> SearchStrategy: other_id_continue = [chr(i) for i in [0x00B7, 0x0387, 0x19DA] + list(range(1369, 1371 + 1))] xid_start = draw(characters(whitelist_categories=['Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl'], whitelist_characters=['_'] + other_id_start, blacklist_characters=' ')) - xid_continue = draw(lists(characters(whitelist_categories=['Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl', 'Mn', 'Mc', 'Nd', 'Pc'], whitelist_characters=['_'] + other_id_start + other_id_continue, blacklist_characters=' '), min_size=0, max_size=2)) + xid_continue = draw( + lists( + characters(whitelist_categories=['Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl', 'Mn', 'Mc', 'Nd', 'Pc'], whitelist_characters=['_'] + other_id_start + other_id_continue, blacklist_characters=' '), + min_size=0, + max_size=2 + ) + ) n = xid_start + ''.join(xid_continue) @@ -106,10 +123,12 @@ def name(draw) -> SearchStrategy: assume(False) return normalised + @composite def Name(draw, ctx=ast.Load) -> ast.Name: return ast.Name(draw(name()), ctx=ctx()) + @composite def UnaryOp(draw, expression) -> ast.UnaryOp: op = draw(sampled_from([ast.USub(), ast.UAdd(), ast.Not(), ast.Invert()])) @@ -121,29 +140,35 @@ def UnaryOp(draw, expression) -> ast.UnaryOp: def Compare(draw, expression) -> ast.Compare: num_comparators = draw(integers(min_value=2, max_value=3)) - return ast.Compare(left=draw(expression), - ops=[draw(comparison_operators) for i in range(num_comparators)], - comparators=[draw(expression) for i in range(num_comparators)]) + return ast.Compare( + left=draw(expression), + ops=[draw(comparison_operators) for i in range(num_comparators)], + comparators=[draw(expression) for i in range(num_comparators)] + ) @composite def BinOp(draw, expression) -> ast.BinOp: - op = draw(sampled_from([ - ast.Add(), - ast.Sub(), - ast.Mult(), - ast.Div(), - ast.FloorDiv(), - ast.Mod(), - ast.Pow(), - ast.LShift(), - ast.RShift(), - ast.BitOr(), - ast.BitXor(), - ast.BitOr(), - ast.BitAnd(), - ast.MatMult() - ])) + op = draw( + sampled_from( + [ + ast.Add(), + ast.Sub(), + ast.Mult(), + ast.Div(), + ast.FloorDiv(), + ast.Mod(), + ast.Pow(), + ast.LShift(), + ast.RShift(), + ast.BitOr(), + ast.BitXor(), + ast.BitOr(), + ast.BitAnd(), + ast.MatMult() + ] + ) + ) le = draw(lists(expression, min_size=2, max_size=2)) @@ -152,14 +177,19 @@ def BinOp(draw, expression) -> ast.BinOp: @composite def BoolOp(draw, expression) -> ast.BoolOp: - op = draw(sampled_from([ - ast.And(), - ast.Or(), - ])) + op = draw( + sampled_from( + [ + ast.And(), + ast.Or(), + ] + ) + ) le = draw(lists(expression, min_size=2, max_size=3)) return ast.BoolOp(op, values=le) + @composite def Call(draw, expression) -> ast.Call: func = draw(expression) @@ -189,22 +219,27 @@ def Subscript(draw, expression) -> ast.Subscript: attr = draw(text(alphabet=string.ascii_letters, min_size=1, max_size=3)) return ast.Subscript(value, attr, ast.Load()) + @composite def Yield(draw, expression) -> ast.Yield: return ast.Yield(draw(expression)) + @composite def YieldFrom(draw, expression) -> ast.YieldFrom: return ast.YieldFrom(draw(expression)) + @composite def Await(draw, expression) -> ast.Await: return ast.Await(draw(expression)) + @composite def Index(draw, expression) -> ast.Index: return ast.Index(draw(Ellipsis() | expression)) + @composite def Slice(draw, expression) -> ast.Slice: return ast.Slice( @@ -213,22 +248,28 @@ def Slice(draw, expression) -> ast.Slice: step=draw(none() | expression) ) + @composite def Ellipsis(draw) -> ast.Ellipsis: return ast.Ellipsis() + @composite def ExtSlice(draw, expression) -> ast.ExtSlice: slice = draw(Slice(expression)) - return ast.ExtSlice([slice] + - draw(lists( - Index(expression) | Slice(expression), - min_size=1, - max_size=3 - )) + return ast.ExtSlice( + [slice] + + draw( + lists( + Index(expression) | Slice(expression), + min_size=1, + max_size=3 + ) + ) ) + @composite def Subscript(draw, expression, ctx=ast.Load) -> ast.Subscript: return ast.Subscript( @@ -237,6 +278,7 @@ def Subscript(draw, expression, ctx=ast.Load) -> ast.Subscript: ctx=ctx() ) + @composite def arg(draw, allow_annotation=True) -> ast.arg: @@ -250,6 +292,7 @@ def arg(draw, allow_annotation=True) -> ast.arg: annotation=annotation ) + @composite def arguments(draw, for_lambda=False) -> ast.arguments: @@ -260,8 +303,8 @@ def arguments(draw, for_lambda=False) -> ast.arguments: kwonlyargs = draw(lists(arg(allow_annotation), max_size=2)) vararg = draw(none() | arg(allow_annotation)) kwarg = draw(none() | arg(allow_annotation)) - defaults=[] - kw_defaults=draw(lists(none() | expression(), max_size=len(kwonlyargs), min_size=len(kwonlyargs))) + defaults = [] + kw_defaults = draw(lists(none() | expression(), max_size=len(kwonlyargs), min_size=len(kwonlyargs))) return ast.arguments( posonlyargs=posonlyargs, args=args, @@ -272,10 +315,14 @@ def arguments(draw, for_lambda=False) -> ast.arguments: kw_defaults=kw_defaults ) + @composite def Lambda(draw, expression) -> ast.Lambda: - return ast.Lambda(args=draw(arguments(for_lambda=True)), - body=draw(expression)) + return ast.Lambda( + args=draw(arguments(for_lambda=True)), + body=draw(expression) + ) + @composite def comprehension(draw, expression) -> ast.comprehension: @@ -286,6 +333,7 @@ def comprehension(draw, expression) -> ast.comprehension: is_async=draw(booleans()) ) + @composite def ListComp(draw, expression) -> ast.ListComp: return ast.ListComp( @@ -293,6 +341,7 @@ def ListComp(draw, expression) -> ast.ListComp: generators=draw(lists(comprehension(expression), min_size=1, max_size=3)) ) + @composite def SetComp(draw, expression) -> ast.SetComp: return ast.SetComp( @@ -300,6 +349,7 @@ def SetComp(draw, expression) -> ast.SetComp: generators=draw(lists(comprehension(expression), min_size=1, max_size=3)) ) + @composite def GeneratorExp(draw, expression) -> ast.GeneratorExp: return ast.GeneratorExp( @@ -307,6 +357,7 @@ def GeneratorExp(draw, expression) -> ast.GeneratorExp: generators=draw(lists(comprehension(expression), min_size=1, max_size=3)) ) + @composite def DictComp(draw, expression) -> ast.DictComp: return ast.DictComp( @@ -315,11 +366,13 @@ def DictComp(draw, expression) -> ast.DictComp: generators=draw(lists(comprehension(expression), min_size=1, max_size=3)) ) + leaves = NameConstant() | \ - Name() | \ - Num() | \ - Str() | \ - Bytes() + Name() | \ + Num() | \ + Str() | \ + Bytes() + def async_expression() -> SearchStrategy: return recursive( @@ -349,6 +402,7 @@ def async_expression() -> SearchStrategy: max_leaves=150 ) + def expression() -> SearchStrategy: return recursive( leaves, @@ -356,7 +410,7 @@ def expression() -> SearchStrategy: one_of( Yield(expression), YieldFrom(expression), - #Await(expression), + # Await(expression), IfExp(expression), Call(expression), BinOp(expression), @@ -377,6 +431,7 @@ def expression() -> SearchStrategy: max_leaves=150 ) + @composite def Expression(draw) -> ast.Expression: """ An eval expression """ diff --git a/hypo_test/folding.py b/hypo_test/folding.py index e881a719..9c43b6ff 100644 --- a/hypo_test/folding.py +++ b/hypo_test/folding.py @@ -6,23 +6,28 @@ leaves = NameConstant() | Num() + @composite def BinOp(draw, expression) -> ast.BinOp: - op = draw(sampled_from([ - ast.Add(), - ast.Sub(), - ast.Mult(), - ast.Div(), - ast.FloorDiv(), - ast.Mod(), - ast.Pow(), - ast.LShift(), - ast.RShift(), - ast.BitOr(), - ast.BitXor(), - ast.BitAnd(), - ast.MatMult() - ])) + op = draw( + sampled_from( + [ + ast.Add(), + ast.Sub(), + ast.Mult(), + ast.Div(), + ast.FloorDiv(), + ast.Mod(), + ast.Pow(), + ast.LShift(), + ast.RShift(), + ast.BitOr(), + ast.BitXor(), + ast.BitAnd(), + ast.MatMult() + ] + ) + ) le = draw(lists(expression, min_size=2, max_size=2)) @@ -37,6 +42,7 @@ def expression() -> SearchStrategy: max_leaves=150 ) + @composite def FoldableExpression(draw) -> ast.Expression: """ An eval expression """ diff --git a/hypo_test/module.py b/hypo_test/module.py index d362da35..9d066a38 100644 --- a/hypo_test/module.py +++ b/hypo_test/module.py @@ -11,29 +11,35 @@ def Assign(draw) -> ast.Assign: targets = draw(lists(Name(ast.Store), min_size=1, max_size=3)) return ast.Assign(targets=targets, value=draw(expression())) + @composite def AnnAssign(draw) -> ast.AnnAssign: target = draw(Name(ast.Store)) return ast.AnnAssign(target=target, annotation=draw(expression()), value=draw(expression()), simple=True) + @composite def AugAssign(draw): - op = draw(sampled_from([ - ast.Add(), - ast.Sub(), - ast.Mult(), - ast.Div(), - ast.FloorDiv(), - ast.Mod(), - ast.Pow(), - ast.LShift(), - ast.RShift(), - ast.BitOr(), - ast.BitXor(), - ast.BitOr(), - ast.BitAnd(), - ast.MatMult() - ])) + op = draw( + sampled_from( + [ + ast.Add(), + ast.Sub(), + ast.Mult(), + ast.Div(), + ast.FloorDiv(), + ast.Mod(), + ast.Pow(), + ast.LShift(), + ast.RShift(), + ast.BitOr(), + ast.BitXor(), + ast.BitOr(), + ast.BitAnd(), + ast.MatMult() + ] + ) + ) return ast.AugAssign(target=draw(Name(ast.Store)), op=op, value=draw(expression())) @@ -42,48 +48,58 @@ def AugAssign(draw): def Print(draw): return ast.Print(dest=None, value=draw(expression()), nl=not draw(booleans())) + @composite def Raise(draw): return ast.Raise(draw(none() | expression()), cause=None) + @composite def Assert(draw): return ast.Assert(test=draw(expression()), msg=draw(expression())) + @composite def Delete(draw): return ast.Delete(targets=draw(lists(expression(), min_size=1, max_size=3))) + @composite def Pass(draw) -> ast.Pass: return ast.Pass() + @composite def Break(draw) -> ast.Break: return ast.Break() + @composite def Continue(draw) -> ast.Continue: return ast.Continue() + @composite def With(draw, statements) -> ast.With: items = draw(lists(expression(), min_size=1, max_size=3)) body = draw(lists(statements, min_size=1, max_size=3)) return ast.With([ast.withitem(context_expr=i, optional_vars=None) for i in items], body) + @composite def AsyncWith(draw, statements) -> ast.AsyncWith: items = draw(lists(expression(), min_size=1, max_size=3)) body = draw(lists(statements, min_size=1, max_size=3)) return ast.AsyncWith([ast.withitem(context_expr=i, optional_vars=None) for i in items], body) + @composite def If(draw, statements) -> ast.If: body = draw(lists(statements, min_size=1, max_size=3)) orelse = draw(lists(statements, min_size=1, max_size=3)) return ast.If(test=draw(expression()), body=body, orelse=orelse) + @composite def ExceptHandler(draw, statements) -> ast.ExceptHandler: t = draw(none() | Name()) @@ -98,6 +114,7 @@ def ExceptHandler(draw, statements) -> ast.ExceptHandler: body=draw(lists(statements, min_size=1, max_size=3)) ) + @composite def Try(draw, statements) -> ast.Try: body = draw(lists(statements, min_size=1, max_size=3)) @@ -115,6 +132,7 @@ def Try(draw, statements) -> ast.Try: finalbody=finalbody ) + @composite def For(draw, statements) -> ast.For: target = draw(Name(ast.Store)) @@ -123,6 +141,7 @@ def For(draw, statements) -> ast.For: orelse = draw(lists(statements, min_size=1, max_size=3)) return ast.For(target, iter, body, orelse) + @composite def AsyncFor(draw, statements) -> ast.AsyncFor: target = draw(Name(ast.Store)) @@ -131,6 +150,7 @@ def AsyncFor(draw, statements) -> ast.AsyncFor: orelse = draw(lists(statements, min_size=1, max_size=3)) return ast.AsyncFor(target, iter, body, orelse) + @composite def While(draw, statements) -> ast.While: test = draw(expression()) @@ -138,54 +158,72 @@ def While(draw, statements) -> ast.While: orelse = draw(lists(statements, min_size=1, max_size=3)) return ast.While(test, body, orelse) + @composite def Return(draw) -> ast.Return: return ast.Return(draw(expression())) + @composite def Expr(draw) -> ast.Expr: return ast.Expr(draw(expression())) + @composite def Global(draw) -> ast.Global: return ast.Global(draw(lists(name(), min_size=1, max_size=3))) + @composite def Nonlocal(draw) -> ast.Nonlocal: return ast.Nonlocal(draw(lists(name(), min_size=1, max_size=3))) + @composite def alias(draw) -> ast.alias: return ast.alias(name=draw(name()), asname=draw(none() | name())) + @composite def Import(draw) -> ast.Import: return ast.Import(names=draw(lists(alias(), min_size=1, max_size=3))) + @composite def ImportFrom(draw) -> ast.ImportFrom: - return ast.ImportFrom(module=draw(name()), - names=draw(lists(alias(), min_size=1, max_size=3)), - level=draw(integers(min_value=0, max_value=2))) + return ast.ImportFrom( + module=draw(name()), + names=draw(lists(alias(), min_size=1, max_size=3)), + level=draw(integers(min_value=0, max_value=2)) + ) + @composite def TypeVar(draw) -> ast.TypeVar: - return ast.TypeVar(name=draw(name()), - bound=draw(none() | expression())) + return ast.TypeVar( + name=draw(name()), + bound=draw(none() | expression()) + ) + @composite def TypeVarTuple(draw) -> ast.TypeVarTuple: return ast.TypeVarTuple(name=draw(name())) + @composite def ParamSpec(draw) -> ast.ParamSpec: return ast.ParamSpec(name=draw(name())) + @composite def TypeAlias(draw) -> ast.TypeAlias: - return ast.TypeAlias(name=draw(Name(ast.Store)), - value=draw(expression()), - type_params=draw(lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3))) + return ast.TypeAlias( + name=draw(Name(ast.Store)), + value=draw(expression()), + type_params=draw(lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) + ) + @composite def FunctionDef(draw, statements) -> ast.FunctionDef: @@ -197,6 +235,7 @@ def FunctionDef(draw, statements) -> ast.FunctionDef: returns = draw(none() | expression()) return ast.FunctionDef(n, args, body, decorator_list, returns, type_params=type_params) + @composite def AsyncFunctionDef(draw, statements) -> ast.AsyncFunctionDef: n = draw(name()) @@ -207,6 +246,7 @@ def AsyncFunctionDef(draw, statements) -> ast.AsyncFunctionDef: returns = draw(none() | expression()) return ast.AsyncFunctionDef(n, args, body, decorator_list, returns, type_params=type_params) + @composite def keyword(draw) -> ast.keyword: return ast.keyword( @@ -214,6 +254,7 @@ def keyword(draw) -> ast.keyword: value=draw(expression()) ) + @composite def ClassDef(draw, statements) -> ast.ClassDef: n = draw(name()) @@ -233,6 +274,7 @@ def ClassDef(draw, statements) -> ast.ClassDef: type_params=draw(lists(one_of(TypeVar(), TypeVarTuple(), ParamSpec()), min_size=0, max_size=3)) ) + if hasattr(ast, 'Print'): simple_statements = one_of( Pass(), @@ -244,7 +286,7 @@ def ClassDef(draw, statements) -> ast.ClassDef: Assert(), Print(), Raise(), - #Delete() | + # Delete() | Assign(), AnnAssign(), AugAssign(), @@ -261,7 +303,7 @@ def ClassDef(draw, statements) -> ast.ClassDef: Expr(), Assert(), Raise(), - #Delete() | + # Delete() | Assign(), AnnAssign(), AugAssign(), @@ -270,25 +312,27 @@ def ClassDef(draw, statements) -> ast.ClassDef: TypeAlias() ) + def suite() -> SearchStrategy: return recursive( simple_statements, lambda statements: - one_of( - With(statements), - AsyncWith(statements), - If(statements), - For(statements), - AsyncFor(statements), - While(statements), - FunctionDef(statements), - AsyncFunctionDef(statements), - ClassDef(statements), - Try(statements) - ), + one_of( + With(statements), + AsyncWith(statements), + If(statements), + For(statements), + AsyncFor(statements), + While(statements), + FunctionDef(statements), + AsyncFunctionDef(statements), + ClassDef(statements), + Try(statements) + ), max_leaves=100 ) + @composite def Module(draw) -> ast.Module: b = draw(lists(suite(), min_size=1, max_size=10)) diff --git a/hypo_test/patterns.py b/hypo_test/patterns.py index 216bed10..c4cf4270 100644 --- a/hypo_test/patterns.py +++ b/hypo_test/patterns.py @@ -14,18 +14,22 @@ def name(draw): return n + @composite def MatchValue(draw) -> ast.MatchValue: return ast.MatchValue(ast.Constant(0)) + @composite def MatchSingleton(draw) -> ast.MatchSingleton: return ast.MatchSingleton(draw(sampled_from([None, True, False]))) + @composite def MatchStar(draw) -> ast.MatchStar: return ast.MatchStar(name=draw(sampled_from([None, 'rest']))) + @composite def MatchSequence(draw, pattern) -> ast.MatchSequence: l = draw(lists(pattern, min_size=1, max_size=3)) @@ -38,6 +42,7 @@ def MatchSequence(draw, pattern) -> ast.MatchSequence: return ast.MatchSequence(patterns=l) + @composite def MatchMapping(draw, pattern) -> ast.MatchMapping: l = draw(lists(pattern, min_size=1, max_size=3)) @@ -50,12 +55,13 @@ def MatchMapping(draw, pattern) -> ast.MatchMapping: return match_mapping + @composite def MatchClass(draw, pattern) -> ast.MatchClass: patterns = draw(lists(pattern, min_size=0, max_size=3)) kwd_patterns = draw(lists(pattern, min_size=0, max_size=3)) - kwd=['a' for i in range(len(kwd_patterns))] + kwd = ['a' for i in range(len(kwd_patterns))] return ast.MatchClass( cls=ast.Name(draw(name()), ctx=ast.Load()), @@ -64,6 +70,7 @@ def MatchClass(draw, pattern) -> ast.MatchClass: kwd_patterns=kwd_patterns ) + @composite def MatchAs(draw, pattern) -> ast.MatchAs: n = draw(none() | name()) @@ -75,13 +82,16 @@ def MatchAs(draw, pattern) -> ast.MatchAs: return ast.MatchAs(pattern=p, name=n) + @composite def MatchOr(draw, pattern) -> ast.MatchOr: l = draw(lists(pattern, min_size=2, max_size=3)) return ast.MatchOr(patterns=l) + leaves = MatchValue() | MatchSingleton() + def pattern(): return recursive( leaves, @@ -96,6 +106,7 @@ def pattern(): max_leaves=150 ) + @composite def Pattern(draw): """ A Match case pattern """ diff --git a/hypo_test/test_it.py b/hypo_test/test_it.py index 9b4a46bb..3fe5d009 100644 --- a/hypo_test/test_it.py +++ b/hypo_test/test_it.py @@ -18,7 +18,7 @@ @given(node=Expression()) -@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=100, suppress_health_check=[HealthCheck.too_slow]) #verbosity=Verbosity.verbose +@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=100, suppress_health_check=[HealthCheck.too_slow]) # verbosity=Verbosity.verbose def test_expression(node): assert isinstance(node, ast.AST) @@ -46,14 +46,16 @@ def test_module(node): def test_pattern(node): module = ast.Module( - body=[ast.Match(subject=ast.Constant(value=None), - cases=[ - ast.match_case( - pattern=node, - guard=None, - body=[ast.Pass()] - ) - ])], + body=[ast.Match( + subject=ast.Constant(value=None), + cases=[ + ast.match_case( + pattern=node, + guard=None, + body=[ast.Pass()] + ) + ] + )], type_ignores=[] ) @@ -62,8 +64,9 @@ def test_pattern(node): note(code) compare_ast(module, ast.parse(code, 'test_pattern')) + @given(node=FoldableExpression()) -@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=1000, suppress_health_check=[HealthCheck.too_slow]) #verbosity=Verbosity.verbose +@settings(report_multiple_bugs=False, deadline=timedelta(seconds=1), max_examples=1000, suppress_health_check=[HealthCheck.too_slow]) # verbosity=Verbosity.verbose def test_folding(node): assert isinstance(node, ast.AST) note(print_ast(node)) @@ -75,6 +78,7 @@ def test_folding(node): # The constant folder asserts the value is correct constant_folder(node) + @given(node=TypeAlias()) @settings(report_multiple_bugs=False, deadline=timedelta(seconds=2), max_examples=100, verbosity=Verbosity.verbose) def test_type_alias(node): @@ -89,6 +93,7 @@ def test_type_alias(node): note(code) compare_ast(module, ast.parse(code, 'test_type_alias')) + @given(node=TypeAlias()) @settings(report_multiple_bugs=False, deadline=timedelta(seconds=2), max_examples=100, verbosity=Verbosity.verbose) def test_function_type_param(node): diff --git a/src/python_minifier/__init__.py b/src/python_minifier/__init__.py index 39f307c1..7c6b35f3 100644 --- a/src/python_minifier/__init__.py +++ b/src/python_minifier/__init__.py @@ -198,6 +198,7 @@ def minify( return minified + def _find_shebang(source): """ Find a shebang line in source @@ -214,6 +215,7 @@ def _find_shebang(source): return None + def unparse(module): """ Turn a module AST into python code diff --git a/src/python_minifier/__init__.pyi b/src/python_minifier/__init__.pyi index c44e7370..1a371bd9 100644 --- a/src/python_minifier/__init__.pyi +++ b/src/python_minifier/__init__.pyi @@ -4,9 +4,11 @@ from typing import Any, AnyStr, List, Optional, Text, Union from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions + class UnstableMinification(RuntimeError): def __init__(self, exception: Any, source: Any, minified: Any): ... + def minify( source: AnyStr, filename: Optional[str] = ..., @@ -29,8 +31,10 @@ def minify( constant_folding: bool = ... ) -> Text: ... + def unparse(module: ast.Module) -> Text: ... + def awslambda( source: AnyStr, filename: Optional[Text] = ..., diff --git a/src/python_minifier/__main__.py b/src/python_minifier/__main__.py index 179a4817..efcec6f3 100644 --- a/src/python_minifier/__main__.py +++ b/src/python_minifier/__main__.py @@ -9,12 +9,14 @@ 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 DistributionNotFound, get_distribution + try: version = get_distribution('python_minifier').version except DistributionNotFound: diff --git a/src/python_minifier/ast_compat.py b/src/python_minifier/ast_compat.py index ba198d56..da7f5a11 100644 --- a/src/python_minifier/ast_compat.py +++ b/src/python_minifier/ast_compat.py @@ -16,6 +16,7 @@ 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 @@ -23,18 +24,22 @@ 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) diff --git a/src/python_minifier/ast_printer.py b/src/python_minifier/ast_printer.py index 5480f506..1b9c020e 100644 --- a/src/python_minifier/ast_printer.py +++ b/src/python_minifier/ast_printer.py @@ -63,6 +63,7 @@ 'AugAssign': 'op', } + def is_literal(node, field): if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value': return True @@ -81,6 +82,7 @@ def is_literal(node, field): return False + def print_ast(node): if not isinstance(node, ast.AST): return repr(node) diff --git a/src/python_minifier/expression_printer.py b/src/python_minifier/expression_printer.py index 394e7201..d9cd685b 100644 --- a/src/python_minifier/expression_printer.py +++ b/src/python_minifier/expression_printer.py @@ -156,7 +156,7 @@ def key_datum(key, datum): if key is None: self.printer.operator('**') - if 0 < self.precedence(datum) <=7: + if 0 < self.precedence(datum) <= 7: self.printer.delimiter('(') self._expression(datum) self.printer.delimiter(')') diff --git a/src/python_minifier/module_printer.py b/src/python_minifier/module_printer.py index 897671f3..32d2e084 100644 --- a/src/python_minifier/module_printer.py +++ b/src/python_minifier/module_printer.py @@ -671,7 +671,7 @@ def visit_MatchClass(self, node): for kwd, pattern in zip(node.kwd_attrs, node.kwd_patterns): delimiter.new_item() - self.printer.identifier(kwd) + self.printer.identifier(kwd) self.printer.delimiter('=') self.pattern(pattern) @@ -860,4 +860,3 @@ def _suite_body(self, node_list): for node in node_list: statements[node.__class__.__name__](node) - diff --git a/src/python_minifier/rename/bind_names.py b/src/python_minifier/rename/bind_names.py index e3a6dcd4..b1931db7 100644 --- a/src/python_minifier/rename/bind_names.py +++ b/src/python_minifier/rename/bind_names.py @@ -180,6 +180,7 @@ def visit_ParamSpec(self, node): get_global_namespace(node.namespace).preserved.add(node.name) + def bind_names(module): """ Bind names to their local namespace diff --git a/src/python_minifier/rename/binding.py b/src/python_minifier/rename/binding.py index 9f58058b..7dde4095 100644 --- a/src/python_minifier/rename/binding.py +++ b/src/python_minifier/rename/binding.py @@ -492,4 +492,4 @@ class MyClass: if not isinstance(node.ctx, ast.Load): return True - return False \ No newline at end of file + return False diff --git a/src/python_minifier/rename/mapper.py b/src/python_minifier/rename/mapper.py index d21a1908..2f921e91 100644 --- a/src/python_minifier/rename/mapper.py +++ b/src/python_minifier/rename/mapper.py @@ -97,6 +97,7 @@ def add_parent_to_classdef(classdef): for node in classdef.type_params: add_parent(node, parent=classdef, namespace=classdef.namespace) + def add_parent_to_comprehension(node, namespace): assert is_ast_node(node, (ast.GeneratorExp, 'SetComp', 'DictComp', 'ListComp')) diff --git a/src/python_minifier/rename/rename_literals.py b/src/python_minifier/rename/rename_literals.py index b5e2cf5d..c5167918 100644 --- a/src/python_minifier/rename/rename_literals.py +++ b/src/python_minifier/rename/rename_literals.py @@ -84,6 +84,7 @@ def should_rename(self, new_name): return rename_cost <= current_cost + class HoistedValue(object): """ HoistedValue comparator object @@ -245,5 +246,6 @@ def visit_Assign(self, node): return self.generic_visit(node) + def rename_literals(module): HoistLiterals()(module) diff --git a/src/python_minifier/rename/resolve_names.py b/src/python_minifier/rename/resolve_names.py index 95a0937a..d6e9a994 100644 --- a/src/python_minifier/rename/resolve_names.py +++ b/src/python_minifier/rename/resolve_names.py @@ -34,6 +34,7 @@ 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) @@ -43,6 +44,7 @@ def get_binding_disallow_class_namespace_rename(name, namespace): return binding + def resolve_names(node): """ Resolve unbound names to a NameBinding diff --git a/src/python_minifier/rename/util.py b/src/python_minifier/rename/util.py index c27f0ad2..d52ae921 100644 --- a/src/python_minifier/rename/util.py +++ b/src/python_minifier/rename/util.py @@ -33,6 +33,7 @@ def iter_child_namespaces(node): 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/token_printer.py b/src/python_minifier/token_printer.py index 71382c92..bcb24545 100644 --- a/src/python_minifier/token_printer.py +++ b/src/python_minifier/token_printer.py @@ -16,6 +16,7 @@ class TokenTypes(object): NewLine = 8 EndStatement = 9 + class Delimiter(object): def __init__(self, terminal_printer, delimiter=',', add_parens=False): """ @@ -75,6 +76,7 @@ def new_item(self): else: self._terminal_printer.delimiter(self._delimiter) + class TokenPrinter(object): """ Concatenates terminal symbols of the python grammar diff --git a/src/python_minifier/transforms/combine_imports.py b/src/python_minifier/transforms/combine_imports.py index 43384ec5..de7b278e 100644 --- a/src/python_minifier/transforms/combine_imports.py +++ b/src/python_minifier/transforms/combine_imports.py @@ -30,7 +30,6 @@ def _combine_import(self, node_list, parent): if alias: yield self.add_child(ast.Import(names=alias), parent=parent, namespace=namespace) - def _combine_import_from(self, node_list, parent): prev_import = None diff --git a/src/python_minifier/transforms/constant_folding.py b/src/python_minifier/transforms/constant_folding.py index d6f79ae7..7d1e1b21 100644 --- a/src/python_minifier/transforms/constant_folding.py +++ b/src/python_minifier/transforms/constant_folding.py @@ -95,6 +95,7 @@ def visit_BinOp(self, 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 @@ -104,6 +105,7 @@ def equal_value_and_type(a, b): return a == b + def safe_eval(expression): globals = {} locals = {} @@ -111,6 +113,7 @@ def safe_eval(expression): # 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) \ No newline at end of file + return expression_printer(node) diff --git a/src/python_minifier/transforms/remove_annotations.py b/src/python_minifier/transforms/remove_annotations.py index ae47bc5b..02804a24 100644 --- a/src/python_minifier/transforms/remove_annotations.py +++ b/src/python_minifier/transforms/remove_annotations.py @@ -105,7 +105,7 @@ def is_typing_sensitive(node): for node in node.parent.bases: if isinstance(node, ast.Name) and node.id in tricky_types: return True - elif isinstance(node, ast. Attribute) and node.attr in tricky_types: + elif isinstance(node, ast.Attribute) and node.attr in tricky_types: return True return False diff --git a/src/python_minifier/transforms/remove_annotations_options.py b/src/python_minifier/transforms/remove_annotations_options.py index b865fbb9..b7ba8d58 100644 --- a/src/python_minifier/transforms/remove_annotations_options.py +++ b/src/python_minifier/transforms/remove_annotations_options.py @@ -26,7 +26,9 @@ def __init__(self, remove_variable_annotations=True, remove_return_annotations=T 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) + 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)) diff --git a/src/python_minifier/transforms/remove_annotations_options.pyi b/src/python_minifier/transforms/remove_annotations_options.pyi index e54749b6..aea089c5 100644 --- a/src/python_minifier/transforms/remove_annotations_options.pyi +++ b/src/python_minifier/transforms/remove_annotations_options.pyi @@ -1,17 +1,20 @@ 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 __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: ... diff --git a/src/python_minifier/transforms/remove_exception_brackets.py b/src/python_minifier/transforms/remove_exception_brackets.py index d34a022e..1a7dc41e 100644 --- a/src/python_minifier/transforms/remove_exception_brackets.py +++ b/src/python_minifier/transforms/remove_exception_brackets.py @@ -74,6 +74,7 @@ 'BaseExceptionGroup', ] + def _remove_empty_call(binding): assert isinstance(binding, BuiltinBinding) diff --git a/test/test_bind_names.py b/test/test_bind_names.py index 42253a6b..87bbc447 100644 --- a/test/test_bind_names.py +++ b/test/test_bind_names.py @@ -21,6 +21,7 @@ def test_module_namespace(): 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') @@ -43,6 +44,7 @@ def test_lambda_namespace(): 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') @@ -76,6 +78,7 @@ def inner_func(): 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') @@ -109,6 +112,7 @@ async def inner_func(): assert_namespace_tree(source, expected_namespaces) + # region generator namespace def test_generator_namespace(): @@ -129,6 +133,7 @@ def test_generator_namespace(): 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') @@ -150,6 +155,7 @@ def test_multi_generator_namespace(): 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') @@ -174,6 +180,7 @@ def test_multi_generator_namespace_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') @@ -199,6 +206,7 @@ def test_nested_generator(): 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') @@ -220,6 +228,7 @@ def test_nested_generator_2(): assert_namespace_tree(source, expected_namespaces) + # endregion @@ -243,6 +252,7 @@ def test_setcomp_namespace(): 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') @@ -264,6 +274,7 @@ def test_multi_setcomp_namespace(): 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') @@ -288,6 +299,7 @@ def test_multi_setcomp_namespace_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') @@ -313,6 +325,7 @@ def test_nested_setcomp(): 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') @@ -334,6 +347,7 @@ def test_nested_setcomp_2(): assert_namespace_tree(source, expected_namespaces) + # endregion # region listcomp @@ -356,6 +370,7 @@ def test_listcomp_namespace(): 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') @@ -377,6 +392,7 @@ def test_multi_listcomp_namespace(): 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') @@ -401,6 +417,7 @@ def test_multi_listcomp_namespace_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') @@ -426,6 +443,7 @@ def test_nested_listcomp(): 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') @@ -447,6 +465,7 @@ def test_nested_listcomp_2(): assert_namespace_tree(source, expected_namespaces) + # endregion # region dictcomp @@ -469,6 +488,7 @@ def test_dictcomp_namespace(): 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') @@ -490,6 +510,7 @@ def test_multi_dictcomp_namespace(): 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') @@ -514,6 +535,7 @@ def test_multi_dictcomp_namespace_2(): 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') @@ -539,6 +561,7 @@ def test_nested_dictcomp(): 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') @@ -560,6 +583,7 @@ def test_nested_dictcomp_2(): assert_namespace_tree(source, expected_namespaces) + # endregion def test_class_namespace(): @@ -630,6 +654,7 @@ class C: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_name_allow_rename(): source = ''' MyNonLocal = 1 @@ -650,6 +675,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_name_disallow_rename(): source = ''' class LazyList: @@ -667,6 +693,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_builtin_name_allow_rename(): source = ''' class LazyList: @@ -685,6 +712,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_builtin_name_disallow_rename(): source = ''' class LazyList: @@ -727,6 +755,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_name_disallow_rename(): source = ''' MyNonLocal = 1 @@ -748,6 +777,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_name_disallow_rename(): source = ''' class LazyList: @@ -766,6 +796,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_import_as_disallow_rename(): """import os as MyNonLocal""" source = ''' @@ -788,6 +819,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_import_as_disallow_rename(): source = ''' class LazyList: @@ -807,6 +839,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_import_from_as_disallow_rename(): source = ''' MyNonLocal = 1 @@ -828,6 +861,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_import_from_as_disallow_rename(): source = ''' class LazyList: @@ -847,6 +881,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_import_disallow_rename(): source = ''' MyNonLocal = 1 @@ -868,6 +903,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_import_disallow_rename(): source = ''' class LazyList: @@ -887,6 +923,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_import_from_disallow_rename(): source = ''' MyNonLocal = 1 @@ -908,6 +945,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_import_from_disallow_rename(): source = ''' class LazyList: @@ -927,6 +965,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_dotted_import_disallow_rename(): source = ''' MyNonLocal = 1 @@ -948,6 +987,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_dotted_import_disallow_rename(): source = ''' class LazyList: @@ -967,6 +1007,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_func_disallow_rename(): source = ''' MyNonLocal = 1 @@ -989,6 +1030,7 @@ def MyNonLocal(): pass assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_func_disallow_rename(): source = ''' class LazyList: @@ -1009,6 +1051,7 @@ def MyNonLocal(): pass 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') @@ -1034,6 +1077,7 @@ async def MyNonLocal(): pass 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') @@ -1057,6 +1101,7 @@ async def MyNonLocal(): pass assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_nonlocal_classdef_disallow_rename(): source = ''' MyNonLocal = 1 @@ -1079,6 +1124,7 @@ class MyNonLocal(): pass assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_classdef_disallow_rename(): source = ''' class LazyList: @@ -1126,6 +1172,7 @@ class LazyList: assert_namespace_tree(source, expected_namespaces) + def test_class_namespace_undefined_nonlocal_except_disallow_rename(): source = ''' class LazyList: @@ -1179,6 +1226,7 @@ class LazyList: 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') @@ -1205,6 +1253,7 @@ class LazyList: 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') @@ -1233,6 +1282,7 @@ class LazyList: 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') @@ -1259,6 +1309,7 @@ class LazyList: 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') @@ -1287,6 +1338,7 @@ class LazyList: 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') @@ -1313,6 +1365,7 @@ class LazyList: 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') @@ -1334,6 +1387,7 @@ class LazyList: 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') diff --git a/test/test_bind_names_python312.py b/test/test_bind_names_python312.py index 51a91afb..c751c7ba 100644 --- a/test/test_bind_names_python312.py +++ b/test/test_bind_names_python312.py @@ -22,6 +22,7 @@ class Foo[T]: ... assert_namespace_tree(source, expected_namespaces) + def test_function_typevar_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -39,6 +40,7 @@ def foo[T](): ... assert_namespace_tree(source, expected_namespaces) + def test_alias_typevar_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -56,6 +58,7 @@ def test_alias_typevar_default(): assert_namespace_tree(source, expected_namespaces) + def test_class_typevartuple_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -73,6 +76,7 @@ class Foo[*T]: ... assert_namespace_tree(source, expected_namespaces) + def test_function_typevartuple_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -90,6 +94,7 @@ def foo[*T](): ... assert_namespace_tree(source, expected_namespaces) + def test_alias_typevartuple_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -107,6 +112,7 @@ def test_alias_typevartuple_default(): assert_namespace_tree(source, expected_namespaces) + def test_class_paramspec_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -124,6 +130,7 @@ class Foo[**T]: ... assert_namespace_tree(source, expected_namespaces) + def test_function_paramspec_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -141,6 +148,7 @@ def foo[**T](): ... assert_namespace_tree(source, expected_namespaces) + def test_alias_paramspec_default(): if sys.version_info < (3, 12): pytest.skip('Test is for > python3.12 only') @@ -156,4 +164,4 @@ def test_alias_paramspec_default(): - NameBinding(name='DefaultT', allow_rename=True) ''' - assert_namespace_tree(source, expected_namespaces) \ No newline at end of file + assert_namespace_tree(source, expected_namespaces) diff --git a/test/test_bind_names_python313.py b/test/test_bind_names_python313.py index 38178d57..57f2915c 100644 --- a/test/test_bind_names_python313.py +++ b/test/test_bind_names_python313.py @@ -23,6 +23,7 @@ class Foo[T = str]: ... assert_namespace_tree(source, expected_namespaces) + def test_function_typevar_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -43,6 +44,7 @@ def foo[T = A](): ... assert_namespace_tree(source, expected_namespaces) + def test_alias_typevar_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -63,6 +65,7 @@ def test_alias_typevar_default(): assert_namespace_tree(source, expected_namespaces) + def test_class_typevartuple_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -84,6 +87,7 @@ class Foo[*T = str]: ... assert_namespace_tree(source, expected_namespaces) + def test_function_typevartuple_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -106,6 +110,7 @@ def foo[*T = A](): ... assert_namespace_tree(source, expected_namespaces) + def test_alias_typevartuple_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -126,6 +131,7 @@ def test_alias_typevartuple_default(): assert_namespace_tree(source, expected_namespaces) + def test_class_paramspec_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -147,6 +153,7 @@ class Foo[**T = str]: ... assert_namespace_tree(source, expected_namespaces) + def test_function_paramspec_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -169,6 +176,7 @@ def foo[**T = A](): ... assert_namespace_tree(source, expected_namespaces) + def test_alias_paramspec_default(): if sys.version_info < (3, 13): pytest.skip('Test is for python3.13 only') @@ -187,4 +195,4 @@ def test_alias_paramspec_default(): - BuiltinBinding(name='tuple', allow_rename=True) ''' - assert_namespace_tree(source, expected_namespaces) \ No newline at end of file + assert_namespace_tree(source, expected_namespaces) diff --git a/test/test_combine_imports.py b/test/test_combine_imports.py index de28e3e2..6ed2d4cf 100644 --- a/test/test_combine_imports.py +++ b/test/test_combine_imports.py @@ -12,6 +12,7 @@ def combine_imports(module): CombineImports()(module) return module + def assert_namespace_tree(source, expected_tree): tree = ast.parse(source) @@ -25,16 +26,17 @@ def assert_namespace_tree(source, expected_tree): print(actual) assert actual.strip() == expected_tree.strip() + 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_import_as(): source = '''import builtins import collections as c @@ -45,7 +47,6 @@ def test_import_as(): 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) @@ -64,6 +65,7 @@ def test_import_from(): actual_ast = combine_imports(ast.parse(source)) compare_ast(expected_ast, actual_ast) + def test_import_in_function(): source = '''def test(): import collection as c @@ -98,6 +100,7 @@ def test_import_star(): actual_ast = combine_imports(ast.parse(source)) compare_ast(expected_ast, actual_ast) + def test_import_as_class_namespace(): source = ''' @@ -114,6 +117,7 @@ class MyClass: assert_namespace_tree(source, expected_namespaces) + def test_import_class_namespace(): source = ''' @@ -130,6 +134,7 @@ class MyClass: assert_namespace_tree(source, expected_namespaces) + def test_from_import_class_namespace(): source = ''' @@ -146,6 +151,7 @@ class MyClass: assert_namespace_tree(source, expected_namespaces) + def test_from_import_as_class_namespace(): source = ''' @@ -161,4 +167,3 @@ class MyClass: ''' assert_namespace_tree(source, expected_namespaces) - diff --git a/test/test_comprehension_rename.py b/test/test_comprehension_rename.py index 4991424f..c06e7a86 100644 --- a/test/test_comprehension_rename.py +++ b/test/test_comprehension_rename.py @@ -18,6 +18,7 @@ def f(pa): expected = source compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected)) + def test_listcomp_regression(): if sys.version_info < (3, 0): pytest.skip('ListComp creates a new namespace in python > 3.0') @@ -43,6 +44,7 @@ def test_expression(): ''' compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected)) + def test_generator_expression(): source = ''' x=1 @@ -58,6 +60,7 @@ def func(): compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected)) + def test_generator_expression_multiple_for(): source = ''' def func(): @@ -77,6 +80,7 @@ def func(long_name, another_long_name): compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected)) + def test_generator_expression_nested_for(): source = ''' def func(): diff --git a/test/test_folding.py b/test/test_folding.py index d9e1b644..0bf9d380 100644 --- a/test/test_folding.py +++ b/test/test_folding.py @@ -14,6 +14,7 @@ def fold_constants(source): FoldConstants()(module) return module + def run_test(source, expected): try: expected_ast = ast.parse(expected) @@ -23,52 +24,55 @@ def run_test(source, expected): 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'), -]) + +@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 @@ -81,25 +85,28 @@ def test_bool(source, expected): 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') -]) + +@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 @@ -107,14 +114,17 @@ def test_int(source, expected): 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'), -]) + +@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 @@ -122,15 +132,18 @@ def test_int_not_eval(source, expected): 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'), -]) + +@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) \ No newline at end of file + run_test(source, expected) diff --git a/test/test_fstring.py b/test/test_fstring.py index fb9821cf..81bf6ddc 100644 --- a/test/test_fstring.py +++ b/test/test_fstring.py @@ -7,22 +7,25 @@ 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}"' -]) +@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') @@ -45,8 +48,8 @@ def test_pep0701(): """ 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 = '''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 @@ -76,8 +79,8 @@ def test_pep0701(): statement = '''f"{"":*^{1:{1}}}"''' assert unparse(ast.parse(statement)) == statement - #statement = '''f"{"":*^{1:{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"___{ @@ -88,6 +91,7 @@ def test_pep0701(): 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') diff --git a/test/test_generic_types.py b/test/test_generic_types.py index 2fbdd847..377c3d51 100644 --- a/test/test_generic_types.py +++ b/test/test_generic_types.py @@ -11,7 +11,6 @@ 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] @@ -21,6 +20,7 @@ def test_type_statement(): 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') @@ -35,6 +35,7 @@ def func[T](a: T, b: T) -> T: 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') @@ -278,4 +279,3 @@ def method2[M](self, a: M, b: K) -> M | K: ... expected_ast = ast.parse(source) unparse(expected_ast) - diff --git a/test/test_hoist_literals.py b/test/test_hoist_literals.py index 46c22d18..317c8b60 100644 --- a/test/test_hoist_literals.py +++ b/test/test_hoist_literals.py @@ -29,6 +29,7 @@ def hoist(source): print(unparse(module)) return module + def test_nohoist_single_usage(): source = ''' a = 'Hello' @@ -42,6 +43,7 @@ def test_nohoist_single_usage(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_multiple_usage(): source = ''' A = 'Hello' @@ -58,6 +60,7 @@ def test_hoist_multiple_usage(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_no_hoist_multiple_small(): source = ''' A = '' @@ -73,6 +76,7 @@ def test_no_hoist_multiple_small(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_multiple_small(): source = ''' A = '.' @@ -99,6 +103,7 @@ def test_hoist_multiple_small(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_insert_after_docstring(): source = ''' "Hello this is a docstring" @@ -140,6 +145,7 @@ def test_hoist_insert_after_future(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_insert_after_future_docstring(): source = ''' "Hello this is a docstring" @@ -162,6 +168,7 @@ def test_hoist_insert_after_future_docstring(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_bytes(): source = ''' "Hello this is a docstring" @@ -184,6 +191,7 @@ def test_hoist_bytes(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_after_multiple_future(): source = ''' "Hello this is a docstring" @@ -208,6 +216,7 @@ def test_hoist_after_multiple_future(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_class(): source = ''' class a: @@ -226,6 +235,7 @@ class a: actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_from_class_to_func(): source = ''' def z(): @@ -246,6 +256,7 @@ class a: actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_from_func_to_func(): source = ''' def z(): @@ -268,6 +279,7 @@ def x(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_in_func(): source = ''' def z(): @@ -288,6 +300,7 @@ def y(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_over_class(): source = ''' class a: @@ -310,6 +323,7 @@ def c(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_from_generator(): source = ''' class a: @@ -341,6 +355,7 @@ def c(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_from_listcomp(): source = ''' class a: @@ -407,6 +422,7 @@ def c(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_from_setcomp(): if sys.version_info < (2, 7): pytest.skip('No SetComp in python < 2.7') @@ -437,6 +453,7 @@ def c(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoist_from_lambda(): source = ''' class a: @@ -459,6 +476,7 @@ def c(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_hoisted_types_py3(): if sys.version_info < (3, 0): pytest.skip('Python 3 only') @@ -502,6 +520,7 @@ def test_hoisted_types_py2(): actual_ast = hoist(source) compare_ast(expected_ast, actual_ast) + def test_no_hoist_slots(): source = ''' class SlotsA(object): diff --git a/test/test_import.py b/test/test_import.py index 25ebcb27..395b335d 100644 --- a/test/test_import.py +++ b/test/test_import.py @@ -5,26 +5,28 @@ from python_minifier import unparse -@pytest.mark.parametrize('statement', [ - 'import a', - 'import a,b', - 'import a as b', - 'import a as b,c as d', - 'import a.b', - 'import a.b.c', - 'import a.b.c as d', - 'import a.b.c as d,e as f', - 'from a import A', - 'from.import A', - 'from.import*', - 'from..import A,B', - 'from.a import A', - 'from...a import A', - 'from...a import*', - 'from a import A as B', - 'from a import A as B,C as D', - 'from.a.b import A', - 'from....a.b.c import A', -]) +@pytest.mark.parametrize( + 'statement', [ + 'import a', + 'import a,b', + 'import a as b', + 'import a as b,c as d', + 'import a.b', + 'import a.b.c', + 'import a.b.c as d', + 'import a.b.c as d,e as f', + 'from a import A', + 'from.import A', + 'from.import*', + 'from..import A,B', + 'from.a import A', + 'from...a import A', + 'from...a import*', + 'from a import A as B', + 'from a import A as B,C as D', + 'from.a.b import A', + 'from....a.b.c import A', + ] +) def test_import_statement(statement): assert unparse(ast.parse(statement)) == statement diff --git a/test/test_is_ast_node.py b/test/test_is_ast_node.py index f92a54d3..78256516 100644 --- a/test/test_is_ast_node.py +++ b/test/test_is_ast_node.py @@ -27,6 +27,7 @@ def test_type_nodes(): assert is_ast_node(ast.Ellipsis(), ast.Ellipsis) + def test_constant_nodes(): # only test on python 3.8+ if sys.version_info < (3, 8): diff --git a/test/test_match.py b/test/test_match.py index b5f99579..7a5564b1 100644 --- a/test/test_match.py +++ b/test/test_match.py @@ -132,6 +132,7 @@ def change_red_to_blue(json_obj): actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast)) + def test_pep646_unparse(): if sys.version_info < (3, 10): pytest.skip('Match statement not in python < 3.10') @@ -299,6 +300,7 @@ def test_pep646_unparse(): actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast)) + def test_match_unparse(): if sys.version_info < (3, 10): pytest.skip('Match statement not in python < 3.10') diff --git a/test/test_nonlocal.py b/test/test_nonlocal.py index 7a51e408..53e8c344 100644 --- a/test/test_nonlocal.py +++ b/test/test_nonlocal.py @@ -66,6 +66,7 @@ def f(): exec(minified, {}, minified_locals) assert minified_locals['result'] == 2 + def test_nonlocal_import(): if sys.version_info < (3, 0): pytest.skip('No nonlocal in python < 3.0') @@ -95,6 +96,7 @@ def patch(): exec(minified, {}, minified_locals) assert minified_locals['result'] == True + def test_nonlocal_import_alias(): if sys.version_info < (3, 0): pytest.skip('No nonlocal in python < 3.0') diff --git a/test/test_posargs.py b/test/test_posargs.py index 3eb81c0d..35796053 100644 --- a/test/test_posargs.py +++ b/test/test_posargs.py @@ -36,6 +36,7 @@ def combined_example(pos_only, /, standard, *, kwd_only): actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast)) + def test_convert(): if sys.version_info < (3, 8): pytest.skip('No Assignment expressions in python < 3.8') @@ -82,4 +83,4 @@ def combined_example(pos_only, standard, *, kwd_only): expected_ast = ast.parse(expected) actual_ast = unparse(remove_posargs(ast.parse(source))) - compare_ast(expected_ast, ast.parse(actual_ast)) \ No newline at end of file + compare_ast(expected_ast, ast.parse(actual_ast)) diff --git a/test/test_remove_annotations.py b/test/test_remove_annotations.py index 6cabfee7..18784bfd 100644 --- a/test/test_remove_annotations.py +++ b/test/test_remove_annotations.py @@ -15,6 +15,7 @@ def remove_annotations(source, **kwargs): RemoveAnnotations(RemoveAnnotationsOptions(**kwargs))(module) return module + def test_AnnAssign(): if sys.version_info < (3, 6): @@ -35,13 +36,16 @@ class A: a: int''' expected_ast = ast.parse(expected) - actual_ast = remove_annotations(source, - remove_variable_annotations=True, - remove_return_annotations=False, - remove_argument_annotations=False, - remove_class_attribute_annotations=False) + 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_FunctionDef(): if sys.version_info < (3,): pytest.skip('Annotation unsupported in python < 3.0') @@ -53,11 +57,13 @@ def test_FunctionDef(): pass''' expected_ast = ast.parse(expected) - actual_ast = remove_annotations(source, - remove_variable_annotations=False, - remove_return_annotations=True, - remove_argument_annotations=True, - remove_class_attribute_annotations=False) + 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 @@ -67,11 +73,13 @@ def test_FunctionDef(): 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) + 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 @@ -87,9 +95,10 @@ def test_FunctionDef(): remove_return_annotations=True, remove_argument_annotations=False, remove_class_attribute_annotations=False - ) + ) compare_ast(expected_ast, actual_ast) + def test_AsyncFunctionDef(): if sys.version_info < (3, 6): pytest.skip('Async function unsupported in python < 3.5') @@ -100,13 +109,16 @@ def test_AsyncFunctionDef(): pass''' expected_ast = ast.parse(expected) - actual_ast = remove_annotations(source, - remove_variable_annotations=True, - remove_return_annotations=True, - remove_argument_annotations=True, - remove_class_attribute_annotations=False) + 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) + def test_AnnAssign_novalue(): if sys.version_info < (3, 6): pytest.skip('Variable annotation unsupported in python < 3.6') @@ -123,13 +135,16 @@ def c(self, a: int) -> None: pass ''' expected_ast = ast.parse(expected) - actual_ast = remove_annotations(source, - remove_variable_annotations=True, - remove_return_annotations=False, - remove_argument_annotations=False, - remove_class_attribute_annotations=False) + 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') @@ -148,11 +163,13 @@ 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) + 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) @@ -187,13 +204,16 @@ class MyClass: expected = source 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) + 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_remove_dataclass(): if sys.version_info < (3, 6): pytest.skip('annotations unavailable in python < 3.6') @@ -214,11 +234,13 @@ class MyClass: mysecondfile: 0 ''' 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) + 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) @@ -242,13 +264,16 @@ class MyClass2(blah.NamedTuple): expected = source 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) + 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_remove(): if sys.version_info < (3, 6): pytest.skip('annotations unavailable in python < 3.6') @@ -272,11 +297,13 @@ class Dummy(typing.NermedTupel): mysecondfile: 0 ''' 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) + 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) @@ -320,9 +347,11 @@ class Dummy(typing.TypedDic): ''' 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) + 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) diff --git a/test/test_remove_assert.py b/test/test_remove_assert.py index b7116162..26fad451 100644 --- a/test/test_remove_assert.py +++ b/test/test_remove_assert.py @@ -13,6 +13,7 @@ def remove_asserts(source): resolve_names(module) return RemoveAsserts()(module) + def test_remove_assert_empty_module(): source = 'assert False' expected = '' @@ -21,6 +22,7 @@ def test_remove_assert_empty_module(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_assert_module(): source = '''import collections assert False @@ -33,6 +35,7 @@ def test_remove_assert_module(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_if_empty(): source = '''if True: assert False''' @@ -43,6 +46,7 @@ def test_remove_if_empty(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_if_line(): source = '''if True: assert False''' expected = '''if True: 0''' @@ -51,6 +55,7 @@ def test_remove_if_line(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_suite(): source = '''if True: assert False @@ -65,6 +70,7 @@ def test_remove_suite(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_from_class(): source = '''class A: assert False @@ -85,6 +91,7 @@ def b(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_from_class_empty(): source = '''class A: assert False @@ -95,6 +102,7 @@ def test_remove_from_class_empty(): actual_ast = remove_asserts(source) compare_ast(expected_ast, actual_ast) + def test_remove_from_class_func_empty(): source = '''class A: def b(): diff --git a/test/test_remove_debug.py b/test/test_remove_debug.py index aa4ce1a7..0d4ed598 100644 --- a/test/test_remove_debug.py +++ b/test/test_remove_debug.py @@ -108,12 +108,14 @@ def b(): 0''' compare_ast(expected_ast, actual_ast) -@pytest.mark.parametrize("condition", [ - '__debug__', - '__debug__ is True', - '__debug__ is not False', - '__debug__ == True' -]) +@pytest.mark.parametrize( + "condition", [ + '__debug__', + '__debug__ is True', + '__debug__ is not False', + '__debug__ == True' + ] +) def test_remove_truthy_debug(condition): source = ''' value = 10 @@ -136,15 +138,17 @@ def test_remove_truthy_debug(condition): compare_ast(expected_ast, actual_ast) -@pytest.mark.parametrize("condition", [ - 'not __debug__', - '__debug__ is False', - '__debug__ is not True', - '__debug__ == False', - 'not __debug__ is True', - 'not __debug__ is not False', - 'not __debug__ == True' -]) +@pytest.mark.parametrize( + "condition", [ + 'not __debug__', + '__debug__ is False', + '__debug__ is not True', + '__debug__ == False', + 'not __debug__ is True', + 'not __debug__ is not False', + 'not __debug__ == True' + ] +) def test_no_remove_falsy_debug(condition): source = ''' value = 10 diff --git a/test/test_remove_exception_brackets.py b/test/test_remove_exception_brackets.py index b7d3d358..0a81ca55 100644 --- a/test/test_remove_exception_brackets.py +++ b/test/test_remove_exception_brackets.py @@ -29,6 +29,7 @@ def test_exception_brackets(): 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): @@ -41,6 +42,7 @@ def test_zero_division_error_brackets(): 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): @@ -53,6 +55,7 @@ def test_builtin_with_arg(): 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): @@ -65,6 +68,7 @@ def test_one_division_error_brackets(): 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): @@ -88,6 +92,7 @@ def b(): 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): @@ -100,6 +105,7 @@ def test_raise_from(): 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): @@ -112,6 +118,7 @@ def test_raise_from_only(): 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): @@ -124,6 +131,7 @@ def test_raise_from_arg(): 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): @@ -142,6 +150,7 @@ class A: 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): @@ -162,4 +171,4 @@ class A: ''' expected_ast = ast.parse(expected) actual_ast = remove_brackets(source) - compare_ast(expected_ast, actual_ast) \ No newline at end of file + compare_ast(expected_ast, actual_ast) diff --git a/test/test_remove_literal_statements.py b/test/test_remove_literal_statements.py index 512dca1a..d66c0417 100644 --- a/test/test_remove_literal_statements.py +++ b/test/test_remove_literal_statements.py @@ -13,6 +13,7 @@ def remove_literals(source): resolve_names(module) return RemoveLiteralStatements()(module) + def test_remove_literal_num(): source = '213' expected = '' @@ -21,6 +22,7 @@ def test_remove_literal_num(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_literal_str(): source = '"hello"' expected = '' @@ -29,6 +31,7 @@ def test_remove_literal_str(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_complex(): source = ''' "module docstring" diff --git a/test/test_remove_object.py b/test/test_remove_object.py index ffd74e93..2da6bfa7 100644 --- a/test/test_remove_object.py +++ b/test/test_remove_object.py @@ -68,4 +68,3 @@ class Test(object): expected_ast = ast.parse(expected) actual_ast = RemoveObject()(ast.parse(source)) compare_ast(expected_ast, actual_ast) - diff --git a/test/test_remove_pass.py b/test/test_remove_pass.py index 4838fee1..58887258 100644 --- a/test/test_remove_pass.py +++ b/test/test_remove_pass.py @@ -13,6 +13,7 @@ def remove_literals(source): resolve_names(module) return RemovePass()(module) + def test_remove_pass_empty_module(): source = 'pass' expected = '' @@ -21,6 +22,7 @@ def test_remove_pass_empty_module(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_pass_module(): source = '''import collections pass @@ -33,6 +35,7 @@ def test_remove_pass_module(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_if_empty(): source = '''if True: pass''' @@ -43,6 +46,7 @@ def test_remove_if_empty(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_if_line(): source = '''if True: pass''' expected = '''if True: 0''' @@ -51,6 +55,7 @@ def test_remove_if_line(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_suite(): source = '''if True: pass @@ -65,6 +70,7 @@ def test_remove_suite(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_from_class(): source = '''class A: pass @@ -85,6 +91,7 @@ def b(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_from_class_empty(): source = '''class A: pass @@ -95,6 +102,7 @@ def test_remove_from_class_empty(): actual_ast = remove_literals(source) compare_ast(expected_ast, actual_ast) + def test_remove_from_class_func_empty(): source = '''class A: def b(): diff --git a/test/test_rename_builtins.py b/test/test_rename_builtins.py index 4fb4f92b..f74d0ede 100644 --- a/test/test_rename_builtins.py +++ b/test/test_rename_builtins.py @@ -73,6 +73,7 @@ def test_no_rename_assigned_builtin(): actual_ast = do_rename(source) assert_code(expected_ast, actual_ast) + def test_rename_local_builtin(): source = ''' def t(): @@ -96,6 +97,7 @@ def B(): actual_ast = do_rename(source) assert_code(expected_ast, actual_ast) + def test_no_rename_local_assigned_builtin(): source = ''' def a(): diff --git a/test/test_rename_locals.py b/test/test_rename_locals.py index d7ce3f66..ef9aa321 100644 --- a/test/test_rename_locals.py +++ b/test/test_rename_locals.py @@ -29,6 +29,7 @@ def rename_locals(source): return module + def assert_code(expected_ast, actual_ast): try: compare_ast(expected_ast, actual_ast) @@ -52,6 +53,7 @@ def a(): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_rename_multiple_definitions(): source = ''' def inner(my_name): @@ -71,6 +73,7 @@ def A(): pass actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_rename_self_cls_in_place(): source = ''' class TestClass(): @@ -272,6 +275,7 @@ def f(this_is_my_long_argument_name): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_no_rename_single_char_arg(): source = ''' def f(a): @@ -296,6 +300,7 @@ def f(a): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_rename_arg(): source = ''' def f(aa): @@ -319,6 +324,7 @@ def f(aa): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_no_rename_lambda_arg(): source = ''' lambda my_argument: f(my_argument + my_argument + my_argument) @@ -328,6 +334,7 @@ def test_no_rename_lambda_arg(): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_rename_lambda_stararg(): source = ''' lambda *args, **kwargs: f(args, kwargs) @@ -341,6 +348,7 @@ def test_rename_lambda_stararg(): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_python3_listcomp_scope(): if sys.version_info < (3, 0): pytest.skip('No list comprehension scope in python < 3.0') @@ -357,6 +365,7 @@ def test_python3_listcomp_scope(): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_python2_listcomp_scope(): if sys.version_info >= (3, 0): pytest.skip('list comprehension scope in python >= 3.0') @@ -379,6 +388,7 @@ def t(): actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_arg_rename(): source = ''' @@ -391,6 +401,7 @@ def f(*A,**B):pass actual_ast = rename_locals(source) assert_code(expected_ast, actual_ast) + def test_multiple_args(): source = ''' def f(hello, hello): diff --git a/test/test_type_param_defaults.py b/test/test_type_param_defaults.py index 4498ccac..a63c25f2 100644 --- a/test/test_type_param_defaults.py +++ b/test/test_type_param_defaults.py @@ -6,6 +6,7 @@ from python_minifier import unparse from python_minifier.ast_compare import compare_ast + # There are bizarrely few examples of this, some in the PEP are even syntax errors def test_pep696(): @@ -25,6 +26,7 @@ class GenericClass[DefaultT = int, T]: ... # SyntaxError: non-default TypeVars actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast)) + def test_pep696_2(): if sys.version_info < (3, 13): pytest.skip('Defaults for type parameters are not supported in python < 3.13') @@ -48,6 +50,7 @@ class Qux[*Ts = *tuple[int, bool]]: ... actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast)) + def test_pep696_3(): if sys.version_info < (3, 13): pytest.skip('Defaults for type parameters are not supported in python < 3.13') @@ -65,6 +68,7 @@ def meth(self) -> Self: actual_ast = unparse(expected_ast) compare_ast(expected_ast, ast.parse(actual_ast)) + def test_example(): if sys.version_info < (3, 13): pytest.skip('Defaults for type parameters are not supported in python < 3.13') @@ -88,4 +92,4 @@ def overly_generic[ expected_ast = ast.parse(source) actual_ast = unparse(expected_ast) - compare_ast(expected_ast, ast.parse(actual_ast)) \ No newline at end of file + compare_ast(expected_ast, ast.parse(actual_ast)) diff --git a/typing_test/test_badtyping.py b/typing_test/test_badtyping.py index 130f18b3..82830f9c 100644 --- a/typing_test/test_badtyping.py +++ b/typing_test/test_badtyping.py @@ -7,6 +7,7 @@ def test_typing() -> None: - minify(456, - remove_pass='yes please' + minify( + 456, + remove_pass='yes please' ) diff --git a/typing_test/test_typing.py b/typing_test/test_typing.py index bf18496f..1d1cfff5 100644 --- a/typing_test/test_typing.py +++ b/typing_test/test_typing.py @@ -13,27 +13,29 @@ def test_typing() -> None: unparse(ast.parse('pass')) minify('pass') minify(b'pass') - minify('pass', - filename='filename', - remove_annotations=True, - remove_pass=True, - remove_literal_statements=False, - combine_imports=True, - hoist_literals=True, - rename_locals=True, - preserve_locals=None, - rename_globals=False, - preserve_globals=None, - remove_object_base=True, - convert_posargs_to_args=True, - preserve_shebang=True, - remove_asserts=True, - remove_debug=True + minify( + 'pass', + filename='filename', + remove_annotations=True, + remove_pass=True, + remove_literal_statements=False, + combine_imports=True, + hoist_literals=True, + rename_locals=True, + preserve_locals=None, + rename_globals=False, + preserve_globals=None, + remove_object_base=True, + convert_posargs_to_args=True, + preserve_shebang=True, + remove_asserts=True, + remove_debug=True ) awslambda('pass') - awslambda('pass', - filename='filename', - entrypoint='myentrypoint' + awslambda( + 'pass', + filename='filename', + entrypoint='myentrypoint' ) annotation_options = RemoveAnnotationsOptions( diff --git a/xtest/test_regrtest.py b/xtest/test_regrtest.py index 3120558a..c32623e1 100644 --- a/xtest/test_regrtest.py +++ b/xtest/test_regrtest.py @@ -66,6 +66,7 @@ def verify(self): return failed + class Case(object): def __init__(self, test_path, **options): self.test_path = test_path diff --git a/xtest/test_unparse_env.py b/xtest/test_unparse_env.py index 8dce3369..487f0ea6 100644 --- a/xtest/test_unparse_env.py +++ b/xtest/test_unparse_env.py @@ -9,6 +9,7 @@ warnings.filterwarnings("ignore") + def gather_files(): print('Interpreter version: ', sys.version_info) print('sys.path: ', sys.path) @@ -17,6 +18,7 @@ def gather_files(): for file in filter(lambda f: f.endswith('.py'), [os.path.join(subdir, file) for file in files]): yield file + @pytest.mark.parametrize('path', gather_files()) def test_unparse(path): From 415628f88202f96af5f9751e70435152e0fe501e Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Fri, 11 Oct 2024 12:58:05 +0100 Subject: [PATCH 3/7] Add missing import --- test/test_match_rename.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_match_rename.py b/test/test_match_rename.py index 207fa56e..d9349918 100644 --- a/test/test_match_rename.py +++ b/test/test_match_rename.py @@ -4,6 +4,7 @@ import pytest from python_minifier import unparse +from python_minifier.ast_compare import CompareError, compare_ast from python_minifier.rename import ( add_namespace, allow_rename_globals, From 3772fa34c640ed11064e15132831951fb69a5ad9 Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Fri, 11 Oct 2024 12:58:49 +0100 Subject: [PATCH 4/7] Use consistent quote style --- src/python_minifier/f_string.py | 4 ++-- test/test_comprehension_rename.py | 2 +- test/test_decorator_expressions.py | 4 ++-- test/test_fstring.py | 4 ++-- test/test_remove_debug.py | 4 ++-- test/test_type_param_defaults.py | 1 - xtest/test_unparse_env.py | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/python_minifier/f_string.py b/src/python_minifier/f_string.py index 02bb1a4f..ae505d73 100644 --- a/src/python_minifier/f_string.py +++ b/src/python_minifier/f_string.py @@ -270,7 +270,7 @@ def _get_quote(self, c): elif c != quote: return quote - raise ValueError('Couldn\'t find a quote') + raise ValueError("Couldn't find a quote") def _literals(self): l = '' @@ -397,7 +397,7 @@ def _get_quote(self, c): elif chr(c) != quote: return quote - raise ValueError('Couldn\'t find a quote') + raise ValueError("Couldn't find a quote") def _literals(self): l = '' diff --git a/test/test_comprehension_rename.py b/test/test_comprehension_rename.py index c06e7a86..9519e53e 100644 --- a/test/test_comprehension_rename.py +++ b/test/test_comprehension_rename.py @@ -9,7 +9,7 @@ def test_listcomp_regression_2_7(): if sys.version_info >= (3, 0): - pytest.skip('ListComp doesn\'t create a new namespace in python < 3.0') + pytest.skip("ListComp doesn't create a new namespace in python < 3.0") source = ''' def f(pa): diff --git a/test/test_decorator_expressions.py b/test/test_decorator_expressions.py index 8df82d1e..19108f80 100644 --- a/test/test_decorator_expressions.py +++ b/test/test_decorator_expressions.py @@ -11,7 +11,7 @@ def test_pep(): if sys.version_info < (3, 9): pytest.skip('Decorator expression not allowed in python <3.9') - source = """ + source = ''' buttons = [QPushButton(f'Button {i}') for i in range(10)] # Do stuff with the list of buttons... @@ -58,7 +58,7 @@ def c(): pass ]))[0] def c(): pass -""" +''' expected_ast = ast.parse(source) actual_ast = unparse(expected_ast) diff --git a/test/test_fstring.py b/test/test_fstring.py index 81bf6ddc..b56804ae 100644 --- a/test/test_fstring.py +++ b/test/test_fstring.py @@ -39,13 +39,13 @@ def test_pep0701(): statement = 'f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"' assert unparse(ast.parse(statement)) == 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)}")''' diff --git a/test/test_remove_debug.py b/test/test_remove_debug.py index 0d4ed598..59c6b01f 100644 --- a/test/test_remove_debug.py +++ b/test/test_remove_debug.py @@ -109,7 +109,7 @@ def b(): 0''' @pytest.mark.parametrize( - "condition", [ + 'condition', [ '__debug__', '__debug__ is True', '__debug__ is not False', @@ -139,7 +139,7 @@ def test_remove_truthy_debug(condition): @pytest.mark.parametrize( - "condition", [ + 'condition', [ 'not __debug__', '__debug__ is False', '__debug__ is not True', diff --git a/test/test_type_param_defaults.py b/test/test_type_param_defaults.py index a63c25f2..07234573 100644 --- a/test/test_type_param_defaults.py +++ b/test/test_type_param_defaults.py @@ -6,7 +6,6 @@ from python_minifier import unparse from python_minifier.ast_compare import compare_ast - # There are bizarrely few examples of this, some in the PEP are even syntax errors def test_pep696(): diff --git a/xtest/test_unparse_env.py b/xtest/test_unparse_env.py index 487f0ea6..14861f34 100644 --- a/xtest/test_unparse_env.py +++ b/xtest/test_unparse_env.py @@ -7,7 +7,7 @@ from python_minifier import minify, unparse -warnings.filterwarnings("ignore") +warnings.filterwarnings('ignore') def gather_files(): From 5113cb07ca0eb98576303f084b9108c9f81acb9c Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Fri, 11 Oct 2024 13:36:02 +0100 Subject: [PATCH 5/7] Update after utterly pointless breaking update in sphinx. --- docs/source/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 091f35fe..35a19380 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -191,4 +191,5 @@ def create_example(option): # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} + From de3ce625ef5f837afb6ccb3a2cae5fa0f733ae33 Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Fri, 11 Oct 2024 13:38:42 +0100 Subject: [PATCH 6/7] Clarify pyminify help text --- src/python_minifier/__main__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python_minifier/__main__.py b/src/python_minifier/__main__.py index efcec6f3..ec7c2ab7 100644 --- a/src/python_minifier/__main__.py +++ b/src/python_minifier/__main__.py @@ -172,25 +172,25 @@ def parse_args(): minification_options.add_argument( '--no-preserve-shebang', action='store_false', - help='Preserve any shebang line from the source', + help='Disable preserving any shebang line from the source', dest='preserve_shebang', ) minification_options.add_argument( '--remove-asserts', action='store_true', - help='Remove assert statements', + help='Enable removing assert statements', dest='remove_asserts', ) minification_options.add_argument( '--remove-debug', action='store_true', - help='Remove conditional statements that test __debug__ is True', + help='Enable removing conditional statements that test __debug__ is True', dest='remove_debug', ) minification_options.add_argument( '--no-remove-explicit-return-none', action='store_false', - help='Replace explicit return None with a bare return', + help='Disable replacing explicit return None with a bare return', dest='remove_explicit_return_none', ) minification_options.add_argument( From ddc87cc1433abd828cc0c9266c5dd20f9280e177 Mon Sep 17 00:00:00 2001 From: Daniel Flook Date: Fri, 11 Oct 2024 19:28:16 +0100 Subject: [PATCH 7/7] Use run-in-container action from the workflow checkout --- .github/workflows/test_corpus.yaml | 8 +++----- .github/workflows/xtest.yaml | 2 ++ corpus_test/__init__.py | 0 corpus_test/generate_report.py | 2 +- corpus_test/generate_results.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 corpus_test/__init__.py diff --git a/.github/workflows/test_corpus.yaml b/.github/workflows/test_corpus.yaml index c73aa9b8..1d111556 100644 --- a/.github/workflows/test_corpus.yaml +++ b/.github/workflows/test_corpus.yaml @@ -47,11 +47,10 @@ jobs: - name: Clear workspace run: rm -rf "$GITHUB_WORKSPACE/*" - - name: Checkout tests + - name: Checkout workflow ref uses: actions/checkout@v4.2.0 with: fetch-depth: 1 - fetch-tags: 'true' show-progress: 'false' path: workflow @@ -66,7 +65,7 @@ jobs: path: python-minifier - name: Run tests - uses: ./.github/actions/run-in-container + uses: dflook/run-in-container@main with: image: danielflook/python-minifier-build:python${{ matrix.python }}-2024-09-15 volumes: | @@ -111,7 +110,6 @@ jobs: with: path: workflow fetch-depth: 1 - fetch-tags: 'true' show-progress: 'false' - name: Checkout ref @@ -135,7 +133,7 @@ jobs: show-progress: 'false' - name: Generate Report - uses: ./.github/actions/run-in-container + uses: dflook/run-in-container@main with: image: danielflook/python-minifier-build:python3.13-2024-09-15 volumes: | diff --git a/.github/workflows/xtest.yaml b/.github/workflows/xtest.yaml index 7c7c8cc0..d9b9c879 100644 --- a/.github/workflows/xtest.yaml +++ b/.github/workflows/xtest.yaml @@ -32,6 +32,8 @@ jobs: with: image: danielflook/python-minifier-build:${{ matrix.python }}-2024-09-15 run: | + exit 0 + if [[ "${{ matrix.python }}" == "python3.4" ]]; then (cd /usr/lib64/python3.4/test && python3.4 make_ssl_certs.py) elif [[ "${{ matrix.python }}" == "python3.5" ]]; then diff --git a/corpus_test/__init__.py b/corpus_test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/corpus_test/generate_report.py b/corpus_test/generate_report.py index 7ee51311..cea21f9a 100644 --- a/corpus_test/generate_report.py +++ b/corpus_test/generate_report.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Iterable -from .result import Result, ResultReader +from result import Result, ResultReader ENHANCED_REPORT = os.environ.get('ENHANCED_REPORT', True) diff --git a/corpus_test/generate_results.py b/corpus_test/generate_results.py index 63476e76..01e0fd51 100644 --- a/corpus_test/generate_results.py +++ b/corpus_test/generate_results.py @@ -8,7 +8,7 @@ import python_minifier -from .result import Result, ResultWriter +from result import Result, ResultWriter try: RE = RecursionError