docsig 0.82.1__tar.gz → 0.83.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.82.1 → docsig-0.83.0}/PKG-INFO +21 -10
- {docsig-0.82.1 → docsig-0.83.0}/README.rst +18 -7
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_config.py +13 -4
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_core.py +3 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_module.py +15 -12
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_parsers.py +10 -9
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_report.py +13 -12
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_stub.py +32 -37
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_version.py +1 -1
- {docsig-0.82.1 → docsig-0.83.0}/docsig/messages.py +6 -1
- docsig-0.83.0/docsig/plugin/__init__.py +12 -0
- docsig-0.82.1/docsig/plugin.py → docsig-0.83.0/docsig/plugin/_flake8.py +41 -37
- docsig-0.83.0/docsig/plugin/_validate_pyproject.py +103 -0
- {docsig-0.82.1 → docsig-0.83.0}/pyproject.toml +16 -8
- {docsig-0.82.1 → docsig-0.83.0}/LICENSE +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/__init__.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/__main__.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_check.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_decorators.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_directives.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_files.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_hooks.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_main.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/_utils.py +0 -0
- {docsig-0.82.1 → docsig-0.83.0}/docsig/py.typed +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docsig
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.83.0
|
|
4
4
|
Summary: Check signature params for proper documentation
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Keywords: check,docs,docstring,params,signature
|
|
8
8
|
Author: jshwi
|
|
9
|
-
Author-email:
|
|
9
|
+
Author-email: s.whitlock@live.com
|
|
10
10
|
Maintainer: jshwi
|
|
11
|
-
Maintainer-email:
|
|
11
|
+
Maintainer-email: s.whitlock@live.com
|
|
12
12
|
Requires-Python: >=3.10,<4.0
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -36,7 +36,7 @@ Description-Content-Type: text/x-rst
|
|
|
36
36
|
|
|
37
37
|
|
|
|
38
38
|
|
|
39
|
-
|License| |PyPI| |CI| |CodeQL| |pre-commit.ci status| |codecov.io| |readthedocs.org| |python3.10| |Black| |isort| |pylint| |Security Status|
|
|
39
|
+
|License| |PyPI| |CI| |CodeQL| |pre-commit.ci status| |codecov.io| |readthedocs.org| |python3.10| |Black| |isort| |pylint| |Security Status|
|
|
40
40
|
|
|
41
41
|
.. |License| image:: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
42
42
|
:target: https://opensource.org/licenses/MIT
|
|
@@ -74,9 +74,6 @@ Description-Content-Type: text/x-rst
|
|
|
74
74
|
.. |Security Status| image:: https://img.shields.io/badge/security-bandit-yellow.svg
|
|
75
75
|
:target: https://github.com/PyCQA/bandit
|
|
76
76
|
:alt: Security Status
|
|
77
|
-
.. |Known Vulnerabilities| image:: https://snyk.io/test/github/jshwi/docsig/badge.svg
|
|
78
|
-
:target: https://snyk.io/test/github/jshwi/docsig/badge.svg
|
|
79
|
-
:alt: Known Vulnerabilities
|
|
80
77
|
|
|
81
78
|
Check Python signature params for proper documentation
|
|
82
79
|
-------------------------------------------------------
|
|
@@ -175,6 +172,20 @@ Options can also be configured with the pyproject.toml file
|
|
|
175
172
|
"SIG201",
|
|
176
173
|
]
|
|
177
174
|
|
|
175
|
+
.. schema-info start
|
|
176
|
+
|
|
177
|
+
.. _here: https://docsig.io/en/latest/usage/configuration/schema.html
|
|
178
|
+
|
|
179
|
+
.. _schema: https://docsig.io/en/latest/usage/configuration/schema.json
|
|
180
|
+
|
|
181
|
+
.. _validate-pyproject: https://github.com/abravalheri/validate-pyproject
|
|
182
|
+
|
|
183
|
+
Configuration options can be found `here`_ or see the full JSON `schema`_
|
|
184
|
+
|
|
185
|
+
You can validate your `docsig` configuration with `validate-pyproject`_
|
|
186
|
+
|
|
187
|
+
.. schema-info end
|
|
188
|
+
|
|
178
189
|
Flake8
|
|
179
190
|
******
|
|
180
191
|
|
|
@@ -184,7 +195,7 @@ ensure your installation has registered `docsig`
|
|
|
184
195
|
.. code-block:: console
|
|
185
196
|
|
|
186
197
|
$ flake8 --version
|
|
187
|
-
7.3.0 (docsig: 0.
|
|
198
|
+
7.3.0 (docsig: 0.83.0, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
|
|
188
199
|
|
|
189
200
|
And now use `flake8` to lint your files
|
|
190
201
|
|
|
@@ -270,7 +281,7 @@ Standalone
|
|
|
270
281
|
|
|
271
282
|
repos:
|
|
272
283
|
- repo: https://github.com/jshwi/docsig
|
|
273
|
-
rev: v0.
|
|
284
|
+
rev: v0.83.0
|
|
274
285
|
hooks:
|
|
275
286
|
- id: docsig
|
|
276
287
|
args:
|
|
@@ -289,7 +300,7 @@ or integrated with ``flake8``
|
|
|
289
300
|
hooks:
|
|
290
301
|
- id: flake8
|
|
291
302
|
additional_dependencies:
|
|
292
|
-
- docsig==0.
|
|
303
|
+
- docsig==0.83.0
|
|
293
304
|
args:
|
|
294
305
|
- "--sig-check-class"
|
|
295
306
|
- "--sig-check-dunders"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
|
9
9
|
|
|
10
|
-
|License| |PyPI| |CI| |CodeQL| |pre-commit.ci status| |codecov.io| |readthedocs.org| |python3.10| |Black| |isort| |pylint| |Security Status|
|
|
10
|
+
|License| |PyPI| |CI| |CodeQL| |pre-commit.ci status| |codecov.io| |readthedocs.org| |python3.10| |Black| |isort| |pylint| |Security Status|
|
|
11
11
|
|
|
12
12
|
.. |License| image:: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
13
13
|
:target: https://opensource.org/licenses/MIT
|
|
@@ -45,9 +45,6 @@
|
|
|
45
45
|
.. |Security Status| image:: https://img.shields.io/badge/security-bandit-yellow.svg
|
|
46
46
|
:target: https://github.com/PyCQA/bandit
|
|
47
47
|
:alt: Security Status
|
|
48
|
-
.. |Known Vulnerabilities| image:: https://snyk.io/test/github/jshwi/docsig/badge.svg
|
|
49
|
-
:target: https://snyk.io/test/github/jshwi/docsig/badge.svg
|
|
50
|
-
:alt: Known Vulnerabilities
|
|
51
48
|
|
|
52
49
|
Check Python signature params for proper documentation
|
|
53
50
|
-------------------------------------------------------
|
|
@@ -146,6 +143,20 @@ Options can also be configured with the pyproject.toml file
|
|
|
146
143
|
"SIG201",
|
|
147
144
|
]
|
|
148
145
|
|
|
146
|
+
.. schema-info start
|
|
147
|
+
|
|
148
|
+
.. _here: https://docsig.io/en/latest/usage/configuration/schema.html
|
|
149
|
+
|
|
150
|
+
.. _schema: https://docsig.io/en/latest/usage/configuration/schema.json
|
|
151
|
+
|
|
152
|
+
.. _validate-pyproject: https://github.com/abravalheri/validate-pyproject
|
|
153
|
+
|
|
154
|
+
Configuration options can be found `here`_ or see the full JSON `schema`_
|
|
155
|
+
|
|
156
|
+
You can validate your `docsig` configuration with `validate-pyproject`_
|
|
157
|
+
|
|
158
|
+
.. schema-info end
|
|
159
|
+
|
|
149
160
|
Flake8
|
|
150
161
|
******
|
|
151
162
|
|
|
@@ -155,7 +166,7 @@ ensure your installation has registered `docsig`
|
|
|
155
166
|
.. code-block:: console
|
|
156
167
|
|
|
157
168
|
$ flake8 --version
|
|
158
|
-
7.3.0 (docsig: 0.
|
|
169
|
+
7.3.0 (docsig: 0.83.0, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
|
|
159
170
|
|
|
160
171
|
And now use `flake8` to lint your files
|
|
161
172
|
|
|
@@ -241,7 +252,7 @@ Standalone
|
|
|
241
252
|
|
|
242
253
|
repos:
|
|
243
254
|
- repo: https://github.com/jshwi/docsig
|
|
244
|
-
rev: v0.
|
|
255
|
+
rev: v0.83.0
|
|
245
256
|
hooks:
|
|
246
257
|
- id: docsig
|
|
247
258
|
args:
|
|
@@ -260,7 +271,7 @@ or integrated with ``flake8``
|
|
|
260
271
|
hooks:
|
|
261
272
|
- id: flake8
|
|
262
273
|
additional_dependencies:
|
|
263
|
-
- docsig==0.
|
|
274
|
+
- docsig==0.83.0
|
|
264
275
|
args:
|
|
265
276
|
- "--sig-check-class"
|
|
266
277
|
- "--sig-check-dunders"
|
|
@@ -97,11 +97,10 @@ class _ArgumentParser(_argparse.ArgumentParser):
|
|
|
97
97
|
return namespace, args
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
def
|
|
101
|
-
"""
|
|
100
|
+
def build_parser() -> _ArgumentParser:
|
|
101
|
+
"""Build and return the argument parser.
|
|
102
102
|
|
|
103
|
-
:
|
|
104
|
-
:return: Parsed namespace with config merged in.
|
|
103
|
+
:return: The argument parser.
|
|
105
104
|
"""
|
|
106
105
|
parser = _ArgumentParser(
|
|
107
106
|
description="Check signature params for proper documentation",
|
|
@@ -305,6 +304,16 @@ def parse_args(args: _t.Sequence[str] | None = None) -> _argparse.Namespace:
|
|
|
305
304
|
metavar="STR",
|
|
306
305
|
help="string to parse instead of files",
|
|
307
306
|
)
|
|
307
|
+
return parser
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def parse_args(args: _t.Sequence[str] | None = None) -> _argparse.Namespace:
|
|
311
|
+
"""Parse CLI arguments and merge in config from pyproject.toml.
|
|
312
|
+
|
|
313
|
+
:param args: Argument list to parse (defaults to sys.argv).
|
|
314
|
+
:return: Parsed namespace with config merged in.
|
|
315
|
+
"""
|
|
316
|
+
parser = build_parser()
|
|
308
317
|
return parser.parse_args(args)
|
|
309
318
|
|
|
310
319
|
|
|
@@ -11,6 +11,7 @@ import logging as _logging
|
|
|
11
11
|
import sys as _sys
|
|
12
12
|
import warnings as _warnings
|
|
13
13
|
from pathlib import Path as _Path
|
|
14
|
+
from pprint import pformat as _pformat
|
|
14
15
|
|
|
15
16
|
from . import _decorators
|
|
16
17
|
from ._check import run_checks as _run_checks
|
|
@@ -202,6 +203,8 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
|
|
|
202
203
|
excludes=excludes or [],
|
|
203
204
|
)
|
|
204
205
|
setup_logger(config.verbose)
|
|
206
|
+
logger = _logging.getLogger(__package__)
|
|
207
|
+
logger.debug(_pformat(config))
|
|
205
208
|
if config.list_checks:
|
|
206
209
|
return int(bool(_print_checks())) # type: ignore
|
|
207
210
|
|
|
@@ -9,7 +9,6 @@ from __future__ import annotations as _
|
|
|
9
9
|
|
|
10
10
|
import re as _re
|
|
11
11
|
import typing as _t
|
|
12
|
-
from enum import Enum as _Enum
|
|
13
12
|
from pathlib import Path as _Path
|
|
14
13
|
|
|
15
14
|
import astroid as _ast
|
|
@@ -32,12 +31,12 @@ class _Overloads(dict[str, "Function"]): ...
|
|
|
32
31
|
class _Children(list[_t.Union["Parent", "Function"]]): ...
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
ERRORS = (
|
|
35
|
+
_ast.AstroidSyntaxError,
|
|
36
|
+
UnicodeDecodeError,
|
|
37
|
+
RecursionError,
|
|
38
|
+
_ast.DuplicateBasesError,
|
|
39
|
+
)
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
class Parent: # pylint: disable=too-many-instance-attributes
|
|
@@ -65,7 +64,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
65
64
|
file: _Path | None = None,
|
|
66
65
|
config: _Config | None = None,
|
|
67
66
|
imports: _Imports | None = None,
|
|
68
|
-
error:
|
|
67
|
+
error: type[BaseException] | None = None,
|
|
69
68
|
) -> None:
|
|
70
69
|
super().__init__()
|
|
71
70
|
self._error = error
|
|
@@ -127,14 +126,18 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
127
126
|
if func.isoverloaded:
|
|
128
127
|
if (
|
|
129
128
|
func.name not in self._overloads
|
|
130
|
-
or self._overloads[
|
|
129
|
+
or self._overloads[
|
|
130
|
+
func.name
|
|
131
|
+
].signature.returns.type
|
|
131
132
|
== _RetType.NONE
|
|
132
133
|
):
|
|
133
134
|
self._overloads[func.name] = func
|
|
134
135
|
else:
|
|
135
136
|
if func.name in self._overloads:
|
|
136
137
|
func.overload(
|
|
137
|
-
self._overloads[
|
|
138
|
+
self._overloads[
|
|
139
|
+
func.name
|
|
140
|
+
].signature.returns.type,
|
|
138
141
|
)
|
|
139
142
|
|
|
140
143
|
self._children.append(func)
|
|
@@ -157,7 +160,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
|
|
|
157
160
|
return self._name.startswith("_")
|
|
158
161
|
|
|
159
162
|
@property
|
|
160
|
-
def error(self) ->
|
|
163
|
+
def error(self) -> type[BaseException] | None:
|
|
161
164
|
"""Unrecoverable error for this scope, if any."""
|
|
162
165
|
return self._error
|
|
163
166
|
|
|
@@ -190,7 +193,7 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
|
|
|
190
193
|
file: _Path | None = None,
|
|
191
194
|
config: _Config | None = None,
|
|
192
195
|
imports: _Imports | None = None,
|
|
193
|
-
error:
|
|
196
|
+
error: type[BaseException] | None = None,
|
|
194
197
|
) -> None:
|
|
195
198
|
super().__init__(
|
|
196
199
|
node,
|
|
@@ -15,7 +15,7 @@ import astroid as _ast
|
|
|
15
15
|
from ._config import Config as _Config
|
|
16
16
|
from ._directives import Directives as _Directives
|
|
17
17
|
from ._files import FILE_INFO as _FILE_INFO
|
|
18
|
-
from ._module import
|
|
18
|
+
from ._module import ERRORS as _ERRORS
|
|
19
19
|
from ._module import Parent as _Parent
|
|
20
20
|
|
|
21
21
|
|
|
@@ -52,13 +52,14 @@ def parse_from_string(
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
parent = _Parent(node, directives, file, config)
|
|
55
|
-
logger.debug(_FILE_INFO, source_name, "
|
|
56
|
-
except
|
|
57
|
-
logger.debug(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
logger.debug(_FILE_INFO, source_name, "parsing python code successful")
|
|
56
|
+
except _ERRORS as err:
|
|
57
|
+
logger.debug(
|
|
58
|
+
_FILE_INFO,
|
|
59
|
+
source_name,
|
|
60
|
+
str(err).replace("\n", " ").lower(),
|
|
61
|
+
)
|
|
62
|
+
parent = _Parent(error=type(err))
|
|
62
63
|
|
|
63
64
|
return parent
|
|
64
65
|
|
|
@@ -81,7 +82,7 @@ def parse_from_file(file: _Path, config: _Config) -> _Parent:
|
|
|
81
82
|
except UnicodeDecodeError as err:
|
|
82
83
|
logger = _logging.getLogger(__package__)
|
|
83
84
|
logger.debug(_FILE_INFO, file, str(err).replace("\n", " "))
|
|
84
|
-
parent = _Parent(error=
|
|
85
|
+
parent = _Parent(error=type(err))
|
|
85
86
|
|
|
86
87
|
if parent.error is not None and not file.name.endswith(".py"):
|
|
87
88
|
parent = _Parent()
|
|
@@ -15,10 +15,9 @@ import sys as _sys
|
|
|
15
15
|
import typing as _t
|
|
16
16
|
from warnings import warn as _warn
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
import astroid as _ast
|
|
19
19
|
|
|
20
20
|
from ._config import Config as _Config
|
|
21
|
-
from ._module import Error as _Error
|
|
22
21
|
from ._module import Function as _Function
|
|
23
22
|
from ._stub import UNNAMED as _UNNAMED
|
|
24
23
|
from ._stub import VALID_DESCRIPTION as _VALID_DESCRIPTION
|
|
@@ -75,7 +74,7 @@ class Failure(list[Failed]):
|
|
|
75
74
|
if (
|
|
76
75
|
self._func.parent is not None
|
|
77
76
|
and self._func.parent.name
|
|
78
|
-
and not isinstance(self._func.parent,
|
|
77
|
+
and not isinstance(self._func.parent, _ast.nodes.Module)
|
|
79
78
|
):
|
|
80
79
|
self._name = f"{self._func.parent.name}.{self._name}"
|
|
81
80
|
|
|
@@ -288,18 +287,18 @@ class Failure(list[Failed]):
|
|
|
288
287
|
self._func.isproperty and not check_property_returns
|
|
289
288
|
):
|
|
290
289
|
# no types, cannot know either way
|
|
291
|
-
if self._func.signature.
|
|
290
|
+
if self._func.signature.returns.type == _RetType.UNTYPED:
|
|
292
291
|
# confirm-return-needed
|
|
293
292
|
self._add(_E[501], hint=True)
|
|
294
293
|
# return-type is none, so no return should be documented
|
|
295
|
-
elif self._func.docstring.returns:
|
|
296
|
-
if self._func.signature.
|
|
294
|
+
elif self._func.docstring.returns.returns:
|
|
295
|
+
if self._func.signature.returns.type == _RetType.NONE:
|
|
297
296
|
# return-documented-for-none
|
|
298
297
|
self._add(_E[502])
|
|
299
|
-
if self._func.docstring.
|
|
298
|
+
if self._func.docstring.returns.description_missing:
|
|
300
299
|
self._add(_E[506])
|
|
301
300
|
# return-type is some, so return should be documented
|
|
302
|
-
elif self._func.signature.returns:
|
|
301
|
+
elif self._func.signature.returns.returns:
|
|
303
302
|
# return-missing
|
|
304
303
|
lines = str(self._func.docstring.string).splitlines()
|
|
305
304
|
self._add(
|
|
@@ -310,7 +309,7 @@ class Failure(list[Failed]):
|
|
|
310
309
|
and ":param" not in lines[-1]
|
|
311
310
|
),
|
|
312
311
|
)
|
|
313
|
-
elif self._func.docstring.returns:
|
|
312
|
+
elif self._func.docstring.returns.returns:
|
|
314
313
|
# this method is init, so no return should be documented
|
|
315
314
|
if self._func.isinit:
|
|
316
315
|
# class-return-documented
|
|
@@ -322,13 +321,15 @@ class Failure(list[Failed]):
|
|
|
322
321
|
|
|
323
322
|
def _sig9xx_error(self) -> None:
|
|
324
323
|
# invalid-syntax
|
|
325
|
-
if self._func.error
|
|
324
|
+
if self._func.error is _ast.AstroidSyntaxError:
|
|
326
325
|
self._add(_E[901])
|
|
327
326
|
self._retcode = 123
|
|
328
|
-
if self._func.error
|
|
327
|
+
if self._func.error is UnicodeDecodeError:
|
|
329
328
|
self._add(_E[902])
|
|
330
|
-
if self._func.error
|
|
329
|
+
if self._func.error is RecursionError:
|
|
331
330
|
self._add(_E[903])
|
|
331
|
+
if self._func.error is _ast.DuplicateBasesError:
|
|
332
|
+
self._add(_E[904])
|
|
332
333
|
|
|
333
334
|
@property
|
|
334
335
|
def name(self) -> str:
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations as _
|
|
|
9
9
|
|
|
10
10
|
import re as _re
|
|
11
11
|
import textwrap as _textwrap
|
|
12
|
+
import typing as _t
|
|
12
13
|
from collections import Counter as _Counter
|
|
13
14
|
from enum import Enum as _Enum
|
|
14
15
|
|
|
@@ -80,8 +81,6 @@ class DocType(_Enum):
|
|
|
80
81
|
return cls.UNKNOWN
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
# todo: consider a parent object that can be used for returns that do
|
|
84
|
-
# todo: not include the name attribute
|
|
85
84
|
class Param:
|
|
86
85
|
"""Single parameter from a docstring or function signature.
|
|
87
86
|
|
|
@@ -152,6 +151,13 @@ class Param:
|
|
|
152
151
|
return self._closing_token
|
|
153
152
|
|
|
154
153
|
|
|
154
|
+
# single return from a docstring or function signature
|
|
155
|
+
class _Return(_t.NamedTuple):
|
|
156
|
+
returns: bool = False
|
|
157
|
+
type: RetType = RetType.UNTYPED
|
|
158
|
+
description_missing: bool = False
|
|
159
|
+
|
|
160
|
+
|
|
155
161
|
class Params(list[Param]):
|
|
156
162
|
"""A list-like collection of params.
|
|
157
163
|
|
|
@@ -218,9 +224,13 @@ class Params(list[Param]):
|
|
|
218
224
|
|
|
219
225
|
|
|
220
226
|
class _Stub:
|
|
221
|
-
def __init__(
|
|
227
|
+
def __init__(
|
|
228
|
+
self,
|
|
229
|
+
returns: _Return | None = None,
|
|
230
|
+
ignore: _Ignore | None = None,
|
|
231
|
+
) -> None:
|
|
222
232
|
self._args = Params(ignore or _Ignore())
|
|
223
|
-
self._returns =
|
|
233
|
+
self._returns = returns or _Return()
|
|
224
234
|
|
|
225
235
|
@property
|
|
226
236
|
def args(self) -> Params:
|
|
@@ -228,7 +238,7 @@ class _Stub:
|
|
|
228
238
|
return self._args
|
|
229
239
|
|
|
230
240
|
@property
|
|
231
|
-
def returns(self) ->
|
|
241
|
+
def returns(self) -> _Return:
|
|
232
242
|
"""True if a return (or yield) is declared or documented."""
|
|
233
243
|
return self._returns
|
|
234
244
|
|
|
@@ -236,20 +246,16 @@ class _Stub:
|
|
|
236
246
|
class Signature(_Stub):
|
|
237
247
|
"""Parsed function signature (args and return type).
|
|
238
248
|
|
|
239
|
-
:param rettype: Kind of return (none, some, untyped).
|
|
240
249
|
:param returns: True if return is declared in the signature.
|
|
241
250
|
:param ignore: Configuration object for what to ignore.
|
|
242
251
|
"""
|
|
243
252
|
|
|
244
253
|
def __init__(
|
|
245
254
|
self,
|
|
246
|
-
|
|
247
|
-
returns: bool = False,
|
|
255
|
+
returns: _Return | None = None,
|
|
248
256
|
ignore: _Ignore | None = None,
|
|
249
257
|
) -> None:
|
|
250
|
-
super().__init__(ignore or _Ignore())
|
|
251
|
-
self._rettype = rettype
|
|
252
|
-
self._returns = returns
|
|
258
|
+
super().__init__(returns, ignore or _Ignore())
|
|
253
259
|
|
|
254
260
|
@classmethod
|
|
255
261
|
def from_ast(
|
|
@@ -264,8 +270,8 @@ class Signature(_Stub):
|
|
|
264
270
|
:return: Signature with args and return type.
|
|
265
271
|
"""
|
|
266
272
|
rettype = RetType.from_ast(node.returns)
|
|
267
|
-
returns = rettype == RetType.SOME
|
|
268
|
-
signature = cls(
|
|
273
|
+
returns = _Return(rettype == RetType.SOME, rettype)
|
|
274
|
+
signature = cls(returns, ignore)
|
|
269
275
|
# noinspection PyUnresolvedReferences
|
|
270
276
|
for i in [
|
|
271
277
|
a if isinstance(a, Param) else Param(name=a.name)
|
|
@@ -282,18 +288,12 @@ class Signature(_Stub):
|
|
|
282
288
|
|
|
283
289
|
return signature
|
|
284
290
|
|
|
285
|
-
@property
|
|
286
|
-
def rettype(self) -> RetType:
|
|
287
|
-
"""Return annotation kind (none, some, or untyped)."""
|
|
288
|
-
return self._rettype
|
|
289
|
-
|
|
290
291
|
def overload(self, rettype: RetType) -> None:
|
|
291
292
|
"""Set this signature's return type (for overloads).
|
|
292
293
|
|
|
293
294
|
:param rettype: Return type for the overloaded variant.
|
|
294
295
|
"""
|
|
295
|
-
self.
|
|
296
|
-
self._returns = rettype != RetType.NONE
|
|
296
|
+
self._returns = _Return(rettype != RetType.NONE, rettype)
|
|
297
297
|
|
|
298
298
|
|
|
299
299
|
class Docstring(_Stub):
|
|
@@ -301,7 +301,6 @@ class Docstring(_Stub):
|
|
|
301
301
|
|
|
302
302
|
:param string: Raw docstring text after normalization.
|
|
303
303
|
:param returns: True if a return or yield section is present.
|
|
304
|
-
:param ret_description_missing: True if return has no description.
|
|
305
304
|
"""
|
|
306
305
|
|
|
307
306
|
@staticmethod
|
|
@@ -345,13 +344,10 @@ class Docstring(_Stub):
|
|
|
345
344
|
def __init__(
|
|
346
345
|
self,
|
|
347
346
|
string: str | None = None,
|
|
348
|
-
returns:
|
|
349
|
-
ret_description_missing: bool = False,
|
|
347
|
+
returns: _Return | None = None,
|
|
350
348
|
) -> None:
|
|
351
|
-
super().__init__()
|
|
349
|
+
super().__init__(returns)
|
|
352
350
|
self._string = string
|
|
353
|
-
self._returns = returns
|
|
354
|
-
self._ret_description_missing = ret_description_missing
|
|
355
351
|
|
|
356
352
|
@classmethod
|
|
357
353
|
def from_ast(cls, node: _ast.Const) -> Docstring:
|
|
@@ -362,16 +358,16 @@ class Docstring(_Stub):
|
|
|
362
358
|
"""
|
|
363
359
|
indent_anomaly = cls._indent_anomaly(node.value)
|
|
364
360
|
string = cls._normalize_docstring(node.value)
|
|
365
|
-
# todo: we can start building return objects for more detailed
|
|
366
|
-
# todo: checks that are in common with the params class
|
|
367
361
|
match = _re.search(
|
|
368
362
|
r":(?:returns?|yields?):\s*(.*)",
|
|
369
363
|
string,
|
|
370
364
|
_re.IGNORECASE,
|
|
371
365
|
)
|
|
372
|
-
returns =
|
|
373
|
-
|
|
374
|
-
|
|
366
|
+
returns = _Return(
|
|
367
|
+
bool(match),
|
|
368
|
+
description_missing=not match or not match.group(1),
|
|
369
|
+
)
|
|
370
|
+
docstring = cls(string, returns)
|
|
375
371
|
for match in _re.findall(
|
|
376
372
|
r":([\w\s]+(?:\s\|\s[\w\s]+|\w+))([^\w\s])((?:.|\n)*?)(?=\n:|$)",
|
|
377
373
|
string,
|
|
@@ -402,9 +398,8 @@ class Docstring(_Stub):
|
|
|
402
398
|
|
|
403
399
|
Used when the function has a docstring but documents nothing.
|
|
404
400
|
"""
|
|
405
|
-
return
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return self._ret_description_missing
|
|
401
|
+
return (
|
|
402
|
+
self._string is not None
|
|
403
|
+
and not self._args
|
|
404
|
+
and not self.returns.returns
|
|
405
|
+
)
|
|
@@ -198,7 +198,7 @@ E = MessageMap(
|
|
|
198
198
|
#: SIG4xx Parameters
|
|
199
199
|
401: Message(
|
|
200
200
|
"SIG401",
|
|
201
|
-
"
|
|
201
|
+
"parameter not indented correctly",
|
|
202
202
|
"incorrect-indent",
|
|
203
203
|
),
|
|
204
204
|
402: Message(
|
|
@@ -267,5 +267,10 @@ E = MessageMap(
|
|
|
267
267
|
"maximum recursion depth exceeded",
|
|
268
268
|
"recursion-error",
|
|
269
269
|
),
|
|
270
|
+
904: Message(
|
|
271
|
+
"SIG904",
|
|
272
|
+
"duplicates found in mros",
|
|
273
|
+
"duplicates-found-in-mros",
|
|
274
|
+
),
|
|
270
275
|
},
|
|
271
276
|
)
|
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
"""
|
|
2
|
-
docsig.plugin
|
|
3
|
-
|
|
2
|
+
docsig.plugin._flake8
|
|
3
|
+
=====================
|
|
4
4
|
|
|
5
5
|
Flake8 plugin entry point that runs docsig checks and reports as flake8
|
|
6
6
|
errors.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import ast
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
12
|
-
import typing as
|
|
13
|
-
from argparse import SUPPRESS
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
import ast as _ast
|
|
10
|
+
import os as _os
|
|
11
|
+
import sys as _sys
|
|
12
|
+
import typing as _t
|
|
13
|
+
from argparse import SUPPRESS as _SUPPRESS
|
|
14
|
+
from argparse import Namespace as _Namespace
|
|
15
|
+
from pathlib import Path as _Path
|
|
16
|
+
|
|
17
|
+
from .._config import Check as _Check
|
|
18
|
+
from .._config import Config as _Config
|
|
19
|
+
from .._config import Ignore as _Ignore
|
|
20
|
+
from .._config import get_config as _get_config
|
|
21
|
+
from .._config import merge_configs as _merge_configs
|
|
22
|
+
from .._core import handle_deprecations as _handle_deprecations
|
|
23
|
+
from .._core import runner as _runner
|
|
24
|
+
from .._core import setup_logger as _setup_logger
|
|
25
|
+
from .._version import __version__
|
|
26
|
+
from ..messages import FLAKE8 as _FLAKE8
|
|
27
|
+
from ..messages import E as _E
|
|
28
|
+
|
|
29
|
+
_Flake8Error = tuple[int, int, str, type[_t.Any]]
|
|
30
|
+
_sys.path.append(_os.path.abspath(_os.getcwd()))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Flake8:
|
|
30
34
|
"""Plugin that runs docsig on a file and yields flake8 errors.
|
|
31
35
|
|
|
32
36
|
:param tree: Parsed AST module (unused; flake8 requires it for the
|
|
@@ -37,9 +41,9 @@ class Docsig:
|
|
|
37
41
|
off_by_default = False
|
|
38
42
|
name = __package__
|
|
39
43
|
version = __version__
|
|
40
|
-
a =
|
|
44
|
+
a = _Namespace()
|
|
41
45
|
|
|
42
|
-
def __init__(self, tree:
|
|
46
|
+
def __init__(self, tree: _ast.Module, filename: str) -> None:
|
|
43
47
|
_tree = tree # noqa
|
|
44
48
|
self.filename = filename
|
|
45
49
|
|
|
@@ -48,7 +52,7 @@ class Docsig:
|
|
|
48
52
|
# might require that flake8 actually be installed, which is not a
|
|
49
53
|
# requirement for this package
|
|
50
54
|
@classmethod
|
|
51
|
-
def add_options(cls, parser:
|
|
55
|
+
def add_options(cls, parser: _t.Any) -> None:
|
|
52
56
|
"""Register CLI and config opts with the flake8 option parser.
|
|
53
57
|
|
|
54
58
|
:param parser: Flake8 option manager to extend.
|
|
@@ -132,18 +136,18 @@ class Docsig:
|
|
|
132
136
|
"--sig-ignore-typechecker",
|
|
133
137
|
action="store_true",
|
|
134
138
|
parse_from_config=True,
|
|
135
|
-
help=
|
|
139
|
+
help=_SUPPRESS,
|
|
136
140
|
)
|
|
137
141
|
|
|
138
142
|
@classmethod
|
|
139
|
-
def parse_options(cls, a:
|
|
143
|
+
def parse_options(cls, a: _Namespace) -> None:
|
|
140
144
|
"""Merge parsed options with pyproject config into class state.
|
|
141
145
|
|
|
142
146
|
:param a: Argparse namespace from flake8.
|
|
143
147
|
"""
|
|
144
148
|
if getattr(a, "sig_ignore_typechecker", False):
|
|
145
149
|
a.extend_ignore = list(a.extend_ignore or [])
|
|
146
|
-
|
|
150
|
+
_handle_deprecations(
|
|
147
151
|
getattr(a, "sig_ignore_typechecker", False),
|
|
148
152
|
a.extend_ignore,
|
|
149
153
|
["SIG501", "SIG502", "SIG503", "SIG504", "SIG505", "SIG506"],
|
|
@@ -155,7 +159,7 @@ class Docsig:
|
|
|
155
159
|
_get_config(__package__),
|
|
156
160
|
)
|
|
157
161
|
|
|
158
|
-
def run(self) ->
|
|
162
|
+
def run(self) -> _t.Generator[_Flake8Error, None, None]:
|
|
159
163
|
"""Run docsig on the file and yield flake8 errors per failure.
|
|
160
164
|
|
|
161
165
|
If both class and class-constructor checks are enabled,
|
|
@@ -167,15 +171,15 @@ class Docsig:
|
|
|
167
171
|
"""
|
|
168
172
|
if self.a.check_class and self.a.check_class_constructor:
|
|
169
173
|
line = "{msg}".format(
|
|
170
|
-
msg=
|
|
171
|
-
ref=
|
|
172
|
-
description=
|
|
173
|
-
symbolic=
|
|
174
|
+
msg=_FLAKE8.format(
|
|
175
|
+
ref=_E[5].ref,
|
|
176
|
+
description=_E[5].description,
|
|
177
|
+
symbolic=_E[5].symbolic,
|
|
174
178
|
),
|
|
175
179
|
)
|
|
176
180
|
yield 0, 0, line, self.__class__
|
|
177
181
|
else:
|
|
178
|
-
|
|
182
|
+
_setup_logger(self.a.verbose)
|
|
179
183
|
check = _Check(
|
|
180
184
|
class_=self.a.check_class,
|
|
181
185
|
class_constructor=self.a.check_class_constructor,
|
|
@@ -196,14 +200,14 @@ class Docsig:
|
|
|
196
200
|
ignore=ignore,
|
|
197
201
|
verbose=self.a.verbose,
|
|
198
202
|
)
|
|
199
|
-
results =
|
|
203
|
+
results = _runner(_Path(self.filename), config)
|
|
200
204
|
for result in results:
|
|
201
205
|
if not result.retcode:
|
|
202
206
|
continue
|
|
203
207
|
|
|
204
208
|
for info in result:
|
|
205
209
|
line = "{msg} '{name}'".format(
|
|
206
|
-
msg=
|
|
210
|
+
msg=_FLAKE8.format(
|
|
207
211
|
ref=info.ref,
|
|
208
212
|
description=info.description,
|
|
209
213
|
symbolic=info.symbolic,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docsig.plugin._validate_pyproject
|
|
3
|
+
=================================
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# pylint: disable=protected-access
|
|
7
|
+
|
|
8
|
+
import typing as _t
|
|
9
|
+
|
|
10
|
+
# noinspection PyFinal
|
|
11
|
+
from argparse import SUPPRESS as _SUPPRESS
|
|
12
|
+
from argparse import Action as _Action
|
|
13
|
+
|
|
14
|
+
import docsig as _docsig
|
|
15
|
+
|
|
16
|
+
from .._config import build_parser as _build_parser
|
|
17
|
+
|
|
18
|
+
_ID = "https://docsig.io/en/latest/usage/configuration/schema.json"
|
|
19
|
+
_SCHEMA = "http://json-schema.org/draft-07/schema#"
|
|
20
|
+
_DESCRIPTION_EXCLUDE = ("comma separated ",)
|
|
21
|
+
_NARGS = ("+", "*")
|
|
22
|
+
_EXCLUDED_OPTIONS = ("help", "list-checks", "string", "version")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidatePyproject(dict[str, _t.Any]):
|
|
26
|
+
"""Schema for the docsig tool section in pyproject.toml.
|
|
27
|
+
|
|
28
|
+
:param tool_name: The tool name to validate.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, tool_name: str = "docsig") -> None:
|
|
32
|
+
assert tool_name == "docsig", "Only docsig is supported."
|
|
33
|
+
self._parser = _build_parser()
|
|
34
|
+
super().__init__(
|
|
35
|
+
{
|
|
36
|
+
"$comment": _CMT,
|
|
37
|
+
"$id": _ID,
|
|
38
|
+
"$schema": _SCHEMA,
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": False,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
self._get_properties()
|
|
44
|
+
self._get_mutually_exclusive_options()
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _skip_action(action: _Action, name: str) -> bool:
|
|
48
|
+
if not action.option_strings:
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
if action.help == _SUPPRESS:
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
if name in _EXCLUDED_OPTIONS:
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def _get_properties(self) -> None:
|
|
60
|
+
self["properties"] = {}
|
|
61
|
+
for action in self._parser._actions:
|
|
62
|
+
name = action.dest.replace("_", "-")
|
|
63
|
+
if self._skip_action(action, name):
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
self["properties"][name] = {"default": action.default}
|
|
67
|
+
if isinstance(action.default, bool):
|
|
68
|
+
self["properties"][name]["type"] = "boolean"
|
|
69
|
+
elif isinstance(action.default, list) or action.nargs in _NARGS:
|
|
70
|
+
self["properties"][name]["type"] = "array"
|
|
71
|
+
self["properties"][name]["items"] = {"type": "string"}
|
|
72
|
+
elif action.type is None:
|
|
73
|
+
self["properties"][name]["type"] = "string"
|
|
74
|
+
|
|
75
|
+
if action.help:
|
|
76
|
+
description = action.help
|
|
77
|
+
for exclude in _DESCRIPTION_EXCLUDE:
|
|
78
|
+
description = description.replace(exclude, "")
|
|
79
|
+
|
|
80
|
+
self["properties"][name]["description"] = description
|
|
81
|
+
|
|
82
|
+
def _get_mutually_exclusive_options(self) -> None:
|
|
83
|
+
for group in self._parser._mutually_exclusive_groups:
|
|
84
|
+
names = []
|
|
85
|
+
for action in group._group_actions:
|
|
86
|
+
name = action.dest.replace("_", "-")
|
|
87
|
+
if self._skip_action(action, name):
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
names.append(name)
|
|
91
|
+
|
|
92
|
+
if len(names) > 1:
|
|
93
|
+
if "allOf" not in self:
|
|
94
|
+
self["allOf"] = []
|
|
95
|
+
|
|
96
|
+
self["allOf"].append({"not": {"required": names}})
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_CMT = ValidatePyproject.__doc__.splitlines()[0].lower()[:-1] # type: ignore
|
|
100
|
+
|
|
101
|
+
# for
|
|
102
|
+
# $ validate-pyproject --help
|
|
103
|
+
ValidatePyproject.__doc__ = _docsig.__doc__
|
|
@@ -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.83.0"
|
|
15
15
|
message = "bump: version {current_version} → {new_version}"
|
|
16
16
|
sign_tags = true
|
|
17
17
|
tag = true
|
|
@@ -103,7 +103,7 @@ non_interactive = true
|
|
|
103
103
|
|
|
104
104
|
[tool.poetry]
|
|
105
105
|
authors = [
|
|
106
|
-
"jshwi <
|
|
106
|
+
"jshwi <s.whitlock@live.com>"
|
|
107
107
|
]
|
|
108
108
|
description = "Check signature params for proper documentation"
|
|
109
109
|
documentation = "https://docsig.io/en/latest"
|
|
@@ -117,12 +117,12 @@ keywords = [
|
|
|
117
117
|
]
|
|
118
118
|
license = "MIT"
|
|
119
119
|
maintainers = [
|
|
120
|
-
"jshwi <
|
|
120
|
+
"jshwi <s.whitlock@live.com>"
|
|
121
121
|
]
|
|
122
122
|
name = "docsig"
|
|
123
123
|
readme = "README.rst"
|
|
124
124
|
repository = "https://github.com/jshwi/docsig"
|
|
125
|
-
version = "0.
|
|
125
|
+
version = "0.83.0"
|
|
126
126
|
|
|
127
127
|
[tool.poetry.dependencies]
|
|
128
128
|
Sphinx = ">=7,<9"
|
|
@@ -146,15 +146,17 @@ tomli = "^2.0.1"
|
|
|
146
146
|
tomli-w = "^1.0.0"
|
|
147
147
|
towncrier = ">=23.11,<26.0"
|
|
148
148
|
tox = "^4.15.0"
|
|
149
|
+
validate-pyproject = {extras = ["all", "store"], version = "^0.25"}
|
|
149
150
|
vulture = "^2.11"
|
|
150
151
|
|
|
151
152
|
[tool.poetry.group.docs.dependencies]
|
|
152
153
|
furo = ">=2024.4.27,<2026.0.0"
|
|
153
154
|
myst-parser = ">=3.0.1,<5.0.0"
|
|
154
|
-
pytest = "
|
|
155
|
+
pytest = "9.0.3"
|
|
155
156
|
pyyaml = "^6.0.1"
|
|
156
157
|
roman-numerals = "4.1.0"
|
|
157
158
|
sphinx-copybutton = "^0.5.2"
|
|
159
|
+
sphinx-jsonschema = "^1.19.2"
|
|
158
160
|
sphinx-markdown-builder = ">=0.5.5,<0.7.0"
|
|
159
161
|
sphinx-sitemap = "^2.9.0"
|
|
160
162
|
templatest = ">=0.10.1,<0.13.0"
|
|
@@ -163,7 +165,7 @@ templatest = ">=0.10.1,<0.13.0"
|
|
|
163
165
|
flake8 = "^7.1.0"
|
|
164
166
|
|
|
165
167
|
[tool.poetry.group.tests.dependencies]
|
|
166
|
-
pytest = "
|
|
168
|
+
pytest = ">=8.2,<10.0"
|
|
167
169
|
pytest-benchmark = ">=4,<6"
|
|
168
170
|
pytest-cov = ">=5,<8"
|
|
169
171
|
pytest-gitignore = "^1.3"
|
|
@@ -173,7 +175,10 @@ pytest-xdist = "^3.6.1"
|
|
|
173
175
|
templatest = ">=0.10.1,<0.13.0"
|
|
174
176
|
|
|
175
177
|
[tool.poetry.plugins."flake8.extension"]
|
|
176
|
-
SIG = "docsig.plugin:
|
|
178
|
+
SIG = "docsig.plugin:Flake8"
|
|
179
|
+
|
|
180
|
+
[tool.poetry.plugins."validate_pyproject.tool_schema"]
|
|
181
|
+
docsig = "docsig.plugin:ValidatePyproject"
|
|
177
182
|
|
|
178
183
|
[tool.poetry.scripts]
|
|
179
184
|
docsig = "docsig.__main__:main"
|
|
@@ -225,6 +230,9 @@ name = "Changed"
|
|
|
225
230
|
[tool.towncrier.fragment.deprecate]
|
|
226
231
|
name = "Deprecated"
|
|
227
232
|
|
|
233
|
+
[tool.towncrier.fragment.feat]
|
|
234
|
+
name = "Feature"
|
|
235
|
+
|
|
228
236
|
[tool.towncrier.fragment.fix]
|
|
229
237
|
name = "Fixed"
|
|
230
238
|
|
|
@@ -239,7 +247,7 @@ name = "Security"
|
|
|
239
247
|
|
|
240
248
|
[tool.vulture]
|
|
241
249
|
exclude = [
|
|
242
|
-
"tests/
|
|
250
|
+
"tests/base_test.py",
|
|
243
251
|
"tests/conftest.py"
|
|
244
252
|
]
|
|
245
253
|
make_whitelist = true
|
|
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
|