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))
|