openrewrite-migrate-python 0.17.0.dev20261024170610__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/PKG-INFO +2 -2
  2. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/pyproject.toml +2 -2
  3. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/collection_literals.py +15 -3
  4. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/complex_transforms.py +1 -3
  5. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/comprehension_cleanup.py +7 -12
  6. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/conditionals.py +11 -7
  7. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/dict_cleanup.py +1 -1
  8. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/expression_simplification.py +5 -12
  9. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/logger_warn.py +3 -3
  10. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/merge_startswith_endswith.py +0 -3
  11. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/range_cleanup.py +6 -7
  12. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/remove_trailing_return_none.py +2 -1
  13. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/string_cleanup.py +5 -6
  14. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/use_items.py +1 -3
  15. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/use_list_clear.py +0 -3
  16. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +8 -0
  17. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/_trivia.py +27 -14
  18. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/_truth_value.py +7 -2
  19. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +21 -0
  20. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +1 -1
  21. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +6 -2
  22. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +27 -5
  23. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +13 -5
  24. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +6 -0
  25. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/__init__.py +9 -2
  26. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_method_renames.py +19 -5
  27. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +64 -22
  28. openrewrite_migrate_python-0.18.0/src/openrewrite_migrate_python/migrate/array_deprecations.py +378 -0
  29. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +18 -8
  30. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/backoff_to_tenacity.py +1 -3
  31. openrewrite_migrate_python-0.18.0/src/openrewrite_migrate_python/migrate/builtins_deprecations.py +161 -0
  32. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/cgi_escape_deprecations.py +9 -6
  33. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +23 -9
  34. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +2 -2
  35. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +11 -1
  36. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/ctypes_deprecations.py +1 -1
  37. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/datetime_utc.py +9 -2
  38. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +8 -4
  39. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +4 -1
  40. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +5 -4
  41. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/future_imports.py +2 -3
  42. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +6 -2
  43. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/glob_deprecations.py +8 -3
  44. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/http_server_deprecations.py +4 -0
  45. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/imp_migrations.py +15 -8
  46. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/importlib_deprecations.py +15 -3
  47. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +26 -5
  48. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +5 -6
  49. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +4 -4
  50. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +10 -3
  51. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +9 -2
  52. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/os_deprecations.py +8 -8
  53. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +16 -12
  54. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +48 -13
  55. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +13 -6
  56. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +4 -0
  57. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/profile_deprecations.py +2 -2
  58. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +6 -4
  59. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +3 -0
  60. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +8 -4
  61. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +7 -7
  62. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +13 -4
  63. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/sre_migrations.py +3 -0
  64. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +8 -4
  65. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +11 -4
  66. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +6 -6
  67. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/text_encoding.py +1 -1
  68. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +6 -2
  69. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/types_deprecations.py +7 -3
  70. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/typing_callable.py +4 -0
  71. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +14 -0
  72. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/typing_functional_syntax.py +2 -2
  73. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +11 -3
  74. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_pandas.py +13 -0
  75. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +13 -0
  76. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +2 -1
  77. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +2 -1
  78. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +13 -14
  79. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/uu_migrations.py +11 -4
  80. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/wave_deprecations.py +6 -0
  81. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/webbrowser_deprecations.py +4 -0
  82. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +10 -3
  83. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +2 -1
  84. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python.egg-info/PKG-INFO +2 -2
  85. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +1 -0
  86. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python.egg-info/requires.txt +1 -1
  87. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_array_deprecations.py +113 -0
  88. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_backoff_to_tenacity.py +1 -23
  89. openrewrite_migrate_python-0.18.0/tests/test_builtins_deprecations.py +114 -0
  90. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_cgi_escape_deprecations.py +3 -1
  91. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_configparser_deprecations.py +2 -0
  92. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_datetime_utc.py +39 -0
  93. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_glob_deprecations.py +18 -0
  94. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_locale_deprecations.py +73 -0
  95. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pep594_migrations.py +22 -0
  96. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pydantic_deprecations.py +19 -4
  97. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pydantic_model_fields.py +33 -4
  98. openrewrite_migrate_python-0.18.0/tests/test_recipe_conventions.py +86 -0
  99. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_typing_deprecations.py +30 -0
  100. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_xml_deprecations.py +26 -0
  101. openrewrite_migrate_python-0.17.0.dev20261024170610/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -201
  102. openrewrite_migrate_python-0.17.0.dev20261024170610/src/openrewrite_migrate_python/migrate/builtins_deprecations.py +0 -89
  103. openrewrite_migrate_python-0.17.0.dev20261024170610/tests/test_builtins_deprecations.py +0 -47
  104. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/setup.cfg +0 -0
  105. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/__init__.py +0 -0
  106. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/__init__.py +0 -0
  107. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/_literals.py +0 -0
  108. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/_negation.py +0 -0
  109. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/assign_return.py +0 -0
  110. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/attr_access.py +0 -0
  111. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/boolean_simplification.py +0 -0
  112. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/comparison_simplification.py +0 -0
  113. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/datetime_cleanup.py +0 -0
  114. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/exception_cleanup.py +0 -0
  115. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/loop_cleanup.py +0 -0
  116. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/merge_nested_with.py +0 -0
  117. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/naming_conventions.py +0 -0
  118. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/negated_operators.py +0 -0
  119. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/none_and_identity.py +0 -0
  120. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/object_inheritance.py +0 -0
  121. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/pandas_cleanup.py +0 -0
  122. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/python_best_practices.py +0 -0
  123. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/redundant_code.py +0 -0
  124. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/unreachable_code.py +0 -0
  125. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/cleanup/yoda_conditions.py +0 -0
  126. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
  127. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
  128. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/_side_effects.py +0 -0
  129. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +0 -0
  130. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/code_quality.py +0 -0
  131. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
  132. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +0 -0
  133. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
  134. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/__init__.py +0 -0
  135. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_decorators.py +0 -0
  136. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_finders.py +0 -0
  137. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
  138. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  139. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_module_imports.py +0 -0
  140. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/_slots.py +0 -0
  141. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  142. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
  143. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
  144. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  145. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
  146. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
  147. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/importlib_resources_deprecations.py +0 -0
  148. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/inspect_deprecations.py +0 -0
  149. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  150. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  151. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  152. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/module_cached_attribute.py +0 -0
  153. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
  154. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
  155. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
  156. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
  157. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  158. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
  159. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
  160. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  161. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  162. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  163. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  164. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  165. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  166. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  167. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python315.py +0 -0
  168. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  169. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
  170. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  171. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  172. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  173. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_aifc_migrations.py +0 -0
  174. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_ast_deprecations.py +0 -0
  175. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_asyncio_coroutine_to_async.py +0 -0
  176. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_asyncio_deprecations.py +0 -0
  177. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_avro_migrations.py +0 -0
  178. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_calendar_deprecations.py +0 -0
  179. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_cgi_parse_deprecations.py +0 -0
  180. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_change_import_recipes.py +0 -0
  181. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_collections_abc_migrations.py +0 -0
  182. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_ctypes_deprecations.py +0 -0
  183. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_distutils_deprecations.py +0 -0
  184. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_distutils_migrations.py +0 -0
  185. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_functools_deprecations.py +0 -0
  186. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_future_imports.py +0 -0
  187. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_gettext_deprecations.py +0 -0
  188. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_html_parser_deprecations.py +0 -0
  189. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_http_server_deprecations.py +0 -0
  190. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_imp_migrations.py +0 -0
  191. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_importlib_deprecations.py +0 -0
  192. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_importlib_resources_deprecations.py +0 -0
  193. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_inspect_deprecations.py +0 -0
  194. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_langchain_classic_imports.py +0 -0
  195. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_langchain_community_imports.py +0 -0
  196. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_langchain_provider_imports.py +0 -0
  197. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  198. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_macpath_deprecations.py +0 -0
  199. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_mailcap_migrations.py +0 -0
  200. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_markers.py +0 -0
  201. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_module_cached_attribute.py +0 -0
  202. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_os_deprecations.py +0 -0
  203. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pathlib_deprecations.py +0 -0
  204. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pep594_system_migrations.py +0 -0
  205. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pkgutil_deprecations.py +0 -0
  206. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_platform_deprecations.py +0 -0
  207. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_profile_deprecations.py +0 -0
  208. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_pydantic_fields.py +0 -0
  209. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_re_deprecations.py +0 -0
  210. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_recipe_reachability.py +0 -0
  211. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_removed_modules_312.py +0 -0
  212. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_shutil_deprecations.py +0 -0
  213. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_socket_deprecations.py +0 -0
  214. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_sre_migrations.py +0 -0
  215. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_ssl_deprecations.py +0 -0
  216. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_string_formatting.py +0 -0
  217. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_sys_deprecations.py +0 -0
  218. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_sys_last_deprecations.py +0 -0
  219. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_tarfile_deprecations.py +0 -0
  220. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_tempfile_deprecations.py +0 -0
  221. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_text_encoding.py +0 -0
  222. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_threading_deprecations.py +0 -0
  223. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_threading_extended.py +0 -0
  224. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_threading_is_alive_deprecation.py +0 -0
  225. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_types_deprecations.py +0 -0
  226. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_typing_alias_moves.py +0 -0
  227. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_typing_callable.py +0 -0
  228. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_typing_functional_syntax.py +0 -0
  229. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_typing_union_to_pipe.py +0 -0
  230. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_unittest_deprecations.py +0 -0
  231. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_upgrade_recipes.py +0 -0
  232. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_upgrade_to_pydantic.py +0 -0
  233. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_urllib_deprecations.py +0 -0
  234. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_wave_deprecations.py +0 -0
  235. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_webbrowser_deprecations.py +0 -0
  236. {openrewrite_migrate_python-0.17.0.dev20261024170610 → openrewrite_migrate_python-0.18.0}/tests/test_xdrlib_migrations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.17.0.dev20261024170610
3
+ Version: 0.18.0
4
4
  Summary: OpenRewrite recipes for migrating Python codebases to newer Python versions.
5
5
  Author-email: "Moderne Inc." <support@moderne.io>
6
6
  License: Moderne Proprietary
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.12
20
20
  Description-Content-Type: text/markdown
21
21
  Provides-Extra: dev
22
- Requires-Dist: openrewrite>=8.93.11; extra == "dev"
22
+ Requires-Dist: openrewrite>=8.94.0; extra == "dev"
23
23
  Requires-Dist: pydantic>=2.11; extra == "dev"
24
24
  Requires-Dist: pytest>=8.0.0; extra == "dev"
25
25
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "openrewrite-migrate-python"
7
7
  description = "OpenRewrite recipes for migrating Python codebases to newer Python versions."
8
- version = "0.17.0.dev20261024170610" # Updated dynamically during release
8
+ version = "0.18.0" # Updated dynamically during release
9
9
  authors = [{ name = "Moderne Inc.", email = "support@moderne.io" }]
10
10
  license = { text = "Moderne Proprietary" }
11
11
  readme = "README.md"
@@ -28,7 +28,7 @@ dependencies = []
28
28
 
29
29
  [project.optional-dependencies]
30
30
  dev = [
31
- "openrewrite>=8.93.11",
31
+ "openrewrite>=8.94.0",
32
32
  # Needed so type-attribution tests can resolve `pydantic` types.
33
33
  "pydantic>=2.11",
34
34
  "pytest>=8.0.0",
@@ -31,6 +31,7 @@ from rewrite.python.tree import Binary as PyBinary, CollectionLiteral, Formatted
31
31
  from rewrite.java import JavaType, Space, TrailingComma
32
32
  from rewrite.java.tree import Empty, Expression, J, Literal, MethodInvocation
33
33
  from rewrite.utils import random_id
34
+ from openrewrite_code_quality_python.codequality._trivia import has_comments
34
35
 
35
36
  # Define category path: Python > Cleanup
36
37
  _Cleanup = [*Python, CategoryDescriptor(display_name="Cleanup")]
@@ -99,7 +100,7 @@ class DictLiteral(Recipe):
99
100
  if isinstance(self.cursor.parent_tree_cursor().value, FormattedString.Value):
100
101
  return visited
101
102
  match = _dict_pattern.match(visited, self.cursor)
102
- if match:
103
+ if match and not has_comments(visited.replace(_prefix=Space.EMPTY)):
103
104
  return _dict_template.apply(self.cursor, values=match) or visited
104
105
  return visited
105
106
 
@@ -149,7 +150,7 @@ class ListLiteral(Recipe):
149
150
  ) -> J:
150
151
  visited = super().visit_method_invocation(method, p)
151
152
  match = _list_pattern.match(visited, self.cursor)
152
- if match:
153
+ if match and not has_comments(visited.replace(_prefix=Space.EMPTY)):
153
154
  return _list_template.apply(self.cursor, values=match) or visited
154
155
  return visited
155
156
 
@@ -199,7 +200,7 @@ class TupleLiteral(Recipe):
199
200
  ) -> J:
200
201
  visited = super().visit_method_invocation(method, p)
201
202
  match = _tuple_pattern.match(visited, self.cursor)
202
- if match:
203
+ if match and not has_comments(visited.replace(_prefix=Space.EMPTY)):
203
204
  return _tuple_template.apply(self.cursor, values=match) or visited
204
205
  return visited
205
206
 
@@ -365,6 +366,17 @@ class UnwrapIterableConstruction(Recipe):
365
366
  # The argument must be a list or tuple literal
366
367
  if not isinstance(arg, CollectionLiteral):
367
368
  return method
369
+ # The literal replaces the call, so only the trivia inside the literal survives.
370
+ arguments = method.padding.arguments
371
+ padded_arg = arguments.padding.elements[0]
372
+ trailing_comma = padded_arg.markers.find_first(TrailingComma)
373
+ if any(
374
+ space.comments for space in (
375
+ method.name.prefix, arguments.before, arg.prefix, padded_arg.after,
376
+ trailing_comma.suffix if trailing_comma else Space.EMPTY,
377
+ )
378
+ ):
379
+ return method
368
380
  if arg.kind not in (CollectionLiteral.Kind.LIST, CollectionLiteral.Kind.TUPLE):
369
381
  return method
370
382
  if target_kind == CollectionLiteral.Kind.SET and all(
@@ -107,9 +107,7 @@ class TernaryToIfExpression(Recipe):
107
107
  and _is_always_truthy(match['x'])
108
108
  and not has_comments(visited.replace(prefix=Space.EMPTY))
109
109
  ):
110
- result = _ternary_template.apply(self.cursor, values=match)
111
- assert result is not None
112
- return result
110
+ return _ternary_template.apply(self.cursor, values=match) or visited
113
111
  return visited
114
112
 
115
113
  return Visitor()
@@ -32,7 +32,7 @@ from rewrite.python.preconditions import uses_method
32
32
  from rewrite.python.scope_utils import scope_of
33
33
  from rewrite.python.type_utils import is_of_class_type
34
34
  from rewrite.python.visitor import PythonVisitor
35
- from rewrite.python.tree import Await, ComprehensionExpression, ExpressionStatement
35
+ from rewrite.python.tree import Await, ComprehensionExpression
36
36
  from rewrite.python.template import template, capture
37
37
  from rewrite.python.template.pattern import MatchResult
38
38
  from rewrite.java.markers import OmitParentheses, TrailingComma
@@ -516,8 +516,7 @@ class IdentityComprehension(Recipe):
516
516
  if kind not in (ComprehensionExpression.Kind.LIST,
517
517
  ComprehensionExpression.Kind.SET):
518
518
  return comp
519
- # The visitor does not visit a comprehension's suffix, so has_comments misses it.
520
- if has_comments(comp.replace(_prefix=Space.EMPTY)) or comp.suffix.comments:
519
+ if has_comments(comp.replace(_prefix=Space.EMPTY)):
521
520
  return comp
522
521
 
523
522
  # Must have exactly one clause with no conditions
@@ -712,7 +711,7 @@ class InvertAnyAllBody(Recipe):
712
711
  return method
713
712
 
714
713
  gen = _get_single_generator(method)
715
- if gen is None:
714
+ if gen is None or has_comments(method.replace(_prefix=Space.EMPTY)):
716
715
  return method
717
716
 
718
717
  # Must have exactly one clause with no conditions
@@ -809,7 +808,7 @@ class ConvertAnyToIn(Recipe):
809
808
  return method
810
809
 
811
810
  gen = _get_single_generator(method)
812
- if gen is None:
811
+ if gen is None or has_comments(method.replace(_prefix=Space.EMPTY)):
813
812
  return method
814
813
 
815
814
  # Must have exactly one clause with no conditions (no if-filter)
@@ -908,7 +907,7 @@ class SimplifyConstantSum(Recipe):
908
907
  return method
909
908
 
910
909
  gen = _get_single_generator(method)
911
- if gen is None:
910
+ if gen is None or has_comments(method.replace(_prefix=Space.EMPTY)):
912
911
  return method
913
912
 
914
913
  # Must have exactly one clause with exactly one condition
@@ -930,14 +929,10 @@ class SimplifyConstantSum(Recipe):
930
929
  condition = clause.conditions[0]
931
930
  cond_expr = condition.expression
932
931
 
933
- # Build bool(cond) as the new result using template.
934
- # template.apply() returns an ExpressionStatement wrapper,
935
- # so extract the inner expression.
936
932
  match = MatchResult({'x': cond_expr})
937
- bool_stmt = _bool_template.apply(self.cursor, values=match)
938
- if not isinstance(bool_stmt, ExpressionStatement):
933
+ bool_call = _bool_template.apply(self.cursor, values=match)
934
+ if not isinstance(bool_call, MethodInvocation):
939
935
  return method
940
- bool_call = bool_stmt.expression
941
936
 
942
937
  # Build new generator: bool(cond) for x in items (no filter)
943
938
  new_clause = clause.replace(_conditions=[])
@@ -20,7 +20,6 @@ from rewrite.markers import Markers
20
20
  from rewrite.utils import random_id
21
21
  from rewrite.python.visitor import PythonVisitor
22
22
  from rewrite.python.template import template, capture
23
- from rewrite.python import ExpressionStatement
24
23
  from rewrite.python import tree as py_tree
25
24
  from rewrite.python.tree import CollectionLiteral, StatementExpression
26
25
  from rewrite.python.method_matcher import MethodMatcher
@@ -82,12 +81,14 @@ def _is_pass_only(block) -> bool:
82
81
  return len(stmts) == 1 and isinstance(stmts[0], py_tree.Pass)
83
82
 
84
83
 
85
- def _negate_condition(cond: Expression, cursor: Cursor) -> Expression:
84
+ def _negate_condition(cond: Expression, cursor: Cursor) -> Expression | None:
86
85
  """``cond`` negated, where ``cursor`` points at the ``if`` it is the condition of."""
87
86
  negated = negated_without_not(cond, cancel_not=True)
88
87
  if negated is None:
89
- negated = _not_template.apply(Cursor(cursor, cond), values={"c": cond})
90
- assert isinstance(negated, Expression)
88
+ at = Cursor(cursor, cond)
89
+ negated = _not_template.apply(at, values={"c": cond})
90
+ if not isinstance(negated, Expression):
91
+ return None
91
92
  return negated.replace(prefix=cond.prefix)
92
93
 
93
94
 
@@ -369,6 +370,8 @@ class SwapIfElseBranches(Recipe):
369
370
  # Negate condition
370
371
  cond = if_stmt.if_condition.tree
371
372
  new_cond = _negate_condition(cond, self.cursor)
373
+ if new_cond is None:
374
+ return if_stmt
372
375
 
373
376
  result = _set_condition(if_stmt, new_cond)
374
377
 
@@ -477,6 +480,8 @@ class RemovePassElif(Recipe):
477
480
  # Negate the elif condition
478
481
  elif_cond = elif_if.if_condition.tree
479
482
  new_cond = _negate_condition(elif_cond, self.cursor)
483
+ if new_cond is None:
484
+ return if_stmt
480
485
  new_elif = _set_condition(elif_if, new_cond)
481
486
 
482
487
  # Use else body as new elif body
@@ -805,9 +810,8 @@ class AssignIfExp(Recipe):
805
810
  "other": false_val,
806
811
  },
807
812
  )
808
- if isinstance(ternary, ExpressionStatement):
809
- ternary = ternary.expression
810
- assert isinstance(ternary, Expression)
813
+ if not isinstance(ternary, Expression):
814
+ return if_stmt
811
815
 
812
816
  # When the if is an elif (body of an Else node), its prefix
813
817
  # is empty — the indentation lives on the parent Else.
@@ -268,7 +268,7 @@ class SimplifyDictionaryUpdate(Recipe):
268
268
  return method
269
269
  if not all(
270
270
  is_side_effect_free(e) for e in (method.select, kv.key, kv.value)
271
- ) or has_comments(arg):
271
+ ) or has_comments(method.replace(_prefix=Space.EMPTY)):
272
272
  return method
273
273
 
274
274
  # Skip inside exception-testing contexts (pytest.raises,
@@ -30,7 +30,6 @@ from rewrite.java.tree import (
30
30
  Ternary,
31
31
  Unary,
32
32
  )
33
- from rewrite.python import ExpressionStatement
34
33
  from rewrite.python.method_matcher import MethodMatcher
35
34
  from rewrite.python.preconditions import uses_method
36
35
  from rewrite.python.tree import CompilationUnit, Slice
@@ -287,10 +286,8 @@ class RemoveRedundantSliceIndex(Recipe):
287
286
  or has_comments(array_access.replace(prefix=Space.EMPTY))
288
287
  ):
289
288
  return array_access
290
- # start must be literal 0
291
- if not isinstance(idx.start, Literal):
292
- return array_access
293
- if idx.start.value != 0:
289
+ # Only the int `0` is the default start.
290
+ if not isinstance(idx.start, Literal) or type(idx.start.value) is not int or idx.start.value != 0:
294
291
  return array_access
295
292
  # stop must be len(same_var)
296
293
  if idx.stop is None or not _is_len_of(idx.stop, array_access.indexed, self.cursor):
@@ -298,10 +295,7 @@ class RemoveRedundantSliceIndex(Recipe):
298
295
  # Replace with seq[:] via template
299
296
  result = _empty_slice_template.apply(
300
297
  self.cursor, values={"seq": array_access.indexed}
301
- )
302
- if isinstance(result, ExpressionStatement):
303
- result = result.expression
304
- assert result is not None
298
+ ) or array_access
305
299
  return result.replace(prefix=array_access.prefix)
306
300
 
307
301
  return Preconditions.check(uses_method(_LEN), Visitor())
@@ -367,9 +361,8 @@ class SimplifyNegativeIndex(Recipe):
367
361
  return array_access
368
362
  # Build -k using template
369
363
  neg_expr = _neg_template.apply(self.cursor, values={"k": k})
370
- if isinstance(neg_expr, ExpressionStatement):
371
- neg_expr = neg_expr.expression
372
- assert neg_expr is not None
364
+ if neg_expr is None:
365
+ return array_access
373
366
  neg_expr = neg_expr.replace(prefix=idx.prefix)
374
367
  # Replace the index inside the JRightPadded wrapper
375
368
  dim = array_access.dimension
@@ -1,10 +1,10 @@
1
1
  """
2
- Recipe for Ruff rule LOG009: undocumented-warn.
2
+ Recipe for Ruff rule G010: logging-warn.
3
3
 
4
4
  Replaces ``logger.warn()`` with ``logger.warning()``. The ``.warn()`` method
5
5
  is an undocumented alias for ``.warning()`` that was deprecated.
6
6
 
7
- See: https://docs.astral.sh/ruff/rules/undocumented-warn/
7
+ See: https://docs.astral.sh/ruff/rules/logging-warn/
8
8
  """
9
9
 
10
10
  from typing import Any, List
@@ -66,7 +66,7 @@ class ReplaceLoggerWarn(Recipe):
66
66
 
67
67
  @property
68
68
  def tags(self) -> List[str]:
69
- return ["python", "cleanup", "ruff", "LOG009"]
69
+ return ["python", "cleanup", "ruff", "G010"]
70
70
 
71
71
  def editor(self) -> TreeVisitor[Any, ExecutionContext]:
72
72
  class Visitor(PythonVisitor[ExecutionContext]):
@@ -21,7 +21,6 @@ from rewrite.decorators import categorize
21
21
  from rewrite.marketplace import Python
22
22
  from rewrite.python.method_matcher import MethodMatcher
23
23
  from rewrite.python.preconditions import uses_method
24
- from rewrite.python.tree import ExpressionStatement
25
24
  from rewrite.python.visitor import PythonVisitor
26
25
  from rewrite.python.template import template, capture
27
26
  from rewrite.java.support_types import Space
@@ -147,8 +146,6 @@ class MergeStartswithEndswith(Recipe):
147
146
  if result is None:
148
147
  result = self._try_merge(operands, "endswith")
149
148
  if result is not None:
150
- if isinstance(result, ExpressionStatement):
151
- result = result.expression
152
149
  return result.replace(prefix=binary.prefix)
153
150
  return super().visit_binary(binary, p)
154
151
 
@@ -15,8 +15,10 @@ from rewrite.python.preconditions import uses_method
15
15
  from rewrite.python.visitor import PythonVisitor
16
16
  from rewrite.python.template import Template, pattern, template, capture
17
17
  from rewrite.python.template.pattern import MatchResult
18
- from rewrite.python.tree import ExpressionStatement, Star
18
+ from rewrite.python.tree import Star
19
+ from rewrite.java.support_types import Space
19
20
  from rewrite.java.tree import J, MethodInvocation
21
+ from openrewrite_code_quality_python.codequality._trivia import has_comments
20
22
 
21
23
  _Cleanup = [*Python, CategoryDescriptor(display_name="Cleanup")]
22
24
 
@@ -28,12 +30,9 @@ def _has_unpacked_argument(method: MethodInvocation) -> bool:
28
30
 
29
31
 
30
32
  def _apply(tmpl: Template, cursor: Cursor, match: MatchResult, method: MethodInvocation) -> J:
31
- # `apply` returns the call wrapped in an `ExpressionStatement`, since a call is also a
32
- # statement. A parse leaves a call bare in every slot.
33
- result = tmpl.apply(cursor, values=match)
34
- if isinstance(result, ExpressionStatement):
35
- result = result.expression
36
- return result or method
33
+ if has_comments(method.replace(_prefix=Space.EMPTY)):
34
+ return method
35
+ return tmpl.apply(cursor, values=match) or method
37
36
 
38
37
 
39
38
  # Pattern/template for range(0, x) -> range(x)
@@ -50,7 +50,8 @@ _Cleanup = [*Python, CategoryDescriptor(display_name="Cleanup")]
50
50
 
51
51
  def _is_none_literal(expr) -> bool:
52
52
  """Check if an expression is the ``None`` literal."""
53
- return isinstance(expr, Literal) and expr.value is None
53
+ # `...` is a Literal whose value is None too.
54
+ return isinstance(expr, Literal) and expr.value_source == "None"
54
55
 
55
56
 
56
57
  @categorize(_Cleanup)
@@ -24,7 +24,7 @@ from rewrite.python.preconditions import uses_method
24
24
  from rewrite.python.type_utils import is_string
25
25
  from rewrite.python.visitor import PythonVisitor
26
26
  from rewrite.python.template import pattern, template, capture
27
- from rewrite.python.tree import ExpressionStatement, FormattedString, NamedArgument, Slice, Star
27
+ from rewrite.python.tree import FormattedString, NamedArgument, Slice, Star
28
28
  from rewrite.java.tree import (
29
29
  ArrayAccess,
30
30
  Assignment,
@@ -413,7 +413,8 @@ class RemoveStrFromFstring(Recipe):
413
413
  if not isinstance(visited, FormattedString.Value):
414
414
  return visited
415
415
  value = visited
416
- if value.format is not None:
416
+ # A debug `{str(x)=}` prints the placeholder's source text.
417
+ if value.format is not None or value.debug:
417
418
  return value
418
419
 
419
420
  expr = value.expression
@@ -613,7 +614,8 @@ _NOT_INLINABLE = set('{}\'"\\\n\r')
613
614
 
614
615
  def _inline_literal_text(value: FormattedString.Value) -> str | None:
615
616
  """The text a constant placeholder prints as, where its source spells exactly that text."""
616
- if value.conversion is not None or value.format is not None:
617
+ # A debug `{5=}` prints its source text before the value.
618
+ if value.conversion is not None or value.format is not None or value.debug:
617
619
  return None
618
620
  expr = value.expression
619
621
  if not isinstance(expr, Literal) or expr.value_source is None:
@@ -871,9 +873,6 @@ class UseStringRemoveAffix(Recipe):
871
873
 
872
874
  values = {'t': select, 'affix': affix_lit}
873
875
  result = _REMOVE_AFFIX_TEMPLATES[remove_method].apply(self.cursor, values=values)
874
- # A parse gives an assignment statement as the bare Assignment.
875
- if isinstance(result, ExpressionStatement):
876
- result = result.expression
877
876
  return result or if_stmt
878
877
 
879
878
  return Preconditions.check(
@@ -24,7 +24,7 @@ from rewrite.python.preconditions import uses_method
24
24
  from rewrite.python.type_utils import is_of_class_type
25
25
  from rewrite.python.visitor import PythonVisitor
26
26
  from rewrite.python.template import template, capture
27
- from rewrite.python.tree import ComprehensionExpression, ExpressionStatement
27
+ from rewrite.python.tree import ComprehensionExpression
28
28
  from rewrite.java.tree import (
29
29
  Expression,
30
30
  ForEachLoop,
@@ -138,8 +138,6 @@ class UseItemsForKeyValueIteration(Recipe):
138
138
  result = _items_template.apply(self.cursor, values={"d": receiver})
139
139
  if result is None:
140
140
  return method
141
- if isinstance(result, ExpressionStatement):
142
- result = result.expression
143
141
  return result.replace(_prefix=method.prefix)
144
142
 
145
143
  return Preconditions.check(uses_method(_ZIP_PATTERN), Visitor())
@@ -23,7 +23,6 @@ from rewrite.python.type_utils import is_of_class_type
23
23
  from rewrite.python.visitor import PythonVisitor
24
24
  from rewrite.python.template import template, capture
25
25
  from rewrite.python import tree as py_tree
26
- from rewrite.python.tree import ExpressionStatement
27
26
  from rewrite.java.tree import ArrayAccess, Empty, J, Space
28
27
  from openrewrite_code_quality_python.codequality._trivia import has_comments
29
28
 
@@ -118,8 +117,6 @@ class UseListClear(Recipe):
118
117
  )
119
118
  if result is None:
120
119
  return del_stmt
121
- if isinstance(result, ExpressionStatement):
122
- result = result.expression
123
120
  return result.replace(_prefix=del_stmt.prefix)
124
121
 
125
122
  return Visitor()
@@ -2,7 +2,9 @@
2
2
 
3
3
  from typing import List
4
4
 
5
+ from rewrite.java.markers import TrailingComma
5
6
  from rewrite.java.support_types import J, Space
7
+ from rewrite.markers import Marker
6
8
  from rewrite.python.template.comparator import PythonComparatorVisitor
7
9
  from rewrite.python.visitor import PythonVisitor
8
10
  from rewrite.visitor import Cursor
@@ -14,6 +16,12 @@ class _CommentTexts(PythonVisitor[List[str]]):
14
16
  p.extend(comment.text for comment in space.comments)
15
17
  return super().visit_space(space, p)
16
18
 
19
+ def visit_marker(self, marker: Marker, p: List[str]) -> Marker:
20
+ # The trivia after a trailing comma lives on its marker, not in a tree slot.
21
+ if isinstance(marker, TrailingComma):
22
+ self.visit_space(marker.suffix, p)
23
+ return super().visit_marker(marker, p)
24
+
17
25
 
18
26
  def _comment_texts(tree: J) -> List[str]:
19
27
  texts: List[str] = []
@@ -135,31 +135,44 @@ def splice_out(
135
135
 
136
136
  The survivor takes the removed statement's place on the page: the trivia
137
137
  up to the removed statement's own line, which holds the preceding
138
- statement's trailing comment, then that line's break and indent. Only
139
- comments standing on their own line before the survivor come from the
140
- survivor; the removed statement's go with it.
138
+ statement's trailing comment, then that line's break and indent. Comments
139
+ on their own lines above a removed statement may introduce what follows,
140
+ so they move to the survivor. A removed statement's trailing comment goes
141
+ with it.
141
142
  """
142
143
  result = []
143
144
  pending: Tuple[Space, Space] | None = None
144
145
  for i, padded in enumerate(padded_statements):
146
+ prefix = padded.element.prefix
145
147
  if i in to_remove:
146
148
  if pending is None:
147
- prefix = padded.element.prefix
148
149
  # A file's first statement starts a line without a break to show for it.
149
150
  pending = split_line(prefix) or (prefix, Space.EMPTY)
151
+ else:
152
+ _, tail = split_line(prefix) or (Space.EMPTY, prefix)
153
+ pending = (pending[0], _continue_with(pending[1], tail))
150
154
  continue
151
155
  if pending is not None:
152
156
  head, line = pending
153
- prefix = padded.element.prefix
154
157
  _, tail = split_line(prefix) or (Space.EMPTY, prefix)
155
- padded = padded.replace(
156
- _element=padded.element.replace(
157
- _prefix=concat(
158
- head,
159
- Space(_comments=tail.comments, _whitespace=line.whitespace),
160
- )
161
- )
162
- )
158
+ padded = padded.replace(_element=padded.element.replace(_prefix=concat(head, _continue_with(line, tail))))
163
159
  pending = None
164
160
  result.append(padded)
165
- return result, end if pending is None else concat(pending[0], end)
161
+ if pending is None:
162
+ return result, end
163
+ head, line = pending
164
+ return result, concat(head, _continue_with(line, end) if line.comments else end)
165
+
166
+
167
+ def _continue_with(lines: Space, tail: Space) -> Space:
168
+ """``lines``, whose last whitespace indents a removed statement, continued by the ``tail`` after that statement.
169
+
170
+ Without comments in ``lines``, its leading line break stands in for the tail's.
171
+ """
172
+ if not lines.comments:
173
+ return Space(_comments=tail.comments, _whitespace=lines.whitespace)
174
+ last = lines.comments[-1]
175
+ return Space(
176
+ _comments=[*lines.comments[:-1], last.replace(_suffix=tail.whitespace), *tail.comments],
177
+ _whitespace=lines.whitespace,
178
+ )
@@ -6,11 +6,13 @@ from rewrite.java.tree import (
6
6
  Assert,
7
7
  Binary as JBinary,
8
8
  ControlParentheses,
9
+ If,
9
10
  Parentheses,
10
11
  Ternary,
11
12
  Unary,
13
+ WhileLoop,
12
14
  )
13
- from rewrite.python.tree import Binary as PyBinary
15
+ from rewrite.python.tree import Binary as PyBinary, ComprehensionExpression
14
16
 
15
17
  _COMPARISONS = {
16
18
  JBinary.Type.Equal,
@@ -60,7 +62,7 @@ def is_chained_comparison(expr: object) -> bool:
60
62
  def is_read_as_truth_value(cursor: Cursor) -> bool:
61
63
  """Whether the value at ``cursor`` is only ever converted to ``bool``.
62
64
 
63
- That holds for a condition, an ``assert`` test and a ``not`` operand.
65
+ That holds for a condition, a comprehension filter, an ``assert`` test and a ``not`` operand.
64
66
  An ``and``/``or`` operand counts only if the ``and``/``or`` itself does,
65
67
  because ``y = a or b`` yields ``b`` itself.
66
68
  """
@@ -71,6 +73,9 @@ def is_read_as_truth_value(cursor: Cursor) -> bool:
71
73
  parent = parent.parent_tree_cursor()
72
74
  value = parent.value
73
75
  if isinstance(value, ControlParentheses):
76
+ # A `match` subject and an `except` clause are held in `ControlParentheses` too.
77
+ return isinstance(parent.parent_tree_cursor().value, (If, WhileLoop))
78
+ if isinstance(value, ComprehensionExpression.Condition):
74
79
  return True
75
80
  if isinstance(value, Unary):
76
81
  return value.operator == Unary.Type.Not
@@ -19,6 +19,7 @@ from rewrite.java.tree import J
19
19
  from rewrite.java.support_types import JavaType, JLeftPadded
20
20
  from rewrite.java.tree import (
21
21
  Binary as JBinary,
22
+ Expression,
22
23
  MethodDeclaration,
23
24
  Parentheses,
24
25
  Unary,
@@ -69,6 +70,24 @@ def _is_in_comparison_dunder(cursor: Cursor) -> bool:
69
70
  return method is not None and method.name.simple_name in _COMPARISON_DUNDERS
70
71
 
71
72
 
73
+ def _drops_comments(unary: Unary, kept: Expression) -> bool:
74
+ """Whether the trivia between ``unary``'s operator and ``kept``'s prefix holds a comment."""
75
+ node: Expression = unary
76
+ while node is not kept:
77
+ if node is not unary and node.prefix.comments:
78
+ return True
79
+ if isinstance(node, Unary):
80
+ if node.padding.operator.before.comments:
81
+ return True
82
+ node = node.expression
83
+ elif isinstance(node, Parentheses):
84
+ if node.padding.tree.after.comments:
85
+ return True
86
+ node = node.tree
87
+ else:
88
+ return True
89
+ return bool(kept.prefix.comments)
90
+
72
91
 
73
92
  @categorize(_CodeQuality)
74
93
  class BooleanChecksNotInverted(Recipe):
@@ -143,6 +162,7 @@ class BooleanChecksNotInverted(Recipe):
143
162
  isinstance(inner, Unary)
144
163
  and inner.operator == Unary.Type.Not
145
164
  and is_read_as_truth_value(self.cursor)
165
+ and not _drops_comments(unary, inner.expression)
146
166
  ):
147
167
  return inner.expression.replace(_prefix=unary.prefix)
148
168
 
@@ -151,6 +171,7 @@ class BooleanChecksNotInverted(Recipe):
151
171
  isinstance(inner, JBinary)
152
172
  and _is_invertible(inner)
153
173
  and not _is_in_comparison_dunder(self.cursor)
174
+ and not _drops_comments(unary, inner)
154
175
  ):
155
176
  inverse = _OPERATOR_INVERSE.get(inner.operator)
156
177
  if inverse is not None:
@@ -78,7 +78,7 @@ class CollapsibleIfStatements(Recipe):
78
78
 
79
79
  @property
80
80
  def display_name(self) -> str:
81
- return "Merge collapsible if statements"
81
+ return "Merge collapsible `if` statements"
82
82
 
83
83
  @property
84
84
  def description(self) -> str:
@@ -75,7 +75,7 @@ class RemoveDuplicateConditions(Recipe):
75
75
 
76
76
  @property
77
77
  def display_name(self) -> str:
78
- return "Remove duplicate conditions in if/elif chains"
78
+ return "Remove duplicate conditions in `if`/`elif` chains"
79
79
 
80
80
  @property
81
81
  def description(self) -> str:
@@ -153,7 +153,11 @@ def _prune_chain(root: If, current: If, seen: list[Expression]) -> If:
153
153
  return _prune_chain(root, updated_current, seen)
154
154
  return root
155
155
  else:
156
- seen.append(cond)
156
+ if is_side_effect_free(cond):
157
+ seen.append(cond)
158
+ else:
159
+ # A condition that may run code or bind a name can change what an earlier one reads.
160
+ seen.clear()
157
161
  return _prune_chain(root, branch, seen)
158
162
 
159
163