Repository: python-lsp/python-lsp-server
Pull request #179 (2022-03-26 21:00:11)

Fix Yapf formatting with CRLF line endings
- This is a follow up of PR #151, because I only noticed this problem after running some tests on Spyder.
- I also added a test for CRLF eols with Autopep8, just to be sure that that functionality doesn't break in the future.


Patch:
diff --git a/pylsp/plugins/flake8_lint.py b/pylsp/plugins/flake8_lint.py
index 9617e37..3707222 100644
--- a/pylsp/plugins/flake8_lint.py
+++ b/pylsp/plugins/flake8_lint.py
@@ -81,7 +81,7 @@ def run_flake8(flake8_executable, args, document):
     try:
         cmd = [flake8_executable]
         cmd.extend(args)
-        p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)  # pylint: disable=consider-using-with
+        p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
     except IOError:
         log.debug("Can't execute %s. Trying with '%s -m flake8'", flake8_executable, sys.executable)
         cmd = [sys.executable, '-m', 'flake8']
diff --git a/pylsp/plugins/pylint_lint.py b/pylsp/plugins/pylint_lint.py
index cbbf01d..d974a2f 100644
--- a/pylsp/plugins/pylint_lint.py
+++ b/pylsp/plugins/pylint_lint.py
@@ -246,7 +246,7 @@ def _run_pylint_stdio(pylint_executable, document, flags):
         cmd = [pylint_executable]
         cmd.extend(flags)
         cmd.extend(['--from-stdin', document.path])
-        p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)  # pylint: disable=consider-using-with
+        p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
     except IOError:
         log.debug("Can't execute %s. Trying with 'python -m pylint'", pylint_executable)
         cmd = ['python', '-m', 'pylint']
diff --git a/pylsp/plugins/yapf_format.py b/pylsp/plugins/yapf_format.py
index 1c90f96..7c47781 100644
--- a/pylsp/plugins/yapf_format.py
+++ b/pylsp/plugins/yapf_format.py
@@ -37,14 +37,14 @@ def pylsp_format_range(document, range):  # pylint: disable=redefined-builtin
 
 
 def _format(document, lines=None):
-    # Yapf doesn't work with CR line endings, so we replace them by '\n'
+    # Yapf doesn't work with CRLF/CR line endings, so we replace them by '\n'
     # and restore them below.
-    replace_cr = False
+    replace_eols = False
     source = document.source
     eol_chars = get_eol_chars(source)
-    if eol_chars == '\r':
-        replace_cr = True
-        source = source.replace('\r', '\n')
+    if eol_chars in ['\r', '\r\n']:
+        replace_eols = True
+        source = source.replace(eol_chars, '\n')
 
     new_source, changed = FormatCode(
         source,
@@ -58,8 +58,8 @@ def _format(document, lines=None):
     if not changed:
         return []
 
-    if replace_cr:
-        new_source = new_source.replace('\n', '\r')
+    if replace_eols:
+        new_source = new_source.replace('\n', eol_chars)
 
     # I'm too lazy at the moment to parse diffs into TextEdit items
     # So let's just return the entire file...


Test patch:
diff --git a/test/plugins/test_autopep8_format.py b/test/plugins/test_autopep8_format.py
index 1e790aa..bb5bc31 100644
--- a/test/plugins/test_autopep8_format.py
+++ b/test/plugins/test_autopep8_format.py
@@ -1,6 +1,8 @@
 # Copyright 2017-2020 Palantir Technologies, Inc.
 # Copyright 2021- Python Language Server Contributors.
 
+import pytest
+
 from pylsp import uris
 from pylsp.plugins.autopep8_format import pylsp_format_document, pylsp_format_range
 from pylsp.workspace import Document
@@ -73,8 +75,9 @@ def test_hanging_indentation(config, workspace):
     assert res[0]['newText'] == CORRECT_INDENTED_DOC
 
 
-def test_cr_line_endings(config, workspace):
-    doc = Document(DOC_URI, workspace, 'import os;import sys\r\rdict(a=1)')
+@pytest.mark.parametrize('newline', ['\r\n', '\r'])
+def test_line_endings(config, workspace, newline):
+    doc = Document(DOC_URI, workspace, f'import os;import sys{2 * newline}dict(a=1)')
     res = pylsp_format_document(config, doc)
 
-    assert res[0]['newText'] == 'import os\rimport sys\r\rdict(a=1)\r'
+    assert res[0]['newText'] == f'import os{newline}import sys{2 * newline}dict(a=1){newline}'
diff --git a/test/plugins/test_flake8_lint.py b/test/plugins/test_flake8_lint.py
index e82a226..59a776a 100644
--- a/test/plugins/test_flake8_lint.py
+++ b/test/plugins/test_flake8_lint.py
@@ -43,8 +43,8 @@ def test_flake8_unsaved(workspace):
 
 
 def test_flake8_lint(workspace):
+    name, doc = temp_document(DOC, workspace)
     try:
-        name, doc = temp_document(DOC, workspace)
         diags = flake8_lint.pylsp_lint(workspace, doc)
         msg = 'F841 local variable \'a\' is assigned to but never used'
         unused_var = [d for d in diags if d['message'] == msg][0]
@@ -54,7 +54,6 @@ def test_flake8_lint(workspace):
         assert unused_var['range']['start'] == {'line': 5, 'character': 1}
         assert unused_var['range']['end'] == {'line': 5, 'character': 11}
         assert unused_var['severity'] == lsp.DiagnosticSeverity.Warning
-
     finally:
         os.remove(name)
 
diff --git a/test/plugins/test_yapf_format.py b/test/plugins/test_yapf_format.py
index 4346985..94f619e 100644
--- a/test/plugins/test_yapf_format.py
+++ b/test/plugins/test_yapf_format.py
@@ -1,6 +1,8 @@
 # Copyright 2017-2020 Palantir Technologies, Inc.
 # Copyright 2021- Python Language Server Contributors.
 
+import pytest
+
 from pylsp import uris
 from pylsp.plugins.yapf_format import pylsp_format_document, pylsp_format_range
 from pylsp.workspace import Document
@@ -60,8 +62,9 @@ def test_config_file(tmpdir, workspace):
     assert pylsp_format_document(doc)[0]['newText'] == "A = [\n    'h', 'w',\n    'a'\n]\n\nB = ['h', 'w']\n"
 
 
-def test_cr_line_endings(workspace):
-    doc = Document(DOC_URI, workspace, 'import os;import sys\r\rdict(a=1)')
+@pytest.mark.parametrize('newline', ['\r\n', '\r'])
+def test_line_endings(workspace, newline):
+    doc = Document(DOC_URI, workspace, f'import os;import sys{2 * newline}dict(a=1)')
     res = pylsp_format_document(doc)
 
-    assert res[0]['newText'] == 'import os\rimport sys\r\rdict(a=1)\r'
+    assert res[0]['newText'] == f'import os{newline}import sys{2 * newline}dict(a=1){newline}'