-
Notifications
You must be signed in to change notification settings - Fork 18
/
flake8_docstrings.py
192 lines (165 loc) · 6.15 KB
/
flake8_docstrings.py
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""Implementation of pydocstyle integration with Flake8.
pydocstyle docstrings convention needs error code and class parser for be
included as module into flake8
"""
import re
supports_ignore_inline_noqa = False
supports_property_decorators = False
supports_ignore_self_only_init = False
try:
import pydocstyle as pep257
module_name = "pydocstyle"
pydocstyle_version = tuple(
int(num) for num in pep257.__version__.split(".")
)
supports_ignore_inline_noqa = pydocstyle_version >= (6, 0, 0)
supports_property_decorators = pydocstyle_version >= (6, 2, 0)
supports_ignore_self_only_init = pydocstyle_version >= (6, 3, 0)
except ImportError:
import pep257
module_name = "pep257"
__version__ = "1.7.0"
__all__ = ("pep257Checker",)
class _ContainsAll:
def __contains__(self, code): # type: (str) -> bool
return True
class EnvironError(pep257.Error):
def __init__(self, err):
super().__init__(
code="D998",
short_desc="EnvironmentError: " + str(err),
context=None,
)
@property
def line(self):
"""Return 0 as line number for EnvironmentError."""
return 0
class AllError(pep257.Error):
def __init__(self, err):
super().__init__(
code="D999",
short_desc=str(err).partition("\n")[0],
context=None,
)
@property
def line(self):
"""pep257.AllError does not contain line number. Return 0 instead."""
return 0
class pep257Checker:
"""Flake8 needs a class to check python file."""
name = "flake8-docstrings"
version = f"{__version__}, {module_name}: {pep257.__version__}"
def __init__(self, tree, filename, lines):
"""Initialize the checker."""
self.tree = tree
self.filename = filename
self.checker = pep257.ConventionChecker()
self.source = "".join(lines)
@classmethod
def add_options(cls, parser):
"""Add plugin configuration option to flake8."""
parser.add_option(
"--docstring-convention",
action="store",
parse_from_config=True,
default="pep257",
choices=sorted(pep257.conventions) + ["all"],
help=(
"pydocstyle docstring convention, default 'pep257'. "
"Use the special value 'all' to enable all codes (note: "
"some codes are conflicting so you'll need to then exclude "
"those)."
),
)
parser.add_option(
"--ignore-decorators",
action="store",
parse_from_config=True,
default=None,
help=(
"pydocstyle ignore-decorators regular expression, "
"default None. "
"Ignore any functions or methods that are decorated by "
"a function with a name fitting this regular expression. "
"The default is not ignore any decorated functions. "
),
)
if supports_property_decorators:
from pydocstyle.config import ConfigurationParser
default_property_decorators = (
ConfigurationParser.DEFAULT_PROPERTY_DECORATORS
)
parser.add_option(
"--property-decorators",
action="store",
parse_from_config=True,
default=default_property_decorators,
help=(
"consider any method decorated with one of these "
"decorators as a property, and consequently allow "
"a docstring which is not in imperative mood; default "
f"is --property-decorators='{default_property_decorators}'"
),
)
if supports_ignore_self_only_init:
parser.add_option(
"--ignore-self-only-init",
action="store_true",
parse_from_config=True,
help="ignore __init__ methods which only have a self param.",
)
@classmethod
def parse_options(cls, options):
"""Parse the configuration options given to flake8."""
cls.convention = options.docstring_convention
cls.ignore_decorators = (
re.compile(options.ignore_decorators)
if options.ignore_decorators
else None
)
if supports_property_decorators:
cls.property_decorators = options.property_decorators
if supports_ignore_self_only_init:
cls.ignore_self_only_init = options.ignore_self_only_init
def _call_check_source(self):
check_source_kwargs = {}
if supports_ignore_inline_noqa:
check_source_kwargs["ignore_inline_noqa"] = True
if supports_property_decorators:
check_source_kwargs["property_decorators"] = (
set(self.property_decorators.split(","))
if self.property_decorators
else None
)
if supports_ignore_self_only_init:
check_source_kwargs[
"ignore_self_only_init"
] = self.ignore_self_only_init
return self.checker.check_source(
self.source,
self.filename,
ignore_decorators=self.ignore_decorators,
**check_source_kwargs,
)
def _check_source(self):
try:
for err in self._call_check_source():
yield err
except pep257.AllError as err:
yield AllError(err)
except OSError as err:
yield EnvironError(err)
def run(self):
"""Use directly check() api from pydocstyle."""
if self.convention == "all":
checked_codes = _ContainsAll()
else:
checked_codes = pep257.conventions[self.convention] | {
"D998",
"D999",
}
for error in self._check_source():
if isinstance(error, pep257.Error) and error.code in checked_codes:
# NOTE(sigmavirus24): Fixes GitLab#3
message = f"{error.code} {error.short_desc}"
yield (error.line, 0, message, type(self))