Skip to content

Keep lambda parameters on one line and parenthesize the body if it expands#21385

Merged
ntBre merged 103 commits intomainfrom
brent/indent-lambda-params
Dec 12, 2025
Merged

Keep lambda parameters on one line and parenthesize the body if it expands#21385
ntBre merged 103 commits intomainfrom
brent/indent-lambda-params

Conversation

@ntBre
Copy link
Copy Markdown
Contributor

@ntBre ntBre commented Nov 11, 2025

Summary

This PR makes two changes to our formatting of lambda expressions:

  1. We now parenthesize the body expression if it expands
  2. We now try to keep the parameters on a single line

The latter of these fixes #8179:

Black formatting and this PR's formatting:

def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        ),
    )

Stable Ruff formatting

def a():
    return b(
        c,
        d,
        e,
        f=lambda self,
        *args,
        **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs),
    )

We don't parenthesize the body expression here because the call to aaaa... has its own parentheses, but adding a binary operator shows the new parenthesization:

@@ -3,7 +3,7 @@
         c,
         d,
         e,
-        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
-            *args, **kwargs
-        ) + 1,
+        f=lambda self, *args, **kwargs: (
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
+        ),
     )

This is actually a new divergence from Black, which formats this input like this:

def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        )
        + 1,
    )

But I think this is an improvement, unlike the case from #8179.

One other, smaller benefit is that because we now add parentheses to lambda bodies, we also remove redundant parentheses:

 @pytest.mark.parametrize(
     "f",
     [
-        lambda x: (x.expanding(min_periods=5).cov(x, pairwise=True)),
-        lambda x: (x.expanding(min_periods=5).corr(x, pairwise=True)),
+        lambda x: x.expanding(min_periods=5).cov(x, pairwise=True),
+        lambda x: x.expanding(min_periods=5).corr(x, pairwise=True),
     ],
 )
 def test_moment_functions_zero_length_pairwise(f):

Test Plan

New tests taken from #8465 and probably a few more I should grab from the ecosystem results.

@ntBre ntBre added formatter Related to the formatter preview Related to preview mode features labels Nov 11, 2025
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Nov 11, 2025

ruff-ecosystem results

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

ℹ️ ecosystem check detected format changes. (+900 -814 lines in 68 files in 19 projects; 36 projects unchanged)

RasaHQ/rasa (+6 -6 lines across 2 files)

ruff format --preview

rasa/nlu/extractors/crf_entity_extractor.py~L101

         CRFEntityExtractorOptions.SUFFIX1: lambda crf_token: crf_token.text[-1:],
         CRFEntityExtractorOptions.BIAS: lambda _: "bias",
         CRFEntityExtractorOptions.POS: lambda crf_token: crf_token.pos_tag,
-        CRFEntityExtractorOptions.POS2: lambda crf_token: crf_token.pos_tag[:2]
-        if crf_token.pos_tag is not None
-        else None,
+        CRFEntityExtractorOptions.POS2: lambda crf_token: (
+            crf_token.pos_tag[:2] if crf_token.pos_tag is not None else None
+        ),
         CRFEntityExtractorOptions.UPPER: lambda crf_token: crf_token.text.isupper(),
         CRFEntityExtractorOptions.DIGIT: lambda crf_token: crf_token.text.isdigit(),
         CRFEntityExtractorOptions.PATTERN: lambda crf_token: crf_token.pattern,

rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py~L86

         "suffix2": lambda token: token.text[-2:],
         "suffix1": lambda token: token.text[-1:],
         "pos": lambda token: token.data.get(POS_TAG_KEY, None),
-        "pos2": lambda token: token.data.get(POS_TAG_KEY, [])[:2]
-        if POS_TAG_KEY in token.data
-        else None,
+        "pos2": lambda token: (
+            token.data.get(POS_TAG_KEY, [])[:2] if POS_TAG_KEY in token.data else None
+        ),
         "upper": lambda token: token.text.isupper(),
         "digit": lambda token: token.text.isdigit(),
     }

PlasmaPy/PlasmaPy (+2 -2 lines across 1 file)

ruff format --preview

src/plasmapy/particles/atomic.py~L1245

         # If it has been indicated that the user wants the interpolator, construct
         # an anonymous function to handle units and sanitize IO
         if return_interpolator:
-            return (
-                lambda x: np.exp(cs(np.log(x.to(u.MeV).value))) * u.MeV * u.cm**2 / u.g
+            return lambda x: (
+                np.exp(cs(np.log(x.to(u.MeV).value))) * u.MeV * u.cm**2 / u.g
             )
 
         return (

apache/airflow (+10 -13 lines across 4 files)

ruff format --preview

airflow-core/tests/unit/cli/commands/test_config_command.py~L355

     def test_lint_detects_multiple_issues(self, stdout_capture):
         with mock.patch(
             "airflow.configuration.conf.has_option",
-            side_effect=lambda section, option, lookup_from_deprecated: option
-            in ["check_slas", "strict_dataset_uri_validation"],
+            side_effect=lambda section, option, lookup_from_deprecated: (
+                option in ["check_slas", "strict_dataset_uri_validation"]
+            ),
         ):
             with stdout_capture as temp_stdout:
                 config_command.lint_config(cli_parser.get_parser().parse_args(["config", "lint"]))

providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor.py~L992

         mock_ti.queued_by_job_id = "10"  # scheduler_job would have updated this after the first adoption
         executor.scheduler_job_id = "20"
         # assume success adopting, `adopt_launched_task` pops `ti_key` from `tis_to_flush_by_key`
-        mock_adopt_launched_task.side_effect = (
-            lambda client, pod, tis_to_flush_by_key: tis_to_flush_by_key.pop(ti_key)
+        mock_adopt_launched_task.side_effect = lambda client, pod, tis_to_flush_by_key: (
+            tis_to_flush_by_key.pop(ti_key)
         )
 
         reset_tis = executor.try_adopt_task_instances([mock_ti])

providers/docker/tests/unit/docker/operators/test_docker.py~L153

         self.client_mock.attach.return_value = self.log_messages
 
         # If logs() is called with tail then only return the last value, otherwise return the whole log.
-        self.client_mock.logs.side_effect = (
-            lambda **kwargs: iter(self.log_messages[-kwargs["tail"] :])
-            if "tail" in kwargs
-            else iter(self.log_messages)
+        self.client_mock.logs.side_effect = lambda **kwargs: (
+            iter(self.log_messages[-kwargs["tail"] :]) if "tail" in kwargs else iter(self.log_messages)
         )
 
         docker_api_client_patcher.return_value = self.client_mock

providers/docker/tests/unit/docker/operators/test_docker.py~L622

         self.client_mock.pull.return_value = [b'{"status":"pull log"}']
         self.client_mock.attach.return_value = iter([b"container log 1 \n", b"container log 2\n"])
         # Make sure the logs side effect is updated after the change
-        self.client_mock.attach.side_effect = (
-            lambda **kwargs: iter(self.log_messages[-kwargs["tail"] :])
-            if "tail" in kwargs
-            else iter(self.log_messages)
+        self.client_mock.attach.side_effect = lambda **kwargs: (
+            iter(self.log_messages[-kwargs["tail"] :]) if "tail" in kwargs else iter(self.log_messages)
         )
 
         kwargs = {

providers/http/tests/unit/http/sensors/test_http.py~L302

             method="GET",
             endpoint="/search",
             data={"client": "ubuntu", "q": "airflow"},
-            response_check=lambda response: ("apache/airflow" in response.text),
+            response_check=lambda response: "apache/airflow" in response.text,
             headers={},
         )
         op.execute({})

apache/superset (+34 -25 lines across 7 files)

ruff format --preview

superset/tags/api.py~L598

     @statsd_metrics
     @rison({"type": "array", "items": {"type": "integer"}})
     @event_logger.log_this_with_context(
-        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
-        f".favorite_status",
+        action=lambda self, *args, **kwargs: (
+            f"{self.__class__.__name__}.favorite_status"
+        ),
         log_to_statsd=False,
     )
     def favorite_status(self, **kwargs: Any) -> Response:

superset/tags/api.py~L696

     @safe
     @statsd_metrics
     @event_logger.log_this_with_context(
-        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
-        f".remove_favorite",
+        action=lambda self, *args, **kwargs: (
+            f"{self.__class__.__name__}.remove_favorite"
+        ),
         log_to_statsd=False,
     )
     def remove_favorite(self, pk: int) -> Response:

tests/integration_tests/datasource_tests.py~L213

     def test_external_metadata_by_name_for_virtual_table_uses_mutator(self):
         self.login(ADMIN_USERNAME)
         with create_and_cleanup_table() as tbl:
-            current_app.config["SQL_QUERY_MUTATOR"] = (
-                lambda sql, **kwargs: "SELECT 456 as intcol, 'def' as mutated_strcol"
+            current_app.config["SQL_QUERY_MUTATOR"] = lambda sql, **kwargs: (
+                "SELECT 456 as intcol, 'def' as mutated_strcol"
             )
 
             params = prison.dumps({

tests/integration_tests/datasource_tests.py~L339

 
         pytest.raises(
             SupersetGenericDBErrorException,
-            lambda: db.session.query(SqlaTable)
-            .filter_by(id=tbl.id)
-            .one_or_none()
-            .external_metadata(),
+            lambda: (
+                db.session.query(SqlaTable)
+                .filter_by(id=tbl.id)
+                .one_or_none()
+                .external_metadata()
+            ),
         )
 
         resp = self.client.get(url)

tests/integration_tests/db_engine_specs/presto_tests.py~L81

     def verify_presto_column(self, column, expected_results):
         inspector = mock.Mock()
         preparer = inspector.engine.dialect.identifier_preparer
-        preparer.quote_identifier = preparer.quote = preparer.quote_schema = (
-            lambda x: f'"{x}"'
+        preparer.quote_identifier = preparer.quote = preparer.quote_schema = lambda x: (
+            f'"{x}"'
         )
         row = mock.Mock()
         row.Column, row.Type, row.Null = column

tests/integration_tests/db_engine_specs/presto_tests.py~L827

     def test_show_columns(self):
         inspector = mock.MagicMock()
         preparer = inspector.engine.dialect.identifier_preparer
-        preparer.quote_identifier = preparer.quote = preparer.quote_schema = (
-            lambda x: f'"{x}"'
+        preparer.quote_identifier = preparer.quote = preparer.quote_schema = lambda x: (
+            f'"{x}"'
         )
         inspector.bind.execute.return_value.fetchall = mock.MagicMock(
             return_value=["a", "b"]

tests/integration_tests/db_engine_specs/presto_tests.py~L843

     def test_show_columns_with_schema(self):
         inspector = mock.MagicMock()
         preparer = inspector.engine.dialect.identifier_preparer
-        preparer.quote_identifier = preparer.quote = preparer.quote_schema = (
-            lambda x: f'"{x}"'
+        preparer.quote_identifier = preparer.quote = preparer.quote_schema = lambda x: (
+            f'"{x}"'
         )
         inspector.bind.execute.return_value.fetchall = mock.MagicMock(
             return_value=["a", "b"]

tests/integration_tests/security/api_tests.py~L187

         self.assert500(self._get_guest_token_with_rls(rls_rule))
 
     @with_config({
-        "GUEST_TOKEN_VALIDATOR_HOOK": lambda x: len(x["rls"]) == 1
-        and "tenant_id=" in x["rls"][0]["clause"]
+        "GUEST_TOKEN_VALIDATOR_HOOK": lambda x: (
+            len(x["rls"]) == 1 and "tenant_id=" in x["rls"][0]["clause"]
+        )
     })
     def test_guest_validator_hook_real_world_example_positive(self):
         """

tests/integration_tests/security/api_tests.py~L201

         self.assert200(self._get_guest_token_with_rls(rls_rule))
 
     @with_config({
-        "GUEST_TOKEN_VALIDATOR_HOOK": lambda x: len(x["rls"]) == 1
-        and "tenant_id=" in x["rls"][0]["clause"]
+        "GUEST_TOKEN_VALIDATOR_HOOK": lambda x: (
+            len(x["rls"]) == 1 and "tenant_id=" in x["rls"][0]["clause"]
+        )
     })
     def test_guest_validator_hook_real_world_example_negative(self):
         """

tests/unit_tests/datasets/test_datetime_format_detector.py~L44

     dataset.database.get_sqla_engine.return_value.__exit__.return_value = None
 
     # Mock apply_limit_to_sql to return SQL with LIMIT
-    dataset.database.apply_limit_to_sql = (
-        lambda sql, limit, force: f"{sql} LIMIT {limit}"
+    dataset.database.apply_limit_to_sql = lambda sql, limit, force: (
+        f"{sql} LIMIT {limit}"
     )
 
     return dataset

tests/unit_tests/importexport/api_test.py~L48

     mocked_export_result = [
         (
             "metadata.yaml",
-            lambda: "version: 1.0.0\ntype: assets\ntimestamp: '2022-01-01T00:00:00+00:00'\n",  # noqa: E501
+            lambda: (
+                "version: 1.0.0\ntype: assets\ntimestamp: '2022-01-01T00:00:00+00:00'\n"
+            ),  # noqa: E501
         ),
         ("databases/example.yaml", lambda: "<DATABASE CONTENTS>"),
     ]

tests/unit_tests/utils/test_core.py~L635

 
 
 @with_config({
-    "USER_AGENT_FUNC": lambda database,
-    source: f"{database.database_name} {source.name}"
+    "USER_AGENT_FUNC": lambda database, source: (
+        f"{database.database_name} {source.name}"
+    )
 })
 def test_get_user_agent_custom(mocker: MockerFixture, app_context: None) -> None:
     database_mock = mocker.MagicMock()

aws/aws-sam-cli (+15 -12 lines across 1 file)

ruff format --preview

samcli/lib/cli_validation/image_repository_validation.py~L70

 
             validators = [
                 Validator(
-                    validation_function=lambda: bool(image_repository)
-                    + bool(image_repositories)
-                    + bool(resolve_image_repos)
-                    > 1,
+                    validation_function=lambda: (
+                        bool(image_repository) + bool(image_repositories) + bool(resolve_image_repos) > 1
+                    ),
                     exception=click.BadOptionUsage(
                         option_name="--image-repositories",
                         ctx=ctx,

samcli/lib/cli_validation/image_repository_validation.py~L82

                     ),
                 ),
                 Validator(
-                    validation_function=lambda: not guided
-                    and not (image_repository or image_repositories or resolve_image_repos)
-                    and required,
+                    validation_function=lambda: (
+                        not guided and not (image_repository or image_repositories or resolve_image_repos) and required
+                    ),
                     exception=click.BadOptionUsage(
                         option_name="--image-repositories",
                         ctx=ctx,

samcli/lib/cli_validation/image_repository_validation.py~L92

                     ),
                 ),
                 Validator(
-                    validation_function=lambda: not guided
-                    and (
-                        image_repositories
-                        and not resolve_image_repos
-                        and not _is_all_image_funcs_provided(template_file, image_repositories, parameters_overrides)
+                    validation_function=lambda: (
+                        not guided
+                        and (
+                            image_repositories
+                            and not resolve_image_repos
+                            and not _is_all_image_funcs_provided(
+                                template_file, image_repositories, parameters_overrides
+                            )
+                        )
                     ),
                     exception=click.BadOptionUsage(
                         option_name="--image-repositories", ctx=ctx, message=image_repos_error_msg

binary-husky/gpt_academic (+30 -26 lines across 3 files)

ruff format --preview

crazy_functions/agent_fns/general.py~L83

             }
             kwargs.update(agent_kwargs)
             agent_handle = agent_cls(**kwargs)
-            agent_handle._print_received_message = (
-                lambda a, b: self.gpt_academic_print_override(agent_kwargs, a, b)
+            agent_handle._print_received_message = lambda a, b: (
+                self.gpt_academic_print_override(agent_kwargs, a, b)
             )
             for d in agent_handle._reply_func_list:
                 if (

crazy_functions/agent_fns/general.py~L93

                 ):
                     d["reply_func"] = gpt_academic_generate_oai_reply
             if agent_kwargs["name"] == "user_proxy":
-                agent_handle.get_human_input = (
-                    lambda a: self.gpt_academic_get_human_input(user_proxy, a)
+                agent_handle.get_human_input = lambda a: (
+                    self.gpt_academic_get_human_input(user_proxy, a)
                 )
                 user_proxy = agent_handle
             if agent_kwargs["name"] == "assistant":

crazy_functions/agent_fns/general.py~L134

                 kwargs = {"code_execution_config": code_execution_config}
                 kwargs.update(agent_kwargs)
                 agent_handle = agent_cls(**kwargs)
-                agent_handle._print_received_message = (
-                    lambda a, b: self.gpt_academic_print_override(agent_kwargs, a, b)
+                agent_handle._print_received_message = lambda a, b: (
+                    self.gpt_academic_print_override(agent_kwargs, a, b)
                 )
                 agents_instances.append(agent_handle)
                 if agent_kwargs["name"] == "user_proxy":
                     user_proxy = agent_handle
-                    user_proxy.get_human_input = (
-                        lambda a: self.gpt_academic_get_human_input(user_proxy, a)
+                    user_proxy.get_human_input = lambda a: (
+                        self.gpt_academic_get_human_input(user_proxy, a)
                     )
             try:
                 groupchat = autogen.GroupChat(

crazy_functions/agent_fns/general.py~L150

                 manager = autogen.GroupChatManager(
                     groupchat=groupchat, **self.define_group_chat_manager_config()
                 )
-                manager._print_received_message = (
-                    lambda a, b: self.gpt_academic_print_override(agent_kwargs, a, b)
+                manager._print_received_message = lambda a, b: (
+                    self.gpt_academic_print_override(agent_kwargs, a, b)
                 )
                 manager.get_human_input = lambda a: self.gpt_academic_get_human_input(
                     manager, a

crazy_functions/crazy_utils.py~L299

         retry_op = retry_times_at_unknown_error
         exceeded_cnt = 0
         mutable[index][2] = "执行中"
-        detect_timeout = (
-            lambda: len(mutable[index]) >= 2
+        detect_timeout = lambda: (
+            len(mutable[index]) >= 2
             and (time.time() - mutable[index][1]) > watch_dog_patience
         )
         while True:

crazy_functions/review_fns/paper_processor/paper_llm_ranker.py~L143

                     )
                 elif search_criteria.query_type == "review":
                     papers.sort(
-                        key=lambda x: 1
-                        if any(
-                            keyword in (getattr(x, "title", "") or "").lower()
-                            or keyword in (getattr(x, "abstract", "") or "").lower()
-                            for keyword in ["review", "survey", "overview"]
-                        )
-                        else 0,
+                        key=lambda x: (
+                            1
+                            if any(
+                                keyword in (getattr(x, "title", "") or "").lower()
+                                or keyword in (getattr(x, "abstract", "") or "").lower()
+                                for keyword in ["review", "survey", "overview"]
+                            )
+                            else 0
+                        ),
                         reverse=True,
                     )
             return papers[:top_k]

crazy_functions/review_fns/paper_processor/paper_llm_ranker.py~L164

         if search_criteria and search_criteria.query_type == "review":
             papers = sorted(
                 papers,
-                key=lambda x: 1
-                if any(
-                    keyword in (getattr(x, "title", "") or "").lower()
-                    or keyword in (getattr(x, "abstract", "") or "").lower()
-                    for keyword in ["review", "survey", "overview"]
-                )
-                else 0,
+                key=lambda x: (
+                    1
+                    if any(
+                        keyword in (getattr(x, "title", "") or "").lower()
+                        or keyword in (getattr(x, "abstract", "") or "").lower()
+                        for keyword in ["review", "survey", "overview"]
+                    )
+                    else 0
+                ),
                 reverse=True,
             )
 

ibis-project/ibis (+135 -119 lines across 7 files)

ruff format --preview

ibis/backends/datafusion/init.py~L246

 
         for name, func in inspect.getmembers(
             udfs,
-            predicate=lambda m: callable(m)
-            and not m.__name__.startswith("_")
-            and m.__module__ == udfs.__name__,
+            predicate=lambda m: (
+                callable(m)
+                and not m.__name__.startswith("_")
+                and m.__module__ == udfs.__name__
+            ),
         ):
             annotations = typing.get_type_hints(func)
             argnames = list(inspect.signature(func).parameters.keys())

ibis/backends/sql/dialects.py~L241

             sge.ArrayAgg: rename_func("array_agg"),
             sge.ArraySort: rename_func("array_sort"),
             sge.Length: rename_func("char_length"),
-            sge.TryCast: lambda self,
-            e: f"TRY_CAST({e.this.sql(self.dialect)} AS {e.to.sql(self.dialect)})",
+            sge.TryCast: lambda self, e: (
+                f"TRY_CAST({e.this.sql(self.dialect)} AS {e.to.sql(self.dialect)})"
+            ),
             sge.DayOfYear: rename_func("dayofyear"),
             sge.DayOfWeek: rename_func("dayofweek"),
             sge.DayOfMonth: rename_func("dayofmonth"),

ibis/backends/tests/test_aggregation.py~L1278

         )
         .groupby("bigint_col")
         .string_col.agg(
-            lambda s: (np.nan if pd.isna(s).all() else pandas_sep.join(s.values))
+            lambda s: np.nan if pd.isna(s).all() else pandas_sep.join(s.values)
         )
         .rename("tmp")
         .sort_index()

ibis/backends/tests/test_window.py~L214

         ),
         param(
             lambda t, win: t.double_col.cummean().over(win),
-            lambda t: (t.double_col.expanding().mean().reset_index(drop=True, level=0)),
+            lambda t: t.double_col.expanding().mean().reset_index(drop=True, level=0),
             id="cummean",
             marks=pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError),
         ),

ibis/backends/tests/test_window.py~L279

         ),
         param(
             lambda t, win: t.double_col.mean().over(win),
-            lambda gb: (
-                gb.double_col.expanding().mean().reset_index(drop=True, level=0)
-            ),
+            lambda gb: gb.double_col.expanding().mean().reset_index(drop=True, level=0),
             id="mean",
             marks=pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError),
         ),

ibis/backends/tests/test_window.py~L335

     [
         param(
             lambda t, win: t.double_col.mean().over(win),
-            lambda df: (df.double_col.expanding().mean()),
+            lambda df: df.double_col.expanding().mean(),
             id="mean",
             marks=[
                 pytest.mark.notimpl(

ibis/backends/tests/test_window.py~L350

             # Disabled on PySpark and Spark backends because in pyspark<3.0.0,
             # Pandas UDFs are only supported on unbounded windows
             lambda t, win: mean_udf(t.double_col).over(win),
-            lambda df: (df.double_col.expanding().mean()),
+            lambda df: df.double_col.expanding().mean(),
             id="mean_udf",
             marks=[
                 pytest.mark.notimpl(

ibis/backends/tests/test_window.py~L538

     [
         param(
             lambda t, win: t.double_col.mean().over(win),
-            lambda gb: (gb.double_col.transform("mean")),
+            lambda gb: gb.double_col.transform("mean"),
             id="mean",
             marks=pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError),
         ),
         param(
             lambda t, win: mean_udf(t.double_col).over(win),
-            lambda gb: (gb.double_col.transform("mean")),
+            lambda gb: gb.double_col.transform("mean"),
             id="mean_udf",
             marks=[
                 pytest.mark.notimpl(

ibis/backends/tests/test_window.py~L1192

     expected = (
         df.sort_values("int_col")
         .groupby(df["int_col"].notnull())
-        .apply(lambda df: (df.int_col.rank(method="min").sub(1).div(len(df) - 1)))
+        .apply(lambda df: df.int_col.rank(method="min").sub(1).div(len(df) - 1))
         .T.reset_index(drop=True)
         .iloc[:, 0]
         .rename(expr.get_name())

ibis/backends/tests/tpc/ds/test_queries.py~L35

         )
         .join(customer, _.ctr_customer_sk == customer.c_customer_sk)
         .filter(
-            lambda t: t.ctr_total_return
-            > ctr2.filter(t.ctr_store_sk == ctr2.ctr_store_sk)
-            .ctr_total_return.mean()
-            .as_scalar()
-            * 1.2
+            lambda t: (
+                t.ctr_total_return
+                > ctr2.filter(t.ctr_store_sk == ctr2.ctr_store_sk)
+                .ctr_total_return.mean()
+                .as_scalar()
+                * 1.2
+            )
         )
         .select(_.c_customer_id)
         .order_by(_.c_customer_id)

ibis/backends/tests/tpc/ds/test_queries.py~L783

                 > 0
             ),
             lambda t: (
-                web_sales.join(date_dim, [("ws_sold_date_sk", "d_date_sk")])
-                .filter(
-                    t.c_customer_sk == web_sales.ws_bill_customer_sk,
-                    _.d_year == 2002,
-                    _.d_moy.between(1, 1 + 3),
+                (
+                    web_sales.join(date_dim, [("ws_sold_date_sk", "d_date_sk")])
+                    .filter(
+                        t.c_customer_sk == web_sales.ws_bill_customer_sk,
+                        _.d_year == 2002,
+                        _.d_moy.between(1, 1 + 3),
+                    )
+                    .count()
+                    > 0
                 )
-                .count()
-                > 0
-            )
-            | (
-                catalog_sales.join(date_dim, [("cs_sold_date_sk", "d_date_sk")])
-                .filter(
-                    t.c_customer_sk == catalog_sales.cs_ship_customer_sk,
-                    _.d_year == 2002,
-                    _.d_moy.between(1, 1 + 3),
+                | (
+                    catalog_sales.join(date_dim, [("cs_sold_date_sk", "d_date_sk")])
+                    .filter(
+                        t.c_customer_sk == catalog_sales.cs_ship_customer_sk,
+                        _.d_year == 2002,
+                        _.d_moy.between(1, 1 + 3),
+                    )
+                    .count()
+                    > 0
                 )
-                .count()
-                > 0
             ),
         )
         .group_by(

ibis/backends/tests/tpc/ds/test_queries.py~L1037

             _.d_date.between(date("2002-02-01"), date("2002-04-02")),
             _.ca_state == "GA",
             _.cc_county == "Williamson County",
-            lambda t: catalog_sales.filter(
-                t.cs_order_number == _.cs_order_number,
-                t.cs_warehouse_sk != _.cs_warehouse_sk,
-            ).count()
-            > 0,
-            lambda t: catalog_returns.filter(
-                t.cs_order_number == _.cr_order_number
-            ).count()
-            == 0,
+            lambda t: (
+                catalog_sales.filter(
+                    t.cs_order_number == _.cs_order_number,
+                    t.cs_warehouse_sk != _.cs_warehouse_sk,
+                ).count()
+                > 0
+            ),
+            lambda t: (
+                catalog_returns.filter(t.cs_order_number == _.cr_order_number).count()
+                == 0
+            ),
         )
         .agg(**{
             "order count": _.cs_order_number.nunique(),

ibis/backends/tests/tpc/ds/test_queries.py~L2057

         item.view()
         .filter(
             _.i_manufact_id.between(738, 738 + 40),
-            lambda i1: item.filter(
-                lambda s: (
-                    (i1.i_manufact == s.i_manufact)
-                    & (
-                        (
-                            (s.i_category == "Women")
-                            & s.i_color.isin(("powder", "khaki"))
-                            & s.i_units.isin(("Ounce", "Oz"))
-                            & s.i_size.isin(("medium", "extra large"))
-                        )
-                        | (
-                            (s.i_category == "Women")
-                            & s.i_color.isin(("brown", "honeydew"))
-                            & s.i_units.isin(("Bunch", "Ton"))
-                            & s.i_size.isin(("N/A", "small"))
-                        )
-                        | (
-                            (s.i_category == "Men")
-                            & s.i_color.isin(("floral", "deep"))
-                            & s.i_units.isin(("N/A", "Dozen"))
-                            & s.i_size.isin(("petite", "petite"))
-                        )
-                        | (
-                            (s.i_category == "Men")
-                            & s.i_color.isin(("light", "cornflower"))
-                            & s.i_units.isin(("Box", "Pound"))
-                            & s.i_size.isin(("medium", "extra large"))
-                        )
-                    )
-                )
-                | (
-                    (i1.i_manufact == s.i_manufact)
-                    & (
+            lambda i1: (
+                item.filter(
+                    lambda s: (
                         (
-                            (s.i_category == "Women")
-                            & s.i_color.isin(("midnight", "snow"))
-                            & s.i_units.isin(("Pallet", "Gross"))
-                            & s.i_size.isin(("medium", "extra large"))
-                        )
-                        | (
-                            (s.i_category == "Women")
-                            & s.i_color.isin(("cyan", "papaya"))
-                            & s.i_units.isin(("Cup", "Dram"))
-                            & s.i_size.isin(("N/A", "small"))
-                        )
-                        | (
-                            (s.i_category == "Men")
-                            & s.i_color.isin(("orange", "frosted"))
-                            & s.i_units.isin(("Each", "Tbl"))
-                            & s.i_size.isin(("petite", "petite"))
+                            (i1.i_manufact == s.i_manufact)
+                            & (
+                                (
+                                    (s.i_category == "Women")
+                                    & s.i_color.isin(("powder", "khaki"))
+                                    & s.i_units.isin(("Ounce", "Oz"))
+                                    & s.i_size.isin(("medium", "extra large"))
+                                )
+                                | (
+                                    (s.i_category == "Women")
+                                    & s.i_color.isin(("brown", "honeydew"))
+                                    & s.i_units.isin(("Bunch", "Ton"))
+                                    & s.i_size.isin(("N/A", "small"))
+                                )
+                                | (
+                                    (s.i_category == "Men")
+                                    & s.i_color.isin(("floral", "deep"))
+                                    & s.i_units.isin(("N/A", "Dozen"))
+                                    & s.i_size.isin(("petite", "petite"))
+                                )
+                                | (
+                                    (s.i_category == "Men")
+                                    & s.i_color.isin(("light", "cornflower"))
+                                    & s.i_units.isin(("Box", "Pound"))
+                                    & s.i_size.isin(("medium", "extra large"))
+                                )
+                            )
                         )
                         | (
-                            (s.i_category == "Men")
-                            & s.i_color.isin(("forest", "ghost"))
-                            & s.i_units.isin(("Lb", "Bundle"))
-                            & s.i_size.isin(("medium", "extra large"))
+                            (i1.i_manufact == s.i_manufact)
+                            & (
+                                (
+                                    (s.i_category == "Women")
+                                    & s.i_color.isin(("midnight", "snow"))
+                                    & s.i_units.isin(("Pallet", "Gross"))
+                                    & s.i_size.isin(("medium", "extra large"))
+                                )
+                                | (
+                                    (s.i_category == "Women")
+                                    & s.i_color.isin(("cyan", "papaya"))
+                                    & s.i_units.isin(("Cup", "Dram"))
+                                    & s.i_size.isin(("N/A", "small"))
+                                )
+                                | (
+                                    (s.i_category == "Men")
+                                    & s.i_color.isin(("orange", "frosted"))
+                                    & s.i_units.isin(("Each", "Tbl"))
+                                    & s.i_size.isin(("petite", "petite"))
+                                )
+                                | (
+                                    (s.i_category == "Men")
+                                    & s.i_color.isin(("forest", "ghost"))
+                                    & s.i_units.isin(("Lb", "Bundle"))
+                                    & s.i_size.isin(("medium", "extra large"))
+                                )
+                            )
                         )
                     )
-                )
-            ).count()
-            > 0,
+                ).count()
+                > 0
+            ),
         )
         .select(_.i_product_name)
         .distinct()

ibis/backends/tests/tpc/ds/test_queries.py~L4491

         customer_total_return.join(customer, [("ctr_customer_sk", "c_customer_sk")])
         .join(customer_address, [("c_current_addr_sk", "ca_address_sk")])
         .filter(
-            lambda ctr1: ctr1.ctr_total_return
-            > (
-                ctr2.filter(ctr1.ctr_state == _.ctr_state).ctr_total_return.mean() * 1.2
-            ).as_scalar(),
+            lambda ctr1: (
+                ctr1.ctr_total_return
+                > (
+                    ctr2.filter(ctr1.ctr_state == _.ctr_state).ctr_total_return.mean()
+                    * 1.2
+                ).as_scalar()
+            ),
             _.ca_state == "GA",
         )
         .select(

ibis/backends/tests/tpc/ds/test_queries.py~L4913

         .filter(
             _.i_manufact_id == 350,
             _.d_date.between(date("2000-01-07"), date("2000-04-26")),
-            lambda t: t.ws_ext_discount_amt
-            > (
-                web_sales.join(date_dim, [("ws_sold_date_sk", "d_date_sk")])
-                .filter(
-                    t.i_item_sk == _.ws_item_sk,
-                    _.d_date.between(date("2000-01-07"), date("2000-04-26")),
+            lambda t: (
+                t.ws_ext_discount_amt
+                > (
+                    web_sales.join(date_dim, [("ws_sold_date_sk", "d_date_sk")])
+                    .filter(
+                        t.i_item_sk == _.ws_item_sk,
+                        _.d_date.between(date("2000-01-07"), date("2000-04-26")),
+                    )
+                    .ws_ext_discount_amt.mean()
+                    .as_scalar()
+                    * 1.3
                 )
-                .ws_ext_discount_amt.mean()
-                .as_scalar()
-                * 1.3
             ),
         )
         .select(_.ws_ext_discount_amt.sum().name("Excess Discount Amount"))

ibis/tests/benchmarks/test_benchmarks.py~L692

     N = 20_000_000
 
     path = str(tmp_path_factory.mktemp("duckdb") / "data.ddb")
-    sql = (
-        lambda var, table, n=N: f"""
+    sql = lambda var, table, n=N: (
+        f"""
         CREATE TABLE {table} AS
         SELECT ROW_NUMBER() OVER () AS id, {var}
         FROM (

ibis/tests/expr/test_value_exprs.py~L926

         operator.gt,
         operator.ge,
         lambda left, right: ibis.timestamp("2017-04-01 00:02:34").between(left, right),
-        lambda left, right: ibis.timestamp("2017-04-01")
-        .cast(dt.date)
-        .between(left, right),
+        lambda left, right: (
+            ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
+        ),
     ],
 )
 def test_string_temporal_compare(op, left, right):

langchain-ai/langchain (+46 -20 lines across 1 file)

ruff format --preview

libs/core/tests/unit_tests/runnables/test_history.py~L53

 
 def test_input_messages() -> None:
     runnable = RunnableLambda(
-        lambda messages: "you said: "
-        + "\n".join(str(m.content) for m in messages if isinstance(m, HumanMessage))
+        lambda messages: (
+            "you said: "
+            + "\n".join(str(m.content) for m in messages if isinstance(m, HumanMessage))
+        )
     )
     store: dict = {}
     get_session_history = _get_get_session_history(store=store)

libs/core/tests/unit_tests/runnables/test_history.py~L82

 
 async def test_input_messages_async() -> None:
     runnable = RunnableLambda(
-        lambda messages: "you said: "
-        + "\n".join(str(m.content) for m in messages if isinstance(m, HumanMessage))
+        lambda messages: (
+            "you said: "
+            + "\n".join(str(m.content) for m in messages if isinstance(m, HumanMessage))
+        )
     )
     store: dict = {}
     get_session_history = _get_get_session_history(store=store)

libs/core/tests/unit_tests/runnables/test_history.py~L113

 
 def test_input_dict() -> None:
     runnable = RunnableLambda(
-        lambda params: "you said: "
-        + "\n".join(
-            str(m.content) for m in params["messages"] if isinstance(m, HumanMessage)
+        lambda params: (
+            "you said: "
+            + "\n".join(
+                str(m.content)
+                for m in params["messages"]
+                if isinstance(m, HumanMessage)
+            )
         )
     )
     get_session_history = _get_get_session_history()

libs/core/tests/unit_tests/runnables/test_history.py~L133

 
 async def test_input_dict_async() -> None:
     runnable = RunnableLambda(
-        lambda params: "you said: "
-        + "\n".join(
-            str(m.content) for m in params["messages"] if isinstance(m, HumanMessage)
+        lambda params: (
+            "you said: "
+            + "\n".join(
+                str(m.content)
+                for m in params["messages"]
+                if isinstance(m, HumanMessage)
+            )
         )
     )
     get_session_history = _get_get_session_history()

libs/core/tests/unit_tests/runnables/test_history.py~L155

 
 def test_input_dict_with_history_key() -> None:
     runnable = RunnableLambda(
-        lambda params: "you said: "
-        + "\n".join(
-            [str(m.content) for m in params["history"] if isinstance(m, HumanMessage)]
-            + [params["input"]]
+        lambda params: (
+            "you said: "
+            + "\n".join(
+                [
+                    str(m.content)
+                    for m in params["history"]
+                    if isinstance(m, HumanMessage)
+                ]
+                + [params["input"]]
+            )
         )
     )
     get_session_history = _get_get_session_history()

libs/core/tests/unit_tests/runnables/test_history.py~L177

 
 async def test_input_dict_with_history_key_async() -> None:
     runnable = RunnableLambda(
-        lambda params: "you said: "
-        + "\n".join(
-            [str(m.content) for m in params["history"] if isinstance(m, HumanMessage)]
-            + [params["input"]]
+        lambda params: (
+            "you said: "
+            + "\n".join(
+                [
+                    str(m.content)
+                    for m in params["history"]
+                    if isinstance(m, HumanMessage)
+                ]
+                + [params["input"]]
+            )
         )
     )
     get_session_history = _get_get_session_history()

libs/core/tests/unit_tests/runnables/test_history.py~L827

 
 def test_get_output_messages_no_value_error() -> None:
     runnable = _RunnableLambdaWithRaiseError(
-        lambda messages: "you said: "
-        + "\n".join(str(m.content) for m in messages if isinstance(m, HumanMessage))
+        lambda messages: (
+            "you said: "
+            + "\n".join(str(m.content) for m in messages if isinstance(m, HumanMessage))
+        )
     )
     store: dict = {}
     get_session_history = _get_get_session_history(store=store)

mlflow/mlflow (+6 -4 lines across 2 files)

ruff format --preview

mlflow/store/model_registry/file_store.py~L895

     def _list_file_model_versions_under_path(self, path) -> list[FileModelVersion]:
       

... (truncated 1844 lines) ...

@ntBre ntBre force-pushed the brent/indent-lambda-params branch 3 times, most recently from b65c407 to 68e09d5 Compare November 11, 2025 20:13
ntBre added a commit that referenced this pull request Nov 11, 2025
@ntBre ntBre force-pushed the brent/indent-lambda-params branch from 68e09d5 to 19326a7 Compare November 12, 2025 13:42
@MichaReiser

This comment was marked as resolved.

@ntBre

This comment was marked as resolved.

@MichaReiser

This comment was marked as resolved.

@ntBre

This comment was marked as resolved.

@ntBre

This comment was marked as resolved.

@ntBre ntBre changed the title [WIP] Indent lambda parameters if parameters wrap [WIP] Keep lambda parameters on one line and parenthesize the body if it expands Nov 12, 2025
@ntBre ntBre changed the title [WIP] Keep lambda parameters on one line and parenthesize the body if it expands Keep lambda parameters on one line and parenthesize the body if it expands Nov 14, 2025
@ntBre

This comment was marked as resolved.

@ntBre ntBre marked this pull request as ready for review November 14, 2025 13:54
@ntBre ntBre requested a review from MichaReiser as a code owner November 14, 2025 13:54
@MichaReiser MichaReiser requested a review from amyreese November 14, 2025 13:56
Comment on lines +674 to +677
+lambda x: (
+ lambda y: (
+ lambda z: (x, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, z)
+ )
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wrapping here is unfortunate. But it's probably not worth special casing, given that it's a very contrived exmaple

Comment on lines +686 to +688
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda x: (
+ y
+ ),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels worse

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, this is like the case I flagged in #21385 (comment). But this also seems pretty contrived to me. It seems very unlikely to have a long lambda with a single-character body where the ( and body are the same length. Unless you think it would be worse for a longer name too.

@MichaReiser
Copy link
Copy Markdown
Member

providers/google/tests/unit/google/cloud/hooks/test_gcs.py~L420

Yeah, this one looks just wrong

@MichaReiser

This comment was marked as resolved.

@ntBre ntBre marked this pull request as draft November 18, 2025 19:15
@ntBre ntBre force-pushed the brent/indent-lambda-params branch 2 times, most recently from c405b41 to 1bfcf51 Compare November 19, 2025 17:46
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Nov 19, 2025

CodSpeed Performance Report

Merging #21385 will not alter performance

Comparing brent/indent-lambda-params (95301b3) with main (3ac58b4)

Summary

✅ 30 untouched
⏩ 22 skipped1

Footnotes

  1. 22 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

ntBre added a commit that referenced this pull request Nov 20, 2025
Summary
--

This PR changes our formatting of `lambda` expressions to keep the parameters on
a single line, at least if there are no comments. This fixes #8179.

Black formatting and this PR's formatting:

```py
def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        ),
    )
```

Stable Ruff formatting

```py
def a():
    return b(
        c,
        d,
        e,
        f=lambda self,
        *args,
        **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs),
    )
```

I split this off from #21385 because it seemed like the simpler change and
helpful to isolate from the body parenthesization ecosystem and performance
changes. However, as Micha pointed out, we need the formatting from #21385 to
land first, so this branch is currently stacked on that one.

Test Plan
--

New formatting on tests from #8465 and #21385
@ntBre
Copy link
Copy Markdown
Contributor Author

ntBre commented Dec 11, 2025

I think that should take care of the code changes, just waiting for the ecosystem comment. I'll download the results, go through them, and then upload a copy here. I was getting a slightly different number of changes when running ruff-ecosystem locally, so I'll stick to the canonical one from CI.

Differently, are my comments in the code too long? It felt like a bad sign that I installed a plugin to hide comments this afternoon to get a better look at the overall code structure. Otherwise it seemed nice to make them very explicit.

@ntBre
Copy link
Copy Markdown
Contributor Author

ntBre commented Dec 11, 2025

Ecosystem results look good to me! As expected and noted in the summary, we now:

  • Keep lambda parameters on one line
  • Parenthesize the lambda body if it expands
  • Remove parentheses around the lambda body if they're no longer needed

ecosystem-result.md

Copy link
Copy Markdown
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looks great to me. My only question is if we can improve call chain formatting. The extra set of parentheses around call chains often feels unnecessary. It would be nice if we could avoid that, but I don't remember what the issue was that we faced. I don't think it's very important and also something we can iterate on later but might be worth giving a short try

This isn't a call chain, but I do find the parentheses very unnecessary.

tests/congruence_tests/test_delete_points.py~L70

     compare_client_results(
         local_client,
         remote_client,
-        lambda c: c.query_points(
-            COLLECTION_NAME,
-            query=vector,
-            using="sparse-image",
-        ).points,
+        lambda c: (
+            c.query_points(
+                COLLECTION_NAME,
+                query=vector,
+                using="sparse-image",
+            ).points
+        ),
     )
 
     found_ids = [

zerver/lib/user_groups.py~L788

 
 def get_recursive_subgroups_union_for_groups(user_group_ids: list[int]) -> QuerySet[UserGroup]:
     cte = CTE.recursive(
-        lambda cte: UserGroup.objects.filter(id__in=user_group_ids)
-        .values(group_id=F("id"))
-        .union(
-            cte.join(NamedUserGroup, direct_supergroups=cte.col.group_id).values(group_id=F("id"))
+        lambda cte: (
+            UserGroup.objects.filter(id__in=user_group_ids)
+            .values(group_id=F("id"))
+            .union(
+                cte.join(NamedUserGroup, direct_supergroups=cte.col.group_id).values(
+                    group_id=F("id")
+                )
+            )
         )
     )
     return with_cte(cte, select=cte.join(UserGroup, id=cte.col.group_id))

Comment thread crates/ruff_python_formatter/src/expression/expr_lambda.rs Outdated
Comment on lines +132 to +135
} else if !preview {
write!(f, [dangling_comments(dangling_after_parameters)])?;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also move this into FormatBody, given that it's the same for both branches and that we moved the responsibility of formatting those comments into FormatBody anyway

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about something like this?

        write!(f, [token(":")])?;

        // In this context, a dangling comment is a comment between the `lambda` and the body.
        if dangling.is_empty() {
            write!(f, [space()])?;
        } else if !preview {
            write!(f, [dangling_comments(dangling)])?;
        }

        if !preview {
            return body.format().fmt(f);
        }

        let fmt_body = FormatBody { body, dangling };

        match self.layout {
            ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f),
            ExprLambdaLayout::Default => fmt_body.fmt(f),
        }

I see what you mean about putting the dangling comments into FormatBody, but this also seems nice because it moves both the preview and layout checks out of FormatBody.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks good too. For as long as we move it into a shared code path


if let Some(parameters) = parameters {
// In this context, a dangling comment can either be a comment between the `lambda` the
let dangling = if let Some(parameters) = parameters {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give this variable a more descriptive name (also in FormatBody). Like dangling where? And how are they different from dangling on line 33

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried a couple of different names like dangling_after_parameters and dangling_body_comments before settling on dangling_header_comments and adding a couple of comments to explain what it means. Hopefully that's a bit better.


struct FormatBody<'a> {
body: &'a Expr,
dangling: &'a [SourceComment],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give this field a better name and add a document what sort of comments they are (where should they be placed. Can they be end of line comments and own line comments?)

Comment thread crates/ruff_python_formatter/src/expression/expr_lambda.rs Outdated
// ```
//
// and alternate between own line and end of line.
let (after_parameters_end_of_line, leading_body_comments) = dangling.split_at(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after_parameters_end_of_line only contain # 1? The after_parameters part is a bit confusing because there are no parameters in the example but I think it's fine

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. I made this name trailing_header_comments since we format them with trailing_comments, and improved the docs here after verifying that the example is correct.

Comment on lines +319 to +347
// Calls and subscripts require special formatting because they have their own
// parentheses, but they can also have an arbitrary amount of text before the
// opening parenthesis. We want to avoid cases where we keep a long callable on the
// same line as the lambda parameters. For example, `db_evmtx...` in:
//
// ```py
// transaction_count = self._query_txs_for_range(
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
// chain_id=_chain_id,
// from_ts=from_ts,
// to_ts=to_ts,
// ),
// )
// ```
//
// should cause the whole lambda body to be parenthesized instead:
//
// ```py
// transaction_count = self._query_txs_for_range(
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: (
// db_evmtx.count_transactions_in_range(
// chain_id=_chain_id,
// from_ts=from_ts,
// to_ts=to_ts,
// )
// ),
// )
// ```
else if matches!(body, Expr::Call(_) | Expr::Subscript(_)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remind me again why we can't use this layout for call chains?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed it looking better this way. Here's a quick example without the call chain formatting:

         param(
-            lambda left, right: (
-                ibis.timestamp("2017-04-01")
-                .cast(dt.date)
-                .between(left, right)
-                .between(left, right)
-            ),
+            lambda left, right: ibis.timestamp("2017-04-01")
+            .cast(dt.date)
+            .between(left, right)
+            .between(left, right),
         ),
     ],

which is like our stable formatting.

@ntBre
Copy link
Copy Markdown
Contributor Author

ntBre commented Dec 12, 2025

To your first example, the .points is what causes us to wrap. Without that, we'll reuse the call parentheses:

def foo():
    compare_client_results(
        local_client,
        remote_client,
        lambda c: (
            c.query_points(
                COLLECTION_NAME,
                query=vector,
                using="sparse-image",
            ).points
        ),
    )

    compare_client_results(
        local_client,
        remote_client,
        lambda c: c.query_points(
            COLLECTION_NAME,
            query=vector,
            using="sparse-image",
        ),
    )

So I think it's okay. I think that's preferable to our stable formatting:

def foo():
    compare_client_results(
        local_client,
        remote_client,
        lambda c: c.query_points(
            COLLECTION_NAME,
            query=vector,
            using="sparse-image",
        ).points,
    )

Like the call chains, I think this becomes less clear especially when followed by arguments after the lambda, similar to the examples from #8179.

def foo():
    compare_client_results(
        local_client,
        remote_client,
        lambda c: c.query_points(
            COLLECTION_NAME,
            query=vector,
            using="sparse-image",
        ).points,
        somewhat_ambiguous_argument=might_be_part_of_the_lambda
    )

@ntBre
Copy link
Copy Markdown
Contributor Author

ntBre commented Dec 12, 2025

Thank you again for all of your help here! I've addressed the new comments and will plan to merge once CI passes, and I verify that the ecosystem checks haven't changed!

@ntBre ntBre merged commit 0ebdebd into main Dec 12, 2025
37 checks passed
@ntBre ntBre deleted the brent/indent-lambda-params branch December 12, 2025 17:02
dcreager added a commit that referenced this pull request Dec 13, 2025
* origin/main: (22 commits)
  [ty] Allow gradual lower/upper bounds in a constraint set (#21957)
  [ty] disallow explicit specialization of type variables themselves (#21938)
  [ty] Improve diagnostics for unsupported binary operations and unsupported augmented assignments (#21947)
  [ty] update implicit root docs (#21955)
  [ty] Enable even more goto-definition on inlay hints (#21950)
  Document known lambda formatting deviations from Black (#21954)
  [ty] fix hover type on named expression target (#21952)
  Bump benchmark dependencies (#21951)
  Keep lambda parameters on one line and parenthesize the body if it expands (#21385)
  [ty] Improve resolution of absolute imports in tests (#21817)
  [ty] Support `__all__ += submodule.__all__`
  [ty] Change frequency of invalid `__all__` debug message
  [ty] Add `KnownUnion::to_type()` (#21948)
  [ty] Classify `cls` as class parameter (#21944)
  [ty] Stabilize rename (#21940)
  [ty] Ignore `__all__` for document and workspace symbol requests
  [ty] Attach db to background request handler task (#21941)
  [ty] Fix outdated version in publish diagnostics after `didChange` (#21943)
  [ty] avoid fixpoint unioning of types containing current-cycle Divergent (#21910)
  [ty] improve bad specialization results & error messages (#21840)
  ...
nicopauss pushed a commit to Intersec/lib-common that referenced this pull request Apr 1, 2026
Note this update introcudes the new warning ISC004, which requires some
fixes in our code.

Released on 2026-01-22.

- Preserve required parentheses in lambda bodies ([#22747](https://github.com/astral-sh/ruff/pull/22747))
- Combine range suppression code diagnostics ([#22613](https://github.com/astral-sh/ruff/pull/22613))
- \[`airflow`] Second positional argument to `Asset`/`Dataset` should not be a dictionary (`AIR303`) ([#22453](https://github.com/astral-sh/ruff/pull/22453))
- \[`ruff`] Detect duplicate entries in `__all__` (`RUF068`) ([#22114](https://github.com/astral-sh/ruff/pull/22114))

- \[`pyupgrade`] Allow shadowing non-builtin bindings (`UP029`) ([#22749](https://github.com/astral-sh/ruff/pull/22749))
- \[`pyupgrade`] Apply `UP045` to string arguments of `typing.cast` ([#22320](https://github.com/astral-sh/ruff/pull/22320))
- \[`flake8-pie`] Detect duplicated declared class fields in `PIE794` ([#22717](https://github.com/astral-sh/ruff/pull/22717))

- \[`flake8-pyi`] Fix inconsistent handling of forward references for `__new__`, `__enter__`, `__aenter__` in `PYI034` ([#22798](https://github.com/astral-sh/ruff/pull/22798))
- \[`flake8-pytest-style`] Support `check` parameter in `PT011` ([#22725](https://github.com/astral-sh/ruff/pull/22725))
- \[`ruff`] Add exception for `ctypes.Structure._fields_` (`RUF012`) ([#22559](https://github.com/astral-sh/ruff/pull/22559))
- Many fixes are now marked unsafe if they would remove comments:
  - \[`flake8-bugbear`] [`B009`](https://github.com/astral-sh/ruff/pull/22656), [`B010`](https://github.com/astral-sh/ruff/pull/22657), [`B013`](https://github.com/astral-sh/ruff/pull/22658), [`B014`](https://github.com/astral-sh/ruff/pull/22659), [`B033`](https://github.com/astral-sh/ruff/pull/22632)
  - \[`flake8-simplify`] [`SIM910`](https://github.com/astral-sh/ruff/pull/22662), [`SIM911`](https://github.com/astral-sh/ruff/pull/22661)
  - \[`pyupgrade`] [`UP007`](https://github.com/astral-sh/ruff/pull/22772), [`UP039`](https://github.com/astral-sh/ruff/pull/22774), [`UP041`](https://github.com/astral-sh/ruff/pull/22773), [`UP045`](https://github.com/astral-sh/ruff/pull/22772)
  - \[`refurb`] [`FURB105`](https://github.com/astral-sh/ruff/pull/22767), [`FURB116`](https://github.com/astral-sh/ruff/pull/22681), [`FURB136`](https://github.com/astral-sh/ruff/pull/22680), [`FURB140`](https://github.com/astral-sh/ruff/pull/22679), [`FURB145`](https://github.com/astral-sh/ruff/pull/22670), [`FURB154`](https://github.com/astral-sh/ruff/pull/22669), [`FURB157`](https://github.com/astral-sh/ruff/pull/22668), [`FURB164`](https://github.com/astral-sh/ruff/pull/22667),[`FURB181`](https://github.com/astral-sh/ruff/pull/22666), [`FURB188`](https://github.com/astral-sh/ruff/pull/22665)
  - \[`ruff`] [`RUF019`](https://github.com/astral-sh/ruff/pull/22663), [`RUF020`](https://github.com/astral-sh/ruff/pull/22664)

- Add `--exit-non-zero-on-format` to formatter exit codes section ([#22761](https://github.com/astral-sh/ruff/pull/22761))
- Update contributing guide for adding a new rule ([#22779](https://github.com/astral-sh/ruff/pull/22779))
- \[`FastAPI`] Document fix safety for `FAST001` ([#22655](https://github.com/astral-sh/ruff/pull/22655))
- \[`flake8-async`] Tweak explanation to focus on latency/efficiency tradeoff (`ASYNC110`) ([#22715](https://github.com/astral-sh/ruff/pull/22715))
- \[`pandas-vet`] Make example error out-of-the-box (`PD002`) ([#22561](https://github.com/astral-sh/ruff/pull/22561))
- \[`refurb`] Make the example work out of box (`FURB101`) ([#22770](https://github.com/astral-sh/ruff/pull/22770))
- \[`refurb`] Make the example work out of box (`FURB103`) ([#22769](https://github.com/astral-sh/ruff/pull/22769))

- [@alejsdev](https://github.com/alejsdev)
- [@ntBre](https://github.com/ntBre)
- [@caiquejjx](https://github.com/caiquejjx)
- [@chirizxc](https://github.com/chirizxc)
- [@denyszhak](https://github.com/denyszhak)
- [@sjyangkevin](https://github.com/sjyangkevin)
- [@MeGaGiGaGon](https://github.com/MeGaGiGaGon)
- [@leandrobbraga](https://github.com/leandrobbraga)
- [@MichaReiser](https://github.com/MichaReiser)
- [@carljm](https://github.com/carljm)
- [@amyreese](https://github.com/amyreese)
- [@zsol](https://github.com/zsol)
- [@harupy](https://github.com/harupy)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                 | Platform                     | Checksum                                                                                                                  |
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.14/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2026-01-15.

This is a follow-up release to 0.14.12. Because of an issue publishing the WASM packages, there is no GitHub release or Git tag for 0.14.12, although the package was published to PyPI. The contents of the 0.14.13 release are identical to 0.14.12.

- \[`flake8-blind-except`] Allow more logging methods (`BLE001`) ([#22057](https://github.com/astral-sh/ruff/pull/22057))
- \[`ruff`] Respect `lint.pydocstyle.property-decorators` in `RUF066` ([#22515](https://github.com/astral-sh/ruff/pull/22515))

- Fix configuration path in `--show-settings` ([#22478](https://github.com/astral-sh/ruff/pull/22478))
- Respect `fmt: skip` for multiple statements on the same logical line ([#22119](https://github.com/astral-sh/ruff/pull/22119))

- \[`pydocstyle`] Update Rust crate imperative to v1.0.7 (`D401`) ([#22519](https://github.com/astral-sh/ruff/pull/22519))
- \[`isort`] Insert imports in alphabetical order (`I002`) ([#22493](https://github.com/astral-sh/ruff/pull/22493))

- Add llms.txt support for documentation ([#22463](https://github.com/astral-sh/ruff/pull/22463))
- Use prek in documentation and CI ([#22505](https://github.com/astral-sh/ruff/pull/22505))
- \[`flake8-pytest-style`] Add `check` parameter example to `PT017` docs ([#22546](https://github.com/astral-sh/ruff/pull/22546))
- \[`ruff`] Make example error out-of-the-box (`RUF103`) ([#22558](https://github.com/astral-sh/ruff/pull/22558))
- \[`ruff`] document `RUF100` trailing comment fix behavior ([#22479](https://github.com/astral-sh/ruff/pull/22479))

- wasm: Require explicit logging initialization ([#22587](https://github.com/astral-sh/ruff/pull/22587))

- [@terror](https://github.com/terror)
- [@harupy](https://github.com/harupy)
- [@Jkhall81](https://github.com/Jkhall81)
- [@dhruvmanila](https://github.com/dhruvmanila)
- [@lubaskinc0de](https://github.com/lubaskinc0de)
- [@zanieb](https://github.com/zanieb)
- [@MeGaGiGaGon](https://github.com/MeGaGiGaGon)
- [@charliermarsh](https://github.com/charliermarsh)
- [@renovate](https://github.com/renovate)
- [@dylwil3](https://github.com/dylwil3)
- [@MichaReiser](https://github.com/MichaReiser)
- [@11happy](https://github.com/11happy)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                 | Platform                     | Checksum                                                                                                                  |
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.13/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2026-01-08.

- Consolidate diagnostics for matched disable/enable suppression comments ([#22099](https://github.com/astral-sh/ruff/pull/22099))
- Report diagnostics for invalid/unmatched range suppression comments ([#21908](https://github.com/astral-sh/ruff/pull/21908))
- \[`airflow`] Passing positional argument into `airflow.lineage.hook.HookLineageCollector.create_asset` is not allowed (`AIR303`) ([#22046](https://github.com/astral-sh/ruff/pull/22046))
- \[`refurb`] Mark `FURB192` fix as always unsafe ([#22210](https://github.com/astral-sh/ruff/pull/22210))
- \[`ruff`] Add `non-empty-init-module` (`RUF067`) ([#22143](https://github.com/astral-sh/ruff/pull/22143))

- Fix GitHub format for multi-line diagnostics ([#22108](https://github.com/astral-sh/ruff/pull/22108))
- \[`flake8-unused-arguments`] Mark `**kwargs` in `TypeVar` as used (`ARG001`) ([#22214](https://github.com/astral-sh/ruff/pull/22214))

- Add `help:` subdiagnostics for several Ruff rules that can sometimes appear to disagree with `ty` ([#22331](https://github.com/astral-sh/ruff/pull/22331))
- \[`pylint`] Demote `PLW1510` fix to display-only ([#22318](https://github.com/astral-sh/ruff/pull/22318))
- \[`pylint`] Ignore identical members (`PLR1714`) ([#22220](https://github.com/astral-sh/ruff/pull/22220))
- \[`pylint`] Improve diagnostic range for `PLC0206` ([#22312](https://github.com/astral-sh/ruff/pull/22312))
- \[`ruff`] Improve fix title for `RUF102` invalid rule code ([#22100](https://github.com/astral-sh/ruff/pull/22100))
- \[`flake8-simplify`]: Avoid unnecessary builtins import for `SIM105` ([#22358](https://github.com/astral-sh/ruff/pull/22358))

- Allow Python 3.15 as valid `target-version` value in preview ([#22419](https://github.com/astral-sh/ruff/pull/22419))
- Check `required-version` before parsing rules ([#22410](https://github.com/astral-sh/ruff/pull/22410))
- Include configured `src` directories when resolving graphs ([#22451](https://github.com/astral-sh/ruff/pull/22451))

- Update `T201` suggestion to not use root logger to satisfy `LOG015` ([#22059](https://github.com/astral-sh/ruff/pull/22059))
- Fix `iter` example in unsafe fixes doc ([#22118](https://github.com/astral-sh/ruff/pull/22118))
- \[`flake8_print`] better suggestion for `basicConfig` in `T201` docs ([#22101](https://github.com/astral-sh/ruff/pull/22101))
- \[`pylint`] Restore the fix safety docs for `PLW0133` ([#22211](https://github.com/astral-sh/ruff/pull/22211))
- Fix Jupyter notebook discovery info for editors ([#22447](https://github.com/astral-sh/ruff/pull/22447))

- [@charliermarsh](https://github.com/charliermarsh)
- [@ntBre](https://github.com/ntBre)
- [@cenviity](https://github.com/cenviity)
- [@njhearp](https://github.com/njhearp)
- [@cbachhuber](https://github.com/cbachhuber)
- [@jelle-openai](https://github.com/jelle-openai)
- [@AlexWaygood](https://github.com/AlexWaygood)
- [@ValdonVitija](https://github.com/ValdonVitija)
- [@BurntSushi](https://github.com/BurntSushi)
- [@Jkhall81](https://github.com/Jkhall81)
- [@PeterJCLaw](https://github.com/PeterJCLaw)
- [@harupy](https://github.com/harupy)
- [@amyreese](https://github.com/amyreese)
- [@sjyangkevin](https://github.com/sjyangkevin)
- [@woodruffw](https://github.com/woodruffw)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                 | Platform                     | Checksum                                                                                                                  |
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.11/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2025-12-18.

- \[formatter] Fluent formatting of method chains ([#21369](https://github.com/astral-sh/ruff/pull/21369))
- \[formatter] Keep lambda parameters on one line and parenthesize the body if it expands ([#21385](https://github.com/astral-sh/ruff/pull/21385))
- \[`flake8-implicit-str-concat`] New rule to prevent implicit string concatenation in collections (`ISC004`) ([#21972](https://github.com/astral-sh/ruff/pull/21972))
- \[`flake8-use-pathlib`] Make fixes unsafe when types change in compound statements (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#22009](https://github.com/astral-sh/ruff/pull/22009))
- \[`refurb`] Extend support for `Path.open` (`FURB101`, `FURB103`) ([#21080](https://github.com/astral-sh/ruff/pull/21080))

- \[`pyupgrade`] Fix parsing named Unicode escape sequences (`UP032`) ([#21901](https://github.com/astral-sh/ruff/pull/21901))

- \[`eradicate`] Ignore `ruff:disable` and `ruff:enable` comments in `ERA001` ([#22038](https://github.com/astral-sh/ruff/pull/22038))
- \[`flake8-pytest-style`] Allow `match` and `check` keyword arguments without an expected exception type (`PT010`) ([#21964](https://github.com/astral-sh/ruff/pull/21964))
- \[syntax-errors] Annotated name cannot be global ([#20868](https://github.com/astral-sh/ruff/pull/20868))

- Add `uv` and `ty` to the Ruff README ([#21996](https://github.com/astral-sh/ruff/pull/21996))
- Document known lambda formatting deviations from Black ([#21954](https://github.com/astral-sh/ruff/pull/21954))
- Update `setup.md` ([#22024](https://github.com/astral-sh/ruff/pull/22024))
- \[`flake8-bandit`] Fix broken link (`S704`) ([#22039](https://github.com/astral-sh/ruff/pull/22039))

- Fix playground Share button showing "Copied!" before clipboard copy completes ([#21942](https://github.com/astral-sh/ruff/pull/21942))

- [@dylwil3](https://github.com/dylwil3)
- [@charliecloudberry](https://github.com/charliecloudberry)
- [@charliermarsh](https://github.com/charliermarsh)
- [@chirizxc](https://github.com/chirizxc)
- [@ntBre](https://github.com/ntBre)
- [@zanieb](https://github.com/zanieb)
- [@amyreese](https://github.com/amyreese)
- [@hauntsaninja](https://github.com/hauntsaninja)
- [@11happy](https://github.com/11happy)
- [@mahiro72](https://github.com/mahiro72)
- [@MichaReiser](https://github.com/MichaReiser)
- [@phongddo](https://github.com/phongddo)
- [@PeterJCLaw](https://github.com/PeterJCLaw)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                 | Platform                     | Checksum                                                                                                                  |
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.10/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2025-12-11.

- \[`ruff`] New `RUF100` diagnostics for unused range suppressions ([#21783](https://github.com/astral-sh/ruff/pull/21783))
- \[`pylint`] Detect subclasses of builtin exceptions (`PLW0133`) ([#21382](https://github.com/astral-sh/ruff/pull/21382))

- Fix comment placement in lambda parameters ([#21868](https://github.com/astral-sh/ruff/pull/21868))
- Skip over trivia tokens after re-lexing ([#21895](https://github.com/astral-sh/ruff/pull/21895))
- \[`flake8-bandit`] Fix false positive when using non-standard `CSafeLoader` path (S506). ([#21830](https://github.com/astral-sh/ruff/pull/21830))
- \[`flake8-bugbear`] Accept immutable slice default arguments (`B008`) ([#21823](https://github.com/astral-sh/ruff/pull/21823))

- \[`pydocstyle`] Suppress `D417` for parameters with `Unpack` annotations ([#21816](https://github.com/astral-sh/ruff/pull/21816))

- Use `memchr` for computing line indexes ([#21838](https://github.com/astral-sh/ruff/pull/21838))

- Document `*.pyw` is included by default in preview ([#21885](https://github.com/astral-sh/ruff/pull/21885))
- Document range suppressions, reorganize suppression docs ([#21884](https://github.com/astral-sh/ruff/pull/21884))
- Update mkdocs-material to 9.7.0 (Insiders now free) ([#21797](https://github.com/astral-sh/ruff/pull/21797))

- [@Avasam](https://github.com/Avasam)
- [@MichaReiser](https://github.com/MichaReiser)
- [@charliermarsh](https://github.com/charliermarsh)
- [@amyreese](https://github.com/amyreese)
- [@phongddo](https://github.com/phongddo)
- [@prakhar1144](https://github.com/prakhar1144)
- [@mahiro72](https://github.com/mahiro72)
- [@ntBre](https://github.com/ntBre)
- [@LoicRiegel](https://github.com/LoicRiegel)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                | Platform                     | Checksum                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.9/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2025-12-04.

- \[`flake8-bugbear`] Catch `yield` expressions within other statements (`B901`) ([#21200](https://github.com/astral-sh/ruff/pull/21200))
- \[`flake8-use-pathlib`] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#21440](https://github.com/astral-sh/ruff/pull/21440))

- Fix syntax error false positives for `await` outside functions ([#21763](https://github.com/astral-sh/ruff/pull/21763))
- \[`flake8-simplify`] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) ([#21479](https://github.com/astral-sh/ruff/pull/21479))

- Suggest using `--output-file` option in GitLab integration ([#21706](https://github.com/astral-sh/ruff/pull/21706))

- \[syntax-error] Default type parameter followed by non-default type parameter ([#21657](https://github.com/astral-sh/ruff/pull/21657))

- [@kieran-ryan](https://github.com/kieran-ryan)
- [@11happy](https://github.com/11happy)
- [@danparizher](https://github.com/danparizher)
- [@ntBre](https://github.com/ntBre)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                | Platform                     | Checksum                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.8/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2025-11-28.

- \[`flake8-bandit`] Handle string literal bindings in suspicious-url-open-usage (`S310`) ([#21469](https://github.com/astral-sh/ruff/pull/21469))
- \[`pylint`] Fix `PLR1708` false positives on nested functions ([#21177](https://github.com/astral-sh/ruff/pull/21177))
- \[`pylint`] Fix suppression for empty dict without tuple key annotation (`PLE1141`) ([#21290](https://github.com/astral-sh/ruff/pull/21290))
- \[`ruff`] Add rule `RUF066` to detect unnecessary class properties ([#21535](https://github.com/astral-sh/ruff/pull/21535))
- \[`ruff`] Catch more dummy variable uses (`RUF052`) ([#19799](https://github.com/astral-sh/ruff/pull/19799))

- \[server] Set severity for non-rule diagnostics ([#21559](https://github.com/astral-sh/ruff/pull/21559))
- \[`flake8-implicit-str-concat`] Avoid invalid fix in (`ISC003`) ([#21517](https://github.com/astral-sh/ruff/pull/21517))
- \[`parser`] Fix panic when parsing IPython escape command expressions ([#21480](https://github.com/astral-sh/ruff/pull/21480))

- Show partial fixability indicator in statistics output ([#21513](https://github.com/astral-sh/ruff/pull/21513))

- [@mikeleppane](https://github.com/mikeleppane)
- [@senekor](https://github.com/senekor)
- [@ShaharNaveh](https://github.com/ShaharNaveh)
- [@JumboBear](https://github.com/JumboBear)
- [@prakhar1144](https://github.com/prakhar1144)
- [@tsvikas](https://github.com/tsvikas)
- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)
- [@AlexWaygood](https://github.com/AlexWaygood)
- [@MichaReiser](https://github.com/MichaReiser)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                | Platform                     | Checksum                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-i686-pc-windows-msvc.zip.sha256)              |
| [ruff-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-pc-windows-msvc.zip)                      | x64 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-pc-windows-msvc.zip.sha256)            |
| [ruff-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-unknown-linux-gnu.tar.gz)           | ARM64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256)      |
| [ruff-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-i686-unknown-linux-gnu.tar.gz)                 | x86 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-i686-unknown-linux-gnu.tar.gz.sha256)         |
| [ruff-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-powerpc64-unknown-linux-gnu.tar.gz)       | PPC64 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-powerpc64le-unknown-linux-gnu.tar.gz)   | PPC64LE Linux                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256)  |
| [ruff-riscv64gc-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-riscv64gc-unknown-linux-gnu.tar.gz)       | RISCV Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256)    |
| [ruff-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-s390x-unknown-linux-gnu.tar.gz)               | S390x Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-s390x-unknown-linux-gnu.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-unknown-linux-gnu.tar.gz)            | x64 Linux                    | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-unknown-linux-gnu.tar.gz.sha256)       |
| [ruff-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-armv7-unknown-linux-gnueabihf.tar.gz)   | ARMv7 Linux                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-armv7-unknown-linux-gnueabihf.tar.gz.sha256)  |
| [ruff-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-unknown-linux-musl.tar.gz)         | ARM64 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-aarch64-unknown-linux-musl.tar.gz.sha256)     |
| [ruff-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-i686-unknown-linux-musl.tar.gz)               | x86 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-i686-unknown-linux-musl.tar.gz.sha256)        |
| [ruff-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-unknown-linux-musl.tar.gz)          | x64 MUSL Linux               | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-x86_64-unknown-linux-musl.tar.gz.sha256)      |
| [ruff-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-arm-unknown-linux-musleabihf.tar.gz)     | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-arm-unknown-linux-musleabihf.tar.gz.sha256)   |
| [ruff-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux             | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.7/ruff-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

Released on 2025-11-21.

- \[`flake8-bandit`] Support new PySNMP API paths (`S508`, `S509`) ([#21374](https://github.com/astral-sh/ruff/pull/21374))

- Adjust own-line comment placement between branches ([#21185](https://github.com/astral-sh/ruff/pull/21185))
- Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value ([#20418](https://github.com/astral-sh/ruff/pull/20418))
- Fix panic when formatting comments in unary expressions ([#21501](https://github.com/astral-sh/ruff/pull/21501))
- Respect `fmt: skip` for compound statements on a single line ([#20633](https://github.com/astral-sh/ruff/pull/20633))
- \[`refurb`] Fix `FURB103` autofix ([#21454](https://github.com/astral-sh/ruff/pull/21454))
- \[`ruff`] Fix false positive for complex conversion specifiers in `logging-eager-conversion` (`RUF065`) ([#21464](https://github.com/astral-sh/ruff/pull/21464))

- \[`ruff`] Avoid false positive on `ClassVar` reassignment (`RUF012`) ([#21478](https://github.com/astral-sh/ruff/pull/21478))

- Render hyperlinks for lint errors ([#21514](https://github.com/astral-sh/ruff/pull/21514))
- Add a `ruff analyze` option to skip over imports in `TYPE_CHECKING` blocks ([#21472](https://github.com/astral-sh/ruff/pull/21472))

- Limit `eglot-format` hook to eglot-managed Python buffers ([#21459](https://github.com/astral-sh/ruff/pull/21459))
- Mention `force-exclude` in "Configuration > Python file discovery" ([#21500](https://github.com/astral-sh/ruff/pull/21500))

- [@ntBre](https://github.com/ntBre)
- [@dylwil3](https://github.com/dylwil3)
- [@gauthsvenkat](https://github.com/gauthsvenkat)
- [@MichaReiser](https://github.com/MichaReiser)
- [@thamer](https://github.com/thamer)
- [@Ruchir28](https://github.com/Ruchir28)
- [@thejcannon](https://github.com/thejcannon)
- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-installer.sh | sh
```

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-installer.ps1 | iex"
```

| File                                                                                                                                                | Platform                     | Checksum                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [ruff-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-aarch64-apple-darwin.tar.gz)                     | Apple Silicon macOS          | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-aarch64-apple-darwin.tar.gz.sha256)           |
| [ruff-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-x86_64-apple-darwin.tar.gz)                      | Intel macOS                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-x86_64-apple-darwin.tar.gz.sha256)            |
| [ruff-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-aarch64-pc-windows-msvc.zip)                     | ARM64 Windows                | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-aarch64-pc-windows-msvc.zip.sha256)           |
| [ruff-i686-pc-windows-msvc.zip](https://github.com/astral-sh/ruff/releases/download/0.14.6/ruff-i686-pc-windows-msvc.zip)                           | x86 Windows                  | [checksum](https://github.com/astral-sh/ruff/releases/download/0.14.6/ru…
nicopauss pushed a commit to Intersec/lib-common that referenced this pull request Apr 1, 2026
Released on 2026-03-19.

- Display output severity in preview ([#23845](astral-sh/ruff#23845))
- Don't show `noqa` hover for non-Python documents ([#24040](astral-sh/ruff#24040))

- \[`pycodestyle`] Recognize `pyrefly:` as a pragma comment (`E501`) ([#24019](astral-sh/ruff#24019))

- Don't return code actions for non-Python documents ([#23905](astral-sh/ruff#23905))

- Add company AI policy to contributing guide ([#24021](astral-sh/ruff#24021))
- Document editor features for Markdown code formatting ([#23924](astral-sh/ruff#23924))
- \[`pylint`] Improve phrasing (`PLC0208`) ([#24033](astral-sh/ruff#24033))

- Use PEP 639 license information ([#19661](astral-sh/ruff#19661))

- [@tmimmanuel](https://github.com/tmimmanuel)
- [@DimitriPapadopoulos](https://github.com/DimitriPapadopoulos)
- [@amyreese](https://github.com/amyreese)
- [@statxc](https://github.com/statxc)
- [@dylwil3](https://github.com/dylwil3)
- [@hunterhogan](https://github.com/hunterhogan)
- [@renovate](https://github.com/renovate)

Released on 2026-03-12.

- Add support for `lazy` import parsing ([#23755](astral-sh/ruff#23755))
- Add support for star-unpacking of comprehensions (PEP 798) ([#23788](astral-sh/ruff#23788))
- Reject semantic syntax errors for lazy imports ([#23757](astral-sh/ruff#23757))
- Drop a few rules from the preview default set ([#23879](astral-sh/ruff#23879))
- \[`airflow`] Flag `Variable.get()` calls outside of task execution context (`AIR003`) ([#23584](astral-sh/ruff#23584))
- \[`airflow`] Flag runtime-varying values in DAG/task constructor arguments (`AIR304`) ([#23631](astral-sh/ruff#23631))
- \[`flake8-bugbear`] Implement `delattr-with-constant` (`B043`) ([#23737](astral-sh/ruff#23737))
- \[`flake8-tidy-imports`] Add `TID254` to enforce lazy imports ([#23777](astral-sh/ruff#23777))
- \[`flake8-tidy-imports`] Allow users to ban lazy imports with `TID254` ([#23847](astral-sh/ruff#23847))
- \[`isort`] Retain `lazy` keyword when sorting imports ([#23762](astral-sh/ruff#23762))
- \[`pyupgrade`] Add `from __future__ import annotations` automatically (`UP006`) ([#23260](astral-sh/ruff#23260))
- \[`refurb`] Support `newline` parameter in `FURB101` for Python 3.13+ ([#23754](astral-sh/ruff#23754))
- \[`ruff`] Add `os-path-commonprefix` (`RUF071`) ([#23814](astral-sh/ruff#23814))
- \[`ruff`] Add unsafe fix for os-path-commonprefix (`RUF071`) ([#23852](astral-sh/ruff#23852))
- \[`ruff`] Limit `RUF036` to typing contexts; make it unsafe for non-typing-only ([#23765](astral-sh/ruff#23765))
- \[`ruff`] Use starred unpacking for `RUF017` in Python 3.15+ ([#23789](astral-sh/ruff#23789))

- Fix `--add-noqa` creating unwanted leading whitespace ([#23773](astral-sh/ruff#23773))
- Fix `--add-noqa` breaking shebangs ([#23577](astral-sh/ruff#23577))
- \[formatter] Fix lambda body formatting for multiline calls and subscripts ([#23866](astral-sh/ruff#23866))
- \[formatter] Preserve required annotation parentheses in annotated assignments ([#23865](astral-sh/ruff#23865))
- \[formatter] Preserve type-expression parentheses in the formatter ([#23867](astral-sh/ruff#23867))
- \[`flake8-annotations`] Fix stack overflow in `ANN401` on quoted annotations with escape sequences ([#23912](astral-sh/ruff#23912))
- \[`pep8-naming`] Check naming conventions in `match` pattern bindings (`N806`, `N815`, `N816`) ([#23899](astral-sh/ruff#23899))
- \[`perflint`] Fix comment duplication in fixes (`PERF401`, `PERF403`) ([#23729](astral-sh/ruff#23729))
- \[`pyupgrade`] Properly trigger `super` change in nested class (`UP008`) ([#22677](astral-sh/ruff#22677))
- \[`ruff`] Avoid syntax errors in `RUF036` fixes ([#23764](astral-sh/ruff#23764))

- \[`flake8-bandit`] Flag `S501` with `requests.request` ([#23873](astral-sh/ruff#23873))
- \[`flake8-executable`] Fix WSL detection in non-Docker containers ([#22879](astral-sh/ruff#22879))
- \[`flake8-print`] Ignore `pprint` calls with `stream=` ([#23787](astral-sh/ruff#23787))

- Update docs for Markdown code block formatting ([#23871](astral-sh/ruff#23871))
- \[`flake8-bugbear`] Fix misleading description for `B904` ([#23731](astral-sh/ruff#23731))

- [@zsol](https://github.com/zsol)
- [@carljm](https://github.com/carljm)
- [@ntBre](https://github.com/ntBre)
- [@Bortlesboat](https://github.com/Bortlesboat)
- [@sososonia-cyber](https://github.com/sososonia-cyber)
- [@chirizxc](https://github.com/chirizxc)
- [@leandrobbraga](https://github.com/leandrobbraga)
- [@11happy](https://github.com/11happy)
- [@Acelogic](https://github.com/Acelogic)
- [@anishgirianish](https://github.com/anishgirianish)
- [@amyreese](https://github.com/amyreese)
- [@xvchris](https://github.com/xvchris)
- [@charliermarsh](https://github.com/charliermarsh)
- [@getehen](https://github.com/getehen)
- [@Dev-iL](https://github.com/Dev-iL)

Released on 2026-03-05.

- Discover Markdown files by default in preview mode ([#23434](astral-sh/ruff#23434))
- \[`perflint`] Extend `PERF102` to comprehensions and generators ([#23473](astral-sh/ruff#23473))
- \[`refurb`] Fix `FURB101` and `FURB103` false positives when I/O variable is used later ([#23542](astral-sh/ruff#23542))
- \[`ruff`] Add fix for `none-not-at-end-of-union` (`RUF036`) ([#22829](astral-sh/ruff#22829))
- \[`ruff`] Fix false positive for `re.split` with empty string pattern (`RUF055`) ([#23634](astral-sh/ruff#23634))

- \[`fastapi`] Handle callable class dependencies with `__call__` method (`FAST003`) ([#23553](astral-sh/ruff#23553))
- \[`pydocstyle`] Fix numpy section ordering (`D420`) ([#23685](astral-sh/ruff#23685))
- \[`pyflakes`] Fix false positive for names shadowing re-exports (`F811`) ([#23356](astral-sh/ruff#23356))
- \[`pyupgrade`] Avoid inserting redundant `None` elements in `UP045` ([#23459](astral-sh/ruff#23459))

- Document extension mapping for Markdown code formatting ([#23574](astral-sh/ruff#23574))
- Update default Python version examples ([#23605](astral-sh/ruff#23605))

- Publish releases to Astral mirror ([#23616](astral-sh/ruff#23616))

- [@amyreese](https://github.com/amyreese)
- [@stakeswky](https://github.com/stakeswky)
- [@chirizxc](https://github.com/chirizxc)
- [@anishgirianish](https://github.com/anishgirianish)
- [@bxff](https://github.com/bxff)
- [@zsol](https://github.com/zsol)
- [@charliermarsh](https://github.com/charliermarsh)
- [@ntBre](https://github.com/ntBre)
- [@kar-ganap](https://github.com/kar-ganap)

Released on 2026-02-26.

This is a follow-up release to 0.15.3 that resolves a panic when the new rule `PLR1712` was enabled with any rule that analyzes definitions, such as many of the `ANN` or `D` rules.

- Fix panic on access to definitions after analyzing definitions ([#23588](astral-sh/ruff#23588))
- \[`pyflakes`] Suppress false positive in `F821` for names used before `del` in stub files ([#23550](astral-sh/ruff#23550))

- Clarify first-party import detection in Ruff ([#23591](astral-sh/ruff#23591))
- Fix incorrect `import-heading` example ([#23568](astral-sh/ruff#23568))

- [@stakeswky](https://github.com/stakeswky)
- [@ntBre](https://github.com/ntBre)
- [@thejcannon](https://github.com/thejcannon)
- [@GeObts](https://github.com/GeObts)

Released on 2026-02-26.

- Drop explicit support for `.qmd` file extension ([#23572](astral-sh/ruff#23572))

  This can now be enabled instead by setting the [`extension`](https://docs.astral.sh/ruff/settings/#extension) option:

  ```toml
  # ruff.toml
  extension = { qmd = "markdown" }

  # pyproject.toml
  [tool.ruff]
  extension = { qmd = "markdown" }
  ```

- Include configured extensions in file discovery ([#23400](astral-sh/ruff#23400))

- \[`flake8-bandit`] Allow suspicious imports in `TYPE_CHECKING` blocks (`S401`-`S415`) ([#23441](astral-sh/ruff#23441))

- \[`flake8-bugbear`] Allow `B901` in pytest hook wrappers ([#21931](astral-sh/ruff#21931))

- \[`flake8-import-conventions`] Add missing conventions from upstream (`ICN001`, `ICN002`) ([#21373](astral-sh/ruff#21373))

- \[`pydocstyle`] Add rule to enforce docstring section ordering (`D420`) ([#23537](astral-sh/ruff#23537))

- \[`pylint`] Implement `swap-with-temporary-variable` (`PLR1712`) ([#22205](astral-sh/ruff#22205))

- \[`ruff`] Add `unnecessary-assign-before-yield` (`RUF070`) ([#23300](astral-sh/ruff#23300))

- \[`ruff`] Support file-level noqa in `RUF102` ([#23535](astral-sh/ruff#23535))

- \[`ruff`] Suppress diagnostic for invalid f-strings before Python 3.12 (`RUF027`) ([#23480](astral-sh/ruff#23480))

- \[`flake8-bandit`] Don't flag `BaseLoader`/`CBaseLoader` as unsafe (`S506`) ([#23510](astral-sh/ruff#23510))

- Avoid infinite loop between `I002` and `PYI025` ([#23352](astral-sh/ruff#23352))
- \[`pyflakes`] Fix false positive for `@overload` from `lint.typing-modules` (`F811`) ([#23357](astral-sh/ruff#23357))
- \[`pyupgrade`] Fix false positive for `TypeVar` default before Python 3.12 (`UP046`) ([#23540](astral-sh/ruff#23540))
- \[`pyupgrade`] Fix handling of `\N` in raw strings (`UP032`) ([#22149](astral-sh/ruff#22149))

- Render sub-diagnostics in the GitHub output format ([#23455](astral-sh/ruff#23455))

- \[`flake8-bugbear`] Tag certain `B007` diagnostics as unnecessary ([#23453](astral-sh/ruff#23453))

- \[`ruff`] Ignore unknown rule codes in `RUF100` ([#23531](astral-sh/ruff#23531))

  These are now flagged by [`RUF102`](https://docs.astral.sh/ruff/rules/invalid-rule-code/) instead.

- Fix missing settings links for several linters ([#23519](astral-sh/ruff#23519))
- Update isort action comments heading ([#23515](astral-sh/ruff#23515))
- \[`pydocstyle`] Fix double comma in description of `D404` ([#23440](astral-sh/ruff#23440))

- Update the Python module (notably `find_ruff_bin`) for parity with uv ([#23406](astral-sh/ruff#23406))

- [@zanieb](https://github.com/zanieb)
- [@o1x3](https://github.com/o1x3)
- [@assadyousuf](https://github.com/assadyousuf)
- [@kar-ganap](https://github.com/kar-ganap)
- [@denyszhak](https://github.com/denyszhak)
- [@amyreese](https://github.com/amyreese)
- [@carljm](https://github.com/carljm)
- [@anishgirianish](https://github.com/anishgirianish)
- [@Bnyro](https://github.com/Bnyro)
- [@danparizher](https://github.com/danparizher)
- [@ntBre](https://github.com/ntBre)
- [@gcomneno](https://github.com/gcomneno)
- [@jaap3](https://github.com/jaap3)
- [@stakeswky](https://github.com/stakeswky)

Released on 2026-02-19.

- Expand the default rule set ([#23385](astral-sh/ruff#23385))

  In preview, Ruff now enables a significantly expanded default rule set of 412
  rules, up from the stable default set of 59 rules. The new rules are mostly a
  superset of the stable defaults, with the exception of these rules, which are
  removed from the preview defaults:

  - [`multiple-imports-on-one-line`](https://docs.astral.sh/ruff/rules/multiple-imports-on-one-line) (`E401`)
  - [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file) (`E402`)
  - [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file) (`E701`)
  - [`multiple-statements-on-one-line-semicolon`](https://docs.astral.sh/ruff/rules/multiple-statements-on-one-line-semicolon) (`E702`)
  - [`useless-semicolon`](https://docs.astral.sh/ruff/rules/useless-semicolon) (`E703`)
  - [`none-comparison`](https://docs.astral.sh/ruff/rules/none-comparison) (`E711`)
  - [`true-false-comparison`](https://docs.astral.sh/ruff/rules/true-false-comparison) (`E712`)
  - [`not-in-test`](https://docs.astral.sh/ruff/rules/not-in-test) (`E713`)
  - [`not-is-test`](https://docs.astral.sh/ruff/rules/not-is-test) (`E714`)
  - [`type-comparison`](https://docs.astral.sh/ruff/rules/type-comparison) (`E721`)
  - [`lambda-assignment`](https://docs.astral.sh/ruff/rules/lambda-assignment) (`E731`)
  - [`ambiguous-variable-name`](https://docs.astral.sh/ruff/rules/ambiguous-variable-name) (`E741`)
  - [`ambiguous-class-name`](https://docs.astral.sh/ruff/rules/ambiguous-class-name) (`E742`)
  - [`ambiguous-function-name`](https://docs.astral.sh/ruff/rules/ambiguous-function-name) (`E743`)
  - [`undefined-local-with-import-star`](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star) (`F403`)
  - [`undefined-local-with-import-star-usage`](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star-usage) (`F405`)
  - [`undefined-local-with-nested-import-star-usage`](https://docs.astral.sh/ruff/rules/undefined-local-with-nested-import-star-usage) (`F406`)
  - [`forward-annotation-syntax-error`](https://docs.astral.sh/ruff/rules/forward-annotation-syntax-error) (`F722`)

  If you use preview and prefer the old defaults, you can restore them with
  configuration like:

  ```toml

  # ruff.toml

  [lint]
  select = ["E4", "E7", "E9", "F"]

  # pyproject.toml

  [tool.ruff.lint]
  select = ["E4", "E7", "E9", "F"]
  ```

  If you do give them a try, feel free to share your feedback in the [GitHub
  discussion](astral-sh/ruff#23203)!

- \[`flake8-pyi`] Also check string annotations (`PYI041`) ([#19023](astral-sh/ruff#19023))

- \[`flake8-async`] Fix `in_async_context` logic ([#23426](astral-sh/ruff#23426))
- \[`ruff`] Fix for `RUF102` should delete entire comment ([#23380](astral-sh/ruff#23380))
- \[`ruff`] Suppress diagnostic for strings with backslashes in interpolations before Python 3.12 (`RUF027`) ([#21069](astral-sh/ruff#21069))
- \[`flake8-bugbear`] Fix `B023` false positive for immediately-invoked lambdas ([#23294](astral-sh/ruff#23294))
- \[parser] Fix false syntax error for match-like annotated assignments ([#23297](astral-sh/ruff#23297))
- \[parser] Fix indentation tracking after line continuations ([#23417](astral-sh/ruff#23417))

- \[`flake8-executable`] Allow global flags in uv shebangs (`EXE003`) ([#22582](astral-sh/ruff#22582))
- \[`pyupgrade`] Fix handling of `typing.{io,re}` (`UP035`) ([#23131](astral-sh/ruff#23131))
- \[`ruff`] Detect `PLC0207` on chained `str.split()` calls ([#23275](astral-sh/ruff#23275))

- Remove invalid inline `noqa` warning ([#23270](astral-sh/ruff#23270))

- Add extension mapping to configuration file options ([#23384](astral-sh/ruff#23384))

- Add `Q004` to the list of conflicting rules ([#23340](astral-sh/ruff#23340))
- \[`ruff`] Expand `lint.external` docs and add sub-diagnostic (`RUF100`, `RUF102`) ([#23268](astral-sh/ruff#23268))

- [@dylwil3](https://github.com/dylwil3)
- [@Jkhall81](https://github.com/Jkhall81)
- [@danparizher](https://github.com/danparizher)
- [@dhruvmanila](https://github.com/dhruvmanila)
- [@harupy](https://github.com/harupy)
- [@ngnpope](https://github.com/ngnpope)
- [@amyreese](https://github.com/amyreese)
- [@kar-ganap](https://github.com/kar-ganap)
- [@robsdedude](https://github.com/robsdedude)
- [@shaanmajid](https://github.com/shaanmajid)
- [@ntBre](https://github.com/ntBre)
- [@toslunar](https://github.com/toslunar)

Released on 2026-02-12.

- \[`airflow`] Add ruff rules to catch deprecated Airflow imports for Airflow 3.1 (`AIR321`) ([#22376](astral-sh/ruff#22376))
- \[`airflow`] Third positional parameter not named `ti_key` should be flagged for `BaseOperatorLink.get_link` (`AIR303`) ([#22828](astral-sh/ruff#22828))
- \[`flake8-gettext`] Fix false negatives for plural argument of `ngettext` (`INT001`, `INT002`, `INT003`) ([#21078](astral-sh/ruff#21078))
- \[`pyflakes`] Fix infinite loop in preview fix for `unused-import` (`F401`) ([#23038](astral-sh/ruff#23038))
- \[`pygrep-hooks`] Detect non-existent mock methods in standalone expressions (`PGH005`) ([#22830](astral-sh/ruff#22830))
- \[`pylint`] Allow dunder submodules and improve diagnostic range (`PLC2701`) ([#22804](astral-sh/ruff#22804))
- \[`pyupgrade`] Improve diagnostic range for tuples (`UP024`) ([#23013](astral-sh/ruff#23013))
- \[`refurb`] Check subscripts in tuple do not use lambda parameters in `reimplemented-operator` (`FURB118`) ([#23079](astral-sh/ruff#23079))
- \[`ruff`] Detect mutable defaults in `field` calls (`RUF008`) ([#23046](astral-sh/ruff#23046))
- \[`ruff`] Ignore std `cmath.inf` (`RUF069`) ([#23120](astral-sh/ruff#23120))
- \[`ruff`] New rule `float-equality-comparison` (`RUF069`) ([#20585](astral-sh/ruff#20585))
- Don't format unlabeled Markdown code blocks ([#23106](astral-sh/ruff#23106))
- Markdown formatting support in LSP ([#23063](astral-sh/ruff#23063))
- Support Quarto Markdown language markers ([#22947](astral-sh/ruff#22947))
- Support formatting `pycon` Markdown code blocks ([#23112](astral-sh/ruff#23112))
- Use extension mapping to select Markdown code block language ([#22934](astral-sh/ruff#22934))

- Avoid false positive for undefined variables in `FAST001` ([#23224](astral-sh/ruff#23224))
- Avoid introducing syntax errors for `FAST003` autofix ([#23227](astral-sh/ruff#23227))
- Avoid suggesting `InitVar` for `__post_init__` that references PEP 695 type parameters ([#23226](astral-sh/ruff#23226))
- Deduplicate type variables in generic functions ([#23225](astral-sh/ruff#23225))
- Fix exception handler parenthesis removal for Python 3.14+ ([#23126](astral-sh/ruff#23126))
- Fix f-string middle panic when parsing t-strings ([#23232](astral-sh/ruff#23232))
- Wrap `RUF020` target for multiline fixes ([#23210](astral-sh/ruff#23210))
- Wrap `UP007` target for multiline fixes ([#23208](astral-sh/ruff#23208))
- Fix missing diagnostics for last range suppression in file ([#23242](astral-sh/ruff#23242))
- \[`pyupgrade`] Fix syntax error on string with newline escape and comment (`UP037`) ([#22968](astral-sh/ruff#22968))

- Use `ruff` instead of `Ruff` as the program name in GitHub output format ([#23240](astral-sh/ruff#23240))
- \[`PT006`] Fix syntax error when unpacking nested tuples in `parametrize` fixes ([#22441](astral-sh/ruff#22441)) ([#22464](astral-sh/ruff#22464))
- \[`airflow`] Catch deprecated attribute access from context key for Airflow 3.0 (`AIR301`) ([#22850](astral-sh/ruff#22850))
- \[`airflow`] Capture deprecated arguments and a decorator (`AIR301`) ([#23170](astral-sh/ruff#23170))
- \[`flake8-boolean-trap`] Add `multiprocessing.Value` to excluded functions for `FBT003` ([#23010](astral-sh/ruff#23010))
- \[`flake8-bugbear`] Add a secondary annotation showing the previous occurrence (`B033`) ([#22634](astral-sh/ruff#22634))
- \[`flake8-type-checking`] Add sub-diagnostic showing the runtime use of an annotation (`TC004`) ([#23091](astral-sh/ruff#23091))
- \[`isort`] Support configurable import section heading comments ([#23151](astral-sh/ruff#23151))
- \[`ruff`] Improve the diagnostic for `RUF012` ([#23202](astral-sh/ruff#23202))

- Suppress diagnostic output for `format --check --silent` ([#17736](astral-sh/ruff#17736))

- Add tabbed shell completion documentation ([#23169](astral-sh/ruff#23169))
- Explain how to enable Markdown formatting for pre-commit hook ([#23077](astral-sh/ruff#23077))
- Fixed import in `runtime-evaluated-decorators` example ([#23187](astral-sh/ruff#23187))
- Update ruff server contributing guide ([#23060](astral-sh/ruff#23060))

- Exclude WASM artifacts from GitHub releases ([#23221](astral-sh/ruff#23221))

- [@mkniewallner](https://github.com/mkniewallner)
- [@bxff](https://github.com/bxff)
- [@dylwil3](https://github.com/dylwil3)
- [@Avasam](https://github.com/Avasam)
- [@amyreese](https://github.com/amyreese)
- [@charliermarsh](https://github.com/charliermarsh)
- [@Alex-ley-scrub](https://github.com/Alex-ley-scrub)
- [@Kalmaegi](https://github.com/Kalmaegi)
- [@danparizher](https://github.com/danparizher)
- [@AiyionPrime](https://github.com/AiyionPrime)
- [@eureka928](https://github.com/eureka928)
- [@11happy](https://github.com/11happy)
- [@Jkhall81](https://github.com/Jkhall81)
- [@chirizxc](https://github.com/chirizxc)
- [@leandrobbraga](https://github.com/leandrobbraga)
- [@tvatter](https://github.com/tvatter)
- [@anishgirianish](https://github.com/anishgirianish)
- [@shaanmajid](https://github.com/shaanmajid)
- [@ntBre](https://github.com/ntBre)
- [@sjyangkevin](https://github.com/sjyangkevin)

Released on 2026-02-03.

Check out the [blog post](https://astral.sh/blog/ruff-v0.15.0) for a migration
guide and overview of the changes!

- Ruff now formats your code according to the 2026 style guide. See the formatter section below or in the blog post for a detailed list of changes.

- The linter now supports block suppression comments. For example, to suppress `N803` for all parameters in this function:

  ```python
  # ruff: disable[N803]
  def foo(
      legacyArg1,
      legacyArg2,
      legacyArg3,
      legacyArg4,
  ): ...
  # ruff: enable[N803]
  ```

  See the [documentation](https://docs.astral.sh/ruff/linter/#block-level) for more details.

- The `ruff:alpine` Docker image is now based on Alpine 3.23 (up from 3.21).

- The `ruff:debian` and `ruff:debian-slim` Docker images are now based on Debian 13 "Trixie" instead of Debian 12 "Bookworm."

- Binaries for the `ppc64` (64-bit big-endian PowerPC) architecture are no longer included in our releases. It should still be possible to build Ruff manually for this platform, if needed.

- Ruff now resolves all `extend`ed configuration files before falling back on a default Python version.

The following rules have been stabilized and are no longer in preview:

- [`blocking-http-call-httpx-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-http-call-httpx-in-async-function)
  (`ASYNC212`)
- [`blocking-path-method-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-path-method-in-async-function)
  (`ASYNC240`)
- [`blocking-input-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-input-in-async-function)
  (`ASYNC250`)
- [`map-without-explicit-strict`](https://docs.astral.sh/ruff/rules/map-without-explicit-strict)
  (`B912`)
- [`if-exp-instead-of-or-operator`](https://docs.astral.sh/ruff/rules/if-exp-instead-of-or-operator)
  (`FURB110`)
- [`single-item-membership-test`](https://docs.astral.sh/ruff/rules/single-item-membership-test)
  (`FURB171`)
- [`missing-maxsplit-arg`](https://docs.astral.sh/ruff/rules/missing-maxsplit-arg) (`PLC0207`)
- [`unnecessary-lambda`](https://docs.astral.sh/ruff/rules/unnecessary-lambda) (`PLW0108`)
- [`unnecessary-empty-iterable-within-deque-call`](https://docs.astral.sh/ruff/rules/unnecessary-empty-iterable-within-deque-call)
  (`RUF037`)
- [`in-empty-collection`](https://docs.astral.sh/ruff/rules/in-empty-collection) (`RUF060`)
- [`legacy-form-pytest-raises`](https://docs.astral.sh/ruff/rules/legacy-form-pytest-raises)
  (`RUF061`)
- [`non-octal-permissions`](https://docs.astral.sh/ruff/rules/non-octal-permissions) (`RUF064`)
- [`invalid-rule-code`](https://docs.astral.sh/ruff/rules/invalid-rule-code) (`RUF102`)
- [`invalid-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-suppression-comment)
  (`RUF103`)
- [`unmatched-suppression-comment`](https://docs.astral.sh/ruff/rules/unmatched-suppression-comment)
  (`RUF104`)
- [`replace-str-enum`](https://docs.astral.sh/ruff/rules/replace-str-enum) (`UP042`)

The following behaviors have been stabilized:

- The `--output-format` flag is now respected when running Ruff in `--watch` mode, and the `full` output format is now used by default, matching the regular CLI output.
- [`builtin-attribute-shadowing`](https://docs.astral.sh/ruff/rules/builtin-attribute-shadowing/) (`A003`) now detects the use of shadowed built-in names in additional contexts like decorators, default arguments, and other attribute definitions.
- [`duplicate-union-member`](https://docs.astral.sh/ruff/rules/duplicate-union-member/) (`PYI016`) now considers `typing.Optional` when searching for duplicate union members.
- [`split-static-string`](https://docs.astral.sh/ruff/rules/split-static-string/) (`SIM905`) now offers an autofix when the `maxsplit` argument is provided, even without a `sep` argument.
- [`dict-get-with-none-default`](https://docs.astral.sh/ruff/rules/dict-get-with-none-default/) (`SIM910`) now applies to more types of key expressions.
- [`super-call-with-parameters`](https://docs.astral.sh/ruff/rules/super-call-with-parameters/) (`UP008`) now has a safe fix when it will not delete comments.
- [`unnecessary-default-type-args`](https://docs.astral.sh/ruff/rules/unnecessary-default-type-args/) (`UP043`) now applies to stub (`.pyi`) files on Python versions before 3.13.

This release introduces the new 2026 style guide, with the following changes:

- Lambda parameters are now kept on the same line and lambda bodies will be parenthesized to let
  them break across multiple lines ([#21385](astral-sh/ruff#21385))
- Parentheses around tuples of exceptions in `except` clauses will now be removed on Python 3.14 and
  later ([#20768](astral-sh/ruff#20768))
- A single empty line is now permitted at the beginning of function bodies ([#21110](astral-sh/ruff#21110))
- Parentheses are avoided for long `as` captures in `match` statements ([#21176](astral-sh/ruff#21176))
- Extra spaces between escaped quotes and ending triple quotes can now be omitted ([#17216](astral-sh/ruff#17216))
- Blank lines are now enforced before classes with decorators in stub files ([#18888](astral-sh/ruff#18888))

- Apply formatting to Markdown code blocks ([#22470](astral-sh/ruff#22470), [#22990](astral-sh/ruff#22990), [#22996](astral-sh/ruff#22996))

  See the [documentation](https://docs.astral.sh/ruff/formatter/#markdown-code-formatting) for more details.

- Fix suppression indentation matching ([#22903](astral-sh/ruff#22903))

- Customize where the `fix_title` sub-diagnostic appears ([#23044](astral-sh/ruff#23044))
- \[`FastAPI`] Add sub-diagnostic explaining why a fix was unavailable (`FAST002`) ([#22565](astral-sh/ruff#22565))
- \[`flake8-annotations`] Don't suggest `NoReturn` for functions raising `NotImplementedError` (`ANN201`, `ANN202`, `ANN205`, `ANN206`) ([#21311](astral-sh/ruff#21311))
- \[`pyupgrade`] Make fix unsafe if it deletes comments (`UP017`) ([#22873](astral-sh/ruff#22873))
- \[`pyupgrade`] Make fix unsafe if it deletes comments (`UP020`) ([#22872](astral-sh/ruff#22872))
- \[`pyupgrade`] Make fix unsafe if it deletes comments (`UP033`) ([#22871](astral-sh/ruff#22871))
- \[`refurb`] Do not add `abc.ABC` if already present (`FURB180`) ([#22234](astral-sh/ruff#22234))
- \[`refurb`] Make fix unsafe if it deletes comments (`FURB110`) ([#22768](astral-sh/ruff#22768))
- \[`ruff`] Add sub-diagnostics with permissions (`RUF064`) ([#22972](astral-sh/ruff#22972))

- Identify notebooks by LSP `didOpen` instead of `.ipynb` file extension ([#22810](astral-sh/ruff#22810))

- Add `--color` CLI option to force colored output ([#22806](astral-sh/ruff#22806))

- Document `-` stdin convention in CLI help text ([#22817](astral-sh/ruff#22817))
- \[`refurb`] Change example to `re.search` with `^` anchor (`FURB167`) ([#22984](astral-sh/ruff#22984))
- Fix link to Sphinx code block directives ([#23041](astral-sh/ruff#23041))
- \[`pydocstyle`] Clarify which quote styles are allowed (`D300`) ([#22825](astral-sh/ruff#22825))
- \[`flake8-bugbear`] Improve docs for `no-explicit-stacklevel` (`B028`) ([#22538](astral-sh/ruff#22538))

- Update MSRV to 1.91 ([#22874](astral-sh/ruff#22874))

- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)
- [@amyreese](https://github.com/amyreese)
- [@Jkhall81](https://github.com/Jkhall81)
- [@cwkang1998](https://github.com/cwkang1998)
- [@manzt](https://github.com/manzt)
- [@11happy](https://github.com/11happy)
- [@hugovk](https://github.com/hugovk)
- [@caiquejjx](https://github.com/caiquejjx)
- [@ntBre](https://github.com/ntBre)
- [@akawd](https://github.com/akawd)
- [@konstin](https://github.com/konstin)

Renovate-Branch: renovate/2024.6-ruff-0.x
Change-Id: I8f8e865435fde1fc736fe2528261a604acb46215
Priv-Id: f7e1d99008e3617149c4b639a9a2bbc06212d064
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

formatter Related to the formatter preview Related to preview mode features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Formatter undocumented deviation: Formatting of long lambda as keyword argument

3 participants