Skip to content

Commit

Permalink
add: docsig as a flake8 plugin (#368)
Browse files Browse the repository at this point in the history
Signed-off-by: Stephen Whitlock <[email protected]>
  • Loading branch information
jshwi committed Jun 28, 2024
1 parent eaa4714 commit 5a05f95
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog/368.add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docsig as a flake8 plugin
245 changes: 245 additions & 0 deletions extensions/flake8_docsig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
"""Flake8 implementation of docsig."""

import ast
import contextlib
import io
import re
import typing as t
from argparse import Namespace

from flake8.options.manager import OptionManager

import docsig

Flake8Error = t.Tuple[int, int, str, t.Type]


class Docsig:
"""Flake8 implementation of docsig class.
:param tree: Ast module, which will not be used by flake8 will
provide.
:param filename: Filename to pass to docsig.
"""

off_by_default = False
name = docsig.__name__
version = docsig.__version__

def __init__(self, tree: ast.Module, filename: str) -> None:
_tree = tree # noqa
self.filename = filename

@classmethod
def add_options(cls, parser: OptionManager) -> None:
"""Add flake8 commandline and config options.
:param parser: Flake8 option manager.
"""
parser.add_option(
"-l",
"--list-checks",
action="store_true",
parse_from_config=True,
help="display a list of all checks and their messages",
)
parser.add_option(
"-c",
"--check-class",
action="store_true",
parse_from_config=True,
help="check class docstrings",
)
parser.add_option(
"-C",
"--check-class-constructor",
action="store_true",
parse_from_config=True,
help="check __init__ methods. Note: mutually incompatible with -c",
)
parser.add_option(
"-D",
"--check-dunders",
action="store_true",
parse_from_config=True,
help="check dunder methods",
)
parser.add_option(
"-m",
"--check-protected-class-methods",
action="store_true",
parse_from_config=True,
help="check public methods belonging to protected classes",
)
parser.add_option(
"-N",
"--check-nested",
action="store_true",
parse_from_config=True,
help="check nested functions and classes",
)
parser.add_option(
"-o",
"--check-overridden",
action="store_true",
parse_from_config=True,
help="check overridden methods",
)
parser.add_option(
"-p",
"--check-protected",
action="store_true",
parse_from_config=True,
help="check protected functions and classes",
)
parser.add_option(
"-P",
"--check-property-returns",
action="store_true",
parse_from_config=True,
help="check property return values",
)
parser.add_option(
"-i",
"--ignore-no-params",
action="store_true",
parse_from_config=True,
help="ignore docstrings where parameters are not documented",
)
parser.add_option(
"-a",
"--ignore-args",
action="store_true",
parse_from_config=True,
help="ignore args prefixed with an asterisk",
)
parser.add_option(
"-k",
"--ignore-kwargs",
action="store_true",
parse_from_config=True,
help="ignore kwargs prefixed with two asterisks",
)
parser.add_option(
"-T",
"--ignore-typechecker",
action="store_true",
parse_from_config=True,
help="ignore checking return values",
)
parser.add_option(
"-I",
"--include-ignored",
action="store_true",
parse_from_config=True,
help="check files even if they match a gitignore pattern",
)
parser.add_option(
"-n",
"--no-ansi",
action="store_true",
parse_from_config=True,
help="disable ansi output",
)
parser.add_option(
"-s",
"--string",
action="store",
metavar="STR",
parse_from_config=True,
help="string to parse instead of files",
)
parser.add_option(
"-d",
"--disable",
metavar="LIST",
parse_from_config=True,
comma_separated_list=True,
default=[],
help="comma separated list of rules to disable",
)
parser.add_option(
"-t",
"--target",
metavar="LIST",
parse_from_config=True,
comma_separated_list=True,
default=[],
help="comma separated list of rules to target",
)
parser.add_option(
"--plugin-verbose",
action="store_true",
help="increase output verbosity",
)
parser.add_option(
"-e",
"--exclude-pattern",
metavar="PATTERN",
help="regular expression of files or dirs to exclude from checks",
)

# noinspection DuplicatedCode
@classmethod
def parse_options(cls, options: Namespace) -> None:
"""Parse flake8 options into am instance accessible dict.
:param options: Argparse namespace.
"""
cls.options_dict = { # type: ignore
"check_class": options.check_class,
"check_class_constructor": options.check_class_constructor,
"check_dunders": options.check_dunders,
"check_protected_class_methods": (
options.check_protected_class_methods
),
"check_nested": options.check_nested,
"check_overridden": options.check_overridden,
"check_protected": options.check_protected,
"check_property_returns": options.check_property_returns,
"ignore_no_params": options.ignore_no_params,
"ignore_args": options.ignore_args,
"ignore_kwargs": options.ignore_kwargs,
"ignore_typechecker": options.ignore_typechecker,
"include_ignored": options.include_ignored,
"no_ansi": options.no_ansi,
"verbose": options.plugin_verbose,
"disable": options.disable,
"target": options.target,
"exclude": options.exclude_pattern,
}

def run(self) -> t.Generator[Flake8Error, None, None]:
"""Run docsig and possibly yield a flake8 error.
:return: Flake8 error, if there is one.
"""
if (
self.options_dict["check_class"] # type: ignore
and self.options_dict["check_class_constructor"] # type: ignore
):
raise ValueError(
"argument check-class-constructor: not allowed with argument"
" check-class"
)

buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
docsig.docsig(self.filename, **self.options_dict) # type: ignore

results = re.split(r"^(?!\s)", buffer.getvalue(), flags=re.MULTILINE)
for result in results:
if not result:
continue

# -- example
#
# docsig/_core.py:40 in _run_check
# SIG101: function is missing a docstring (funct...)
try:
header, remainder = result.splitlines()[:2]
lineno, func_name = header.split(":", 1)[1].split(" in ", 1)
line = f"{remainder.lstrip().replace(':', '')} '{func_name}'"
yield int(lineno), 0, line, self.__class__
except ValueError:
print(result.rstrip())
45 changes: 43 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ omit = [

[tool.deptry.per_rule_ignores]
DEP004 = [
"flake8",
"git",
"yaml"
]
Expand Down Expand Up @@ -127,7 +128,10 @@ arcon = ">=0.4.0"
astroid = "^3.0.1"
click = "^8.1.7"
pathspec = "^0.12.1"
python = "^3.8"
python = "^3.8.1"

[tool.poetry.extras]
flake8 = ["flake8"]

[tool.poetry.group.dev.dependencies]
black = "^24.4.2"
Expand All @@ -153,6 +157,9 @@ sphinx-copybutton = "^0.5.2"
sphinx-markdown-builder = ">=0.5.5,<0.7.0"
templatest = "^0.10.1"

[tool.poetry.group.flake8.dependencies]
flake8 = "^7.1.0"

[tool.poetry.group.tests.dependencies]
pytest = "^8.2.0"
pytest-benchmark = "^4.0.0"
Expand All @@ -162,6 +169,9 @@ pytest-sugar = "^1.0.0"
pytest-xdist = "^3.6.1"
templatest = "^0.10.1"

[tool.poetry.plugins."flake8.extension"]
SIG = "extensions.flake8_docsig:Docsig"

[tool.poetry.scripts]
docsig = "docsig.__main__:main"

Expand Down

0 comments on commit 5a05f95

Please sign in to comment.