forked from python-lsp/python-lsp-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpydocstyle_lint.py
More file actions
133 lines (105 loc) · 4.19 KB
/
pydocstyle_lint.py
File metadata and controls
133 lines (105 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.
import contextlib
import logging
import os
import re
import sys
import pydocstyle
from pylsp import hookimpl, lsp
log = logging.getLogger(__name__)
# PyDocstyle is a little verbose in debug message
pydocstyle_logger = logging.getLogger(pydocstyle.utils.__name__)
pydocstyle_logger.setLevel(logging.INFO)
DEFAULT_MATCH_RE = pydocstyle.config.ConfigurationParser.DEFAULT_MATCH_RE
DEFAULT_MATCH_DIR_RE = pydocstyle.config.ConfigurationParser.DEFAULT_MATCH_DIR_RE
@hookimpl
def pylsp_settings():
# Default pydocstyle to disabled
return {'plugins': {'pydocstyle': {'enabled': False}}}
@hookimpl
def pylsp_lint(config, workspace, document):
# pylint: disable=too-many-locals
with workspace.report_progress("lint: pydocstyle"):
settings = config.plugin_settings('pydocstyle', document_path=document.path)
log.debug("Got pydocstyle settings: %s", settings)
# Explicitly passing a path to pydocstyle means it doesn't respect the --match flag, so do it ourselves
filename_match_re = re.compile(settings.get('match', DEFAULT_MATCH_RE) + '$')
if not filename_match_re.match(os.path.basename(document.path)):
return []
# Likewise with --match-dir
dir_match_re = re.compile(settings.get('matchDir', DEFAULT_MATCH_DIR_RE) + '$')
if not dir_match_re.match(os.path.basename(os.path.dirname(document.path))):
return []
args = [document.path]
if settings.get('convention'):
args.append('--convention=' + settings['convention'])
if settings.get('addSelect'):
args.append('--add-select=' + ','.join(settings['addSelect']))
if settings.get('addIgnore'):
args.append('--add-ignore=' + ','.join(settings['addIgnore']))
elif settings.get('select'):
args.append('--select=' + ','.join(settings['select']))
elif settings.get('ignore'):
args.append('--ignore=' + ','.join(settings['ignore']))
log.info("Using pydocstyle args: %s", args)
conf = pydocstyle.config.ConfigurationParser()
with _patch_sys_argv(args):
# TODO(gatesn): We can add more pydocstyle args here from our pylsp config
conf.parse()
# Will only yield a single filename, the document path
diags = []
for (
filename,
checked_codes,
ignore_decorators,
property_decorators,
ignore_self_only_init,
) in conf.get_files_to_check():
errors = pydocstyle.checker.ConventionChecker().check_source(
document.source,
filename,
ignore_decorators=ignore_decorators,
property_decorators=property_decorators,
ignore_self_only_init=ignore_self_only_init,
)
try:
for error in errors:
if error.code not in checked_codes:
continue
diags.append(_parse_diagnostic(document, error))
except pydocstyle.parser.ParseError:
# In the case we cannot parse the Python file, just continue
pass
log.debug("Got pydocstyle errors: %s", diags)
return diags
def _parse_diagnostic(document, error):
lineno = error.definition.start - 1
line = document.lines[0] if document.lines else ""
start_character = len(line) - len(line.lstrip())
end_character = len(line)
return {
'source': 'pydocstyle',
'code': error.code,
'message': error.message,
'severity': lsp.DiagnosticSeverity.Warning,
'range': {
'start': {
'line': lineno,
'character': start_character
},
'end': {
'line': lineno,
'character': end_character
}
}
}
@contextlib.contextmanager
def _patch_sys_argv(arguments):
old_args = sys.argv
# Preserve argv[0] since it's the executable
sys.argv = old_args[0:1] + arguments
try:
yield
finally:
sys.argv = old_args