Repository: spyder-ide/spyder

Issue:
Wrong E275 warning if keyword is at the end of a line
## Problem Description

As reported in https://github.com/PyCQA/pycodestyle/issues/1102, Spyder is wrongly reporting `E275` (https://www.flake8rules.com/rules/E275.html) warnings when a keyword is located at the end of a line.

This is occurring with `pycodestyle 2.9.1`, in which E275 checking was added.

```python
# -*- coding: utf-8 -*-
try:
    xabs = [
        abs(x) for x in
        [1, -2, 3, -4]
        ]
except Exception:
    pass
else:
    pass

import pycodestyle
sut = pycodestyle.StyleGuide()
results = sut.input_file(filename=__file__)
print(results)
```

![image](https://user-images.githubusercontent.com/10170372/191849076-51c1b1a7-2d4b-4525-aaf5-0688a37e9b16.png)

The problem seems to be related with how `end-of-line characters` are handled before sending them to pycodestyle. I currently use `Carriage return and line feed (Windows)`, but there is no such False warning if I use `Line feed (UNIX)` end-of-line characters.

On the opposite, if I use `Carriage return (Mac)`, then I get even more False positives.

![image](https://user-images.githubusercontent.com/10170372/191850855-487d6630-c096-4725-b120-3ea747d7b231.png)


## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 5.3.0.dev0 b563ebda0
* Python version: 3.8.10 64-bit
* Qt version: 5.15.2
* PyQt5 version: 5.15.7
* Operating System: Windows 10



Discussion:
Hi @jnsebgosselin thanks for the feedback! Could you update to the latest dev version and check again? Let us know!
Thanks @dalthviz for your quick reply!

I've updated to the latest commit on the 5.x branch (https://github.com/spyder-ide/spyder/commit/84d1b6ca0) and I still get false E275 positives when using `CRLF (Windows)` end-of-line characters.
I see, thanks for the testing @jnsebgosselin ! Not totally sure, but maybe to fix this some changes at the [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server) will need to be done then @ccordoba12 ?
Yeah, we probably need to replace Windows line endings for Linux ones in PyLSP before passing the file to Pycodestyle. 

Resolving patch:
diff --git a/external-deps/python-lsp-server/pylsp/plugins/pycodestyle_lint.py b/external-deps/python-lsp-server/pylsp/plugins/pycodestyle_lint.py
--- a/external-deps/python-lsp-server/pylsp/plugins/pycodestyle_lint.py
+++ b/external-deps/python-lsp-server/pylsp/plugins/pycodestyle_lint.py
@@ -2,8 +2,11 @@
 # Copyright 2021- Python Language Server Contributors.
 
 import logging
+
 import pycodestyle
+
 from pylsp import hookimpl, lsp
+from pylsp._utils import get_eol_chars
 
 try:
     from autopep8 import continued_indentation as autopep8_c_i
@@ -39,8 +42,18 @@ def pylsp_lint(workspace, document):
         kwargs = {k: v for k, v in opts.items() if v}
         styleguide = pycodestyle.StyleGuide(kwargs)
 
+        # Use LF to lint file because other line endings can give false positives.
+        # See spyder-ide/spyder#19565 for context.
+        source = document.source
+        eol_chars = get_eol_chars(source)
+        if eol_chars in ['\r', '\r\n']:
+            source = source.replace(eol_chars, '\n')
+            lines = source.splitlines(keepends=True)
+        else:
+            lines = document.lines
+
         c = pycodestyle.Checker(
-            filename=document.uri, lines=document.lines, options=styleguide.options,
+            filename=document.path, lines=lines, options=styleguide.options,
             report=PyCodeStyleDiagnosticReport(styleguide.options)
         )
         c.check_all()
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -1110,6 +1110,16 @@ def start_completion_services(self):
             additional_msg = " cloned editor"
         else:
             additional_msg = ""
+
+            # We need to be sure that this signal is disconnected before
+            # calling document_did_open below because that method creates a
+            # unique connection for it and this method is called every time the
+            # server is restarted.
+            try:
+                self._timer_sync_symbols_and_folding.timeout.disconnect()
+            except (TypeError, RuntimeError):
+                pass
+
             self.document_did_open()
 
         logger.debug(u"Completion services available for {0}: {1}".format(
@@ -1175,8 +1185,8 @@ def document_did_open(self):
 
         # The connect is performed here instead of in __init__() because
         # notify_close() may have been called (which disconnects the signal).
-        # Qt.UniqueConnection is used to avoid duplicate signal-slot connections
-        # (just in case).
+        # Qt.UniqueConnection is used to avoid duplicate signal-slot
+        # connections (just in case).
         #
         # Note: PyQt5 throws if the signal is not unique (= already connected).
         # It is an error if this happens because as per LSP specification


Test patch:
diff --git a/external-deps/python-lsp-server/test/plugins/test_pycodestyle_lint.py b/external-deps/python-lsp-server/test/plugins/test_pycodestyle_lint.py
--- a/external-deps/python-lsp-server/test/plugins/test_pycodestyle_lint.py
+++ b/external-deps/python-lsp-server/test/plugins/test_pycodestyle_lint.py
@@ -2,6 +2,9 @@
 # Copyright 2021- Python Language Server Contributors.
 
 import os
+
+import pytest
+
 from pylsp import lsp, uris
 from pylsp.workspace import Document
 from pylsp.plugins import pycodestyle_lint
@@ -110,3 +113,22 @@ def test_pycodestyle_config(workspace):
     assert not [d for d in diags if d['code'] == 'W191']
     assert not [d for d in diags if d['code'] == 'E201']
     assert [d for d in diags if d['code'] == 'W391']
+
+
+@pytest.mark.parametrize('newline', ['\r\n', '\r'])
+def test_line_endings(workspace, newline):
+    """
+    Check that Pycodestyle doesn't generate false positives with line endings
+    other than LF.
+    """
+    # Create simple source that should give false positives
+    source = f"try:{newline}    1/0{newline}except Exception:{newline}    pass{newline}"
+
+    # Create document
+    doc = Document(DOC_URI, workspace, source)
+
+    # Get diagnostics
+    diags = pycodestyle_lint.pylsp_lint(workspace, doc)
+
+    # Assert no diagnostics were given
+    assert len(diags) == 0