diff --git a/isort/output.py b/isort/output.py index ada89f75..bfbaee37 100644 --- a/isort/output.py +++ b/isort/output.py @@ -403,26 +403,30 @@ def _with_from_imports( if not config.only_sections: output.extend( - with_comments( - from_comments, - wrap.line( - import_start + as_import, parsed.line_separator, config + wrap.line( + with_comments( + from_comments, + import_start + as_import, + removed=config.ignore_comments, + comment_prefix=config.comment_prefix, ), - removed=config.ignore_comments, - comment_prefix=config.comment_prefix, + parsed.line_separator, + config, ) for as_import in sorting.sort(config, as_imports[from_import]) ) else: output.extend( - with_comments( - from_comments, - wrap.line( - import_start + as_import, parsed.line_separator, config + wrap.line( + with_comments( + from_comments, + import_start + as_import, + removed=config.ignore_comments, + comment_prefix=config.comment_prefix, ), - removed=config.ignore_comments, - comment_prefix=config.comment_prefix, + parsed.line_separator, + config, ) for as_import in as_imports[from_import] ) @@ -518,6 +522,8 @@ def _with_from_imports( ) if opening_comment: lines[0] += opening_comment + if config.multi_line_output == wrap.Modes.NOQA: # type: ignore[attr-defined] # noqa: E501 + lines[0] = wrap.line(lines[0], parsed.line_separator, config) output.append(parsed.line_separator.join(lines)) else: output.append( @@ -719,13 +725,15 @@ def _with_straight_imports( if inline_comments: combined_inline_comments = " ".join(c for c in inline_comments if c) if combined_inline_comments: - output.append( + line_content = ( f"{import_type} {combined_straight_imports} # {combined_inline_comments}" ) else: - output.append(f"{import_type} {combined_straight_imports} #") + line_content = f"{import_type} {combined_straight_imports} #" else: - output.append(f"{import_type} {combined_straight_imports}") + line_content = f"{import_type} {combined_straight_imports}" + + output.append(wrap.line(line_content, parsed.line_separator, config)) return output @@ -747,15 +755,16 @@ def _with_straight_imports( comments_above = parsed.categorized_comments["above"]["straight"].pop(module, None) if comments_above: output.extend(comments_above) - output.extend( - with_comments( + for idef, imodule in import_definition: + line_content = with_comments( parsed.categorized_comments["straight"].get(imodule), idef, removed=config.ignore_comments, comment_prefix=config.comment_prefix, ) - for idef, imodule in import_definition - ) + if config.multi_line_output == wrap.Modes.NOQA: # type: ignore[attr-defined] + line_content = wrap.line(line_content, parsed.line_separator, config) + output.append(line_content) return output diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py index f843ff84..e47ab93a 100644 --- a/tests/unit/test_regressions.py +++ b/tests/unit/test_regressions.py @@ -2117,3 +2117,90 @@ def test_noqa_wrap_mode_does_not_accumulate_spaces_with_as_import(): first_pass, multi_line_output=7, force_single_line=True, line_length=40 ) assert second_pass == first_pass + + +def test_noqa_added_to_long_force_single_line_as_import_with_comment_issue_2093(): + """A long ``as`` import with inline comment must get ``# NOQA`` in NOQA mode. + + With ``force_single_line`` an aliased import that carries an inline comment + and overflows the line length must still receive ``# NOQA``. Previously + ``with_comments()`` wrapped ``wrap.line()``, so the length check ran on the + import without the comment and ``# NOQA`` was never added. + """ + to_sort = ( + "from my_package.my_module import super_long_file_name as super_long_alias" + " # type: ignore\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" + " # type: ignore # NOQA\n" + ) + assert first_pass.count("NOQA") == 1 + + second_pass = isort.code( + first_pass, multi_line_output=7, force_single_line=True, line_length=40 + ) + assert second_pass == first_pass + + +def test_noqa_added_to_long_as_import_with_opening_comment_issue_2093(): + """A long ``as`` import with opening-line comment must get ``# NOQA`` in NOQA mode. + + When ``use_parentheses`` is enabled, opening-line comments are kept on the + ``from X import (`` line. The ``wrap.line()`` call ran before the comment was + attached, so it saw a short import and never added ``# NOQA``. The fix re-runs + ``wrap.line()`` in NOQA mode after the comment is attached. + """ + to_sort = ( + "from my_package.my_module import (\n" + " super_long_file_name as super_long_alias # type: ignore\n" + ")\n" + ) + + first_pass = isort.code(to_sort, multi_line_output=7, line_length=40) + assert "# NOQA" in first_pass + assert first_pass.count("NOQA") == 1 + + second_pass = isort.code(first_pass, multi_line_output=7, line_length=40) + assert second_pass == first_pass + + +def test_noqa_added_to_long_combined_straight_imports_issue_2093(): + """Long combined straight imports must get ``# NOQA`` in NOQA mode. + + With ``combine_straight_imports`` enabled, multiple ``import`` statements + are merged into a single ``import a, b, c, ...`` line. Previously this line + was appended directly without going through ``wrap.line()``, so ``# NOQA`` + was never added even when it exceeded the line length. + """ + to_sort = "import a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p\n" + + first_pass = isort.code( + to_sort, multi_line_output=7, combine_straight_imports=True, line_length=40 + ) + assert first_pass == ("import a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p # NOQA\n") + assert first_pass.count("NOQA") == 1 + + second_pass = isort.code( + first_pass, multi_line_output=7, combine_straight_imports=True, line_length=40 + ) + assert second_pass == first_pass + + +def test_noqa_added_to_long_straight_import_issue_2093(): + """Long straight imports must get ``# NOQA`` in NOQA mode. + + Straight imports (``import x`` or ``import x as y``) that exceed the line + length were emitted directly without going through ``wrap.line()``, so + ``# NOQA`` was never added. The fix wraps each import through ``wrap.line()``. + """ + to_sort = "import aaaa_long_module_name as bbbb_long_alias_name\n" + + first_pass = isort.code(to_sort, multi_line_output=7, line_length=40) + assert first_pass == "import aaaa_long_module_name as bbbb_long_alias_name # NOQA\n" + assert first_pass.count("NOQA") == 1 + + second_pass = isort.code(first_pass, multi_line_output=7, line_length=40) + assert second_pass == first_pass