docsig 0.81.2__tar.gz → 0.82.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docsig-0.81.2 → docsig-0.82.0}/PKG-INFO +4 -4
- {docsig-0.81.2 → docsig-0.82.0}/README.rst +3 -3
- docsig-0.82.0/docsig/_check.py +82 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_config.py +3 -3
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_core.py +20 -186
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_directives.py +45 -5
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_files.py +14 -21
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_module.py +41 -60
- docsig-0.82.0/docsig/_parsers.py +89 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_report.py +72 -16
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_stub.py +45 -32
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_version.py +1 -1
- {docsig-0.81.2 → docsig-0.82.0}/docsig/messages.py +20 -5
- {docsig-0.81.2 → docsig-0.82.0}/pyproject.toml +2 -2
- {docsig-0.81.2 → docsig-0.82.0}/LICENSE +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/__init__.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/__main__.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_decorators.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_hooks.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_main.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/_utils.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/plugin.py +0 -0
- {docsig-0.81.2 → docsig-0.82.0}/docsig/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docsig
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.82.0
|
|
4
4
|
Summary: Check signature params for proper documentation
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -184,7 +184,7 @@ ensure your installation has registered `docsig`
|
|
|
184
184
|
.. code-block:: console
|
|
185
185
|
|
|
186
186
|
$ flake8 --version
|
|
187
|
-
7.3.0 (docsig: 0.
|
|
187
|
+
7.3.0 (docsig: 0.82.0, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
|
|
188
188
|
|
|
189
189
|
And now use `flake8` to lint your files
|
|
190
190
|
|
|
@@ -270,7 +270,7 @@ Standalone
|
|
|
270
270
|
|
|
271
271
|
repos:
|
|
272
272
|
- repo: https://github.com/jshwi/docsig
|
|
273
|
-
rev: v0.
|
|
273
|
+
rev: v0.82.0
|
|
274
274
|
hooks:
|
|
275
275
|
- id: docsig
|
|
276
276
|
args:
|
|
@@ -289,7 +289,7 @@ or integrated with ``flake8``
|
|
|
289
289
|
hooks:
|
|
290
290
|
- id: flake8
|
|
291
291
|
additional_dependencies:
|
|
292
|
-
- docsig==0.
|
|
292
|
+
- docsig==0.82.0
|
|
293
293
|
args:
|
|
294
294
|
- "--sig-check-class"
|
|
295
295
|
- "--sig-check-dunders"
|
|
@@ -155,7 +155,7 @@ ensure your installation has registered `docsig`
|
|
|
155
155
|
.. code-block:: console
|
|
156
156
|
|
|
157
157
|
$ flake8 --version
|
|
158
|
-
7.3.0 (docsig: 0.
|
|
158
|
+
7.3.0 (docsig: 0.82.0, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
|
|
159
159
|
|
|
160
160
|
And now use `flake8` to lint your files
|
|
161
161
|
|
|
@@ -241,7 +241,7 @@ Standalone
|
|
|
241
241
|
|
|
242
242
|
repos:
|
|
243
243
|
- repo: https://github.com/jshwi/docsig
|
|
244
|
-
rev: v0.
|
|
244
|
+
rev: v0.82.0
|
|
245
245
|
hooks:
|
|
246
246
|
- id: docsig
|
|
247
247
|
args:
|
|
@@ -260,7 +260,7 @@ or integrated with ``flake8``
|
|
|
260
260
|
hooks:
|
|
261
261
|
- id: flake8
|
|
262
262
|
additional_dependencies:
|
|
263
|
-
- docsig==0.
|
|
263
|
+
- docsig==0.82.0
|
|
264
264
|
args:
|
|
265
265
|
- "--sig-check-class"
|
|
266
266
|
- "--sig-check-dunders"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docsig._check
|
|
3
|
+
=============
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations as _
|
|
7
|
+
|
|
8
|
+
from ._config import Config as _Config
|
|
9
|
+
from ._module import Function as _Function
|
|
10
|
+
from ._module import Parent as _Parent
|
|
11
|
+
from ._report import Failure as _Failure
|
|
12
|
+
from ._report import Failures as _Failures
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _should_check_function(
|
|
16
|
+
function: _Function,
|
|
17
|
+
parent: _Parent,
|
|
18
|
+
config: _Config,
|
|
19
|
+
) -> bool:
|
|
20
|
+
if function.isoverridden and not config.check.overridden:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
if function.isprotected and not config.check.protected:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
if function.isdunder and not config.check.dunders:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
if function.docstring.bare and config.ignore.no_params:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
if function.isinit and (
|
|
33
|
+
not (config.check.class_ or config.check.class_constructor)
|
|
34
|
+
or (parent.isprotected and not config.check.protected)
|
|
35
|
+
):
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _run_check(
|
|
42
|
+
child: _Parent,
|
|
43
|
+
parent: _Parent,
|
|
44
|
+
config: _Config,
|
|
45
|
+
failures: _Failures,
|
|
46
|
+
) -> None:
|
|
47
|
+
if isinstance(child, _Function) and _should_check_function(
|
|
48
|
+
child,
|
|
49
|
+
parent,
|
|
50
|
+
config,
|
|
51
|
+
):
|
|
52
|
+
failure = _Failure(child, config)
|
|
53
|
+
if failure:
|
|
54
|
+
failures.append(failure)
|
|
55
|
+
|
|
56
|
+
# recurse for either class methods or, if enabled, nested functions
|
|
57
|
+
if not isinstance(child, _Function) or config.check.nested:
|
|
58
|
+
for child_of_child in child.children:
|
|
59
|
+
_run_check(child_of_child, child, config, failures)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_checks(module: _Parent, config: _Config) -> _Failures:
|
|
63
|
+
"""Run checks on the module and return a list of failures.
|
|
64
|
+
|
|
65
|
+
Traverse the module's functions and classes. A callable is checked
|
|
66
|
+
only if it is public or if configuration allows protected or
|
|
67
|
+
overridden members. Failures are collected and returned.
|
|
68
|
+
|
|
69
|
+
:param module: A module object that contains functions or classes.
|
|
70
|
+
:param config: Configuration object.
|
|
71
|
+
:return: A list of function and class failures.
|
|
72
|
+
"""
|
|
73
|
+
failures = _Failures()
|
|
74
|
+
for child in module.children:
|
|
75
|
+
if (
|
|
76
|
+
not child.isprotected
|
|
77
|
+
or config.check.protected
|
|
78
|
+
or config.check.protected_class_methods
|
|
79
|
+
):
|
|
80
|
+
_run_check(child, module, config, failures)
|
|
81
|
+
|
|
82
|
+
return failures
|
|
@@ -18,7 +18,7 @@ from pathlib import Path as _Path
|
|
|
18
18
|
import tomli as _tomli
|
|
19
19
|
|
|
20
20
|
from ._version import __version__
|
|
21
|
-
from .messages import Messages
|
|
21
|
+
from .messages import Messages as _Messages
|
|
22
22
|
|
|
23
23
|
PYPROJECT_TOML = "pyproject.toml"
|
|
24
24
|
|
|
@@ -361,8 +361,8 @@ class Config:
|
|
|
361
361
|
|
|
362
362
|
check: Check = _field(default_factory=Check)
|
|
363
363
|
ignore: Ignore = _field(default_factory=Ignore)
|
|
364
|
-
target:
|
|
365
|
-
disable:
|
|
364
|
+
target: _Messages = _field(default_factory=_Messages)
|
|
365
|
+
disable: _Messages = _field(default_factory=_Messages)
|
|
366
366
|
exclude: list[str] = _field(default_factory=list)
|
|
367
367
|
excludes: list[str] | None = None
|
|
368
368
|
list_checks: bool = False
|
|
@@ -8,30 +8,21 @@ Entry point and orchestration for running docstring/signature checks.
|
|
|
8
8
|
from __future__ import annotations as _
|
|
9
9
|
|
|
10
10
|
import logging as _logging
|
|
11
|
-
import os as _os
|
|
12
11
|
import sys as _sys
|
|
13
12
|
import warnings as _warnings
|
|
14
13
|
from pathlib import Path as _Path
|
|
15
|
-
from tokenize import TokenError as _TokenError
|
|
16
|
-
from warnings import warn as _warn
|
|
17
|
-
|
|
18
|
-
import astroid as _ast
|
|
19
14
|
|
|
20
15
|
from . import _decorators
|
|
16
|
+
from ._check import run_checks as _run_checks
|
|
21
17
|
from ._config import Check as _Check
|
|
22
18
|
from ._config import Config as _Config
|
|
23
19
|
from ._config import Ignore as _Ignore
|
|
24
|
-
from .
|
|
25
|
-
from .
|
|
26
|
-
from .
|
|
27
|
-
from ._module import Error as _Error
|
|
28
|
-
from ._module import Function as _Function
|
|
29
|
-
from ._module import Parent as _Parent
|
|
30
|
-
from ._report import Failure as _Failure
|
|
20
|
+
from ._files import Files as _Files
|
|
21
|
+
from ._parsers import parse_from_file as _parse_from_file
|
|
22
|
+
from ._parsers import parse_from_string as _parse_from_string
|
|
31
23
|
from ._report import Failures as _Failures
|
|
24
|
+
from ._report import report as _report
|
|
32
25
|
from ._utils import print_checks as _print_checks
|
|
33
|
-
from .messages import NEW as _NEW
|
|
34
|
-
from .messages import TEMPLATE as _TEMPLATE
|
|
35
26
|
from .messages import E as _E
|
|
36
27
|
from .messages import Messages as _Messages
|
|
37
28
|
|
|
@@ -70,159 +61,6 @@ def setup_logger(verbose: bool) -> None:
|
|
|
70
61
|
logger.addHandler(stream_handler)
|
|
71
62
|
|
|
72
63
|
|
|
73
|
-
def _should_check_function(
|
|
74
|
-
child: _Function,
|
|
75
|
-
parent: _Parent,
|
|
76
|
-
config: _Config,
|
|
77
|
-
) -> bool:
|
|
78
|
-
if child.isoverridden and not config.check.overridden:
|
|
79
|
-
return False
|
|
80
|
-
|
|
81
|
-
if child.isprotected and not config.check.protected:
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
if child.isdunder and not config.check.dunders:
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
if child.docstring.bare and config.ignore.no_params:
|
|
88
|
-
return False
|
|
89
|
-
|
|
90
|
-
if child.isinit and (
|
|
91
|
-
not (config.check.class_ or config.check.class_constructor)
|
|
92
|
-
or (parent.isprotected and not config.check.protected)
|
|
93
|
-
):
|
|
94
|
-
return False
|
|
95
|
-
|
|
96
|
-
return True
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _run_check(
|
|
100
|
-
child: _Parent,
|
|
101
|
-
parent: _Parent,
|
|
102
|
-
config: _Config,
|
|
103
|
-
failures: _Failures,
|
|
104
|
-
) -> None:
|
|
105
|
-
if isinstance(child, _Function) and _should_check_function(
|
|
106
|
-
child,
|
|
107
|
-
parent,
|
|
108
|
-
config,
|
|
109
|
-
):
|
|
110
|
-
failure = _Failure(child, config.target, config.check.property_returns)
|
|
111
|
-
if failure:
|
|
112
|
-
failures.append(failure)
|
|
113
|
-
|
|
114
|
-
# recurse for either class methods or, if enabled, nested functions
|
|
115
|
-
if not isinstance(child, _Function) or config.check.nested:
|
|
116
|
-
for func in child.children:
|
|
117
|
-
_run_check(func, child, config, failures)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _from_file(path: _Path, config: _Config) -> _Parent:
|
|
121
|
-
try:
|
|
122
|
-
code = path.read_text(encoding="utf-8")
|
|
123
|
-
parent = _from_str(
|
|
124
|
-
code,
|
|
125
|
-
config,
|
|
126
|
-
str(path)[:-3].replace(_os.sep, ".").replace("-", "_"),
|
|
127
|
-
path,
|
|
128
|
-
)
|
|
129
|
-
except UnicodeDecodeError as err:
|
|
130
|
-
logger = _logging.getLogger(__package__)
|
|
131
|
-
logger.debug(_FILE_INFO, path, str(err).replace("\n", " "))
|
|
132
|
-
parent = _Parent(error=_Error.UNICODE)
|
|
133
|
-
|
|
134
|
-
if parent.error is not None and not path.name.endswith(".py"):
|
|
135
|
-
parent = _Parent()
|
|
136
|
-
|
|
137
|
-
return parent
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _from_str(
|
|
141
|
-
code: str,
|
|
142
|
-
config: _Config,
|
|
143
|
-
module_name: str = "",
|
|
144
|
-
path: _Path | None = None,
|
|
145
|
-
) -> _Parent:
|
|
146
|
-
logger = _logging.getLogger(__package__)
|
|
147
|
-
source_name = path or "stdin"
|
|
148
|
-
try:
|
|
149
|
-
node = _ast.parse(code, module_name, str(path))
|
|
150
|
-
try:
|
|
151
|
-
directives = _Directives.from_text(code, config.disable)
|
|
152
|
-
except _TokenError as err:
|
|
153
|
-
directives = _Directives()
|
|
154
|
-
logger.debug(
|
|
155
|
-
_FILE_INFO,
|
|
156
|
-
source_name,
|
|
157
|
-
f"error parsing comments {err}".lower(),
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
parent = _Parent(
|
|
161
|
-
node,
|
|
162
|
-
directives,
|
|
163
|
-
path,
|
|
164
|
-
config.ignore.args,
|
|
165
|
-
config.ignore.kwargs,
|
|
166
|
-
config.check.class_constructor,
|
|
167
|
-
)
|
|
168
|
-
logger.debug(_FILE_INFO, source_name, "Parsing Python code successful")
|
|
169
|
-
except _ast.AstroidSyntaxError as err:
|
|
170
|
-
logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
|
|
171
|
-
parent = _Parent(error=_Error.SYNTAX)
|
|
172
|
-
|
|
173
|
-
return parent
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _get_failures(module: _Parent, config: _Config) -> _Failures:
|
|
177
|
-
failures = _Failures()
|
|
178
|
-
for top_level in module.children:
|
|
179
|
-
if (
|
|
180
|
-
not top_level.isprotected
|
|
181
|
-
or config.check.protected
|
|
182
|
-
or config.check.protected_class_methods
|
|
183
|
-
):
|
|
184
|
-
_run_check(top_level, module, config, failures)
|
|
185
|
-
|
|
186
|
-
return failures
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def _report(
|
|
190
|
-
failures: _Failures,
|
|
191
|
-
config: _Config,
|
|
192
|
-
path: str | None = None,
|
|
193
|
-
) -> int:
|
|
194
|
-
retcodes = [0]
|
|
195
|
-
for failure in failures:
|
|
196
|
-
retcodes.append(failure.retcode)
|
|
197
|
-
path_prefix = f"{path}:" if path is not None else ""
|
|
198
|
-
header = f"{path_prefix}{failure.lineno} in {failure.name}"
|
|
199
|
-
if not config.no_ansi and _sys.stdout.isatty():
|
|
200
|
-
header = f"\033[35m{header}\033[0m"
|
|
201
|
-
|
|
202
|
-
print(header)
|
|
203
|
-
for item in failure:
|
|
204
|
-
extra = None
|
|
205
|
-
if item.hint:
|
|
206
|
-
extra = f"hint: {item.hint}"
|
|
207
|
-
|
|
208
|
-
if item.new:
|
|
209
|
-
extra = "warning: please remember to fix this or disable it"
|
|
210
|
-
_warn(_NEW.format(ref=item.ref), FutureWarning, stacklevel=3)
|
|
211
|
-
|
|
212
|
-
print(
|
|
213
|
-
" "
|
|
214
|
-
+ _TEMPLATE.format(
|
|
215
|
-
ref=item.ref,
|
|
216
|
-
description=item.description,
|
|
217
|
-
symbolic=item.symbolic,
|
|
218
|
-
),
|
|
219
|
-
)
|
|
220
|
-
if extra is not None:
|
|
221
|
-
print(f" {extra}")
|
|
222
|
-
|
|
223
|
-
return max(retcodes)
|
|
224
|
-
|
|
225
|
-
|
|
226
64
|
def handle_deprecations(
|
|
227
65
|
ignore_typechecker: bool,
|
|
228
66
|
disable: list,
|
|
@@ -245,15 +83,15 @@ def handle_deprecations(
|
|
|
245
83
|
disable.extend(messages)
|
|
246
84
|
|
|
247
85
|
|
|
248
|
-
def runner(
|
|
86
|
+
def runner(file: _Path, config: _Config) -> _Failures:
|
|
249
87
|
"""Run checks for a single file and return collected failures.
|
|
250
88
|
|
|
251
|
-
:param
|
|
89
|
+
:param file: Path to the file to check.
|
|
252
90
|
:param config: Configuration object.
|
|
253
91
|
:return: Collected failures for the file.
|
|
254
92
|
"""
|
|
255
|
-
module =
|
|
256
|
-
return
|
|
93
|
+
module = _parse_from_file(file, config)
|
|
94
|
+
return _run_checks(module, config)
|
|
257
95
|
|
|
258
96
|
|
|
259
97
|
@_decorators.parse_msgs
|
|
@@ -367,20 +205,16 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
|
|
|
367
205
|
if config.list_checks:
|
|
368
206
|
return int(bool(_print_checks())) # type: ignore
|
|
369
207
|
|
|
370
|
-
if string
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
patterns=config.exclude,
|
|
375
|
-
excludes=config.excludes,
|
|
376
|
-
include_ignored=config.include_ignored,
|
|
377
|
-
)
|
|
378
|
-
for path_ in paths:
|
|
379
|
-
failures = runner(path_, config)
|
|
380
|
-
retcodes.append(_report(failures, config, str(path_)))
|
|
208
|
+
if string:
|
|
209
|
+
module = _parse_from_string(string, config)
|
|
210
|
+
failures = _run_checks(module, config)
|
|
211
|
+
return _report(failures, config)
|
|
381
212
|
|
|
382
|
-
|
|
213
|
+
retcodes = [0]
|
|
214
|
+
files = _Files(path, config)
|
|
215
|
+
for file in files:
|
|
216
|
+
failures = runner(file, config)
|
|
217
|
+
retcode = _report(failures, config, str(file))
|
|
218
|
+
retcodes.append(retcode)
|
|
383
219
|
|
|
384
|
-
|
|
385
|
-
failures = _get_failures(module, config)
|
|
386
|
-
return _report(failures, config)
|
|
220
|
+
return max(retcodes)
|
|
@@ -15,6 +15,10 @@ from .messages import E as _E
|
|
|
15
15
|
from .messages import Messages as _Messages
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class Comments(_t.List["Comment"]):
|
|
19
|
+
"""List of comments."""
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
class Comment(_Messages):
|
|
19
23
|
"""A single comment directive.
|
|
20
24
|
|
|
@@ -23,12 +27,16 @@ class Comment(_Messages):
|
|
|
23
27
|
"""
|
|
24
28
|
|
|
25
29
|
_valid_kinds = "enable", "disable"
|
|
30
|
+
_valid_flags = ("next",)
|
|
26
31
|
|
|
27
32
|
def __init__(self, string: str, col: int) -> None:
|
|
28
33
|
super().__init__()
|
|
29
34
|
self._ismodule = col == 0
|
|
30
35
|
parts = string.split("=")
|
|
31
|
-
|
|
36
|
+
subparts = parts[0].split("-")
|
|
37
|
+
self._kind = subparts[0]
|
|
38
|
+
self._flag = None if len(subparts) == 1 else subparts[1]
|
|
39
|
+
self._isnext = self._flag == "next"
|
|
32
40
|
if len(parts) == 1:
|
|
33
41
|
self.extend(_E.all)
|
|
34
42
|
else:
|
|
@@ -59,6 +67,21 @@ class Comment(_Messages):
|
|
|
59
67
|
"""Whether this is a disable directive."""
|
|
60
68
|
return self._kind == self._valid_kinds[1]
|
|
61
69
|
|
|
70
|
+
@property
|
|
71
|
+
def isnext(self) -> bool:
|
|
72
|
+
"""Whether this directive applies to the next line only."""
|
|
73
|
+
return self._isnext
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def isvalidflag(self) -> bool:
|
|
77
|
+
"""Whether the directive flag (if any) is valid."""
|
|
78
|
+
return self._flag is None or self._flag in self._valid_flags
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def flag(self) -> str | None:
|
|
82
|
+
"""Flag for this directive (e.g. next), or None."""
|
|
83
|
+
return self._flag
|
|
84
|
+
|
|
62
85
|
@classmethod
|
|
63
86
|
def parse(cls, comment: str, col: int) -> Comment | None:
|
|
64
87
|
"""Parse a comment into a docsig directive when present.
|
|
@@ -73,10 +96,6 @@ class Comment(_Messages):
|
|
|
73
96
|
return None
|
|
74
97
|
|
|
75
98
|
|
|
76
|
-
class Comments(_t.List[Comment]):
|
|
77
|
-
"""List of comments."""
|
|
78
|
-
|
|
79
|
-
|
|
80
99
|
class Directives(_t.Dict[int, _t.Tuple[Comments, _Messages]]):
|
|
81
100
|
"""Map line number to comments and disabled messages for that line.
|
|
82
101
|
|
|
@@ -96,6 +115,9 @@ class Directives(_t.Dict[int, _t.Tuple[Comments, _Messages]]):
|
|
|
96
115
|
directives = cls()
|
|
97
116
|
fin = _StringIO(text)
|
|
98
117
|
comments = Comments()
|
|
118
|
+
enable_next = False
|
|
119
|
+
next_comments = Comments(comments)
|
|
120
|
+
next_messages = _Messages(messages)
|
|
99
121
|
for line in _tokenize.generate_tokens(fin.readline):
|
|
100
122
|
# do nothing for these line types
|
|
101
123
|
if line.type in (_tokenize.NAME, _tokenize.OP, _tokenize.DEDENT):
|
|
@@ -119,12 +141,30 @@ class Directives(_t.Dict[int, _t.Tuple[Comments, _Messages]]):
|
|
|
119
141
|
i for i in messages if i not in comment
|
|
120
142
|
)
|
|
121
143
|
|
|
144
|
+
# if directive has a 'next' flag then alter the
|
|
145
|
+
# module level scope, not the inline scope (which
|
|
146
|
+
# behaves similar to next), so that inline
|
|
147
|
+
# directives are included and not overwritten
|
|
148
|
+
if comment.isnext and not enable_next:
|
|
149
|
+
enable_next = True
|
|
150
|
+
next_comments = Comments(comments)
|
|
151
|
+
next_messages = _Messages(messages)
|
|
152
|
+
|
|
122
153
|
# if module level directive, then make changes
|
|
123
154
|
# globally
|
|
124
155
|
if comment.ismodule:
|
|
125
156
|
comments = scoped_comments
|
|
126
157
|
messages = scoped_messages
|
|
127
158
|
|
|
159
|
+
# if in a 'next' module level scope and the line type is a
|
|
160
|
+
# newline (not a comment to allow 'next' directives to be
|
|
161
|
+
# stacked) then leave the 'next' module level scope and
|
|
162
|
+
# reset the global messages to before the 'next' directive
|
|
163
|
+
elif enable_next and line.type != _tokenize.NL:
|
|
164
|
+
enable_next = False
|
|
165
|
+
comments = next_comments
|
|
166
|
+
messages = next_messages
|
|
167
|
+
|
|
128
168
|
# check that a scoped message has not updated this first, as
|
|
129
169
|
# they take precedence over global messages
|
|
130
170
|
if lineno not in directives:
|
|
@@ -17,6 +17,8 @@ from pathspec import PathSpec as _PathSpec
|
|
|
17
17
|
from pathspec.patterns import GitWildMatchPattern as _GitWildMatchPattern
|
|
18
18
|
from wcmatch.pathlib import Path as _WcPath
|
|
19
19
|
|
|
20
|
+
from ._config import Config as _Config
|
|
21
|
+
|
|
20
22
|
FILE_INFO = "%s: %s"
|
|
21
23
|
|
|
22
24
|
|
|
@@ -69,55 +71,46 @@ def _glob(path: _Path, pattern: str) -> bool:
|
|
|
69
71
|
return _WcPath(str(path)).globmatch(pattern) # type: ignore
|
|
70
72
|
|
|
71
73
|
|
|
72
|
-
class
|
|
74
|
+
class Files(_t.List[_Path]):
|
|
73
75
|
"""Collect paths to check (gitignore and exclude applied).
|
|
74
76
|
|
|
75
77
|
:param paths: Path(s) to collect (files or directories).
|
|
76
|
-
:param
|
|
77
|
-
exclude from checks.
|
|
78
|
-
:param excludes: Files or dirs to exclude from checks.
|
|
79
|
-
:param include_ignored: Check files even if they match a gitignore
|
|
80
|
-
pattern.
|
|
78
|
+
:param config: Configuration object.
|
|
81
79
|
"""
|
|
82
80
|
|
|
83
81
|
def __init__(
|
|
84
82
|
self,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
excludes: list[str] | None = None,
|
|
88
|
-
include_ignored: bool = False,
|
|
83
|
+
paths: tuple[str | _Path, ...],
|
|
84
|
+
config: _Config,
|
|
89
85
|
) -> None:
|
|
90
86
|
super().__init__()
|
|
91
|
-
self._include_ignored = include_ignored
|
|
87
|
+
self._include_ignored = config.include_ignored
|
|
92
88
|
self._gitignore = _Gitignore()
|
|
93
|
-
|
|
89
|
+
logger = _logging.getLogger(__package__)
|
|
94
90
|
for path in paths:
|
|
95
91
|
self._populate(_Path(path))
|
|
96
92
|
|
|
97
93
|
for path in list(self):
|
|
98
94
|
if str(path) != "." and (
|
|
99
|
-
any(_re.match(i, str(path)) for i in
|
|
100
|
-
or any(_glob(path, i) for i in excludes or [])
|
|
95
|
+
any(_re.match(i, str(path)) for i in config.exclude)
|
|
96
|
+
or any(_glob(path, i) for i in config.excludes or [])
|
|
101
97
|
):
|
|
102
|
-
|
|
103
|
-
FILE_INFO,
|
|
104
|
-
path,
|
|
105
|
-
"in exclude list, skipping",
|
|
106
|
-
)
|
|
98
|
+
logger.debug(FILE_INFO, path, "in exclude list, skipping")
|
|
107
99
|
self.remove(path)
|
|
108
100
|
|
|
109
101
|
self.sort()
|
|
110
102
|
|
|
111
103
|
def _populate(self, root: _Path) -> None:
|
|
104
|
+
logger = _logging.getLogger(__package__)
|
|
112
105
|
if not root.exists():
|
|
113
106
|
if root.is_symlink():
|
|
114
|
-
|
|
107
|
+
logger.debug(FILE_INFO, root, "broken link, skipping")
|
|
115
108
|
return
|
|
116
109
|
|
|
117
110
|
raise FileNotFoundError(root)
|
|
118
111
|
|
|
119
112
|
if not self._include_ignored and self._gitignore.match_file(root):
|
|
120
|
-
|
|
113
|
+
logger.debug(FILE_INFO, root, "in gitignore, skipping")
|
|
121
114
|
return
|
|
122
115
|
|
|
123
116
|
if root.is_file():
|
|
@@ -14,6 +14,7 @@ from pathlib import Path as _Path
|
|
|
14
14
|
|
|
15
15
|
import astroid as _ast
|
|
16
16
|
|
|
17
|
+
from ._config import Config as _Config
|
|
17
18
|
from ._directives import Comments as _Comments
|
|
18
19
|
from ._directives import Directives as _Directives
|
|
19
20
|
from ._stub import Docstring as _Docstring
|
|
@@ -22,15 +23,24 @@ from ._stub import Signature as _Signature
|
|
|
22
23
|
from .messages import Messages as _Messages
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
class _Imports(_t.Dict[str, str]):
|
|
27
|
+
"""Represents python imports."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _Overloads(_t.Dict[str, "Function"]):
|
|
31
|
+
"""Represents overloaded methods."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _Children(_t.List[_t.Union["Parent", "Function"]]):
|
|
35
|
+
"""Represents children of an object."""
|
|
36
|
+
|
|
37
|
+
|
|
25
38
|
class Error(_Enum):
|
|
26
39
|
"""Represents an unrecoverable error."""
|
|
27
40
|
|
|
28
41
|
SYNTAX = 1
|
|
29
42
|
UNICODE = 2
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class _Imports(_t.Dict[str, str]):
|
|
33
|
-
"""Represents python imports."""
|
|
43
|
+
RECURSION = 3
|
|
34
44
|
|
|
35
45
|
|
|
36
46
|
class Parent: # pylint: disable=too-many-instance-attributes
|
|
@@ -38,12 +48,8 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
38
48
|
|
|
39
49
|
:param node: AST node for this scope.
|
|
40
50
|
:param directives: Directives and excluded errors per line.
|
|
41
|
-
:param
|
|
42
|
-
:param
|
|
43
|
-
:param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
|
|
44
|
-
:param check_class_constructor: Check the class constructor's
|
|
45
|
-
docstring. Otherwise, expect the constructor's documentation to
|
|
46
|
-
be on the class level docstring.
|
|
51
|
+
:param file: Path for this scope (or None).
|
|
52
|
+
:param config: Configuration object.
|
|
47
53
|
:param imports: Imports in this scope.
|
|
48
54
|
:param error: Unrecoverable error for this scope, if any.
|
|
49
55
|
"""
|
|
@@ -59,28 +65,25 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
59
65
|
| None
|
|
60
66
|
) = None,
|
|
61
67
|
directives: _Directives | None = None,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
ignore_kwargs: bool = False,
|
|
65
|
-
check_class_constructor: bool = False,
|
|
68
|
+
file: _Path | None = None,
|
|
69
|
+
config: _Config | None = None,
|
|
66
70
|
imports: _Imports | None = None,
|
|
67
71
|
error: Error | None = None,
|
|
68
72
|
) -> None:
|
|
69
73
|
super().__init__()
|
|
70
|
-
self._name = "module"
|
|
71
74
|
self._error = error
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
74
|
-
self._check_class_constructor = check_class_constructor
|
|
75
|
+
self._directives = directives or _Directives()
|
|
76
|
+
self._config = config or _Config()
|
|
75
77
|
self._children = _Children()
|
|
76
78
|
self._imports = imports or _Imports()
|
|
77
79
|
self._overloads = _Overloads()
|
|
78
80
|
if node is None:
|
|
81
|
+
self._name = "module"
|
|
79
82
|
if not isinstance(self, Function) and error is not None:
|
|
80
|
-
self._children.append(Function(
|
|
83
|
+
self._children.append(Function(file, error=error))
|
|
81
84
|
else:
|
|
82
85
|
self._name = node.name
|
|
83
|
-
self._parse_ast(node,
|
|
86
|
+
self._parse_ast(node, file)
|
|
84
87
|
|
|
85
88
|
def _parse_ast(
|
|
86
89
|
self,
|
|
@@ -90,19 +93,18 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
90
93
|
| _ast.nodes.FunctionDef
|
|
91
94
|
| _ast.nodes.NodeNG
|
|
92
95
|
),
|
|
93
|
-
|
|
94
|
-
path: _Path | None = None,
|
|
96
|
+
file: _Path | None = None,
|
|
95
97
|
) -> None:
|
|
96
98
|
# need to keep track of `comments` as, even though they are
|
|
97
99
|
# resolved in the directive object, they are needed to notify
|
|
98
100
|
# the user in the case that they are invalid
|
|
99
|
-
parent_comments, parent_disabled =
|
|
101
|
+
parent_comments, parent_disabled = self._directives.get(
|
|
100
102
|
node.lineno,
|
|
101
103
|
(_Comments(), _Messages()),
|
|
102
104
|
)
|
|
103
105
|
if hasattr(node, "body"):
|
|
104
106
|
for subnode in node.body:
|
|
105
|
-
comments, disabled =
|
|
107
|
+
comments, disabled = self._directives.get(
|
|
106
108
|
subnode.lineno,
|
|
107
109
|
(_Comments(), _Messages()),
|
|
108
110
|
)
|
|
@@ -119,12 +121,10 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
119
121
|
func = Function(
|
|
120
122
|
subnode,
|
|
121
123
|
comments,
|
|
122
|
-
|
|
124
|
+
self._directives,
|
|
123
125
|
disabled,
|
|
124
|
-
|
|
125
|
-
self.
|
|
126
|
-
self._ignore_kwargs,
|
|
127
|
-
self._check_class_constructor,
|
|
126
|
+
file,
|
|
127
|
+
self._config,
|
|
128
128
|
self._imports,
|
|
129
129
|
)
|
|
130
130
|
if func.isoverloaded:
|
|
@@ -145,16 +145,14 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
145
145
|
self._children.append(
|
|
146
146
|
Parent(
|
|
147
147
|
subnode,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
self.
|
|
151
|
-
self._ignore_kwargs,
|
|
152
|
-
self._check_class_constructor,
|
|
148
|
+
self._directives,
|
|
149
|
+
file,
|
|
150
|
+
self._config,
|
|
153
151
|
self._imports,
|
|
154
152
|
),
|
|
155
153
|
)
|
|
156
154
|
else:
|
|
157
|
-
self._parse_ast(subnode,
|
|
155
|
+
self._parse_ast(subnode, file)
|
|
158
156
|
|
|
159
157
|
@property
|
|
160
158
|
def isprotected(self) -> bool:
|
|
@@ -179,12 +177,8 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
|
|
|
179
177
|
:param comments: Comment directives for this function.
|
|
180
178
|
:param directives: Directives keyed by line.
|
|
181
179
|
:param messages: Disabled checks for this function.
|
|
182
|
-
:param
|
|
183
|
-
:param
|
|
184
|
-
:param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
|
|
185
|
-
:param check_class_constructor: If the function is the class
|
|
186
|
-
constructor, use its own docstring. Otherwise, use the class
|
|
187
|
-
level docstring for the constructor function.
|
|
180
|
+
:param file: Path for this function (or None).
|
|
181
|
+
:param config: Configuration object.
|
|
188
182
|
:param imports: Imports in this scope.
|
|
189
183
|
:param error: Unrecoverable error for this function, if any.
|
|
190
184
|
"""
|
|
@@ -196,20 +190,16 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
|
|
|
196
190
|
comments: _Comments | None = None,
|
|
197
191
|
directives: _Directives | None = None,
|
|
198
192
|
messages: _Messages | None = None,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
ignore_kwargs: bool = False,
|
|
202
|
-
check_class_constructor: bool = False,
|
|
193
|
+
file: _Path | None = None,
|
|
194
|
+
config: _Config | None = None,
|
|
203
195
|
imports: _Imports | None = None,
|
|
204
196
|
error: Error | None = None,
|
|
205
197
|
) -> None:
|
|
206
198
|
super().__init__(
|
|
207
199
|
node,
|
|
208
200
|
directives or _Directives(),
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
ignore_kwargs,
|
|
212
|
-
check_class_constructor,
|
|
201
|
+
file,
|
|
202
|
+
config,
|
|
213
203
|
imports,
|
|
214
204
|
)
|
|
215
205
|
self._comments = comments or _Comments()
|
|
@@ -232,10 +222,9 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
|
|
|
232
222
|
|
|
233
223
|
self._signature = self._signature.from_ast(
|
|
234
224
|
node,
|
|
235
|
-
|
|
236
|
-
ignore_kwargs,
|
|
225
|
+
self._config.ignore,
|
|
237
226
|
)
|
|
238
|
-
if self.isinit and not
|
|
227
|
+
if self.isinit and not self._config.check.class_constructor:
|
|
239
228
|
# docstring for __init__ is expected on the class
|
|
240
229
|
# docstring
|
|
241
230
|
relevant_doc_node = self._parent.doc_node
|
|
@@ -364,11 +353,3 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
|
|
|
364
353
|
:param rettype: Return type of the overloaded variant.
|
|
365
354
|
"""
|
|
366
355
|
self._signature.overload(rettype)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
class _Overloads(_t.Dict[str, Function]):
|
|
370
|
-
"""Represents overloaded methods."""
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
class _Children(_t.List[_t.Union[Parent, Function]]):
|
|
374
|
-
"""Represents children of an object."""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docsig._parsers
|
|
3
|
+
===============
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations as _
|
|
7
|
+
|
|
8
|
+
import logging as _logging
|
|
9
|
+
import os as _os
|
|
10
|
+
from pathlib import Path as _Path
|
|
11
|
+
from tokenize import TokenError as _TokenError
|
|
12
|
+
|
|
13
|
+
import astroid as _ast
|
|
14
|
+
|
|
15
|
+
from ._config import Config as _Config
|
|
16
|
+
from ._directives import Directives as _Directives
|
|
17
|
+
from ._files import FILE_INFO as _FILE_INFO
|
|
18
|
+
from ._module import Error as _Error
|
|
19
|
+
from ._module import Parent as _Parent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_from_string(
|
|
23
|
+
code: str,
|
|
24
|
+
config: _Config,
|
|
25
|
+
module_name: str = "",
|
|
26
|
+
file: _Path | None = None,
|
|
27
|
+
) -> _Parent:
|
|
28
|
+
"""Build a Parent from a string of Python code.
|
|
29
|
+
|
|
30
|
+
Parses AST and comment directives (AST does not include comments,
|
|
31
|
+
so directives are parsed separately). On syntax error, returns a
|
|
32
|
+
Parent with an error set.
|
|
33
|
+
|
|
34
|
+
:param code: Python source to parse.
|
|
35
|
+
:param config: Configuration object.
|
|
36
|
+
:param module_name: Module name, or empty string.
|
|
37
|
+
:param file: Path for the source (or None for stdin).
|
|
38
|
+
:return: Parent for the parsed code or syntax error.
|
|
39
|
+
"""
|
|
40
|
+
logger = _logging.getLogger(__package__)
|
|
41
|
+
source_name = file or "stdin"
|
|
42
|
+
try:
|
|
43
|
+
node = _ast.parse(code, module_name, str(file))
|
|
44
|
+
try:
|
|
45
|
+
directives = _Directives.from_text(code, config.disable)
|
|
46
|
+
except _TokenError as err:
|
|
47
|
+
directives = _Directives()
|
|
48
|
+
logger.debug(
|
|
49
|
+
_FILE_INFO,
|
|
50
|
+
source_name,
|
|
51
|
+
f"error parsing comments {err}".lower(),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
parent = _Parent(node, directives, file, config)
|
|
55
|
+
logger.debug(_FILE_INFO, source_name, "Parsing Python code successful")
|
|
56
|
+
except _ast.AstroidSyntaxError as err:
|
|
57
|
+
logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
|
|
58
|
+
parent = _Parent(error=_Error.SYNTAX)
|
|
59
|
+
except RecursionError as err:
|
|
60
|
+
logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
|
|
61
|
+
parent = _Parent(error=_Error.RECURSION)
|
|
62
|
+
|
|
63
|
+
return parent
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_from_file(file: _Path, config: _Config) -> _Parent:
|
|
67
|
+
"""Build a Parent from a file containing Python code.
|
|
68
|
+
|
|
69
|
+
Reads the file and delegates to parse_from_string. On UnicodeError,
|
|
70
|
+
returns a Parent with a Unicode error. On syntax error and a non-.py
|
|
71
|
+
path, returns an empty Parent (not treated as Python).
|
|
72
|
+
|
|
73
|
+
:param file: Path to the file to parse.
|
|
74
|
+
:param config: Configuration object.
|
|
75
|
+
:return: Parent for the parsed file or an error/empty Parent.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
code = file.read_text(encoding="utf-8")
|
|
79
|
+
module_name = str(file)[:-3].replace(_os.sep, ".").replace("-", "_")
|
|
80
|
+
parent = parse_from_string(code, config, module_name, file)
|
|
81
|
+
except UnicodeDecodeError as err:
|
|
82
|
+
logger = _logging.getLogger(__package__)
|
|
83
|
+
logger.debug(_FILE_INFO, file, str(err).replace("\n", " "))
|
|
84
|
+
parent = _Parent(error=_Error.UNICODE)
|
|
85
|
+
|
|
86
|
+
if parent.error is not None and not file.name.endswith(".py"):
|
|
87
|
+
parent = _Parent()
|
|
88
|
+
|
|
89
|
+
return parent
|
|
@@ -10,11 +10,14 @@ return the highest exit code.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations as _
|
|
12
12
|
|
|
13
|
-
import contextlib
|
|
13
|
+
import contextlib as _contextlib
|
|
14
|
+
import sys as _sys
|
|
14
15
|
import typing as _t
|
|
16
|
+
from warnings import warn as _warn
|
|
15
17
|
|
|
16
18
|
from astroid.nodes.scoped_nodes import scoped_nodes as _scoped_nodes
|
|
17
19
|
|
|
20
|
+
from ._config import Config as _Config
|
|
18
21
|
from ._module import Error as _Error
|
|
19
22
|
from ._module import Function as _Function
|
|
20
23
|
from ._stub import UNNAMED as _UNNAMED
|
|
@@ -24,14 +27,19 @@ from ._stub import Params as _Params
|
|
|
24
27
|
from ._stub import RetType as _RetType
|
|
25
28
|
from ._utils import almost_equal as _almost_equal
|
|
26
29
|
from ._utils import sentence_tokenizer as _sentence_tokenizer
|
|
30
|
+
from .messages import NEW as _NEW
|
|
31
|
+
from .messages import TEMPLATE as _TEMPLATE
|
|
27
32
|
from .messages import E as _E
|
|
28
33
|
from .messages import Message as _Message
|
|
29
|
-
from .messages import Messages as _Messages
|
|
30
34
|
|
|
31
35
|
_MIN_MATCH = 0.8
|
|
32
36
|
_MAX_MATCH = 1.0
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
class Failures(_t.List["Failure"]):
|
|
40
|
+
"""Sequence of Failure instances (one per function checked)."""
|
|
41
|
+
|
|
42
|
+
|
|
35
43
|
class Failed(_t.NamedTuple):
|
|
36
44
|
"""Single reported issue."""
|
|
37
45
|
|
|
@@ -51,21 +59,17 @@ class Failure(_t.List[Failed]):
|
|
|
51
59
|
violation.
|
|
52
60
|
|
|
53
61
|
:param func: Function under check.
|
|
54
|
-
:param
|
|
55
|
-
:param check_property_returns: Run return checks on properties.
|
|
62
|
+
:param config: Configuration object.
|
|
56
63
|
"""
|
|
57
64
|
|
|
58
|
-
def __init__(
|
|
59
|
-
self,
|
|
60
|
-
func: _Function,
|
|
61
|
-
target: _Messages,
|
|
62
|
-
check_property_returns: bool,
|
|
63
|
-
) -> None:
|
|
65
|
+
def __init__(self, func: _Function, config: _Config) -> None:
|
|
64
66
|
super().__init__()
|
|
65
67
|
self._retcode = 0
|
|
66
68
|
self._func = func
|
|
67
|
-
if target:
|
|
68
|
-
self._func.messages.extend(
|
|
69
|
+
if config.target:
|
|
70
|
+
self._func.messages.extend(
|
|
71
|
+
i for i in _E.all if i not in config.target
|
|
72
|
+
)
|
|
69
73
|
|
|
70
74
|
self._name = self._func.name
|
|
71
75
|
if (
|
|
@@ -89,7 +93,7 @@ class Failure(_t.List[Failed]):
|
|
|
89
93
|
sig = self._func.signature.args.get(index)
|
|
90
94
|
self._sig4xx_parameters(doc, sig)
|
|
91
95
|
|
|
92
|
-
self._sig5xx_returns(
|
|
96
|
+
self._sig5xx_returns(config.check.property_returns)
|
|
93
97
|
|
|
94
98
|
self.sort()
|
|
95
99
|
|
|
@@ -139,7 +143,7 @@ class Failure(_t.List[Failed]):
|
|
|
139
143
|
# then we know that they aren't almost equal, param1 is
|
|
140
144
|
# missing and does need to be inserted in the docstring
|
|
141
145
|
if is_equal:
|
|
142
|
-
with
|
|
146
|
+
with _contextlib.suppress(IndexError):
|
|
143
147
|
is_equal = to[count].name != from_[count + 1].name
|
|
144
148
|
|
|
145
149
|
if not is_equal:
|
|
@@ -157,6 +161,13 @@ class Failure(_t.List[Failed]):
|
|
|
157
161
|
else:
|
|
158
162
|
# unknown-inline-directive
|
|
159
163
|
self._add(_E[2], directive=comment.kind)
|
|
164
|
+
elif not comment.isvalidflag:
|
|
165
|
+
if comment.ismodule:
|
|
166
|
+
# unknown-module-directive-flag
|
|
167
|
+
self._add(_E[6], directive=comment.kind, flag=comment.flag)
|
|
168
|
+
else:
|
|
169
|
+
# unknown-inline-directive-flag
|
|
170
|
+
self._add(_E[7], directive=comment.kind, flag=comment.flag)
|
|
160
171
|
else:
|
|
161
172
|
for rule in comment:
|
|
162
173
|
if not rule.isknown:
|
|
@@ -316,6 +327,8 @@ class Failure(_t.List[Failed]):
|
|
|
316
327
|
self._retcode = 123
|
|
317
328
|
if self._func.error == _Error.UNICODE:
|
|
318
329
|
self._add(_E[902])
|
|
330
|
+
if self._func.error == _Error.RECURSION:
|
|
331
|
+
self._add(_E[903])
|
|
319
332
|
|
|
320
333
|
@property
|
|
321
334
|
def name(self) -> str:
|
|
@@ -333,5 +346,48 @@ class Failure(_t.List[Failed]):
|
|
|
333
346
|
return self._retcode
|
|
334
347
|
|
|
335
348
|
|
|
336
|
-
|
|
337
|
-
|
|
349
|
+
def report(
|
|
350
|
+
failures: Failures,
|
|
351
|
+
config: _Config,
|
|
352
|
+
file: str | None = None,
|
|
353
|
+
) -> int:
|
|
354
|
+
"""Print failures to stdout and return the highest exit code.
|
|
355
|
+
|
|
356
|
+
Iterates over failures, prints each with path, line header, and
|
|
357
|
+
messages, then returns the maximum retcode (0 or non-zero).
|
|
358
|
+
|
|
359
|
+
:param failures: Failures to print (one Failure per function).
|
|
360
|
+
:param config: Config for ANSI and formatting.
|
|
361
|
+
:param file: Module path when failures came from a file (optional).
|
|
362
|
+
:return: Exit code (non-zero if any check failed).
|
|
363
|
+
"""
|
|
364
|
+
retcodes = [0]
|
|
365
|
+
for failure in failures:
|
|
366
|
+
retcodes.append(failure.retcode)
|
|
367
|
+
path_prefix = f"{file}:" if file is not None else ""
|
|
368
|
+
header = f"{path_prefix}{failure.lineno} in {failure.name}"
|
|
369
|
+
if not config.no_ansi and _sys.stdout.isatty():
|
|
370
|
+
header = f"\033[35m{header}\033[0m"
|
|
371
|
+
|
|
372
|
+
print(header)
|
|
373
|
+
for item in failure:
|
|
374
|
+
extra = None
|
|
375
|
+
if item.hint:
|
|
376
|
+
extra = f"hint: {item.hint}"
|
|
377
|
+
|
|
378
|
+
if item.new:
|
|
379
|
+
extra = "warning: please remember to fix this or disable it"
|
|
380
|
+
_warn(_NEW.format(ref=item.ref), FutureWarning, stacklevel=3)
|
|
381
|
+
|
|
382
|
+
print(
|
|
383
|
+
" "
|
|
384
|
+
+ _TEMPLATE.format(
|
|
385
|
+
ref=item.ref,
|
|
386
|
+
description=item.description,
|
|
387
|
+
symbolic=item.symbolic,
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
if extra is not None:
|
|
391
|
+
print(f" {extra}")
|
|
392
|
+
|
|
393
|
+
return max(retcodes)
|
|
@@ -16,6 +16,8 @@ from enum import Enum as _Enum
|
|
|
16
16
|
import astroid as _ast
|
|
17
17
|
import sphinx.ext.napoleon as _s
|
|
18
18
|
|
|
19
|
+
from ._config import Ignore as _Ignore
|
|
20
|
+
|
|
19
21
|
# no function will accidentally have this name
|
|
20
22
|
UNNAMED = "-1000"
|
|
21
23
|
|
|
@@ -100,11 +102,11 @@ class Param:
|
|
|
100
102
|
indent: int = 0,
|
|
101
103
|
closing_token: str = ":",
|
|
102
104
|
) -> None:
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
105
|
-
self.
|
|
106
|
-
self.
|
|
107
|
-
self.
|
|
105
|
+
self._kind = kind
|
|
106
|
+
self._name = name
|
|
107
|
+
self._description = description
|
|
108
|
+
self._indent = indent
|
|
109
|
+
self._closing_token = closing_token
|
|
108
110
|
|
|
109
111
|
def __eq__(self, other: object) -> bool:
|
|
110
112
|
iseq = False
|
|
@@ -125,33 +127,52 @@ class Param:
|
|
|
125
127
|
"""True if the parameter name starts with an underscore."""
|
|
126
128
|
return str(self.name).startswith("_")
|
|
127
129
|
|
|
130
|
+
@property
|
|
131
|
+
def kind(self) -> DocType:
|
|
132
|
+
"""Type of the param."""
|
|
133
|
+
return self._kind
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def name(self) -> str | None:
|
|
137
|
+
"""Name of the param."""
|
|
138
|
+
return self._name
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def description(self) -> str | None:
|
|
142
|
+
"""Description of param."""
|
|
143
|
+
return self._description
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def indent(self) -> int:
|
|
147
|
+
"""Number of spaces in the indent."""
|
|
148
|
+
return self._indent
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def closing_token(self) -> str:
|
|
152
|
+
"""Token used to terminate the param name definition."""
|
|
153
|
+
return self._closing_token
|
|
154
|
+
|
|
128
155
|
|
|
129
156
|
class Params(_t.List[Param]):
|
|
130
157
|
"""A list-like collection of params.
|
|
131
158
|
|
|
132
|
-
:param
|
|
133
|
-
:param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
|
|
159
|
+
:param ignore: Configuration object for what to ignore.
|
|
134
160
|
"""
|
|
135
161
|
|
|
136
|
-
def __init__(
|
|
137
|
-
self,
|
|
138
|
-
ignore_args: bool = False,
|
|
139
|
-
ignore_kwargs: bool = False,
|
|
140
|
-
) -> None:
|
|
162
|
+
def __init__(self, ignore: _Ignore) -> None:
|
|
141
163
|
super().__init__()
|
|
142
|
-
self.
|
|
143
|
-
self._ignore_kwargs = ignore_kwargs
|
|
164
|
+
self._ignore = ignore
|
|
144
165
|
self._duplicates: list[Param] = []
|
|
145
166
|
|
|
146
167
|
def append(self, value: Param) -> None:
|
|
147
168
|
if not value.isprotected and any(
|
|
148
169
|
(
|
|
149
170
|
value.kind == DocType.PARAM,
|
|
150
|
-
(value.kind == DocType.ARG and not self.
|
|
171
|
+
(value.kind == DocType.ARG and not self._ignore.args),
|
|
151
172
|
(
|
|
152
173
|
value.kind == DocType.KWARG
|
|
153
174
|
and not (
|
|
154
|
-
self.
|
|
175
|
+
self._ignore.kwargs
|
|
155
176
|
or any(i.kind == DocType.KWARG for i in self)
|
|
156
177
|
)
|
|
157
178
|
),
|
|
@@ -198,12 +219,8 @@ class Params(_t.List[Param]):
|
|
|
198
219
|
|
|
199
220
|
|
|
200
221
|
class _Stub:
|
|
201
|
-
def __init__(
|
|
202
|
-
self
|
|
203
|
-
ignore_args: bool = False,
|
|
204
|
-
ignore_kwargs: bool = False,
|
|
205
|
-
) -> None:
|
|
206
|
-
self._args = Params(ignore_args, ignore_kwargs)
|
|
222
|
+
def __init__(self, ignore: _Ignore | None = None) -> None:
|
|
223
|
+
self._args = Params(ignore or _Ignore())
|
|
207
224
|
self._returns = False
|
|
208
225
|
|
|
209
226
|
@property
|
|
@@ -222,18 +239,16 @@ class Signature(_Stub):
|
|
|
222
239
|
|
|
223
240
|
:param rettype: Kind of return (none, some, untyped).
|
|
224
241
|
:param returns: True if return is declared in the signature.
|
|
225
|
-
:param
|
|
226
|
-
:param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
|
|
242
|
+
:param ignore: Configuration object for what to ignore.
|
|
227
243
|
"""
|
|
228
244
|
|
|
229
245
|
def __init__(
|
|
230
246
|
self,
|
|
231
247
|
rettype: RetType = RetType.NONE,
|
|
232
248
|
returns: bool = False,
|
|
233
|
-
|
|
234
|
-
ignore_kwargs: bool = False,
|
|
249
|
+
ignore: _Ignore | None = None,
|
|
235
250
|
) -> None:
|
|
236
|
-
super().__init__(
|
|
251
|
+
super().__init__(ignore or _Ignore())
|
|
237
252
|
self._rettype = rettype
|
|
238
253
|
self._returns = returns
|
|
239
254
|
|
|
@@ -241,19 +256,17 @@ class Signature(_Stub):
|
|
|
241
256
|
def from_ast(
|
|
242
257
|
cls,
|
|
243
258
|
node: _ast.nodes.Module | _ast.nodes.ClassDef | _ast.nodes.FunctionDef,
|
|
244
|
-
|
|
245
|
-
ignore_kwargs: bool = False,
|
|
259
|
+
ignore: _Ignore,
|
|
246
260
|
) -> Signature:
|
|
247
261
|
"""Build Signature from a function or class AST node.
|
|
248
262
|
|
|
249
263
|
:param node: AST node (function, class, or module).
|
|
250
|
-
:param
|
|
251
|
-
:param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
|
|
264
|
+
:param ignore: Configuration object for what to ignore.
|
|
252
265
|
:return: Signature with args and return type.
|
|
253
266
|
"""
|
|
254
267
|
rettype = RetType.from_ast(node.returns)
|
|
255
268
|
returns = rettype == RetType.SOME
|
|
256
|
-
signature = cls(rettype, returns,
|
|
269
|
+
signature = cls(rettype, returns, ignore)
|
|
257
270
|
# noinspection PyUnresolvedReferences
|
|
258
271
|
for i in [
|
|
259
272
|
a if isinstance(a, Param) else Param(name=a.name)
|
|
@@ -6,7 +6,7 @@ Error message definitions (Message, MessageMap, E) and format templates
|
|
|
6
6
|
for docstring-check output.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from __future__ import annotations
|
|
9
|
+
from __future__ import annotations as _
|
|
10
10
|
|
|
11
11
|
import typing as _t
|
|
12
12
|
|
|
@@ -24,6 +24,10 @@ NEW = """\
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class Messages(_t.List["Message"]):
|
|
28
|
+
"""Sequence of Message instances, typically for one failure."""
|
|
29
|
+
|
|
30
|
+
|
|
27
31
|
class Message(_t.NamedTuple):
|
|
28
32
|
"""One docstring-check error (ref, description, symbolic, hint)."""
|
|
29
33
|
|
|
@@ -63,10 +67,6 @@ class Message(_t.NamedTuple):
|
|
|
63
67
|
)
|
|
64
68
|
|
|
65
69
|
|
|
66
|
-
class Messages(_t.List[Message]):
|
|
67
|
-
"""Sequence of Message instances, typically for one failure."""
|
|
68
|
-
|
|
69
|
-
|
|
70
70
|
class MessageMap(_t.Dict[int, Message]):
|
|
71
71
|
"""Mapping from integer key to Message (used for the E registry)."""
|
|
72
72
|
|
|
@@ -125,6 +125,16 @@ E = MessageMap(
|
|
|
125
125
|
"both mutually exclusive class options configured",
|
|
126
126
|
"mutually-exclusive-options",
|
|
127
127
|
),
|
|
128
|
+
6: Message(
|
|
129
|
+
"SIG006",
|
|
130
|
+
"unknown module comment flag for {directive} '{flag}'",
|
|
131
|
+
"unknown-module-directive-flag",
|
|
132
|
+
),
|
|
133
|
+
7: Message(
|
|
134
|
+
"SIG007",
|
|
135
|
+
"unknown inline comment flag for {directive} '{flag}'",
|
|
136
|
+
"unknown-inline-directive-flag",
|
|
137
|
+
),
|
|
128
138
|
#: SIG1xx Missing
|
|
129
139
|
101: Message(
|
|
130
140
|
"SIG101",
|
|
@@ -252,5 +262,10 @@ E = MessageMap(
|
|
|
252
262
|
"failed to read file",
|
|
253
263
|
"unicode-decode-error",
|
|
254
264
|
),
|
|
265
|
+
903: Message(
|
|
266
|
+
"SIG903",
|
|
267
|
+
"maximum recursion depth exceeded",
|
|
268
|
+
"recursion-error",
|
|
269
|
+
),
|
|
255
270
|
},
|
|
256
271
|
)
|
|
@@ -11,7 +11,7 @@ line-length = 79
|
|
|
11
11
|
allow_dirty = true
|
|
12
12
|
commit = true
|
|
13
13
|
commit_args = "-sS"
|
|
14
|
-
current_version = "0.
|
|
14
|
+
current_version = "0.82.0"
|
|
15
15
|
message = "bump: version {current_version} → {new_version}"
|
|
16
16
|
sign_tags = true
|
|
17
17
|
tag = true
|
|
@@ -122,7 +122,7 @@ maintainers = [
|
|
|
122
122
|
name = "docsig"
|
|
123
123
|
readme = "README.rst"
|
|
124
124
|
repository = "https://github.com/jshwi/docsig"
|
|
125
|
-
version = "0.
|
|
125
|
+
version = "0.82.0"
|
|
126
126
|
|
|
127
127
|
[tool.poetry.dependencies]
|
|
128
128
|
Sphinx = ">=7,<9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|