Repository: PyCQA/flake8

Issue:
Sort FileCheckers alphabetically
Whenever I run *flake8* on a large set of code which has multiple violations, I am presented with an apparently deterministic, but not alphabetical order of the files. This makes it hard to work on them in a coordinated manner, as the file list inside the file manager or IDE usually is sorted alphabetically. Current example output, where I would expect `dummies.py` to be displayed before `sample.py`:

```bash
(.env) stefan@localhost:~/projects/samples> flake8 samples/
samples/sample.py:79:1: E501 line too long (100 > 80 characters)
samples/dummies.py:68:7: E265 block comment should start with '# '
```

Skimming through the available options, the documentation and the source code, this does not seem to be supported for now. Judging from the source code, this seems like some hard-coded behavior of `flake8.checker.Manager.report` and therefore hard to implement (or hacky) to fix from the outside.

This might be fixed by simply replacing the iteration over all the checkers inside the aforementioned method by something like the following snippet, although this will not separate files and directories if this is something which should be considered:

```python3
for checker in sorted(self._all_checkers, key=operator.attrgetter('filename')):
   ...
```

Comments:

Patch:
diff --git a/src/flake8/checker.py b/src/flake8/checker.py
index 2d7bcd9..2cae545 100644
--- a/src/flake8/checker.py
+++ b/src/flake8/checker.py
@@ -6,6 +6,7 @@ import contextlib
 import errno
 import logging
 import multiprocessing.pool
+import operator
 import signal
 import tokenize
 from typing import Any
@@ -180,8 +181,9 @@ class Manager:
             A tuple of the total results found and the results reported.
         """
         results_reported = results_found = 0
+        self.results.sort(key=operator.itemgetter(0))
         for filename, results, _ in self.results:
-            results.sort(key=lambda tup: (tup[1], tup[2]))
+            results.sort(key=operator.itemgetter(1, 2))
             with self.style_guide.processing_file(filename):
                 results_reported += self._handle_results(filename, results)
             results_found += len(results)


Test patch:
diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py
index db60f6b..96d1182 100644
--- a/tests/integration/test_main.py
+++ b/tests/integration/test_main.py
@@ -98,6 +98,26 @@ t.py:1:1: F401 'os' imported but unused
     assert err == ""
 
 
+def test_errors_sorted(tmpdir, capsys):
+    with tmpdir.as_cwd():
+        for c in "abcde":
+            tmpdir.join(f"{c}.py").write("import os\n")
+        assert cli.main(["./"]) == 1
+
+    # file traversal was done in inode-order before
+    # this uses a significant number of files such that it's unlikely to pass
+    expected = """\
+./a.py:1:1: F401 'os' imported but unused
+./b.py:1:1: F401 'os' imported but unused
+./c.py:1:1: F401 'os' imported but unused
+./d.py:1:1: F401 'os' imported but unused
+./e.py:1:1: F401 'os' imported but unused
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
+    assert err == ""
+
+
 def test_extend_exclude(tmpdir, capsys):
     """Ensure that `flake8 --extend-exclude` works."""
     for d in ["project", "vendor", "legacy", ".git", ".tox", ".hg"]: