Skip to content

Commit 563b9b4

Browse files
authored
Merge branch 'develop' into excavator/policy-bot-oss
2 parents 28d3816 + eb479ff commit 563b9b4

File tree

6 files changed

+240
-18
lines changed

6 files changed

+240
-18
lines changed

.github/workflows/release.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: PyLS Release
2+
3+
on:
4+
release:
5+
types:
6+
- created
7+
8+
9+
jobs:
10+
build:
11+
name: Linux Py${{ matrix.PYTHON_VERSION }}
12+
runs-on: ubuntu-latest
13+
env:
14+
CI: 'true'
15+
OS: 'linux'
16+
PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }}
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
PYTHON_VERSION: ['3.8']
21+
timeout-minutes: 10
22+
steps:
23+
- uses: actions/cache@v1
24+
with:
25+
path: ~/.cache/pip
26+
key: ${{ runner.os }}-${{ matrix.PYTHON_VERSION }}-pip-${{ hashFiles('setup.py') }}
27+
restore-keys: ${{ runner.os }}-${{ matrix.PYTHON_VERSION }}-pip-
28+
- uses: actions/checkout@v2
29+
with:
30+
fetch-depth: 0
31+
- uses: actions/setup-python@v2
32+
with:
33+
python-version: ${{ matrix.PYTHON_VERSION }}
34+
architecture: 'x64'
35+
- run: python -m pip install --upgrade pip setuptools wheel twine
36+
- name: Build and publish python-language-server
37+
env:
38+
TWINE_USERNAME: __token__
39+
TWINE_PASSWORD: ${{ secrets.PYPI_PYLS_TOKEN }}
40+
run: |
41+
python setup.py bdist_wheel --universal
42+
python setup.py sdist
43+
python -m twine check dist/*
44+
python -m twine upload dist/*

.github/workflows/test-mac.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
OS: 'macos'
1919
PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }}
2020
strategy:
21-
fail-fast: false
21+
fail-fast: false
2222
matrix:
2323
PYTHON_VERSION: ['3.8', '3.7', '3.6', '2.7']
2424
timeout-minutes: 10
@@ -31,7 +31,7 @@ jobs:
3131
- uses: actions/checkout@v2
3232
- uses: actions/setup-python@v2
3333
with:
34-
python-version: ${{ matrix.PYTHON_VERSION }}
34+
python-version: ${{ matrix.PYTHON_VERSION }}
3535
architecture: 'x64'
3636
- name: Create Jedi environment for testing
3737
if: matrix.PYTHON_VERSION != '2.7'

pyls/plugins/pylint_lint.py

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import collections
44
import logging
55
import sys
6+
import re
7+
from subprocess import Popen, PIPE
68

79
from pylint.epylint import py_run
810
from pyls import hookimpl, lsp
@@ -154,12 +156,149 @@ def _build_pylint_flags(settings):
154156
def pyls_settings():
155157
# Default pylint to disabled because it requires a config
156158
# file to be useful.
157-
return {'plugins': {'pylint': {'enabled': False, 'args': []}}}
159+
return {'plugins': {'pylint': {
160+
'enabled': False,
161+
'args': [],
162+
# disabled by default as it can slow down the workflow
163+
'executable': None,
164+
}}}
158165

159166

160167
@hookimpl
161168
def pyls_lint(config, document, is_saved):
169+
"""Run pylint linter."""
162170
settings = config.plugin_settings('pylint')
163171
log.debug("Got pylint settings: %s", settings)
172+
# pylint >= 2.5.0 is required for working through stdin and only
173+
# available with python3
174+
if settings.get('executable') and sys.version_info[0] >= 3:
175+
flags = build_args_stdio(settings)
176+
pylint_executable = settings.get('executable', 'pylint')
177+
return pylint_lint_stdin(pylint_executable, document, flags)
164178
flags = _build_pylint_flags(settings)
165179
return PylintLinter.lint(document, is_saved, flags=flags)
180+
181+
182+
def build_args_stdio(settings):
183+
"""Build arguments for calling pylint.
184+
185+
:param settings: client settings
186+
:type settings: dict
187+
188+
:return: arguments to path to pylint
189+
:rtype: list
190+
"""
191+
pylint_args = settings.get('args')
192+
if pylint_args is None:
193+
return []
194+
return pylint_args
195+
196+
197+
def pylint_lint_stdin(pylint_executable, document, flags):
198+
"""Run pylint linter from stdin.
199+
200+
This runs pylint in a subprocess with popen.
201+
This allows passing the file from stdin and as a result
202+
run pylint on unsaved files. Can slowdown the workflow.
203+
204+
:param pylint_executable: path to pylint executable
205+
:type pylint_executable: string
206+
:param document: document to run pylint on
207+
:type document: pyls.workspace.Document
208+
:param flags: arguments to path to pylint
209+
:type flags: list
210+
211+
:return: linting diagnostics
212+
:rtype: list
213+
"""
214+
pylint_result = _run_pylint_stdio(pylint_executable, document, flags)
215+
return _parse_pylint_stdio_result(document, pylint_result)
216+
217+
218+
def _run_pylint_stdio(pylint_executable, document, flags):
219+
"""Run pylint in popen.
220+
221+
:param pylint_executable: path to pylint executable
222+
:type pylint_executable: string
223+
:param document: document to run pylint on
224+
:type document: pyls.workspace.Document
225+
:param flags: arguments to path to pylint
226+
:type flags: list
227+
228+
:return: result of calling pylint
229+
:rtype: string
230+
"""
231+
log.debug("Calling %s with args: '%s'", pylint_executable, flags)
232+
try:
233+
cmd = [pylint_executable]
234+
cmd.extend(flags)
235+
cmd.extend(['--from-stdin', document.path])
236+
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
237+
except IOError:
238+
log.debug("Can't execute %s. Trying with 'python -m pylint'", pylint_executable)
239+
cmd = ['python', '-m', 'pylint']
240+
cmd.extend(flags)
241+
cmd.extend(['--from-stdin', document.path])
242+
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
243+
(stdout, stderr) = p.communicate(document.source.encode())
244+
if stderr:
245+
log.error("Error while running pylint '%s'", stderr.decode())
246+
return stdout.decode()
247+
248+
249+
def _parse_pylint_stdio_result(document, stdout):
250+
"""Parse pylint results.
251+
252+
:param document: document to run pylint on
253+
:type document: pyls.workspace.Document
254+
:param stdout: pylint results to parse
255+
:type stdout: string
256+
257+
:return: linting diagnostics
258+
:rtype: list
259+
"""
260+
diagnostics = []
261+
lines = stdout.splitlines()
262+
for raw_line in lines:
263+
parsed_line = re.match(r'(.*):(\d*):(\d*): (\w*): (.*)', raw_line)
264+
if not parsed_line:
265+
log.debug("Pylint output parser can't parse line '%s'", raw_line)
266+
continue
267+
268+
parsed_line = parsed_line.groups()
269+
if len(parsed_line) != 5:
270+
log.debug("Pylint output parser can't parse line '%s'", raw_line)
271+
continue
272+
273+
_, line, character, code, msg = parsed_line
274+
line = int(line) - 1
275+
character = int(character)
276+
severity_map = {
277+
'C': lsp.DiagnosticSeverity.Information,
278+
'E': lsp.DiagnosticSeverity.Error,
279+
'F': lsp.DiagnosticSeverity.Error,
280+
'R': lsp.DiagnosticSeverity.Hint,
281+
'W': lsp.DiagnosticSeverity.Warning,
282+
}
283+
severity = severity_map[code[0]]
284+
diagnostics.append(
285+
{
286+
'source': 'pylint',
287+
'code': code,
288+
'range': {
289+
'start': {
290+
'line': line,
291+
'character': character
292+
},
293+
'end': {
294+
'line': line,
295+
# no way to determine the column
296+
'character': len(document.lines[line]) - 1
297+
}
298+
},
299+
'message': msg,
300+
'severity': severity,
301+
}
302+
)
303+
304+
return diagnostics

setup.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
#!/usr/bin/env python
2+
import sys
23
from setuptools import find_packages, setup
34
import versioneer
5+
import sys
46

57
README = open('README.rst', 'r').read()
68

9+
install_requires = [
10+
'configparser; python_version<"3.0"',
11+
'future>=0.14.0; python_version<"3"',
12+
'backports.functools_lru_cache; python_version<"3.2"',
13+
'jedi>=0.17.0,<0.18.0',
14+
'python-jsonrpc-server>=0.4.0',
15+
'pluggy']
16+
17+
if sys.version_info[0] == 2:
18+
install_requires.append('ujson<=2.0.3; platform_system!="Windows"')
19+
else:
20+
install_requires.append('ujson>=3.0.0')
21+
722

823
setup(
924
name='python-language-server',
@@ -31,15 +46,7 @@
3146
# your project is installed. For an analysis of "install_requires" vs pip's
3247
# requirements files see:
3348
# https://packaging.python.org/en/latest/requirements.html
34-
install_requires=[
35-
'configparser; python_version<"3.0"',
36-
'future>=0.14.0; python_version<"3"',
37-
'backports.functools_lru_cache; python_version<"3.2"',
38-
'jedi>=0.17.0,<0.18.0',
39-
'python-jsonrpc-server>=0.3.2',
40-
'pluggy',
41-
'ujson<=1.35; platform_system!="Windows"'
42-
],
49+
install_requires=install_requires,
4350

4451
# List additional groups of dependencies here (e.g. development
4552
# dependencies). You can install these using the following syntax,
@@ -53,7 +60,9 @@
5360
'pycodestyle>=2.6.0,<2.7.0',
5461
'pydocstyle>=2.0.0',
5562
'pyflakes>=2.2.0,<2.3.0',
56-
'pylint',
63+
# pylint >= 2.5.0 is required for working through stdin and only
64+
# available with python3
65+
'pylint>=2.5.0' if sys.version_info.major >= 3 else 'pylint',
5766
'rope>=0.10.5',
5867
'yapf',
5968
],
@@ -63,12 +72,14 @@
6372
'pycodestyle': ['pycodestyle>=2.6.0,<2.7.0'],
6473
'pydocstyle': ['pydocstyle>=2.0.0'],
6574
'pyflakes': ['pyflakes>=2.2.0,<2.3.0'],
66-
'pylint': ['pylint'],
75+
'pylint': [
76+
'pylint>=2.5.0' if sys.version_info.major >= 3 else 'pylint'],
6777
'rope': ['rope>0.10.5'],
6878
'yapf': ['yapf'],
69-
'test': ['versioneer', 'pylint', 'pytest', 'mock', 'pytest-cov',
70-
'coverage', 'numpy', 'pandas', 'matplotlib',
71-
'pyqt5;python_version>="3"', 'flaky'],
79+
'test': ['versioneer',
80+
'pylint>=2.5.0' if sys.version_info.major >= 3 else 'pylint',
81+
'pytest', 'mock', 'pytest-cov', 'coverage', 'numpy', 'pandas',
82+
'matplotlib', 'pyqt5;python_version>="3"', 'flaky'],
7283
},
7384

7485
# To provide executable scripts, use entry points in preference to the

test/plugins/test_pylint_lint.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import tempfile
55

6-
from test import py2_only, py3_only
6+
from test import py2_only, py3_only, IS_PY3
77
from pyls import lsp, uris
88
from pyls.workspace import Document
99
from pyls.plugins import pylint_lint
@@ -49,6 +49,20 @@ def test_pylint(config, workspace):
4949
assert unused_import['range']['start'] == {'line': 0, 'character': 0}
5050
assert unused_import['severity'] == lsp.DiagnosticSeverity.Warning
5151

52+
if IS_PY3:
53+
# test running pylint in stdin
54+
config.plugin_settings('pylint')['executable'] = 'pylint'
55+
diags = pylint_lint.pyls_lint(config, doc, True)
56+
57+
msg = 'Unused import sys (unused-import)'
58+
unused_import = [d for d in diags if d['message'] == msg][0]
59+
60+
assert unused_import['range']['start'] == {
61+
'line': 0,
62+
'character': 0,
63+
}
64+
assert unused_import['severity'] == lsp.DiagnosticSeverity.Warning
65+
5266

5367
@py3_only
5468
def test_syntax_error_pylint_py3(config, workspace):
@@ -60,6 +74,15 @@ def test_syntax_error_pylint_py3(config, workspace):
6074
assert diag['range']['start'] == {'line': 0, 'character': 12}
6175
assert diag['severity'] == lsp.DiagnosticSeverity.Error
6276

77+
# test running pylint in stdin
78+
config.plugin_settings('pylint')['executable'] = 'pylint'
79+
diag = pylint_lint.pyls_lint(config, doc, True)[0]
80+
81+
assert diag['message'].startswith('invalid syntax')
82+
# Pylint doesn't give column numbers for invalid syntax.
83+
assert diag['range']['start'] == {'line': 0, 'character': 12}
84+
assert diag['severity'] == lsp.DiagnosticSeverity.Error
85+
6386

6487
@py2_only
6588
def test_syntax_error_pylint_py2(config, workspace):

vscode-client/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@
259259
"uniqueItems": false,
260260
"description": "Arguments to pass to pylint."
261261
},
262+
"pyls.plugins.pylint.executable": {
263+
"type": "string",
264+
"default": null,
265+
"description": "Executable to run pylint with. Enabling this will run pylint on unsaved files via stdin. Can slow down workflow. Only works with python3."
266+
},
262267
"pyls.plugins.rope_completion.enabled": {
263268
"type": "boolean",
264269
"default": true,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy