docsig 0.91.0__tar.gz → 0.91.1__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.91.0 → docsig-0.91.1}/PKG-INFO +8 -8
- {docsig-0.91.0 → docsig-0.91.1}/README.rst +7 -7
- docsig-0.91.0/docsig/_report.py → docsig-0.91.1/docsig/_checker.py +19 -207
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_core.py +3 -3
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_decorators.py +1 -1
- docsig-0.91.1/docsig/_diagnostic.py +155 -0
- docsig-0.91.1/docsig/_report.py +94 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_stub.py +2 -16
- docsig-0.91.0/docsig/_check.py → docsig-0.91.1/docsig/_traverse.py +3 -3
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_version.py +1 -1
- {docsig-0.91.0 → docsig-0.91.1}/pyproject.toml +2 -2
- docsig-0.91.0/docsig/_diagnostic.py +0 -21
- {docsig-0.91.0 → docsig-0.91.1}/LICENSE +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/LICENSES/SPHINX_LICENSE +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/__init__.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/__main__.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_config.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_directives.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_files.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_hooks.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_main.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_module.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_parsers.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_utils.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/_vendor/sphinx/ext/napoleon.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/messages.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/plugin/__init__.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/plugin/_flake8.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/plugin/_validate_pyproject.py +0 -0
- {docsig-0.91.0 → docsig-0.91.1}/docsig/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docsig
|
|
3
|
-
Version: 0.91.
|
|
3
|
+
Version: 0.91.1
|
|
4
4
|
Summary: Check signature params for proper documentation
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -59,7 +59,7 @@ Description-Content-Type: text/x-rst
|
|
|
59
59
|
:target: https://docsig.io/en/latest/?badge=latest
|
|
60
60
|
:alt: readthedocs.org
|
|
61
61
|
.. |python3.10| image:: https://img.shields.io/badge/python-3.10-blue.svg
|
|
62
|
-
:target: https://www.python.org/downloads/release/python-
|
|
62
|
+
:target: https://www.python.org/downloads/release/python-310
|
|
63
63
|
:alt: python3.10
|
|
64
64
|
.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
65
65
|
:target: https://github.com/psf/black
|
|
@@ -80,7 +80,7 @@ Description-Content-Type: text/x-rst
|
|
|
80
80
|
:target: https://github.com/jshwi/docsig/actions/workflows/build-intellij-plugin.yaml
|
|
81
81
|
:alt: Build
|
|
82
82
|
.. |VS Code Build| image:: https://github.com/jshwi/docsig/actions/workflows/build-vscode-extension.yaml/badge.svg
|
|
83
|
-
:target: https://github.com/jshwi/docsig/actions/workflows/build-vscode-extension.yaml
|
|
83
|
+
:target: https://github.com/jshwi/docsig/actions/workflows/build-vscode-extension.yaml
|
|
84
84
|
:alt: Build
|
|
85
85
|
.. |VS Code Extension| image:: https://img.shields.io/badge/vscode%20extension-v1.0.1-007ACC
|
|
86
86
|
:target: https://marketplace.visualstudio.com/items?itemName=jshwi.docsig
|
|
@@ -125,14 +125,14 @@ Commandline
|
|
|
125
125
|
[--check-protected-class-methods] [--ignore-args] [--ignore-kwargs]
|
|
126
126
|
[--ignore-no-params] [-d LIST] [-t LIST] [-e PATTERN] [-E PATH [PATH ...]]
|
|
127
127
|
[-i] [-s STR]
|
|
128
|
-
[path
|
|
128
|
+
[path ...]
|
|
129
129
|
|
|
130
130
|
Check signature params for proper documentation
|
|
131
131
|
|
|
132
132
|
positional arguments:
|
|
133
133
|
path directories or files to check
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
options:
|
|
136
136
|
-h, --help show this help message and exit
|
|
137
137
|
-V, --version show program's version number and exit
|
|
138
138
|
-l, --list-checks display a list of all checks and their messages
|
|
@@ -206,7 +206,7 @@ ensure your installation has registered `docsig`
|
|
|
206
206
|
.. code-block:: console
|
|
207
207
|
|
|
208
208
|
$ flake8 --version
|
|
209
|
-
7.3.0 (docsig: 0.91.
|
|
209
|
+
7.3.0 (docsig: 0.91.1, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
|
|
210
210
|
|
|
211
211
|
And now use `flake8` to lint your files
|
|
212
212
|
|
|
@@ -292,7 +292,7 @@ Standalone
|
|
|
292
292
|
|
|
293
293
|
repos:
|
|
294
294
|
- repo: https://github.com/jshwi/docsig
|
|
295
|
-
rev: v0.91.
|
|
295
|
+
rev: v0.91.1
|
|
296
296
|
hooks:
|
|
297
297
|
- id: docsig
|
|
298
298
|
args:
|
|
@@ -311,7 +311,7 @@ or integrated with ``flake8``
|
|
|
311
311
|
hooks:
|
|
312
312
|
- id: flake8
|
|
313
313
|
additional_dependencies:
|
|
314
|
-
- docsig==0.91.
|
|
314
|
+
- docsig==0.91.1
|
|
315
315
|
args:
|
|
316
316
|
- "--sig-check-class"
|
|
317
317
|
- "--sig-check-dunders"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
:target: https://docsig.io/en/latest/?badge=latest
|
|
32
32
|
:alt: readthedocs.org
|
|
33
33
|
.. |python3.10| image:: https://img.shields.io/badge/python-3.10-blue.svg
|
|
34
|
-
:target: https://www.python.org/downloads/release/python-
|
|
34
|
+
:target: https://www.python.org/downloads/release/python-310
|
|
35
35
|
:alt: python3.10
|
|
36
36
|
.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
37
37
|
:target: https://github.com/psf/black
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
:target: https://github.com/jshwi/docsig/actions/workflows/build-intellij-plugin.yaml
|
|
53
53
|
:alt: Build
|
|
54
54
|
.. |VS Code Build| image:: https://github.com/jshwi/docsig/actions/workflows/build-vscode-extension.yaml/badge.svg
|
|
55
|
-
:target: https://github.com/jshwi/docsig/actions/workflows/build-vscode-extension.yaml
|
|
55
|
+
:target: https://github.com/jshwi/docsig/actions/workflows/build-vscode-extension.yaml
|
|
56
56
|
:alt: Build
|
|
57
57
|
.. |VS Code Extension| image:: https://img.shields.io/badge/vscode%20extension-v1.0.1-007ACC
|
|
58
58
|
:target: https://marketplace.visualstudio.com/items?itemName=jshwi.docsig
|
|
@@ -97,14 +97,14 @@ Commandline
|
|
|
97
97
|
[--check-protected-class-methods] [--ignore-args] [--ignore-kwargs]
|
|
98
98
|
[--ignore-no-params] [-d LIST] [-t LIST] [-e PATTERN] [-E PATH [PATH ...]]
|
|
99
99
|
[-i] [-s STR]
|
|
100
|
-
[path
|
|
100
|
+
[path ...]
|
|
101
101
|
|
|
102
102
|
Check signature params for proper documentation
|
|
103
103
|
|
|
104
104
|
positional arguments:
|
|
105
105
|
path directories or files to check
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
options:
|
|
108
108
|
-h, --help show this help message and exit
|
|
109
109
|
-V, --version show program's version number and exit
|
|
110
110
|
-l, --list-checks display a list of all checks and their messages
|
|
@@ -178,7 +178,7 @@ ensure your installation has registered `docsig`
|
|
|
178
178
|
.. code-block:: console
|
|
179
179
|
|
|
180
180
|
$ flake8 --version
|
|
181
|
-
7.3.0 (docsig: 0.91.
|
|
181
|
+
7.3.0 (docsig: 0.91.1, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
|
|
182
182
|
|
|
183
183
|
And now use `flake8` to lint your files
|
|
184
184
|
|
|
@@ -264,7 +264,7 @@ Standalone
|
|
|
264
264
|
|
|
265
265
|
repos:
|
|
266
266
|
- repo: https://github.com/jshwi/docsig
|
|
267
|
-
rev: v0.91.
|
|
267
|
+
rev: v0.91.1
|
|
268
268
|
hooks:
|
|
269
269
|
- id: docsig
|
|
270
270
|
args:
|
|
@@ -283,7 +283,7 @@ or integrated with ``flake8``
|
|
|
283
283
|
hooks:
|
|
284
284
|
- id: flake8
|
|
285
285
|
additional_dependencies:
|
|
286
|
-
- docsig==0.91.
|
|
286
|
+
- docsig==0.91.1
|
|
287
287
|
args:
|
|
288
288
|
- "--sig-check-class"
|
|
289
289
|
- "--sig-check-dunders"
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
|
-
docsig.
|
|
3
|
-
|
|
2
|
+
docsig._checker
|
|
3
|
+
===============
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Run docstring and signature checks for a single function.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import contextlib as _contextlib
|
|
9
|
-
import json as _json
|
|
10
|
-
import os as _os
|
|
11
|
-
import sys as _sys
|
|
12
9
|
import typing as _t
|
|
13
|
-
from warnings import warn as _warn
|
|
14
10
|
|
|
15
11
|
import astroid as _ast
|
|
16
12
|
|
|
17
13
|
from ._config import Config as _Config
|
|
14
|
+
from ._diagnostic import Collector as _Collector
|
|
18
15
|
from ._diagnostic import Diagnostic as _Diagnostic
|
|
16
|
+
from ._diagnostic import FunctionResult as _FunctionResult
|
|
19
17
|
from ._module import Function as _Function
|
|
20
18
|
from ._stub import UNNAMED as _UNNAMED
|
|
21
19
|
from ._stub import VALID_DESCRIPTION as _VALID_DESCRIPTION
|
|
@@ -24,8 +22,6 @@ from ._stub import Params as _Params
|
|
|
24
22
|
from ._stub import RetType as _RetType
|
|
25
23
|
from ._utils import almost_equal as _almost_equal
|
|
26
24
|
from ._utils import sentence_tokenizer as _sentence_tokenizer
|
|
27
|
-
from .messages import NEW as _NEW
|
|
28
|
-
from .messages import TEMPLATE as _TEMPLATE
|
|
29
25
|
from .messages import E as _E
|
|
30
26
|
from .messages import Message as _Message
|
|
31
27
|
|
|
@@ -33,42 +29,6 @@ _MIN_MATCH = 0.8
|
|
|
33
29
|
_MAX_MATCH = 1.0
|
|
34
30
|
|
|
35
31
|
|
|
36
|
-
def check_function(func: _Function, config: _Config) -> "_FunctionResult":
|
|
37
|
-
"""Run configured checks for one function and return the result.
|
|
38
|
-
|
|
39
|
-
:param func: Function under check.
|
|
40
|
-
:param config: Configuration object.
|
|
41
|
-
:return: Collected diagnostics for the function.
|
|
42
|
-
"""
|
|
43
|
-
return FunctionChecker(func, config).run()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class RetCode:
|
|
47
|
-
"""RetCode object.
|
|
48
|
-
|
|
49
|
-
:param code: Initial return code, if any, otherwise zero.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, code: int = 0) -> None:
|
|
53
|
-
self._data = [code]
|
|
54
|
-
|
|
55
|
-
def add(self, code: int) -> None:
|
|
56
|
-
"""Add a return code.
|
|
57
|
-
|
|
58
|
-
:param code: Return code to add.
|
|
59
|
-
"""
|
|
60
|
-
self._data.append(code)
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def result(self) -> int:
|
|
64
|
-
"""Maximum return code."""
|
|
65
|
-
return max(self._data)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class Failures(list["_FunctionResult"]):
|
|
69
|
-
"""Sequence of result instances (one per function checked)."""
|
|
70
|
-
|
|
71
|
-
|
|
72
32
|
class FunctionChecker: # pylint: disable=too-few-public-methods
|
|
73
33
|
"""Collect docstring and signature failures for one function.
|
|
74
34
|
|
|
@@ -99,7 +59,7 @@ class FunctionChecker: # pylint: disable=too-few-public-methods
|
|
|
99
59
|
|
|
100
60
|
self._collector = _Collector(func, self._name, self._func.lineno)
|
|
101
61
|
|
|
102
|
-
def run(self) ->
|
|
62
|
+
def run(self) -> _FunctionResult:
|
|
103
63
|
"""Run the function checks and return the result.
|
|
104
64
|
|
|
105
65
|
:return: Function result.
|
|
@@ -211,15 +171,17 @@ class FunctionChecker: # pylint: disable=too-few-public-methods
|
|
|
211
171
|
|
|
212
172
|
def _sig2xx_signature(self) -> None:
|
|
213
173
|
if self._func.docstring.args.duplicated:
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
174
|
+
# reduce each duplicate group to one representative without
|
|
175
|
+
# mutating the list during iteration (which skips items)
|
|
176
|
+
seen: set[str | None] = set()
|
|
177
|
+
deduped: list[_Param] = []
|
|
178
|
+
for arg in self._func.docstring.args:
|
|
179
|
+
if arg.name not in seen:
|
|
180
|
+
seen.add(arg.name)
|
|
181
|
+
deduped.append(arg)
|
|
182
|
+
|
|
183
|
+
list.clear(self._func.docstring.args)
|
|
184
|
+
list.extend(self._func.docstring.args, deduped)
|
|
223
185
|
|
|
224
186
|
# duplicate-params-found
|
|
225
187
|
self._add(_E[201])
|
|
@@ -277,6 +239,8 @@ class FunctionChecker: # pylint: disable=too-few-public-methods
|
|
|
277
239
|
not in (
|
|
278
240
|
"`",
|
|
279
241
|
".",
|
|
242
|
+
"!",
|
|
243
|
+
"?",
|
|
280
244
|
)
|
|
281
245
|
):
|
|
282
246
|
self._add(_E[306])
|
|
@@ -361,155 +325,3 @@ class FunctionChecker: # pylint: disable=too-few-public-methods
|
|
|
361
325
|
# duplicates-found-in-mros
|
|
362
326
|
if self._func.error is _ast.DuplicateBasesError:
|
|
363
327
|
self._add(_E[904])
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
class _Collector:
|
|
367
|
-
def __init__(
|
|
368
|
-
self,
|
|
369
|
-
func: _Function,
|
|
370
|
-
qualified_name: str,
|
|
371
|
-
lineno: int,
|
|
372
|
-
) -> None:
|
|
373
|
-
self._func = func
|
|
374
|
-
self._qualified_name = qualified_name
|
|
375
|
-
self._lineno = lineno
|
|
376
|
-
self._diagnostics: list[_Diagnostic] = []
|
|
377
|
-
self._retcode = RetCode()
|
|
378
|
-
|
|
379
|
-
def add(
|
|
380
|
-
self,
|
|
381
|
-
value: _Message,
|
|
382
|
-
include_hint: bool = False,
|
|
383
|
-
**kwargs: _t.Any,
|
|
384
|
-
) -> None:
|
|
385
|
-
"""Add a diagnostic message.
|
|
386
|
-
|
|
387
|
-
:param value: Message to add.
|
|
388
|
-
:param include_hint: Whether to include the hint.
|
|
389
|
-
:param kwargs: Additional arguments to format the description.
|
|
390
|
-
"""
|
|
391
|
-
self._retcode.add(int(not value.new))
|
|
392
|
-
diagnostic = _Diagnostic(
|
|
393
|
-
self._qualified_name,
|
|
394
|
-
value.ref,
|
|
395
|
-
value.description.format(**kwargs),
|
|
396
|
-
value.symbolic,
|
|
397
|
-
self._lineno,
|
|
398
|
-
value.hint if include_hint else None,
|
|
399
|
-
value.new,
|
|
400
|
-
)
|
|
401
|
-
if (
|
|
402
|
-
value not in self._func.messages
|
|
403
|
-
and diagnostic not in self._diagnostics
|
|
404
|
-
):
|
|
405
|
-
self._diagnostics.append(diagnostic)
|
|
406
|
-
|
|
407
|
-
@property
|
|
408
|
-
def diagnostics(self) -> list[_Diagnostic]:
|
|
409
|
-
"""Diagnostics sorted for stable output."""
|
|
410
|
-
return sorted(self._diagnostics)
|
|
411
|
-
|
|
412
|
-
@property
|
|
413
|
-
def retcode(self) -> RetCode:
|
|
414
|
-
"""Exit code (non-zero if any check failed)."""
|
|
415
|
-
return self._retcode
|
|
416
|
-
|
|
417
|
-
def __bool__(self) -> bool:
|
|
418
|
-
return bool(self._diagnostics)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
class _FunctionResult:
|
|
422
|
-
def __init__(
|
|
423
|
-
self,
|
|
424
|
-
name: str,
|
|
425
|
-
lineno: int,
|
|
426
|
-
collector: _Collector,
|
|
427
|
-
) -> None:
|
|
428
|
-
self._name = name
|
|
429
|
-
self._lineno = lineno
|
|
430
|
-
self._collector = collector
|
|
431
|
-
|
|
432
|
-
@property
|
|
433
|
-
def name(self) -> str:
|
|
434
|
-
"""Qualified name (Class.method) when nested, else bare name."""
|
|
435
|
-
return self._name
|
|
436
|
-
|
|
437
|
-
@property
|
|
438
|
-
def lineno(self) -> int:
|
|
439
|
-
"""Line number of the function in the source."""
|
|
440
|
-
return self._lineno
|
|
441
|
-
|
|
442
|
-
@property
|
|
443
|
-
def retcode(self) -> int:
|
|
444
|
-
"""Exit code (non-zero if any check failed)."""
|
|
445
|
-
return self._collector.retcode.result
|
|
446
|
-
|
|
447
|
-
def __iter__(self) -> _t.Iterator[_Diagnostic]:
|
|
448
|
-
return iter(self._collector.diagnostics)
|
|
449
|
-
|
|
450
|
-
def __bool__(self) -> bool:
|
|
451
|
-
return bool(self._collector)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
# TODO: make report json by default and wrap with a reporter for cli
|
|
455
|
-
def report(
|
|
456
|
-
failures: Failures,
|
|
457
|
-
config: _Config,
|
|
458
|
-
file: str | None = None,
|
|
459
|
-
) -> int:
|
|
460
|
-
"""Print failures and return the highest exit code.
|
|
461
|
-
|
|
462
|
-
Iterates over failures, prints each with path, line header, and
|
|
463
|
-
messages, then returns the maximum retcode (0 or non-zero).
|
|
464
|
-
|
|
465
|
-
:param failures: Failures to print (one FunctionResult per
|
|
466
|
-
function).
|
|
467
|
-
:param config: Config for ANSI and formatting.
|
|
468
|
-
:param file: Module path when failures came from a file (optional).
|
|
469
|
-
:return: Exit code (non-zero if any check failed).
|
|
470
|
-
"""
|
|
471
|
-
format_json = _os.getenv("_DOCSIG_FORMAT_JSON") is not None
|
|
472
|
-
retcodes = RetCode()
|
|
473
|
-
output = []
|
|
474
|
-
obj = []
|
|
475
|
-
for result in failures:
|
|
476
|
-
retcodes.add(result.retcode)
|
|
477
|
-
path_prefix = f"{file}:" if file is not None else ""
|
|
478
|
-
header = f"{path_prefix}{result.lineno} in {result.name}"
|
|
479
|
-
if not config.no_ansi and _sys.stdout.isatty():
|
|
480
|
-
header = f"\033[35m{header}\033[0m"
|
|
481
|
-
|
|
482
|
-
output.append(header)
|
|
483
|
-
for item in result:
|
|
484
|
-
extra = None
|
|
485
|
-
if item.hint:
|
|
486
|
-
extra = f"hint: {item.hint}"
|
|
487
|
-
|
|
488
|
-
if item.new:
|
|
489
|
-
extra = "warning: please remember to fix this or disable it"
|
|
490
|
-
_warn(_NEW.format(ref=item.ref), FutureWarning, stacklevel=3)
|
|
491
|
-
|
|
492
|
-
msg = _TEMPLATE.format(
|
|
493
|
-
ref=item.ref,
|
|
494
|
-
description=item.description,
|
|
495
|
-
symbolic=item.symbolic,
|
|
496
|
-
)
|
|
497
|
-
output.append(f" {msg}")
|
|
498
|
-
if extra is not None:
|
|
499
|
-
output.append(f" {extra}")
|
|
500
|
-
|
|
501
|
-
obj.append(
|
|
502
|
-
{
|
|
503
|
-
"line": None if result.retcode == 2 else item.lineno,
|
|
504
|
-
"message": msg,
|
|
505
|
-
"exit": result.retcode,
|
|
506
|
-
},
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
if format_json:
|
|
510
|
-
print(_json.dumps(obj).strip()) # pragma: no cover
|
|
511
|
-
|
|
512
|
-
elif output:
|
|
513
|
-
print("\n".join(output))
|
|
514
|
-
|
|
515
|
-
return retcodes.result
|
|
@@ -12,17 +12,17 @@ from pathlib import Path as _Path
|
|
|
12
12
|
from pprint import pformat as _pformat
|
|
13
13
|
|
|
14
14
|
from . import _decorators
|
|
15
|
-
from ._check import run_checks as _run_checks
|
|
16
15
|
from ._config import Check as _Check
|
|
17
16
|
from ._config import Config as _Config
|
|
18
17
|
from ._config import Filters as _Filters
|
|
19
18
|
from ._config import Ignore as _Ignore
|
|
19
|
+
from ._diagnostic import Failures as _Failures
|
|
20
|
+
from ._diagnostic import RetCode as _RetCode
|
|
20
21
|
from ._files import Files as _Files
|
|
21
22
|
from ._parsers import parse_from_file as _parse_from_file
|
|
22
23
|
from ._parsers import parse_from_string as _parse_from_string
|
|
23
|
-
from ._report import Failures as _Failures
|
|
24
|
-
from ._report import RetCode as _RetCode
|
|
25
24
|
from ._report import report as _report
|
|
25
|
+
from ._traverse import run_checks as _run_checks
|
|
26
26
|
from ._utils import get_parent_that_has as _get_parent_that_has
|
|
27
27
|
from ._utils import print_checks as _print_checks
|
|
28
28
|
from .messages import E as _E
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docsig._diagnostic
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
Diagnostic records and per-function check results for docsig runs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import typing as _t
|
|
9
|
+
from dataclasses import dataclass as _dataclass
|
|
10
|
+
|
|
11
|
+
from ._module import Function as _Function
|
|
12
|
+
from .messages import Message as _Message
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Failures(list["FunctionResult"]):
|
|
16
|
+
"""Sequence of result instances (one per function checked)."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RetCode:
|
|
20
|
+
"""RetCode object.
|
|
21
|
+
|
|
22
|
+
:param code: Initial return code, if any, otherwise zero.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, code: int = 0) -> None:
|
|
26
|
+
self._data = [code]
|
|
27
|
+
|
|
28
|
+
def add(self, code: int) -> None:
|
|
29
|
+
"""Add a return code.
|
|
30
|
+
|
|
31
|
+
:param code: Return code to add.
|
|
32
|
+
"""
|
|
33
|
+
self._data.append(code)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def result(self) -> int:
|
|
37
|
+
"""Maximum return code."""
|
|
38
|
+
return max(self._data)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@_dataclass(frozen=True, order=True)
|
|
42
|
+
class Diagnostic: # pylint: disable=too-few-public-methods
|
|
43
|
+
"""Single reported issue for one function."""
|
|
44
|
+
|
|
45
|
+
name: str
|
|
46
|
+
ref: str
|
|
47
|
+
description: str
|
|
48
|
+
symbolic: str
|
|
49
|
+
lineno: int
|
|
50
|
+
hint: str | None = None
|
|
51
|
+
new: bool = False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Collector:
|
|
55
|
+
"""Collect diagnostics and exit weights for one function.
|
|
56
|
+
|
|
57
|
+
:param func: Function to collect diagnostics for.
|
|
58
|
+
:param qualified_name: Qualified name (Class.method) when nested,
|
|
59
|
+
else bare name.
|
|
60
|
+
:param lineno: Line number of the function in the source.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
func: _Function,
|
|
66
|
+
qualified_name: str,
|
|
67
|
+
lineno: int,
|
|
68
|
+
) -> None:
|
|
69
|
+
self._func = func
|
|
70
|
+
self._qualified_name = qualified_name
|
|
71
|
+
self._lineno = lineno
|
|
72
|
+
self._diagnostics: list[Diagnostic] = []
|
|
73
|
+
self._retcode = RetCode()
|
|
74
|
+
|
|
75
|
+
def add(
|
|
76
|
+
self,
|
|
77
|
+
value: _Message,
|
|
78
|
+
include_hint: bool = False,
|
|
79
|
+
**kwargs: _t.Any,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Add a diagnostic message.
|
|
82
|
+
|
|
83
|
+
:param value: Message to add.
|
|
84
|
+
:param include_hint: Whether to include the hint.
|
|
85
|
+
:param kwargs: Additional arguments to format the description.
|
|
86
|
+
"""
|
|
87
|
+
self._retcode.add(int(not value.new))
|
|
88
|
+
diagnostic = Diagnostic(
|
|
89
|
+
self._qualified_name,
|
|
90
|
+
value.ref,
|
|
91
|
+
value.description.format(**kwargs),
|
|
92
|
+
value.symbolic,
|
|
93
|
+
self._lineno,
|
|
94
|
+
value.hint if include_hint else None,
|
|
95
|
+
value.new,
|
|
96
|
+
)
|
|
97
|
+
if (
|
|
98
|
+
value not in self._func.messages
|
|
99
|
+
and diagnostic not in self._diagnostics
|
|
100
|
+
):
|
|
101
|
+
self._diagnostics.append(diagnostic)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def diagnostics(self) -> list[Diagnostic]:
|
|
105
|
+
"""Diagnostics sorted for stable output."""
|
|
106
|
+
return sorted(self._diagnostics)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def retcode(self) -> RetCode:
|
|
110
|
+
"""Exit code (non-zero if any check failed)."""
|
|
111
|
+
return self._retcode
|
|
112
|
+
|
|
113
|
+
def __bool__(self) -> bool:
|
|
114
|
+
return bool(self._diagnostics)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class FunctionResult:
|
|
118
|
+
"""Diagnostics and exit code for one checked function.
|
|
119
|
+
|
|
120
|
+
:param name: Qualified name (Class.method) when nested, else bare
|
|
121
|
+
name.
|
|
122
|
+
:param lineno: Line number of the function in the source.
|
|
123
|
+
:param collector: Collector for the function.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
name: str,
|
|
129
|
+
lineno: int,
|
|
130
|
+
collector: Collector,
|
|
131
|
+
) -> None:
|
|
132
|
+
self._name = name
|
|
133
|
+
self._lineno = lineno
|
|
134
|
+
self._collector = collector
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def name(self) -> str:
|
|
138
|
+
"""Qualified name (Class.method) when nested, else bare name."""
|
|
139
|
+
return self._name
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def lineno(self) -> int:
|
|
143
|
+
"""Line number of the function in the source."""
|
|
144
|
+
return self._lineno
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def retcode(self) -> int:
|
|
148
|
+
"""Exit code (non-zero if any check failed)."""
|
|
149
|
+
return self._collector.retcode.result
|
|
150
|
+
|
|
151
|
+
def __iter__(self) -> _t.Iterator[Diagnostic]:
|
|
152
|
+
return iter(self._collector.diagnostics)
|
|
153
|
+
|
|
154
|
+
def __bool__(self) -> bool:
|
|
155
|
+
return bool(self._collector)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docsig._report
|
|
3
|
+
==============
|
|
4
|
+
|
|
5
|
+
Format and print docstring-check diagnostics for CLI and tooling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json as _json
|
|
9
|
+
import os as _os
|
|
10
|
+
import sys as _sys
|
|
11
|
+
from warnings import warn as _warn
|
|
12
|
+
|
|
13
|
+
from ._checker import FunctionChecker as _FunctionChecker
|
|
14
|
+
from ._config import Config as _Config
|
|
15
|
+
from ._diagnostic import Failures as _Failures
|
|
16
|
+
from ._diagnostic import FunctionResult as _FunctionResult
|
|
17
|
+
from ._diagnostic import RetCode as _RetCode
|
|
18
|
+
from ._module import Function as _Function
|
|
19
|
+
from .messages import NEW as _NEW
|
|
20
|
+
from .messages import TEMPLATE as _TEMPLATE
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_function(func: _Function, config: _Config) -> _FunctionResult:
|
|
24
|
+
"""Run configured checks for one function and return the result.
|
|
25
|
+
|
|
26
|
+
:param func: Function under check.
|
|
27
|
+
:param config: Configuration object.
|
|
28
|
+
:return: Collected diagnostics for the function.
|
|
29
|
+
"""
|
|
30
|
+
return _FunctionChecker(func, config).run()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# TODO: make report json by default and wrap with a reporter for cli
|
|
34
|
+
def report(
|
|
35
|
+
failures: _Failures,
|
|
36
|
+
config: _Config,
|
|
37
|
+
file: str | None = None,
|
|
38
|
+
) -> int:
|
|
39
|
+
"""Print failures and return the highest exit code.
|
|
40
|
+
|
|
41
|
+
Iterates over failures, prints each with path, line header, and
|
|
42
|
+
messages, then returns the maximum retcode (0 or non-zero).
|
|
43
|
+
|
|
44
|
+
:param failures: Failures to print (one FunctionResult per
|
|
45
|
+
function).
|
|
46
|
+
:param config: Config for ANSI and formatting.
|
|
47
|
+
:param file: Module path when failures came from a file (optional).
|
|
48
|
+
:return: Exit code (non-zero if any check failed).
|
|
49
|
+
"""
|
|
50
|
+
format_json = _os.getenv("_DOCSIG_FORMAT_JSON") is not None
|
|
51
|
+
retcodes = _RetCode()
|
|
52
|
+
output = []
|
|
53
|
+
obj = []
|
|
54
|
+
for result in failures:
|
|
55
|
+
retcodes.add(result.retcode)
|
|
56
|
+
path_prefix = f"{file}:" if file is not None else ""
|
|
57
|
+
header = f"{path_prefix}{result.lineno} in {result.name}"
|
|
58
|
+
if not config.no_ansi and _sys.stdout.isatty():
|
|
59
|
+
header = f"\033[35m{header}\033[0m"
|
|
60
|
+
|
|
61
|
+
output.append(header)
|
|
62
|
+
for item in result:
|
|
63
|
+
extra = None
|
|
64
|
+
if item.hint:
|
|
65
|
+
extra = f"hint: {item.hint}"
|
|
66
|
+
|
|
67
|
+
if item.new:
|
|
68
|
+
extra = "warning: please remember to fix this or disable it"
|
|
69
|
+
_warn(_NEW.format(ref=item.ref), FutureWarning, stacklevel=3)
|
|
70
|
+
|
|
71
|
+
msg = _TEMPLATE.format(
|
|
72
|
+
ref=item.ref,
|
|
73
|
+
description=item.description,
|
|
74
|
+
symbolic=item.symbolic,
|
|
75
|
+
)
|
|
76
|
+
output.append(f" {msg}")
|
|
77
|
+
if extra is not None:
|
|
78
|
+
output.append(f" {extra}")
|
|
79
|
+
|
|
80
|
+
obj.append(
|
|
81
|
+
{
|
|
82
|
+
"line": None if result.retcode == 2 else item.lineno,
|
|
83
|
+
"message": msg,
|
|
84
|
+
"exit": result.retcode,
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if format_json:
|
|
89
|
+
print(_json.dumps(obj).strip()) # pragma: no cover
|
|
90
|
+
|
|
91
|
+
elif output:
|
|
92
|
+
print("\n".join(output))
|
|
93
|
+
|
|
94
|
+
return retcodes.result
|
|
@@ -179,7 +179,6 @@ class Params(list[Param]):
|
|
|
179
179
|
def __init__(self, ignore: _Ignore) -> None:
|
|
180
180
|
super().__init__()
|
|
181
181
|
self._ignore = ignore
|
|
182
|
-
self._duplicates: list[Param] = []
|
|
183
182
|
|
|
184
183
|
def append(self, value: Param) -> None:
|
|
185
184
|
if not value.isprotected and any(
|
|
@@ -219,20 +218,7 @@ class Params(list[Param]):
|
|
|
219
218
|
|
|
220
219
|
Only names are compared; descriptions are ignored.
|
|
221
220
|
"""
|
|
222
|
-
for
|
|
223
|
-
if v > 1:
|
|
224
|
-
for i in self:
|
|
225
|
-
if i.name == k:
|
|
226
|
-
# record the duplicates for later analysis
|
|
227
|
-
self._duplicates.append(i)
|
|
228
|
-
return True
|
|
229
|
-
|
|
230
|
-
return False
|
|
231
|
-
|
|
232
|
-
@property
|
|
233
|
-
def duplicates(self) -> list[Param]:
|
|
234
|
-
"""Params that share a name."""
|
|
235
|
-
return self._duplicates
|
|
221
|
+
return any(v > 1 for v in _Counter(i.name for i in self).values())
|
|
236
222
|
|
|
237
223
|
|
|
238
224
|
class _Stub:
|
|
@@ -394,7 +380,7 @@ class Docstring(_Stub):
|
|
|
394
380
|
indent_anomaly = cls._indent_anomaly(node.value)
|
|
395
381
|
string = cls._normalize_docstring(node.value)
|
|
396
382
|
match = _re.search(
|
|
397
|
-
r"^[ \t]*:(?:returns?|yields
|
|
383
|
+
r"^[ \t]*:(?:returns?|yields?|rtype):\s*(.*)",
|
|
398
384
|
string,
|
|
399
385
|
_re.IGNORECASE | _re.MULTILINE,
|
|
400
386
|
)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
|
-
docsig.
|
|
3
|
-
|
|
2
|
+
docsig._traverse
|
|
3
|
+
================
|
|
4
4
|
|
|
5
5
|
Traverse modules and run docstring/signature checks per function.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from ._config import Config as _Config
|
|
9
|
+
from ._diagnostic import Failures as _Failures
|
|
9
10
|
from ._module import Function as _Function
|
|
10
11
|
from ._module import Parent as _Parent
|
|
11
|
-
from ._report import Failures as _Failures
|
|
12
12
|
from ._report import check_function as _check_function
|
|
13
13
|
|
|
14
14
|
|
|
@@ -29,7 +29,7 @@ maintainers = [
|
|
|
29
29
|
name = "docsig"
|
|
30
30
|
readme = "README.rst"
|
|
31
31
|
requires-python = ">=3.10"
|
|
32
|
-
version = "0.91.
|
|
32
|
+
version = "0.91.1"
|
|
33
33
|
|
|
34
34
|
[project.entry-points."flake8.extension"]
|
|
35
35
|
SIG = "docsig.plugin:Flake8"
|
|
@@ -47,7 +47,7 @@ line-length = 79
|
|
|
47
47
|
allow_dirty = true
|
|
48
48
|
commit = true
|
|
49
49
|
commit_args = "-sS"
|
|
50
|
-
current_version = "0.91.
|
|
50
|
+
current_version = "0.91.1"
|
|
51
51
|
message = "bump: version {current_version} → {new_version}"
|
|
52
52
|
sign_tags = true
|
|
53
53
|
tag = true
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
docsig._diagnostic
|
|
3
|
-
==================
|
|
4
|
-
|
|
5
|
-
Diagnostic records and per-function check results for docsig runs.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass as _dataclass
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@_dataclass(frozen=True, order=True)
|
|
12
|
-
class Diagnostic: # pylint: disable=too-few-public-methods
|
|
13
|
-
"""Single reported issue for one function."""
|
|
14
|
-
|
|
15
|
-
name: str
|
|
16
|
-
ref: str
|
|
17
|
-
description: str
|
|
18
|
-
symbolic: str
|
|
19
|
-
lineno: int
|
|
20
|
-
hint: str | None = None
|
|
21
|
-
new: bool = False
|
|
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
|
|
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
|