Repository: timothycrosley/isort (python)

Issue:
isort insists on moving PyCharm noinspection comment, breaking it
[This import statement](https://github.com/eventbrite/pysoa/blob/9ce65ce5e7a2f934070bb30754f88d2cb400e9ff/tests/server/test_autoreloader.py#L17-L24) imports several protected (start with underscore) members from a module. PyCharm includes a special comment that instructs its inspector to ignore this and not issue a warning:

```
# noinspection PyProtectedMember
```

This comment must go directly above the import statement that imports the protected members. If it goes anywhere else, it has no effect and PyCharm issues the warning. isort insists on moving this comment from where it's supposed to go to above the import group:

```
@@ -10,11 +10,11 @@
 import time
 import unittest
 
+# noinspection PyProtectedMember
 import pysoa
 import pysoa.client
 import pysoa.server
 import pysoa.server.autoreload
-# noinspection PyProtectedMember
 from pysoa.server.autoreload import (
     NEED_RELOAD_EXIT_CODE,
     AbstractReloader,
Apply suggested changes to '/Users/nwilliams/eventbrite/pysoa/tests/server/test_autoreloader.py' [y/n/q]? n
```

I have to skip it every time, which triggers warnings from our linter. isort should not attempt to move this comment to the top of the group. It should leave it where it is.

Pull request description:
Keep correct comment location with force_sort_within_sections
Fixes #726 

Patch:
diff --git a/isort/isort.py b/isort/isort.py
index 48f65453..518c5f53 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -699,6 +699,9 @@ class _SortImports:
                 ),
             )
 
+            if self.config["force_sort_within_sections"]:
+                copied_comments = copy.deepcopy(self.comments)
+
             section_output = []  # type: List[str]
             if self.config["from_first"]:
                 self._add_from_imports(
@@ -727,8 +730,6 @@ class _SortImports:
 
                 def by_module(line: str) -> str:
                     section = "B"
-                    if line.startswith("#"):
-                        return "AA"
 
                     line = re.sub("^from ", "", line)
                     line = re.sub("^import ", "", line)
@@ -738,8 +739,27 @@ class _SortImports:
                         line = line.lower()
                     return "{}{}".format(section, line)
 
+                # Remove comments
+                section_output = [
+                    line for line in section_output if not line.startswith("#")
+                ]
+
                 section_output = nsorted(section_output, key=by_module)
 
+                # Add comments back
+                all_comments = copied_comments["above"]["from"]
+                all_comments.update(copied_comments["above"]["straight"])
+                comment_indexes = {}
+                for module, comment_list in all_comments.items():
+                    for idx, line in enumerate(section_output):
+                        if module in line:
+                            comment_indexes[idx] = comment_list
+                added = 0
+                for idx, comment_list in comment_indexes.items():
+                    for comment in comment_list:
+                        section_output.insert(idx + added, comment)
+                        added += 1
+
             section_name = section
             no_lines_before = section_name in self.config["no_lines_before"]
 


Test patch:
diff --git a/test_isort.py b/test_isort.py
index ae5eee89..0742e445 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -4222,6 +4222,25 @@ def test_isort_ensures_blank_line_between_import_and_comment() -> None:
     assert SortImports(file_contents=test_input, **config).output == expected_output
 
 
+def test_moving_comments_issue_726():
+    config = {"force_sort_within_sections": 1}  # type: Dict[str, Any]
+    test_input = (
+        "import Blue.models as BlueModels\n"
+        "# comment for PlaidModel\n"
+        "from Plaid.models import PlaidModel\n"
+    )
+    assert SortImports(file_contents=test_input, **config).output == test_input
+
+    test_input = (
+        "# comment for BlueModels\n"
+        "import Blue.models as BlueModels\n"
+        "# comment for PlaidModel\n"
+        "# another comment for PlaidModel\n"
+        "from Plaid.models import PlaidModel\n"
+    )
+    assert SortImports(file_contents=test_input, **config).output == test_input
+
+
 def test_pyi_formatting_issue_942(tmpdir) -> None:
     test_input = "import os\n\n\ndef my_method():\n"
     expected_py_output = test_input.splitlines()