From 9274fcaaec6beb511eefa1e927703411ff57cd9d Mon Sep 17 00:00:00 2001 From: Sarath Francis Date: Mon, 15 Jun 2026 04:00:52 -0400 Subject: [PATCH] Fix NOQA wrap mode accumulating spaces before the comment on each run In NOQA wrap mode (multi_line_output=7) isort appends `# NOQA` to an import it can't fit on one line. When such a line is re-processed, the auto-added comment is parsed back off and re-added through `add_to_line`. `parse()` returns the text that precedes the `#`, which still includes the two spaces from the previous `comment_prefix`; `add_to_line` then appended the prefix on top of that leftover whitespace, so every pass inserted two more spaces (`x # NOQA` -> `x # NOQA` -> ...) and the output never reached a fixpoint. Strip that orphaned trailing whitespace before re-adding the comment, but only when an existing comment was actually parsed off, so the indentation-only bases used by the multi-line wrap modes keep their indentation. Fixes #2394. --- isort/comments.py | 12 ++++++++++-- tests/unit/test_regressions.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/isort/comments.py b/isort/comments.py index 50d28a62..33e1bb1a 100644 --- a/isort/comments.py +++ b/isort/comments.py @@ -30,6 +30,14 @@ def add_to_line( if comment not in unique_comments: unique_comments.append(comment) comment_text = "; ".join(unique_comments) + base, existing_comment = parse(original_string) + if existing_comment is not None: + # An existing comment was stripped off, leaving the whitespace that used to + # precede it (the previous ``comment_prefix`` spacing). Drop it so re-adding a + # comment does not accumulate leading spaces on every run. Bases without a + # comment (e.g. the indentation-only strings used by the multi-line wrap modes) + # are left untouched so their indentation is preserved. + base = base.rstrip() if comment_text: - return f"{parse(original_string)[0]}{comment_prefix} {comment_text}" - return f"{parse(original_string)[0]}{comment_prefix}" + return f"{base}{comment_prefix} {comment_text}" + return f"{base}{comment_prefix}" diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py index ce959ac5..f843ff84 100644 --- a/tests/unit/test_regressions.py +++ b/tests/unit/test_regressions.py @@ -2094,3 +2094,26 @@ def test_noqa_wrap_mode_idempotent_with_existing_comment(): second_pass = isort.code(first_pass, multi_line_output=7) assert second_pass == first_pass assert second_pass.count("NOQA") == 1 + + +def test_noqa_wrap_mode_does_not_accumulate_spaces_with_as_import(): + """A long ``as`` import in NOQA mode must not grow extra spaces before ``# NOQA``. + + With ``force_single_line`` an aliased import that overflows the line length is emitted + on its own line and gets a ``# NOQA`` appended. The auto-added comment is re-parsed on + the next run and put back through ``add_to_line``, which used to keep the whitespace that + had preceded the stripped ``#`` and then add the comment prefix on top of it - so every + pass inserted two more spaces (``import x # NOQA`` -> ``import x # NOQA`` -> ...), + never reaching a fixpoint. See issue #2394. + """ + to_sort = "from my_package.my_module import super_long_file_name as super_long_alias\n" + + first_pass = isort.code(to_sort, multi_line_output=7, force_single_line=True, line_length=40) + assert first_pass == ( + "from my_package.my_module import super_long_file_name as super_long_alias # NOQA\n" + ) + + second_pass = isort.code( + first_pass, multi_line_output=7, force_single_line=True, line_length=40 + ) + assert second_pass == first_pass