Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore nitpick warnings with regular expressions using nitpick_ignore_regex #9131

Merged
merged 5 commits into from May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/usage/configuration.rst
Expand Up @@ -419,6 +419,20 @@ General configuration

.. versionadded:: 1.1

.. confval:: nitpick_ignore_regex

An extended version of :confval:`nitpick_ignore`, which instead interprets
the ``type`` and ``target`` strings as regular expressions. Note, that the
regular expression must match the whole string (as if the ``^`` and ``$``
markers were inserted).

For example, ``(r'py:.*', r'foo.*bar\.B.*')`` will ignore nitpicky warnings
for all python entities that start with ``'foo'`` and have ``'bar.B'`` in
them, such as ``('py:const', 'foo_package.bar.BAZ_VALUE')`` or
``('py:class', 'food.bar.Barman')``.

.. versionadded:: 4.1

.. confval:: numfig

If true, figures, tables and code-blocks are automatically numbered if they
Expand Down
1 change: 1 addition & 0 deletions sphinx/config.py
Expand Up @@ -131,6 +131,7 @@ class Config:
'manpages_url': (None, 'env', []),
'nitpicky': (False, None, []),
'nitpick_ignore': ([], None, []),
'nitpick_ignore_regex': ([], None, []),
'numfig': (False, 'env', []),
'numfig_secnum_depth': (1, 'env', []),
'numfig_format': ({}, 'env', []), # will be initialized in init_numfig_format()
Expand Down
16 changes: 15 additions & 1 deletion sphinx/transforms/post_transforms/__init__.py
Expand Up @@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""

import re
from typing import Any, Dict, List, Optional, Tuple, Type, cast

from docutils import nodes
Expand Down Expand Up @@ -171,14 +172,27 @@ def warn_missing_reference(self, refdoc: str, typ: str, target: str,
warn = node.get('refwarn')
if self.config.nitpicky:
warn = True
dtype = '%s:%s' % (domain.name, typ) if domain else typ
if self.config.nitpick_ignore:
dtype = '%s:%s' % (domain.name, typ) if domain else typ
if (dtype, target) in self.config.nitpick_ignore:
warn = False
# for "std" types also try without domain name
if (not domain or domain.name == 'std') and \
(typ, target) in self.config.nitpick_ignore:
warn = False
if self.config.nitpick_ignore_regex:
def matches_ignore(entry_type: str, entry_target: str) -> bool:
for ignore_type, ignore_target in self.config.nitpick_ignore_regex:
if re.fullmatch(ignore_type, entry_type) and \
re.fullmatch(ignore_target, entry_target):
return True
return False
if matches_ignore(dtype, target):
warn = False
# for "std" types also try without domain name
if (not domain or domain.name == 'std') and \
matches_ignore(typ, target):
warn = False
if not warn:
return

Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-nitpicky-warnings/conf.py
@@ -0,0 +1 @@
nitpicky = True
7 changes: 7 additions & 0 deletions tests/roots/test-nitpicky-warnings/index.rst
@@ -0,0 +1,7 @@
test-nitpicky-warnings
======================

:py:const:`prefix.anything.postfix`
:py:class:`prefix.anything`
:py:class:`anything.postfix`
:js:class:`prefix.anything.postfix`
74 changes: 74 additions & 0 deletions tests/test_config.py
Expand Up @@ -311,3 +311,77 @@ def test_check_enum_for_list_failed(logger):
config.init_values()
check_confval_types(None, config)
assert logger.warning.called


nitpick_warnings = [
"WARNING: py:const reference target not found: prefix.anything.postfix",
"WARNING: py:class reference target not found: prefix.anything",
"WARNING: py:class reference target not found: anything.postfix",
"WARNING: js:class reference target not found: prefix.anything.postfix",
]


@pytest.mark.sphinx(testroot='nitpicky-warnings')
def test_nitpick_base(app, status, warning):
app.builder.build_all()

warning = warning.getvalue().strip().split('\n')
assert len(warning) == len(nitpick_warnings)
for actual, expected in zip(warning, nitpick_warnings):
assert expected in actual


@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
'nitpick_ignore': [
('py:const', 'prefix.anything.postfix'),
('py:class', 'prefix.anything'),
('py:class', 'anything.postfix'),
('js:class', 'prefix.anything.postfix'),
],
})
def test_nitpick_ignore(app, status, warning):
app.builder.build_all()
assert not len(warning.getvalue().strip())


@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
'nitpick_ignore_regex': [
(r'py:.*', r'.*postfix'),
(r'.*:class', r'prefix.*'),
]
})
def test_nitpick_ignore_regex1(app, status, warning):
app.builder.build_all()
assert not len(warning.getvalue().strip())


@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
'nitpick_ignore_regex': [
(r'py:.*', r'prefix.*'),
(r'.*:class', r'.*postfix'),
]
})
def test_nitpick_ignore_regex2(app, status, warning):
app.builder.build_all()
assert not len(warning.getvalue().strip())


@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
'nitpick_ignore_regex': [
# None of these should match
(r'py:', r'.*'),
(r':class', r'.*'),
(r'', r'.*'),
(r'.*', r'anything'),
(r'.*', r'prefix'),
(r'.*', r'postfix'),
(r'.*', r''),
]
})
def test_nitpick_ignore_regex_fullmatch(app, status, warning):
app.builder.build_all()

warning = warning.getvalue().strip().split('\n')
assert len(warning) == len(nitpick_warnings)
for actual, expected in zip(warning, nitpick_warnings):
assert expected in actual