Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ before_install:
- "if grep '\t' *.py *.rst; then echo 'Tabs are bad, please use four spaces.'; false; fi"
- "if grep -n -r '[[:blank:]]$' *.py *.rst; then echo 'Please remove trailing whitespace.'; false; fi"
- pip install --upgrade pip setuptools
- pip install flake8 flake8-docstrings restructuredtext-lint
- pip install flake8 flake8-docstrings rstcheck
- "if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then pip install flake8-black; fi"
- echo "Using restructuredtext-lint to check documentation"
- restructuredtext-lint *.rst
- echo "Using rstcheck to check documentation"
- rstcheck *.rst
- echo "Using flake8 to check Python code"
- flake8 setup.py flake8_rst_docstrings.py

Expand Down
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ has a flake8 plugin called ``flake8-docstrings``, see:
- https://github.com/PyCQA/flake8-docstrings

The reStructuredText (RST) validation is done by calling ``docutils`` via
Todd Wolfson's ``restructuredtext-lint`` code:
Steven Myint's ``rstcheck`` code:

- http://docutils.sourceforge.net/
- https://github.com/twolfson/restructuredtext-lint
- https://github.com/myint/rstcheck

I recommend you *also* install the related `flake8-docstrings
<https://gitlab.com/pycqa/flake8-docstrings>`_ plugin, which brings
Expand Down Expand Up @@ -219,6 +218,8 @@ Version History
======= ========== ===========================================================
Version Released Changes
------- ---------- -----------------------------------------------------------
v0.1.0 *Pending* - Switched from ``restructuredtext-lint`` to ``rstcheck``
which also checks syntax of any code blocks within RST.
v0.0.13 2019-12-26 - Adds ``RST218`` and ``RST219``.
v0.0.12 2019-11-18 - Adds ``RST213`` to ``RST217``.
v0.0.11 2019-08-07 - Configuration options to define additional directives and
Expand Down
69 changes: 44 additions & 25 deletions flake8_rst_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from io import StringIO
from io import TextIOWrapper

import rstcheck

#####################################
# Start of backported tokenize code #
#####################################
Expand Down Expand Up @@ -136,10 +138,7 @@ def tokenize_open(filename):
# End of backported tokenize code #
###################################

import restructuredtext_lint as rst_lint


__version__ = "0.0.13"
__version__ = "0.1.0"


log = logging.getLogger(__name__)
Expand All @@ -149,6 +148,7 @@ def tokenize_open(filename):
rst_fail_parse = 901
rst_fail_all = 902
rst_fail_lint = 903
rst_unknown_prefix = 999

# Level 1 - info
code_mapping_info = {
Expand Down Expand Up @@ -205,7 +205,7 @@ def tokenize_open(filename):
}


def code_mapping(level, msg, extra_directives, extra_roles, default=99):
def code_mapping(level, msg, default=99):
"""Return an error code between 0 and 99."""
try:
return code_mappings_by_level[level][msg]
Expand All @@ -219,11 +219,6 @@ def code_mapping(level, msg, extra_directives, extra_roles, default=99):
# ---> 'Unknown interpreted text role'
if msg.count('"') == 2 and ' "' in msg and msg.endswith('".'):
txt = msg[: msg.index(' "')]
value = msg.split('"', 2)[1]
if txt == "Unknown directive type" and value in extra_directives:
return 0
if txt == "Unknown interpreted text role" and value in extra_roles:
return 0
return code_mappings_by_level[level].get(txt, default)
return default

Expand Down Expand Up @@ -1011,8 +1006,7 @@ def add_options(cls, parser):
@classmethod
def parse_options(cls, options):
"""Adding black-config option."""
cls.extra_directives = options.rst_directives
cls.extra_roles = options.rst_roles
rstcheck.ignore_directives_and_roles(options.rst_directives, options.rst_roles)

def run(self):
"""Use docutils to check docstrings are valid RST."""
Expand Down Expand Up @@ -1054,10 +1048,9 @@ def run(self):
# leading whitespace from each line - this avoids false
# positive severe error "Unexpected section title."
unindented = trim(dequote_docstring(definition.docstring))
# Off load RST validation to reStructuredText-lint
# Off load RST validation to rstcheck
# which calls docutils internally.
# TODO: Should we pass the Python filename as filepath?
rst_errors = list(rst_lint.lint(unindented))
rst_errors = list(rstcheck.check(unindented))
except Exception as err:
# e.g. UnicodeDecodeError
msg = "%s%03i %s" % (
Expand All @@ -1067,10 +1060,7 @@ def run(self):
)
yield definition.start, 0, msg, type(self)
continue
for rst_error in rst_errors:
# TODO - make this a configuration option?
if rst_error.level <= 1:
continue
for line_number, rst_error in rst_errors:
# Levels:
#
# 0 - debug --> we don't receive these
Expand All @@ -1080,21 +1070,50 @@ def run(self):
# 4 - severe --> RST4## codes
#
# Map the string to a unique code:
msg = rst_error.message.split("\n", 1)[0]
code = code_mapping(
rst_error.level, msg, self.extra_directives, self.extra_roles
)
if rst_error.startswith('(INFO/1) No directive entry for "'):
# Upgrade level and change to old rst-lint wording
level = 3
rst_error = (
'(ERROR/3) Unknown directive type "%s".'
% rst_error.split(' "', 1)[1].split('"', 1)[0]
)
elif rst_error.startswith('(INFO/1) No role entry for "'):
# Upgrade level and change to old rst-lint wording
level = 3
rst_error = (
'(ERROR/3) Unknown interpreted text role "%s".'
% rst_error.split(' "', 1)[1].split('"', 1)[0]
)

elif rst_error.startswith("(INFO/1) "):
level = 1
elif rst_error.startswith("(WARNING/2) "):
level = 2
elif rst_error.startswith("(ERROR/3) "):
level = 3
elif rst_error.startswith("(SEVERE/4) "):
level = 4
else:
msg = "%s%03i %s" % (
rst_prefix,
rst_unknown_prefix,
"Unexpected prefix: %r" % rst_error,
)
yield definition.start + line_number, 0, msg, type(self)
continue
msg = rst_error.split(None, 1)[1]
code = code_mapping(level, msg)
if not code:
# We ignored it, e.g. a known Sphinx role
continue
assert 0 < code < 100, code
code += 100 * rst_error.level
code += 100 * level
msg = "%s%03i %s" % (rst_prefix, code, msg)

# This will return the line number by combining the
# start of the docstring with the offet within it.
# We don't know the column number, leaving as zero.
yield definition.start + rst_error.line, 0, msg, type(self)
yield definition.start + line_number, 0, msg, type(self)

def load_source(self):
"""Load the source for the specified file."""
Expand Down