Skip to content

Commit 4bd351f

Browse files
ExpectationMaxccordoba12
authored andcommitted
Extend pylint linting integration (#539)
1 parent e436ff0 commit 4bd351f

File tree

3 files changed

+58
-25
lines changed

3 files changed

+58
-25
lines changed

pyls/plugins/pylint_lint.py

+35-16
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
"""Linter plugin for pylint."""
33
import collections
44
import json
5+
import logging
56
import sys
67

78
from pylint.epylint import py_run
89
from pyls import hookimpl, lsp
910

1011

12+
log = logging.getLogger(__name__)
13+
14+
1115
class PylintLinter(object):
1216
last_diags = collections.defaultdict(list)
1317

@@ -55,14 +59,21 @@ def lint(cls, document, is_saved, flags=''):
5559
path = document.path
5660
if sys.platform.startswith('win'):
5761
path = path.replace('\\', '/')
58-
out, _err = py_run(
59-
'{} -f json {}'.format(path, flags), return_std=True
60-
)
62+
63+
pylint_call = '{} -f json {}'.format(path, flags)
64+
log.debug("Calling pylint with '%s'", pylint_call)
65+
json_out, err = py_run(pylint_call, return_std=True)
66+
67+
# Get strings
68+
json_out = json_out.getvalue()
69+
err = err.getvalue()
70+
71+
if err != '':
72+
log.error("Error calling pylint: '%s'", err)
6173

6274
# pylint prints nothing rather than [] when there are no diagnostics.
6375
# json.loads will not parse an empty string, so just return.
64-
json_str = out.getvalue()
65-
if not json_str.strip():
76+
if not json_out.strip():
6677
cls.last_diags[document.path] = []
6778
return []
6879

@@ -88,24 +99,21 @@ def lint(cls, document, is_saved, flags=''):
8899
# * refactor
89100
# * warning
90101
diagnostics = []
91-
for diag in json.loads(json_str):
102+
for diag in json.loads(json_out):
92103
# pylint lines index from 1, pyls lines index from 0
93104
line = diag['line'] - 1
94-
# But both index columns from 0
95-
col = diag['column']
96-
97-
# It's possible that we're linting an empty file. Even an empty
98-
# file might fail linting if it isn't named properly.
99-
end_col = len(document.lines[line]) if document.lines else 0
100105

101106
err_range = {
102107
'start': {
103108
'line': line,
104-
'character': col,
109+
# Index columns start from 0
110+
'character': diag['column'],
105111
},
106112
'end': {
107113
'line': line,
108-
'character': end_col,
114+
# It's possible that we're linting an empty file. Even an empty
115+
# file might fail linting if it isn't named properly.
116+
'character': len(document.lines[line]) if document.lines else 0,
109117
},
110118
}
111119

@@ -131,6 +139,17 @@ def lint(cls, document, is_saved, flags=''):
131139
return diagnostics
132140

133141

142+
def _build_pylint_flags(settings):
143+
"""Build arguments for calling pylint."""
144+
pylint_args = settings.get('args')
145+
if pylint_args is None:
146+
return ''
147+
return ' '.join(pylint_args)
148+
149+
134150
@hookimpl
135-
def pyls_lint(document, is_saved):
136-
return PylintLinter.lint(document, is_saved)
151+
def pyls_lint(config, document, is_saved):
152+
settings = config.plugin_settings('pylint')
153+
log.debug("Got pylint settings: %s", settings)
154+
flags = _build_pylint_flags(settings)
155+
return PylintLinter.lint(document, is_saved, flags=flags)

test/plugins/test_pylint_lint.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ def write_temp_doc(document, contents):
3838
temp_file.write(contents)
3939

4040

41-
def test_pylint():
41+
def test_pylint(config):
4242
with temp_document(DOC) as doc:
43-
diags = pylint_lint.pyls_lint(doc, True)
43+
diags = pylint_lint.pyls_lint(config, doc, True)
4444

4545
msg = '[unused-import] Unused import sys'
4646
unused_import = [d for d in diags if d['message'] == msg][0]
@@ -49,22 +49,22 @@ def test_pylint():
4949
assert unused_import['severity'] == lsp.DiagnosticSeverity.Warning
5050

5151

52-
def test_syntax_error_pylint():
52+
def test_syntax_error_pylint(config):
5353
with temp_document(DOC_SYNTAX_ERR) as doc:
54-
diag = pylint_lint.pyls_lint(doc, True)[0]
54+
diag = pylint_lint.pyls_lint(config, doc, True)[0]
5555

5656
assert diag['message'].startswith('[syntax-error] invalid syntax')
5757
# Pylint doesn't give column numbers for invalid syntax.
5858
assert diag['range']['start'] == {'line': 0, 'character': 0}
5959
assert diag['severity'] == lsp.DiagnosticSeverity.Error
6060

6161

62-
def test_lint_free_pylint():
62+
def test_lint_free_pylint(config):
6363
# Can't use temp_document because it might give us a file that doesn't
6464
# match pylint's naming requirements. We should be keeping this file clean
6565
# though, so it works for a test of an empty lint.
6666
assert not pylint_lint.pyls_lint(
67-
Document(uris.from_fs_path(__file__)), True)
67+
config, Document(uris.from_fs_path(__file__)), True)
6868

6969

7070
def test_lint_caching():
@@ -95,10 +95,10 @@ def test_lint_caching():
9595
assert not pylint_lint.PylintLinter.lint(doc, False, flags)
9696

9797

98-
def test_per_file_caching():
98+
def test_per_file_caching(config):
9999
# Ensure that diagnostics are cached per-file.
100100
with temp_document(DOC) as doc:
101-
assert pylint_lint.pyls_lint(doc, True)
101+
assert pylint_lint.pyls_lint(config, doc, True)
102102

103103
assert not pylint_lint.pyls_lint(
104-
Document(uris.from_fs_path(__file__)), False)
104+
config, Document(uris.from_fs_path(__file__)), False)

vscode-client/package.json

+14
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,20 @@
225225
"default": true,
226226
"description": "Enable or disable the plugin."
227227
},
228+
"pyls.plugins.pylint.enabled": {
229+
"type": "boolean",
230+
"default": true,
231+
"description": "Enable or disable the plugin."
232+
},
233+
"pyls.plugins.pylint.args": {
234+
"type": "array",
235+
"default": null,
236+
"items": {
237+
"type": "string"
238+
},
239+
"uniqueItems": false,
240+
"description": "Arguments to pass to pylint."
241+
},
228242
"pyls.plugins.rope_completion.enabled": {
229243
"type": "boolean",
230244
"default": true,

0 commit comments

Comments
 (0)