Skip to content

Commit

Permalink
Depend on python-lsp-server
Browse files Browse the repository at this point in the history
Palantir's python-language-server is now unmaintained.
python-lsp-server is a community-maintained fork.
  • Loading branch information
haplo committed May 18, 2021
1 parent 6ed04c1 commit 1489e85
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 76 deletions.
10 changes: 5 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ Mypy plugin for PYLS
.. image:: https://github.com/Richardk2n/pyls-mypy/workflows/Python%20package/badge.svg?branch=master
:target: https://github.com/Richardk2n/pyls-mypy/

This is a plugin for the Palantir's Python Language Server (https://github.com/palantir/python-language-server)
This is a plugin for the [Python LSP Server](https://github.com/python-lsp/python-lsp-server).

It, like mypy, requires Python 3.6 or newer.


Installation
------------

Install into the same virtualenv as pyls itself.
Install into the same virtualenv as python-lsp-server itself.

``pip install mypy-ls``

Expand All @@ -31,7 +31,7 @@ Depending on your editor, the configuration (found in a file called mypy-ls.cfg
::

{
"enabled": True,
"live_mode": True,
"strict": False
"enabled": True,
"live_mode": True,
"strict": False
}
77 changes: 40 additions & 37 deletions mypy_ls/plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
File that contains the pyls plugin mypy-ls.
File that contains the pylsp plugin mypy-ls.
Created on Fri Jul 10 09:53:57 2020
Expand All @@ -12,9 +12,9 @@
import os.path
import logging
from mypy import api as mypy_api
from pyls import hookimpl
from pyls.workspace import Document, Workspace
from pyls.config.config import Config
from pylsp import hookimpl
from pylsp.workspace import Document, Workspace
from pylsp.config.config import Config
from typing import Optional, Dict, Any, IO, List
import atexit

Expand All @@ -27,7 +27,9 @@
tmpFile: Optional[IO[str]] = None


def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[str, Any]]:
def parse_line(
line: str, document: Optional[Document] = None
) -> Optional[Dict[str, Any]]:
"""
Return a language-server diagnostic from a line of the Mypy error report.
Expand All @@ -54,51 +56,53 @@ def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[
if file_path != "<string>": # live mode
# results from other files can be included, but we cannot return
# them.
if document and document.path and not document.path.endswith(
file_path):
log.warning("discarding result for %s against %s", file_path,
document.path)
if document and document.path and not document.path.endswith(file_path):
log.warning(
"discarding result for %s against %s", file_path, document.path
)
return None

lineno = int(linenoStr or 1) - 1 # 0-based line number
offset = int(offsetStr or 1) - 1 # 0-based offset
errno = 2
if severity == 'error':
if severity == "error":
errno = 1
diag: Dict[str, Any] = {
'source': 'mypy',
'range': {
'start': {'line': lineno, 'character': offset},
"source": "mypy",
"range": {
"start": {"line": lineno, "character": offset},
# There may be a better solution, but mypy does not provide end
'end': {'line': lineno, 'character': offset + 1}
"end": {"line": lineno, "character": offset + 1},
},
'message': msg,
'severity': errno
"message": msg,
"severity": errno,
}
if document:
# although mypy does not provide the end of the affected range, we
# can make a good guess by highlighting the word that Mypy flagged
word = document.word_at_position(diag['range']['start'])
word = document.word_at_position(diag["range"]["start"])
if word:
diag['range']['end']['character'] = (
diag['range']['start']['character'] + len(word))
diag["range"]["end"]["character"] = diag["range"]["start"][
"character"
] + len(word)

return diag
return None


@hookimpl
def pyls_lint(config: Config, workspace: Workspace, document: Document,
is_saved: bool) -> List[Dict[str, Any]]:
def pylsp_lint(
config: Config, workspace: Workspace, document: Document, is_saved: bool
) -> List[Dict[str, Any]]:
"""
Lints.
Parameters
----------
config : Config
The pyls config.
The pylsp config.
workspace : Workspace
The pyls workspace.
The pylsp workspace.
document : Document
The document to be linted.
is_saved : bool
Expand All @@ -110,27 +114,25 @@ def pyls_lint(config: Config, workspace: Workspace, document: Document,
List of the linting data.
"""
settings = config.plugin_settings('mypy-ls')
live_mode = settings.get('live_mode', True)
args = ['--incremental',
'--show-column-numbers',
'--follow-imports', 'silent']
settings = config.plugin_settings("mypy-ls")
live_mode = settings.get("live_mode", True)
args = ["--incremental", "--show-column-numbers", "--follow-imports", "silent"]

global tmpFile
if live_mode and not is_saved and tmpFile:
tmpFile = open(tmpFile.name, "w")
tmpFile.write(document.source)
tmpFile.close()
args.extend(['--shadow-file', document.path, tmpFile.name])
args.extend(["--shadow-file", document.path, tmpFile.name])
elif not is_saved:
return []

if mypyConfigFile:
args.append('--config-file')
args.append("--config-file")
args.append(mypyConfigFile)
args.append(document.path)
if settings.get('strict', False):
args.append('--strict')
if settings.get("strict", False):
args.append("--strict")

report, errors, _ = mypy_api.run(args)

Expand All @@ -144,14 +146,14 @@ def pyls_lint(config: Config, workspace: Workspace, document: Document,


@hookimpl
def pyls_settings(config: Config) -> Dict[str, Dict[str, Dict[str, str]]]:
def pylsp_settings(config: Config) -> Dict[str, Dict[str, Dict[str, str]]]:
"""
Read the settings.
Parameters
----------
config : Config
The pyls config.
The pylsp config.
Returns
-------
Expand Down Expand Up @@ -187,10 +189,11 @@ def init(workspace: str) -> Dict[str, str]:
configuration = eval(file.read())
global mypyConfigFile
mypyConfigFile = findConfigFile(workspace, "mypy.ini")
if (("enabled" not in configuration or configuration["enabled"])
and ("live_mode" not in configuration or configuration["live_mode"])):
if ("enabled" not in configuration or configuration["enabled"]) and (
"live_mode" not in configuration or configuration["live_mode"]
):
global tmpFile
tmpFile = tempfile.NamedTemporaryFile('w', delete=False)
tmpFile = tempfile.NamedTemporaryFile("w", delete=False)
tmpFile.close()
return configuration

Expand Down
7 changes: 5 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
python-language-server>=0.34.0
mypy
future;python_version < '3'
flake8
configparser
python-lsp-server
mypy;python_version >= '3.2'
8 changes: 4 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[metadata]
name = mypy-ls
author = Tom van Ommeren, Richard Kellnberger
description = Mypy linter for the Python Language Server
description = Mypy linter for the Python LSP Server
url = https://github.com/Richardk2n/pyls-mypy
long_description = file: README.rst
license='MIT'
Expand All @@ -17,13 +17,13 @@ classifiers =
[options]
python_requires = >= 3.6
packages = find:
install_requires =
python-language-server>=0.34.0
install_requires =
python-lsp-server
mypy


[options.entry_points]
pyls = mypy_ls = mypy_ls.plugin
pylsp = mypy_ls = mypy_ls.plugin

[options.extras_require]
test =
Expand Down
53 changes: 25 additions & 28 deletions test/test_plugin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pytest

from pyls.workspace import Workspace, Document
from pyls.config.config import Config
from pyls import uris
from pylsp.workspace import Workspace, Document
from pylsp.config.config import Config
from pylsp import uris
from mock import Mock
from mypy_ls import plugin

Expand All @@ -12,10 +12,8 @@
TYPE_ERR_MSG = '"Dict[<nothing>, <nothing>]" has no attribute "append"'

TEST_LINE = 'test_plugin.py:279:8: error: "Request" has no attribute "id"'
TEST_LINE_WITHOUT_COL = ('test_plugin.py:279: '
'error: "Request" has no attribute "id"')
TEST_LINE_WITHOUT_LINE = ('test_plugin.py: '
'error: "Request" has no attribute "id"')
TEST_LINE_WITHOUT_COL = "test_plugin.py:279: " 'error: "Request" has no attribute "id"'
TEST_LINE_WITHOUT_LINE = "test_plugin.py: " 'error: "Request" has no attribute "id"'


@pytest.fixture
Expand All @@ -27,7 +25,6 @@ def workspace(tmpdir):


class FakeConfig(object):

def __init__(self):
self._root_path = "C:"

Expand All @@ -37,53 +34,53 @@ def plugin_settings(self, plugin, document_path=None):

def test_settings():
config = FakeConfig()
settings = plugin.pyls_settings(config)
settings = plugin.pylsp_settings(config)
assert settings == {"plugins": {"mypy-ls": {}}}


def test_plugin(workspace):
config = FakeConfig()
doc = Document(DOC_URI, workspace, DOC_TYPE_ERR)
workspace = None
plugin.pyls_settings(config)
diags = plugin.pyls_lint(config, workspace, doc, is_saved=False)
plugin.pylsp_settings(config)
diags = plugin.pylsp_lint(config, workspace, doc, is_saved=False)

assert len(diags) == 1
diag = diags[0]
assert diag['message'] == TYPE_ERR_MSG
assert diag['range']['start'] == {'line': 0, 'character': 0}
assert diag['range']['end'] == {'line': 0, 'character': 1}
assert diag["message"] == TYPE_ERR_MSG
assert diag["range"]["start"] == {"line": 0, "character": 0}
assert diag["range"]["end"] == {"line": 0, "character": 1}


def test_parse_full_line(workspace):
doc = Document(DOC_URI, workspace)
diag = plugin.parse_line(TEST_LINE, doc)
assert diag['message'] == '"Request" has no attribute "id"'
assert diag['range']['start'] == {'line': 278, 'character': 7}
assert diag['range']['end'] == {'line': 278, 'character': 8}
assert diag["message"] == '"Request" has no attribute "id"'
assert diag["range"]["start"] == {"line": 278, "character": 7}
assert diag["range"]["end"] == {"line": 278, "character": 8}


def test_parse_line_without_col(workspace):
doc = Document(DOC_URI, workspace)
diag = plugin.parse_line(TEST_LINE_WITHOUT_COL, doc)
assert diag['message'] == '"Request" has no attribute "id"'
assert diag['range']['start'] == {'line': 278, 'character': 0}
assert diag['range']['end'] == {'line': 278, 'character': 1}
assert diag["message"] == '"Request" has no attribute "id"'
assert diag["range"]["start"] == {"line": 278, "character": 0}
assert diag["range"]["end"] == {"line": 278, "character": 1}


def test_parse_line_without_line(workspace):
doc = Document(DOC_URI, workspace)
diag = plugin.parse_line(TEST_LINE_WITHOUT_LINE, doc)
assert diag['message'] == '"Request" has no attribute "id"'
assert diag['range']['start'] == {'line': 0, 'character': 0}
assert diag['range']['end'] == {'line': 0, 'character': 6}
assert diag["message"] == '"Request" has no attribute "id"'
assert diag["range"]["start"] == {"line": 0, "character": 0}
assert diag["range"]["end"] == {"line": 0, "character": 6}


@pytest.mark.parametrize('word,bounds', [('', (7, 8)), ('my_var', (7, 13))])
@pytest.mark.parametrize("word,bounds", [("", (7, 8)), ("my_var", (7, 13))])
def test_parse_line_with_context(monkeypatch, word, bounds, workspace):
doc = Document(DOC_URI, workspace)
monkeypatch.setattr(Document, 'word_at_position', lambda *args: word)
monkeypatch.setattr(Document, "word_at_position", lambda *args: word)
diag = plugin.parse_line(TEST_LINE, doc)
assert diag['message'] == '"Request" has no attribute "id"'
assert diag['range']['start'] == {'line': 278, 'character': bounds[0]}
assert diag['range']['end'] == {'line': 278, 'character': bounds[1]}
assert diag["message"] == '"Request" has no attribute "id"'
assert diag["range"]["start"] == {"line": 278, "character": bounds[0]}
assert diag["range"]["end"] == {"line": 278, "character": bounds[1]}

0 comments on commit 1489e85

Please sign in to comment.