Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add mypy plugin to core plugins #838

Open
wants to merge 58 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
b593e81
Initial commit
tomv564 Aug 8, 2017
3020e30
initial commit
tomv564 Aug 8, 2017
300cafc
Add readme
tomv564 Aug 8, 2017
a04eaa9
Rename to readme
tomv564 Aug 8, 2017
ef1b5d4
Update readme
tomv564 Aug 8, 2017
630ac5e
Add note about cloning and install until package is published
tomv564 Aug 8, 2017
93be6ac
Clean up old test content
tomv564 Aug 8, 2017
a60e7d0
Remove copyright notices
tomv564 Aug 8, 2017
62ebafa
Remove mypy as optional dependency
tomv564 Aug 8, 2017
8c81ffb
Also strip message field
tomv564 Aug 8, 2017
be5843a
Parse mypy result with regex
tomv564 Aug 8, 2017
bf163ac
Handle results without line or column
tomv564 Aug 8, 2017
888d4f9
Update installation instructions
tomv564 Aug 8, 2017
c3b5a04
add manifest to include required files
tomv564 Aug 13, 2017
e594a57
Don't default strictness options on command line
tomv564 Aug 29, 2017
4c72712
message contains now quotes in pyls 0.18.0
Mic92 May 25, 2018
6cf5aec
Merge pull request #10 from Mic92/fix-plugin-test
tomv564 May 28, 2018
f674023
Add travis config
tomv564 Jun 10, 2018
b86d6c4
Install flake8, remove old unittest cmd
tomv564 Jun 10, 2018
5ce0ce4
Fix long lines
tomv564 Jun 10, 2018
8eee89e
Add badge
tomv564 Jun 10, 2018
9a301f2
Use RST for badge
tomv564 Jun 10, 2018
1663ada
Add pypi badge
tomv564 Jun 10, 2018
78a7301
Remove future and configparser from install_requires
tomv564 Jun 10, 2018
985cd8a
Highlight the word at the point Mypy flags it
Jun 16, 2018
8e50bc8
Add some tests for how word_at_position might react
Jun 21, 2018
285fd99
Merge pull request #11 from necaris/feat/highlight-flagged-word
tomv564 Jun 21, 2018
83df547
Add '--follow-imports silent' to mypy invocation
eliwe Aug 6, 2018
189833c
Finalize entire word highlighting
st4lk Sep 14, 2018
320ab50
Merge pull request #12 from eliwe/master
tomv564 Oct 29, 2018
d169c25
Merge pull request #13 from st4lk/finalize-entire-work-highlight
tomv564 Oct 29, 2018
d5dc4b3
Exclude versioneer from escape code inspection
tomv564 Oct 29, 2018
a595d6a
Add live_mode=false option to only check saved document
tomv564 Oct 29, 2018
0e7ec60
Fix test
tomv564 Oct 29, 2018
c70ca23
lint fixes
tomv564 Oct 29, 2018
d3e4991
Fix mypy==0.700 compatibility.
singulared Apr 12, 2019
e488270
Merge pull request #21 from singulared/master
tomv564 Apr 12, 2019
0cac94d
Exclude errors from other files when using follow-imports
tomv564 Sep 16, 2019
83aa872
Update README.rst
tomv564 Oct 1, 2019
3cf9973
Fix diagnostics start position
AmjadHD Oct 2, 2019
e5ca814
Merge pull request #31 from AmjadHD/master
tomv564 Oct 3, 2019
8447538
When live_mode is off, only lint when document is saved
tomv564 Oct 3, 2019
f719b12
Moved the metadata to setup.cfg
KOLANICH Oct 4, 2019
c1f2f55
Fix test
tomv564 Oct 5, 2019
8ad660a
Fix readme code block
tomv564 Oct 5, 2019
c714fa3
Added support for 'strict' setting
Oct 21, 2019
fbc830f
Merge pull request #33 from dodomorandi/strict_support
tomv564 Oct 26, 2019
2949582
Install and use future only if python2
e-kwsm Mar 14, 2020
b02babf
Merge pull request #32 from KOLANICH/setup.cfg
tomv564 Mar 14, 2020
3b105b4
Merge pull request #38 from e-kwsm/future
tomv564 Mar 14, 2020
22dd48c
merge pyls-mypy with pyls
steff456 Jul 23, 2020
1883b3a
add mypy plugin in the core plugins of pyls
steff456 Jul 23, 2020
454509e
Avoid loading third party plugin for mypy
steff456 Aug 3, 2020
ef46994
Unpin mypy version
steff456 Aug 3, 2020
f12ce3c
fix typo
steff456 Aug 3, 2020
7dd99f5
fix tests
steff456 Aug 3, 2020
68f9afc
remove python 2 from matrix in appveyor
steff456 Aug 3, 2020
c795881
move tests to python 3 in circleci
steff456 Aug 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ jobs:
- image: "python:2.7-stretch"
steps:
- checkout
- run: pip install -e .[all] .[test]
- run: py.test -v test/
- run: pylint pyls test
- run: pycodestyle pyls test
- run: pyflakes pyls test
- run: exit 0

python3-test:
docker:
Expand All @@ -22,6 +18,9 @@ jobs:
- run: /tmp/pyenv/bin/python -m pip install loghub
- run: pip install -e .[all] .[test]
- run: py.test -v test/
- run: pylint pyls test
- run: pycodestyle pyls test
- run: pyflakes pyls test

lint:
docker:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,6 @@ ENV/

# Special files
.DS_Store

# mypy
.mypy_cache/
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ If the respective dependencies are found, the following optional providers will
* pydocstyle_ linter for docstring style checking (disabled by default)
* autopep8_ for code formatting
* YAPF_ for code formatting (preferred over autopep8)
* mypy for type linting

Optional providers can be installed using the `extras` syntax. To install YAPF_ formatting for example:

Expand All @@ -38,15 +39,14 @@ All optional providers can be installed using:

``pip install 'python-language-server[all]'``

If you get an error similar to ``'install_requires' must be a string or list of strings`` then please upgrade setuptools before trying again.
If you get an error similar to ``'install_requires' must be a string or list of strings`` then please upgrade setuptools before trying again.

``pip install -U setuptools``

3rd Party Plugins
~~~~~~~~~~~~~~~~~
Installing these plugins will add extra functionality to the language server:

* pyls-mypy_ Mypy type checking for Python 3
* pyls-isort_ Isort import sort code formatting
* pyls-black_ for code formatting using Black_

Expand Down
4 changes: 0 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ environment:
global:
APPVEYOR_RDP_PASSWORD: "dcca4c4863E30d56c2e0dda6327370b3#"
matrix:
- PYTHON: "C:\\Python27"
PYTHON_VERSION: "2.7.15"
PYTHON_ARCH: "64"

- PYTHON: "C:\\Python36"
PYTHON_VERSION: "3.6.8"
PYTHON_ARCH: "64"
Expand Down
4 changes: 4 additions & 0 deletions pyls/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def __init__(self, root_uri, init_opts, process_id, capabilities):
# However I don't want all plugins to have to catch ImportError and re-throw. So here we'll filter
# out any entry points that throw ImportError assuming one or more of their dependencies isn't present.
for entry_point in pkg_resources.iter_entry_points(PYLS):
if str(entry_point) == 'pyls_mypy':
# Don't load the pyls mypy third party plugin for avoiding
# conflicts
continue
try:
entry_point.load()
except ImportError as e:
Expand Down
82 changes: 82 additions & 0 deletions pyls/plugins/mypy_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import re
import logging
from mypy import api as mypy_api
from pyls import hookimpl

line_pattern = r"([^:]+):(?:(\d+):)?(?:(\d+):)? (\w+): (.*)"

log = logging.getLogger(__name__)


def parse_line(line, document=None):
'''
Return a language-server diagnostic from a line of the Mypy error report;
optionally, use the whole document to provide more context on it.
'''
result = re.match(line_pattern, line)
if result:
file_path, lineno, offset, severity, msg = result.groups()
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)
return None

lineno = int(lineno or 1) - 1 # 0-based line number
offset = int(offset or 1) - 1 # 0-based offset
errno = 2
if severity == 'error':
errno = 1
diag = {
'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}
},
'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'])
if word:
diag['range']['end']['character'] = (
diag['range']['start']['character'] + len(word))

return diag


@hookimpl
def pyls_lint(config, workspace, document, is_saved):
settings = config.plugin_settings('mypy')
live_mode = settings.get('live_mode', True)
if live_mode:
args = ['--incremental',
'--show-column-numbers',
'--follow-imports', 'silent',
'--command', document.source]
elif is_saved:
args = ['--incremental',
'--show-column-numbers',
'--follow-imports', 'silent',
document.path]
else:
return []

if settings.get('strict', False):
args.append('--strict')

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

diagnostics = []
for line in report.splitlines():
diag = parse_line(line, document)
if diag:
diagnostics.append(diag)

return diagnostics
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
'autopep8',
'flake8>=3.8.0',
'mccabe>=0.6.0,<0.7.0',
'mypy>=0.782',
'pycodestyle>=2.6.0,<2.7.0',
'pydocstyle>=2.0.0',
'pyflakes>=2.2.0,<2.3.0',
Expand Down Expand Up @@ -91,6 +92,7 @@
'jedi_signature_help = pyls.plugins.signature',
'jedi_symbols = pyls.plugins.symbols',
'mccabe = pyls.plugins.mccabe_lint',
'mypy = pyls.plugins.mypy_lint',
'preload = pyls.plugins.preload_imports',
'pycodestyle = pyls.plugins.pycodestyle_lint',
'pydocstyle = pyls.plugins.pydocstyle_lint',
Expand Down
73 changes: 73 additions & 0 deletions test/plugins/test_mypy_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import pytest
import pyls.plugins.mypy_lint as plugin
from pyls.workspace import Document

DOC_URI = __file__
DOC_TYPE_ERR = """{}.append(3)
"""
TYPE_ERR_MSG = '"Dict[<nothing>, <nothing>]" has no attribute "append"'

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


class FakeConfig(object):
def plugin_settings(self, plugin, document_path=None):
return {}


@pytest.fixture
def tmp_workspace(temp_workspace_factory):
return temp_workspace_factory({
DOC_URI: DOC_TYPE_ERR
})


def test_plugin(tmp_workspace):
config = FakeConfig()
doc = Document(DOC_URI, tmp_workspace, DOC_TYPE_ERR)
workspace = tmp_workspace
diags = plugin.pyls_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}


def test_parse_full_line(tmp_workspace):
doc = Document(DOC_URI, tmp_workspace, DOC_TYPE_ERR)
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}


def test_parse_line_without_col(tmp_workspace):
doc = Document(DOC_URI, tmp_workspace, DOC_TYPE_ERR)
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}


def test_parse_line_without_line(tmp_workspace):
doc = Document(DOC_URI, tmp_workspace, DOC_TYPE_ERR)
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': 1}


@pytest.mark.parametrize('word,bounds', [('', (7, 8)), ('my_var', (7, 13))])
def test_parse_line_with_context(tmp_workspace, monkeypatch, word, bounds):
doc = Document(DOC_URI, tmp_workspace, 'DOC_TYPE_ERR')
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]}
10 changes: 10 additions & 0 deletions vscode-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@
"default": 15,
"description": "The minimum threshold that triggers warnings about cyclomatic complexity."
},
"pyls.plugins.mypy.enabled": {
"type": "boolean",
"default": false,
"description": "Enable type linting."
},
"pyls.plugins.mypy.live_mode": {
"type": "boolean",
"default": false,
"description": "Enable live mode type linting."
},
"pyls.plugins.preload.enabled": {
"type": "boolean",
"default": true,
Expand Down