[Python-checkins] gh-86404: Doc: Drop now unused make suspicious and rstlint. (GH-98179)

JulienPalard webhook-mailer at python.org
Tue Oct 11 09:32:04 EDT 2022


https://github.com/python/cpython/commit/4067c6d7fe9b0b266367aafa8cde71e2761cb764
commit: 4067c6d7fe9b0b266367aafa8cde71e2761cb764
branch: main
author: Julien Palard <julien at palard.fr>
committer: JulienPalard <julien at palard.fr>
date: 2022-10-11T15:31:33+02:00
summary:

gh-86404: Doc: Drop now unused make suspicious and rstlint. (GH-98179)

They have been replaced by
[sphinx-lint](https://github.com/sphinx-contrib/sphinx-lint).

files:
A Misc/NEWS.d/next/Documentation/2022-10-11-09-40-50.gh-issue-86404.dEAb8W.rst
D Doc/tools/extensions/suspicious.py
D Doc/tools/rstlint.py
D Doc/tools/susp-ignored.csv
M Doc/Makefile
M Doc/README.rst
M Doc/make.bat
M Doc/tools/extensions/pyspecific.py
M Doc/whatsnew/3.12.rst

diff --git a/Doc/Makefile b/Doc/Makefile
index 5b6a95813abe..f52087409a04 100644
--- a/Doc/Makefile
+++ b/Doc/Makefile
@@ -22,7 +22,7 @@ ALLSPHINXOPTS = -b $(BUILDER) -d build/doctrees $(PAPEROPT_$(PAPER)) -j auto \
                 $(SPHINXOPTS) $(SPHINXERRORHANDLING) . build/$(BUILDER) $(SOURCES)
 
 .PHONY: help build html htmlhelp latex text texinfo changes linkcheck \
-	suspicious coverage doctest pydoc-topics htmlview clean dist check serve \
+	coverage doctest pydoc-topics htmlview clean dist check serve \
 	autobuild-dev autobuild-stable venv
 
 help:
@@ -42,7 +42,6 @@ help:
 	@echo "  doctest    to run doctests in the documentation"
 	@echo "  pydoc-topics  to regenerate the pydoc topics file"
 	@echo "  dist       to create a \"dist\" directory with archived docs for download"
-	@echo "  suspicious to check for suspicious markup in output text"
 	@echo "  check      to run a check for frequent markup errors"
 
 build:
@@ -110,18 +109,6 @@ linkcheck:
 	     "or in build/$(BUILDER)/output.txt"; \
 	false; }
 
-suspicious: BUILDER = suspicious
-suspicious:
-	@$(MAKE) build BUILDER=$(BUILDER) || { \
-	echo "Suspicious check complete; look for any errors in the above output" \
-	     "or in build/$(BUILDER)/suspicious.csv.  If all issues are false" \
-	     "positives, append that file to tools/susp-ignored.csv."; \
-	false; }
-	@echo "⚠ make suspicious is deprecated and will be removed soon."
-	@echo "⚠ Use:"
-	@echo "⚠     make check"
-	@echo "⚠ instead."
-
 coverage: BUILDER = coverage
 coverage: build
 	@echo "Coverage finished; see c.txt and python.txt in build/coverage"
diff --git a/Doc/README.rst b/Doc/README.rst
index d67cad79916b..a3bb5fa5445c 100644
--- a/Doc/README.rst
+++ b/Doc/README.rst
@@ -93,9 +93,6 @@ Available make targets are:
   plain text documentation for the labels defined in
   ``tools/pyspecific.py`` -- pydoc needs these to show topic and keyword help.
 
-* "suspicious", which checks the parsed markup for text that looks like
-  malformed and thus unconverted reST.
-
 * "check", which checks for frequent markup errors.
 
 * "serve", which serves the build/html directory on port 8000.
diff --git a/Doc/make.bat b/Doc/make.bat
index 4f0b3c11f4fa..87d8359ef112 100644
--- a/Doc/make.bat
+++ b/Doc/make.bat
@@ -109,7 +109,7 @@ echo.always available include:
 echo.
 echo.   Provided by Sphinx:
 echo.      html, htmlhelp, latex, text
-echo.      suspicious, linkcheck, changes, doctest
+echo.      linkcheck, changes, doctest
 echo.   Provided by this script:
 echo.      clean, check, htmlview
 echo.
diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py
index 8c3aa47ad1c7..3b9f7442f75b 100644
--- a/Doc/tools/extensions/pyspecific.py
+++ b/Doc/tools/extensions/pyspecific.py
@@ -37,10 +37,6 @@
     from sphinx.domains.python import PyClassmember as PyMethod
     from sphinx.domains.python import PyModulelevel as PyFunction
 
-# Support for checking for suspicious markup
-
-import suspicious
-
 
 ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s'
 GH_ISSUE_URI = 'https://github.com/python/cpython/issues/%s'
@@ -686,7 +682,6 @@ def setup(app):
     app.add_directive('audit-event-table', AuditEventListDirective)
     app.add_directive('deprecated-removed', DeprecatedRemoved)
     app.add_builder(PydocTopicsBuilder)
-    app.add_builder(suspicious.CheckSuspiciousMarkupBuilder)
     app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
     app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command)
     app.add_object_type('2to3fixer', '2to3fixer', '%s (2to3 fixer)')
diff --git a/Doc/tools/extensions/suspicious.py b/Doc/tools/extensions/suspicious.py
deleted file mode 100644
index 2d581a8a6c3d..000000000000
--- a/Doc/tools/extensions/suspicious.py
+++ /dev/null
@@ -1,251 +0,0 @@
-"""
-Try to detect suspicious constructs, resembling markup
-that has leaked into the final output.
-
-Suspicious lines are reported in a comma-separated-file,
-``suspicious.csv``, located in the output directory.
-
-The file is utf-8 encoded, and each line contains four fields:
-
- * document name (normalized)
- * line number in the source document
- * problematic text
- * complete line showing the problematic text in context
-
-It is common to find many false positives. To avoid reporting them
-again and again, they may be added to the ``ignored.csv`` file
-(located in the configuration directory). The file has the same
-format as ``suspicious.csv`` with a few differences:
-
-  - each line defines a rule; if the rule matches, the issue
-    is ignored.
-  - line number may be empty (that is, nothing between the
-    commas: ",,"). In this case, line numbers are ignored (the
-    rule matches anywhere in the file).
-  - the last field does not have to be a complete line; some
-    surrounding text (never more than a line) is enough for
-    context.
-
-Rules are processed sequentially. A rule matches when:
-
- * document names are the same
- * problematic texts are the same
- * line numbers are close to each other (5 lines up or down)
- * the rule text is completely contained into the source line
-
-The simplest way to create the ignored.csv file is by copying
-undesired entries from suspicious.csv (possibly trimming the last
-field.)
-
-Copyright 2009 Gabriel A. Genellina
-
-"""
-
-import os
-import re
-import csv
-
-from docutils import nodes
-from sphinx.builders import Builder
-import sphinx.util
-
-detect_all = re.compile(r'''
-    ::(?=[^=])|            # two :: (but NOT ::=)
-    :[a-zA-Z][a-zA-Z0-9]+| # :foo
-    `|                     # ` (seldom used by itself)
-    (?<!\.)\.\.[ \t]*\w+:  # .. foo: (but NOT ... else:)
-    ''', re.VERBOSE).finditer
-
-
-class Rule:
-    def __init__(self, docname, lineno, issue, line):
-        """A rule for ignoring issues"""
-        self.docname = docname # document to which this rule applies
-        self.lineno = lineno   # line number in the original source;
-                               # this rule matches only near that.
-                               # None -> don't care
-        self.issue = issue     # the markup fragment that triggered this rule
-        self.line = line       # text of the container element (single line only)
-        self.used = False
-
-    def __repr__(self):
-        return '{0.docname},,{0.issue},{0.line}'.format(self)
-
-
-
-class dialect(csv.excel):
-    """Our dialect: uses only linefeed as newline."""
-    lineterminator = '\n'
-
-
-class CheckSuspiciousMarkupBuilder(Builder):
-    """
-    Checks for possibly invalid markup that may leak into the output.
-    """
-    name = 'suspicious'
-    logger = sphinx.util.logging.getLogger("CheckSuspiciousMarkupBuilder")
-
-    def init(self):
-        # create output file
-        self.log_file_name = os.path.join(self.outdir, 'suspicious.csv')
-        open(self.log_file_name, 'w').close()
-        # load database of previously ignored issues
-        self.load_rules(os.path.join(os.path.dirname(__file__), '..',
-                                     'susp-ignored.csv'))
-
-    def get_outdated_docs(self):
-        return self.env.found_docs
-
-    def get_target_uri(self, docname, typ=None):
-        return ''
-
-    def prepare_writing(self, docnames):
-        pass
-
-    def write_doc(self, docname, doctree):
-        # set when any issue is encountered in this document
-        self.any_issue = False
-        self.docname = docname
-        visitor = SuspiciousVisitor(doctree, self)
-        doctree.walk(visitor)
-
-    def finish(self):
-        unused_rules = [rule for rule in self.rules if not rule.used]
-        if unused_rules:
-            self.logger.warning(
-                'Found %s/%s unused rules: %s' % (
-                    len(unused_rules), len(self.rules),
-                    '\n'.join(repr(rule) for rule in unused_rules),
-                )
-            )
-        return
-
-    def check_issue(self, line, lineno, issue):
-        if not self.is_ignored(line, lineno, issue):
-            self.report_issue(line, lineno, issue)
-
-    def is_ignored(self, line, lineno, issue):
-        """Determine whether this issue should be ignored."""
-        docname = self.docname
-        for rule in self.rules:
-            if rule.docname != docname: continue
-            if rule.issue != issue: continue
-            # Both lines must match *exactly*. This is rather strict,
-            # and probably should be improved.
-            # Doing fuzzy matches with levenshtein distance could work,
-            # but that means bringing other libraries...
-            # Ok, relax that requirement: just check if the rule fragment
-            # is contained in the document line
-            if rule.line not in line: continue
-            # Check both line numbers. If they're "near"
-            # this rule matches. (lineno=None means "don't care")
-            if (rule.lineno is not None) and \
-                abs(rule.lineno - lineno) > 5: continue
-            # if it came this far, the rule matched
-            rule.used = True
-            return True
-        return False
-
-    def report_issue(self, text, lineno, issue):
-        self.any_issue = True
-        self.write_log_entry(lineno, issue, text)
-        self.logger.warning('[%s:%d] "%s" found in "%-.120s"' %
-                                (self.docname, lineno, issue, text))
-        self.app.statuscode = 1
-
-    def write_log_entry(self, lineno, issue, text):
-        f = open(self.log_file_name, 'a')
-        writer = csv.writer(f, dialect)
-        writer.writerow([self.docname, lineno, issue, text.strip()])
-        f.close()
-
-    def load_rules(self, filename):
-        """Load database of previously ignored issues.
-
-        A csv file, with exactly the same format as suspicious.csv
-        Fields: document name (normalized), line number, issue, surrounding text
-        """
-        self.logger.info("loading ignore rules... ", nonl=1)
-        self.rules = rules = []
-        try:
-            f = open(filename, 'r')
-        except IOError:
-            return
-        for i, row in enumerate(csv.reader(f)):
-            if len(row) != 4:
-                raise ValueError(
-                    "wrong format in %s, line %d: %s" % (filename, i+1, row))
-            docname, lineno, issue, text = row
-            if lineno:
-                lineno = int(lineno)
-            else:
-                lineno = None
-            rule = Rule(docname, lineno, issue, text)
-            rules.append(rule)
-        f.close()
-        self.logger.info('done, %d rules loaded' % len(self.rules))
-
-
-def get_lineno(node):
-    """Obtain line number information for a node."""
-    lineno = None
-    while lineno is None and node:
-        node = node.parent
-        lineno = node.line
-    return lineno
-
-
-def extract_line(text, index):
-    """text may be a multiline string; extract
-    only the line containing the given character index.
-
-    >>> extract_line("abc\ndefgh\ni", 6)
-    >>> 'defgh'
-    >>> for i in (0, 2, 3, 4, 10):
-    ...   print extract_line("abc\ndefgh\ni", i)
-    abc
-    abc
-    abc
-    defgh
-    defgh
-    i
-    """
-    p = text.rfind('\n', 0, index) + 1
-    q = text.find('\n', index)
-    if q < 0:
-        q = len(text)
-    return text[p:q]
-
-
-class SuspiciousVisitor(nodes.GenericNodeVisitor):
-
-    lastlineno = 0
-
-    def __init__(self, document, builder):
-        nodes.GenericNodeVisitor.__init__(self, document)
-        self.builder = builder
-
-    def default_visit(self, node):
-        if isinstance(node, (nodes.Text, nodes.image)): # direct text containers
-            text = node.astext()
-            # lineno seems to go backwards sometimes (?)
-            self.lastlineno = lineno = max(get_lineno(node) or 0, self.lastlineno)
-            seen = set() # don't report the same issue more than only once per line
-            for match in detect_all(text):
-                issue = match.group()
-                line = extract_line(text, match.start())
-                if (issue, line) not in seen:
-                    self.builder.check_issue(line, lineno, issue)
-                    seen.add((issue, line))
-
-    unknown_visit = default_visit
-
-    def visit_document(self, node):
-        self.lastlineno = 0
-
-    def visit_comment(self, node):
-        # ignore comments -- too much false positives.
-        # (although doing this could miss some errors;
-        # there were two sections "commented-out" by mistake
-        # in the Python docs that would not be caught)
-        raise nodes.SkipNode
diff --git a/Doc/tools/rstlint.py b/Doc/tools/rstlint.py
deleted file mode 100644
index 4ea68ef3b030..000000000000
--- a/Doc/tools/rstlint.py
+++ /dev/null
@@ -1,408 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# Check for stylistic and formal issues in .rst and .py
-# files included in the documentation.
-#
-# 01/2009, Georg Brandl
-
-# TODO: - wrong versions in versionadded/changed
-#       - wrong markup after versionchanged directive
-
-import os
-import re
-import sys
-import getopt
-from string import ascii_letters
-from os.path import join, splitext, abspath, exists
-from collections import defaultdict
-
-directives = [
-    # standard docutils ones
-    'admonition', 'attention', 'caution', 'class', 'compound', 'container',
-    'contents', 'csv-table', 'danger', 'date', 'default-role', 'epigraph',
-    'error', 'figure', 'footer', 'header', 'highlights', 'hint', 'image',
-    'important', 'include', 'line-block', 'list-table', 'meta', 'note',
-    'parsed-literal', 'pull-quote', 'raw', 'replace',
-    'restructuredtext-test-directive', 'role', 'rubric', 'sectnum', 'sidebar',
-    'table', 'target-notes', 'tip', 'title', 'topic', 'unicode', 'warning',
-    # Sphinx and Python docs custom ones
-    'acks', 'attribute', 'autoattribute', 'autoclass', 'autodata',
-    'autoexception', 'autofunction', 'automethod', 'automodule',
-    'availability', 'centered', 'cfunction', 'class', 'classmethod', 'cmacro',
-    'cmdoption', 'cmember', 'code-block', 'confval', 'cssclass', 'ctype',
-    'currentmodule', 'cvar', 'data', 'decorator', 'decoratormethod',
-    'deprecated-removed', 'deprecated(?!-removed)', 'describe', 'directive',
-    'doctest', 'envvar', 'event', 'exception', 'function', 'glossary',
-    'highlight', 'highlightlang', 'impl-detail', 'index', 'literalinclude',
-    'method', 'miscnews', 'module', 'moduleauthor', 'opcode', 'pdbcommand',
-    'productionlist', 'program', 'role', 'sectionauthor', 'seealso',
-    'sourcecode', 'staticmethod', 'tabularcolumns', 'testcode', 'testoutput',
-    'testsetup', 'toctree', 'todo', 'todolist', 'versionadded',
-    'versionchanged'
-]
-
-roles = [
-    "(?<!py):class:",
-    "(?<!:c|py):func:",
-    "(?<!py):meth:",
-    "(?<!:py):mod:",
-    ":exc:",
-    ":issue:",
-    ":attr:",
-    ":c:func:",
-    ":ref:",
-    ":const:",
-    ":term:",
-    "(?<!:c|py):data:",
-    ":keyword:",
-    ":file:",
-    ":pep:",
-    ":c:type:",
-    ":c:member:",
-    ":option:",
-    ":rfc:",
-    ":envvar:",
-    ":c:data:",
-    ":source:",
-    ":mailheader:",
-    ":program:",
-    ":c:macro:",
-    ":dfn:",
-    ":kbd:",
-    ":command:",
-    ":mimetype:",
-    ":opcode:",
-    ":manpage:",
-    ":py:data:",
-    ":RFC:",
-    ":pdbcmd:",
-    ":abbr:",
-    ":samp:",
-    ":token:",
-    ":PEP:",
-    ":sup:",
-    ":py:class:",
-    ":menuselection:",
-    ":doc:",
-    ":sub:",
-    ":py:meth:",
-    ":newsgroup:",
-    ":code:",
-    ":py:func:",
-    ":makevar:",
-    ":guilabel:",
-    ":title-reference:",
-    ":py:mod:",
-    ":download:",
-    ":2to3fixer:",
-]
-
-all_directives = "(" + "|".join(directives) + ")"
-all_roles = "(" + "|".join(roles) + ")"
-
-# Find comments that looks like a directive, like:
-# .. versionchanged 3.6
-# or
-# .. versionchanged: 3.6
-# as it should be:
-# .. versionchanged:: 3.6
-seems_directive_re = re.compile(r"(?<!\.)\.\. %s([^a-z:]|:(?!:))" % all_directives)
-
-# Find directive prefixed with three dots instead of two, like:
-# ... versionchanged:: 3.6
-# instead of:
-# .. versionchanged:: 3.6
-three_dot_directive_re = re.compile(r"\.\.\. %s::" % all_directives)
-
-# Find role used with double backticks instead of simple backticks like:
-# :const:``None``
-# instead of:
-# :const:`None`
-double_backtick_role = re.compile(r"(?<!``)%s``" % all_roles)
-
-
-# Find role used with no backticks instead of simple backticks like:
-# :const:None
-# instead of:
-# :const:`None`
-role_with_no_backticks = re.compile(r"%s[^` ]" % all_roles)
-
-# Find role glued with another word like:
-# the:c:func:`PyThreadState_LeaveTracing` function.
-# instead of:
-# the :c:func:`PyThreadState_LeaveTracing` function.
-role_glued_with_word = re.compile(r"[a-zA-Z]%s" % all_roles)
-
-default_role_re = re.compile(r"(^| )`\w([^`]*?\w)?`($| )")
-leaked_markup_re = re.compile(r"[a-z]::\s|`|\.\.\s*\w+:")
-
-
-checkers = {}
-
-checker_props = {'severity': 1, 'falsepositives': False}
-
-
-def checker(*suffixes, **kwds):
-    """Decorator to register a function as a checker."""
-    def deco(func):
-        for suffix in suffixes:
-            checkers.setdefault(suffix, []).append(func)
-        for prop in checker_props:
-            setattr(func, prop, kwds.get(prop, checker_props[prop]))
-        return func
-    return deco
-
-
- at checker('.py', severity=4)
-def check_syntax(fn, lines):
-    """Check Python examples for valid syntax."""
-    code = ''.join(lines)
-    if '\r' in code:
-        if os.name != 'nt':
-            yield 0, '\\r in code file'
-        code = code.replace('\r', '')
-    try:
-        compile(code, fn, 'exec')
-    except SyntaxError as err:
-        yield err.lineno, 'not compilable: %s' % err
-
-
- at checker('.rst', severity=2)
-def check_suspicious_constructs(fn, lines):
-    """Check for suspicious reST constructs."""
-    inprod = False
-    for lno, line in enumerate(lines, start=1):
-        if seems_directive_re.search(line):
-            yield lno, "comment seems to be intended as a directive"
-        if three_dot_directive_re.search(line):
-            yield lno, "directive should start with two dots, not three."
-        if double_backtick_role.search(line):
-            yield lno, "role use a single backtick, double backtick found."
-        if role_with_no_backticks.search(line):
-            yield lno, "role use a single backtick, no backtick found."
-        if role_glued_with_word.search(line):
-            yield lno, "missing space before role"
-        if ".. productionlist::" in line:
-            inprod = True
-        elif not inprod and default_role_re.search(line):
-            yield lno, "default role used"
-        elif inprod and not line.strip():
-            inprod = False
-
-
- at checker('.py', '.rst')
-def check_whitespace(fn, lines):
-    """Check for whitespace and line length issues."""
-    for lno, line in enumerate(lines):
-        if '\r' in line:
-            yield lno+1, '\\r in line'
-        if '\t' in line:
-            yield lno+1, 'OMG TABS!!!1'
-        if line[:-1].rstrip(' \t') != line[:-1]:
-            yield lno+1, 'trailing whitespace'
-
-
- at checker('.rst', severity=0)
-def check_line_length(fn, lines):
-    """Check for line length; this checker is not run by default."""
-    for lno, line in enumerate(lines):
-        if len(line) > 81:
-            # don't complain about tables, links and function signatures
-            if line.lstrip()[0] not in '+|' and \
-               'http://' not in line and \
-               not line.lstrip().startswith(('.. function',
-                                             '.. method',
-                                             '.. cfunction')):
-                yield lno+1, "line too long"
-
-
- at checker('.html', severity=2, falsepositives=True)
-def check_leaked_markup(fn, lines):
-    """Check HTML files for leaked reST markup; this only works if
-    the HTML files have been built.
-    """
-    for lno, line in enumerate(lines):
-        if leaked_markup_re.search(line):
-            yield lno+1, 'possibly leaked markup: %r' % line
-
-
-def hide_literal_blocks(lines):
-    """Tool to remove literal blocks from given lines.
-
-    It yields empty lines in place of blocks, so line numbers are
-    still meaningful.
-    """
-    in_block = False
-    for line in lines:
-        if line.endswith("::\n"):
-            in_block = True
-        elif in_block:
-            if line == "\n" or line.startswith(" "):
-                line = "\n"
-            else:
-                in_block = False
-        yield line
-
-
-def type_of_explicit_markup(line):
-    if re.match(fr'\.\. {all_directives}::', line):
-        return 'directive'
-    if re.match(r'\.\. \[[0-9]+\] ', line):
-        return 'footnote'
-    if re.match(r'\.\. \[[^\]]+\] ', line):
-        return 'citation'
-    if re.match(r'\.\. _.*[^_]: ', line):
-        return 'target'
-    if re.match(r'\.\. \|[^\|]*\| ', line):
-        return 'substitution_definition'
-    return 'comment'
-
-
-def hide_comments(lines):
-    """Tool to remove comments from given lines.
-
-    It yields empty lines in place of comments, so line numbers are
-    still meaningful.
-    """
-    in_multiline_comment = False
-    for line in lines:
-        if line == "..\n":
-            in_multiline_comment = True
-        elif in_multiline_comment:
-            if line == "\n" or line.startswith(" "):
-                line = "\n"
-            else:
-                in_multiline_comment = False
-        if line.startswith(".. ") and type_of_explicit_markup(line) == 'comment':
-            line = "\n"
-        yield line
-
-
-
- at checker(".rst", severity=2)
-def check_missing_surrogate_space_on_plural(fn, lines):
-    r"""Check for missing 'backslash-space' between a code sample a letter.
-
-    Good: ``Point``\ s
-    Bad: ``Point``s
-    """
-    in_code_sample = False
-    check_next_one = False
-    for lno, line in enumerate(hide_comments(hide_literal_blocks(lines))):
-        tokens = line.split("``")
-        for token_no, token in enumerate(tokens):
-            if check_next_one:
-                if token[0] in ascii_letters:
-                    yield lno + 1, f"Missing backslash-space between code sample and {token!r}."
-                check_next_one = False
-            if token_no == len(tokens) - 1:
-                continue
-            if in_code_sample:
-                check_next_one = True
-            in_code_sample = not in_code_sample
-
-def main(argv):
-    usage = '''\
-Usage: %s [-v] [-f] [-s sev] [-i path]* [path]
-
-Options:  -v       verbose (print all checked file names)
-          -f       enable checkers that yield many false positives
-          -s sev   only show problems with severity >= sev
-          -i path  ignore subdir or file path
-''' % argv[0]
-    try:
-        gopts, args = getopt.getopt(argv[1:], 'vfs:i:')
-    except getopt.GetoptError:
-        print(usage)
-        return 2
-
-    verbose = False
-    severity = 1
-    ignore = []
-    falsepos = False
-    for opt, val in gopts:
-        if opt == '-v':
-            verbose = True
-        elif opt == '-f':
-            falsepos = True
-        elif opt == '-s':
-            severity = int(val)
-        elif opt == '-i':
-            ignore.append(abspath(val))
-
-    if len(args) == 0:
-        path = '.'
-    elif len(args) == 1:
-        path = args[0]
-    else:
-        print(usage)
-        return 2
-
-    if not exists(path):
-        print('Error: path %s does not exist' % path)
-        return 2
-
-    count = defaultdict(int)
-
-    print("""⚠ rstlint.py is no longer maintained here and will be removed
-⚠ in a future release.
-⚠ Please use https://pypi.org/p/sphinx-lint instead.
-""")
-
-    for root, dirs, files in os.walk(path):
-        # ignore subdirs in ignore list
-        if abspath(root) in ignore:
-            del dirs[:]
-            continue
-
-        for fn in files:
-            fn = join(root, fn)
-            if fn[:2] == './':
-                fn = fn[2:]
-
-            # ignore files in ignore list
-            if abspath(fn) in ignore:
-                continue
-
-            ext = splitext(fn)[1]
-            checkerlist = checkers.get(ext, None)
-            if not checkerlist:
-                continue
-
-            if verbose:
-                print('Checking %s...' % fn)
-
-            try:
-                with open(fn, 'r', encoding='utf-8') as f:
-                    lines = list(f)
-            except (IOError, OSError) as err:
-                print('%s: cannot open: %s' % (fn, err))
-                count[4] += 1
-                continue
-
-            for checker in checkerlist:
-                if checker.falsepositives and not falsepos:
-                    continue
-                csev = checker.severity
-                if csev >= severity:
-                    for lno, msg in checker(fn, lines):
-                        print('[%d] %s:%d: %s' % (csev, fn, lno, msg))
-                        count[csev] += 1
-    if verbose:
-        print()
-    if not count:
-        if severity > 1:
-            print('No problems with severity >= %d found.' % severity)
-        else:
-            print('No problems found.')
-    else:
-        for severity in sorted(count):
-            number = count[severity]
-            print('%d problem%s with severity %d found.' %
-                  (number, number > 1 and 's' or '', severity))
-    return int(bool(count))
-
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv))
diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv
deleted file mode 100644
index 0dba0744b5fe..000000000000
--- a/Doc/tools/susp-ignored.csv
+++ /dev/null
@@ -1,400 +0,0 @@
-c-api/arg,,:ref,"PyArg_ParseTuple(args, ""O|O:ref"", &object, &callback)"
-c-api/list,,:high,list[low:high]
-c-api/sequence,,:i2,del o[i1:i2]
-c-api/sequence,,:i2,o[i1:i2]
-c-api/tuple,,:high,p[low:high]
-c-api/unicode,,:end,str[start:end]
-c-api/unicode,,:start,unicode[start:start+length]
-distutils/examples,,`,This is the description of the ``foobar`` package.
-distutils/setupscript,,::,
-extending/embedding,,:numargs,"if(!PyArg_ParseTuple(args, "":numargs""))"
-extending/extending,,:myfunction,"PyArg_ParseTuple(args, ""D:myfunction"", &c);"
-extending/extending,,:set,"if (PyArg_ParseTuple(args, ""O:set_callback"", &temp)) {"
-extending/newtypes,,:call,"if (!PyArg_ParseTuple(args, ""sss:call"", &arg1, &arg2, &arg3)) {"
-faq/programming,,:chr,">=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr("
-faq/programming,,:reduce,"print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,"
-faq/programming,,:reduce,"Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,"
-faq/windows,,:d48eceb,"Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)] on win32"
-howto/curses,,:black,"colors when it activates color mode.  They are: 0:black, 1:red,"
-howto/curses,,:red,"colors when it activates color mode.  They are: 0:black, 1:red,"
-howto/curses,,:green,"2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  The"
-howto/curses,,:yellow,"2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  The"
-howto/curses,,:blue,"2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  The"
-howto/curses,,:magenta,"2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  The"
-howto/curses,,:cyan,"2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  The"
-howto/curses,,:white,"2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  The"
-howto/descriptor,,:root,"INFO:root"
-howto/descriptor,,:Updating,"root:Updating"
-howto/descriptor,,:Accessing,"root:Accessing"
-howto/instrumentation,,::,python$target:::function-entry
-howto/instrumentation,,:function,python$target:::function-entry
-howto/instrumentation,,::,python$target:::function-return
-howto/instrumentation,,:function,python$target:::function-return
-howto/instrumentation,,:call,156641360502280  function-entry:call_stack.py:start:23
-howto/instrumentation,,:start,156641360502280  function-entry:call_stack.py:start:23
-howto/instrumentation,,:function,156641360518804  function-entry: call_stack.py:function_1:1
-howto/instrumentation,,:function,156641360532797  function-entry:  call_stack.py:function_3:9
-howto/instrumentation,,:function,156641360546807 function-return:  call_stack.py:function_3:10
-howto/instrumentation,,:function,156641360563367 function-return: call_stack.py:function_1:2
-howto/instrumentation,,:function,156641360578365  function-entry: call_stack.py:function_2:5
-howto/instrumentation,,:function,156641360591757  function-entry:  call_stack.py:function_1:1
-howto/instrumentation,,:function,156641360605556  function-entry:   call_stack.py:function_3:9
-howto/instrumentation,,:function,156641360617482 function-return:   call_stack.py:function_3:10
-howto/instrumentation,,:function,156641360629814 function-return:  call_stack.py:function_1:2
-howto/instrumentation,,:function,156641360642285 function-return: call_stack.py:function_2:6
-howto/instrumentation,,:function,156641360656770  function-entry: call_stack.py:function_3:9
-howto/instrumentation,,:function,156641360669707 function-return: call_stack.py:function_3:10
-howto/instrumentation,,:function,156641360687853  function-entry: call_stack.py:function_4:13
-howto/instrumentation,,:function,156641360700719 function-return: call_stack.py:function_4:14
-howto/instrumentation,,:function,156641360719640  function-entry: call_stack.py:function_5:18
-howto/instrumentation,,:function,156641360732567 function-return: call_stack.py:function_5:21
-howto/instrumentation,,:call,156641360747370 function-return:call_stack.py:start:28
-howto/instrumentation,,:start,156641360747370 function-return:call_stack.py:start:28
-howto/ipaddress,,:DB8,>>> ipaddress.ip_address('2001:DB8::1')
-howto/ipaddress,,::,>>> ipaddress.ip_address('2001:DB8::1')
-howto/ipaddress,,:db8,IPv6Address('2001:db8::1')
-howto/ipaddress,,::,IPv6Address('2001:db8::1')
-howto/ipaddress,,::,IPv6Address('::1')
-howto/ipaddress,,:db8,>>> ipaddress.ip_network('2001:db8::0/96')
-howto/ipaddress,,::,>>> ipaddress.ip_network('2001:db8::0/96')
-howto/ipaddress,,:db8,IPv6Network('2001:db8::/96')
-howto/ipaddress,,::,IPv6Network('2001:db8::/96')
-howto/ipaddress,,:db8,IPv6Network('2001:db8::/128')
-howto/ipaddress,,::,IPv6Network('2001:db8::/128')
-howto/ipaddress,,:db8,IPv6Interface('2001:db8::1/96')
-howto/ipaddress,,::,IPv6Interface('2001:db8::1/96')
-howto/ipaddress,,:db8,>>> addr6 = ipaddress.ip_address('2001:db8::1')
-howto/ipaddress,,::,>>> addr6 = ipaddress.ip_address('2001:db8::1')
-howto/ipaddress,,:db8,>>> host6 = ipaddress.ip_interface('2001:db8::1/96')
-howto/ipaddress,,::,>>> host6 = ipaddress.ip_interface('2001:db8::1/96')
-howto/ipaddress,,:db8,>>> net6 = ipaddress.ip_network('2001:db8::0/96')
-howto/ipaddress,,::,>>> net6 = ipaddress.ip_network('2001:db8::0/96')
-howto/ipaddress,,:ffff,IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
-howto/ipaddress,,::,IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
-howto/ipaddress,,::,IPv6Address('::ffff:ffff')
-howto/ipaddress,,:ffff,IPv6Address('::ffff:ffff')
-howto/ipaddress,,:db8,'2001:db8::/96'
-howto/ipaddress,,::,'2001:db8::/96'
-howto/ipaddress,,:db8,>>> ipaddress.ip_interface('2001:db8::1/96')
-howto/ipaddress,,::,>>> ipaddress.ip_interface('2001:db8::1/96')
-howto/ipaddress,,:db8,'2001:db8::1'
-howto/ipaddress,,::,'2001:db8::1'
-howto/ipaddress,,:db8,IPv6Address('2001:db8::ffff:ffff')
-howto/ipaddress,,::,IPv6Address('2001:db8::ffff:ffff')
-howto/ipaddress,,:ffff,IPv6Address('2001:db8::ffff:ffff')
-howto/logging,,:And,"WARNING:And this, too"
-howto/logging,,:And,"WARNING:root:And this, too"
-howto/logging,,:And,"ERROR:root:And non-ASCII stuff, too, like "
-howto/logging,,:Doing,INFO:root:Doing something
-howto/logging,,:Finished,INFO:root:Finished
-howto/logging,,:logger,severity:logger name:message
-howto/logging,,:Look,WARNING:root:Look before you leap!
-howto/logging,,:message,severity:logger name:message
-howto/logging,,:root,DEBUG:root:This message should go to the log file
-howto/logging,,:root,INFO:root:Doing something
-howto/logging,,:root,INFO:root:Finished
-howto/logging,,:root,INFO:root:So should this
-howto/logging,,:root,"ERROR:root:And non-ASCII stuff, too, like "
-howto/logging,,:root,INFO:root:Started
-howto/logging,,:root,"WARNING:root:And this, too"
-howto/logging,,:root,WARNING:root:Look before you leap!
-howto/logging,,:root,WARNING:root:Watch out!
-howto/logging,,:So,INFO:root:So should this
-howto/logging,,:So,INFO:So should this
-howto/logging,,:Started,INFO:root:Started
-howto/logging,,:This,DEBUG:root:This message should go to the log file
-howto/logging,,:This,DEBUG:This message should appear on the console
-howto/logging,,:Watch,WARNING:root:Watch out!
-howto/pyporting,,::,Programming Language :: Python :: 2
-howto/pyporting,,::,Programming Language :: Python :: 3
-howto/regex,,::,
-howto/regex,,:foo,(?:foo)
-howto/urllib2,,:password,"""joe:password at example.com"""
-library/__main__,,`,
-library/ast,,:upper,lower:upper
-library/ast,,:step,lower:upper:step
-library/audioop,,:ipos,"# factor = audioop.findfactor(in_test[ipos*2:ipos*2+len(out_test)],"
-library/configparser,,:home,my_dir: ${Common:home_dir}/twosheds
-library/configparser,,:option,${section:option}
-library/configparser,,:path,python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python}
-library/configparser,,:Python,python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python}
-library/configparser,,:system,path: ${Common:system_dir}/Library/Frameworks/
-library/datetime,,:MM,
-library/datetime,,:SS,
-library/decimal,,:optional,"trailneg:optional trailing minus indicator"
-library/difflib,,:ahi,a[alo:ahi]
-library/difflib,,:bhi,b[blo:bhi]
-library/difflib,,:i1,
-library/difflib,,:i2,
-library/difflib,,:j2,
-library/doctest,,`,``factorial`` from the ``example`` module:
-library/doctest,,`,The ``example`` module
-library/doctest,,`,Using ``factorial``
-library/exceptions,,:err,err.object[err.start:err.end]
-library/functions,,:step,a[start:stop:step]
-library/functions,,:stop,"a[start:stop, i]"
-library/functions,,:stop,a[start:stop:step]
-library/hashlib,,:LEAF,"h00 = blake2b(buf[0:LEAF_SIZE], fanout=FANOUT, depth=DEPTH,"
-library/http.client,,:port,host:port
-library/http.cookies,,`,!#$%&'*+-.^_`|~:
-library/imaplib,,:MM,"""DD-Mmm-YYYY HH:MM:SS"
-library/imaplib,,:SS,"""DD-Mmm-YYYY HH:MM:SS"
-library/inspect,,:int,">>> def foo(a, *, b:int, **kwargs):"
-library/inspect,,:int,"'(a, *, b:int, **kwargs)'"
-library/inspect,,:int,'b:int'
-library/ipaddress,,:db8,>>> ipaddress.ip_address('2001:db8::')
-library/ipaddress,,::,>>> ipaddress.ip_address('2001:db8::')
-library/ipaddress,,:db8,IPv6Address('2001:db8::')
-library/ipaddress,,::,IPv6Address('2001:db8::')
-library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000')
-library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000')
-library/ipaddress,,:db8,'2001:db8::1000'
-library/ipaddress,,::,'2001:db8::1000'
-library/ipaddress,,:db8,">>> f'{ipaddress.IPv6Address(""2001:db8::1000""):s}'"
-library/ipaddress,,::,">>> f'{ipaddress.IPv6Address(""2001:db8::1000""):s}'"
-library/ipaddress,,::,IPv6Address('ff02::5678%1')
-library/ipaddress,,::,fe80::1234
-library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer"
-library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer"
-library/ipaddress,,::,"""::abc:7:def"""
-library/ipaddress,,:def,"""::abc:7:def"""
-library/ipaddress,,::,::FFFF/96
-library/ipaddress,,::,2002::/16
-library/ipaddress,,::,2001::/32
-library/ipaddress,,::,>>> str(ipaddress.IPv6Address('::1'))
-library/ipaddress,,::,'::1'
-library/ipaddress,,:ff00,ffff:ff00::
-library/ipaddress,,:db00,2001:db00::0/24
-library/ipaddress,,::,2001:db00::0/24
-library/ipaddress,,:db00,2001:db00::0/ffff:ff00::
-library/ipaddress,,::,2001:db00::0/ffff:ff00::
-library/itertools,,:step,elements from seq[start:stop:step]
-library/itertools,,::,kernel = tuple(kernel)[::-1]
-library/itertools,,:stop,elements from seq[start:stop:step]
-library/logging.handlers,,:port,host:port
-library/logging,,:root,WARNING:root:Watch out!
-library/logging,,:Watch,WARNING:root:Watch out!
-library/mmap,,:i2,obj[i1:i2]
-library/multiprocessing,,`,# Add more tasks using `put()`
-library/multiprocessing,,:queue,">>> QueueManager.register('get_queue', callable=lambda:queue)"
-library/multiprocessing,,`,# register the Foo class; make `f()` and `g()` accessible via proxy
-library/multiprocessing,,`,# register the Foo class; make `g()` and `_h()` accessible via proxy
-library/multiprocessing,,`,# register the generator function baz; use `GeneratorProxy` to make proxies
-library/nntplib,,:bytes,:bytes
-library/nntplib,,:lines,:lines
-library/optparse,,:len,"del parser.rargs[:len(value)]"
-library/os.path,,:foo,c:foo
-library/pathlib,,:bar,">>> PureWindowsPath('c:/Windows', 'd:bar')"
-library/pathlib,,:bar,PureWindowsPath('d:bar')
-library/pathlib,,:Program,>>> PureWindowsPath('c:Program Files/').root
-library/pathlib,,:Program,>>> PureWindowsPath('c:Program Files/').anchor
-library/pdb,,:lineno,filename:lineno
-library/pickle,,:memory,"conn = sqlite3.connect("":memory:"")"
-library/posix,,`,"CFLAGS=""`getconf LFS_CFLAGS`"" OPT=""-g -O2 $CFLAGS"""
-library/pprint,,::,"'Programming Language :: Python :: 2.6',"
-library/pprint,,::,"'Programming Language :: Python :: 2.7',"
-library/pprint,,::,"'classifiers': ['Development Status :: 3 - Alpha',"
-library/pprint,,::,"'Intended Audience :: Developers',"
-library/pprint,,::,"'License :: OSI Approved :: MIT License',"
-library/pprint,,::,"'Programming Language :: Python :: 2',"
-library/pprint,,::,"'Programming Language :: Python :: 3',"
-library/pprint,,::,"'Programming Language :: Python :: 3.2',"
-library/pprint,,::,"'Programming Language :: Python :: 3.3',"
-library/pprint,,::,"'Programming Language :: Python :: 3.4',"
-library/pprint,,::,"'Topic :: Software Development :: Build Tools'],"
-library/profile,,:lineno,filename:lineno(function)
-library/pyexpat,,:elem1,<py:elem1 />
-library/pyexpat,,:py,"xmlns:py = ""http://www.python.org/ns/"">"
-library/random,,:len,new_diff = mean(combined[:len(drug)]) - mean(combined[len(drug):])
-library/readline,,:bind,"python:bind -v"
-library/readline,,:bind,"python:bind ^I rl_complete"
-library/smtplib,,:port,method must support that as well as a regular host:port
-library/socket,,::,'5aef:2b::8'
-library/socket,,:can,"return (can_id, can_dlc, data[:can_dlc])"
-library/socket,,:len,fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
-library/sqlite3,,:year,"cur.execute(""select * from lang where first_appeared=:year"", {""year"": 1972})"
-library/sqlite3,,:memory,
-library/sqlite3,,:template,"con = sqlite3.connect(""file:template.db?mode=ro"", uri=True)"
-library/sqlite3,,:nosuchdb,"con = sqlite3.connect(""file:nosuchdb.db?mode=rw"", uri=True)"
-library/sqlite3,,:mem1,"con1 = sqlite3.connect(""file:mem1?mode=memory&cache=shared"", uri=True)"
-library/sqlite3,,:mem1,"con2 = sqlite3.connect(""file:mem1?mode=memory&cache=shared"", uri=True)"
-library/ssl,,:My,"Organizational Unit Name (eg, section) []:My Group"
-library/ssl,,:My,"Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc."
-library/ssl,,:myserver,"Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com"
-library/ssl,,:MyState,State or Province Name (full name) [Some-State]:MyState
-library/ssl,,:ops,Email Address []:ops at myserver.mygroup.myorganization.com
-library/ssl,,:Some,"Locality Name (eg, city) []:Some City"
-library/ssl,,:US,Country Name (2 letter code) [AU]:US
-library/stdtypes,,:end,s[start:end]
-library/stdtypes,,::,>>> hash(v[::-2]) == hash(b'abcefg'[::-2])
-library/stdtypes,,:len,s[len(s):len(s)]
-library/stdtypes,,::,>>> y = m[::2]
-library/stdtypes,,::,>>> z = y[::-2]
-library/string,,`,"!""#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
-library/tarfile,,:bz2,
-library/tarfile,,:compression,filemode[:compression]
-library/tarfile,,:gz,
-library/tarfile,,:xz,'a:xz'
-library/tarfile,,:xz,'r:xz'
-library/tarfile,,:xz,'w:xz'
-library/time,,:mm,
-library/time,,:ss,
-library/tracemalloc,,:limit,"for index, stat in enumerate(top_stats[:limit], 1):"
-library/turtle,,::,Example::
-library/unittest,,:foo,"self.assertEqual(cm.output, ['INFO:foo:first message',"
-library/unittest,,:first,"self.assertEqual(cm.output, ['INFO:foo:first message',"
-library/unittest,,:foo,'ERROR:foo.bar:second message'])
-library/unittest,,:second,'ERROR:foo.bar:second message'])
-library/urllib.request,,:close,Connection:close
-library/urllib.request,,:port,:port
-library/urllib.request,,:lang,"xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en"" lang=""en"">\n\n<head>\n"
-library/urllib.request,,:password,"""joe:password at python.org"""
-library/urllib.parse,,:scheme,<URL:scheme://host/path>
-library/urllib.parse,,:scheme,URL:scheme://host/path
-library/uuid,,:uuid,urn:uuid:12345678-1234-5678-1234-567812345678
-library/venv,,:param,":param nodist: If true, setuptools and pip are not installed into the"
-library/venv,,:param,":param progress: If setuptools or pip are installed, the progress of the"
-library/venv,,:param,":param nopip: If true, pip is not installed into the created"
-library/venv,,:param,:param context: The information for the virtual environment
-library/xmlrpc.client,,:nil,ex:nil
-library/xmlrpc.client,,:pass,http://user:pass@host:port/path
-library/xmlrpc.client,,:pass,user:pass
-library/xmlrpc.client,,:port,http://user:pass@host:port/path
-license,,`,"``Software''), to deal in the Software without restriction, including"
-license,,`,"THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,"
-license,,`,* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
-license,,`,THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-license,,`,* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
-license,,`,THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
-license,,:zooko,mailto:zooko at zooko.com
-reference/expressions,,:index,x[index:index]
-reference/lexical_analysis,,`,$       ?       `
-reference/lexical_analysis,,:fileencoding,# vim:fileencoding=<encoding-name>
-tutorial/datastructures,,:value,It is also possible to delete a key:value
-tutorial/datastructures,,:value,key:value pairs within the braces adds initial key:value pairs
-tutorial/stdlib2,,:config,"logging.warning('Warning:config file %s not found', 'server.conf')"
-tutorial/stdlib2,,:config,WARNING:root:Warning:config file server.conf not found
-tutorial/stdlib2,,:Critical,CRITICAL:root:Critical error -- shutting down
-tutorial/stdlib2,,:Error,ERROR:root:Error occurred
-tutorial/stdlib2,,:root,CRITICAL:root:Critical error -- shutting down
-tutorial/stdlib2,,:root,ERROR:root:Error occurred
-tutorial/stdlib2,,:root,WARNING:root:Warning:config file server.conf not found
-tutorial/stdlib2,,:start,extra = data[start:start+extra_size]
-tutorial/stdlib2,,:start,"fields = struct.unpack('<IIIHH', data[start:start+16])"
-tutorial/stdlib2,,:start,filename = data[start:start+filenamesize]
-tutorial/stdlib2,,:Warning,WARNING:root:Warning:config file server.conf not found
-using/cmdline,,:errorhandler,:errorhandler
-using/cmdline,,:message,action:message:category:module:lineno
-using/cmdline,,:category,action:message:category:module:lineno
-using/cmdline,,:module,action:message:category:module:lineno
-using/cmdline,,:lineno,action:message:category:module:lineno
-using/cmdline,,::,-W ignore::DeprecationWarning
-using/unix,,:Packaging,https://en.opensuse.org/Portal:Packaging
-whatsnew/2.0,,:len,
-whatsnew/2.3,,::,
-whatsnew/2.3,,:config,
-whatsnew/2.3,,:Critical,
-whatsnew/2.3,,:Error,
-whatsnew/2.3,,:Problem,
-whatsnew/2.3,,:root,
-whatsnew/2.3,,:Warning,
-whatsnew/2.4,,::,
-whatsnew/2.4,,:System,
-whatsnew/2.5,,:memory,:memory:
-whatsnew/2.5,,:step,[start:stop:step]
-whatsnew/2.5,,:stop,[start:stop:step]
-whatsnew/2.7,,::,"ParseResult(scheme='http', netloc='[1080::8:800:200C:417A]',"
-whatsnew/2.7,,::,>>> urlparse.urlparse('http://[1080::8:800:200C:417A]/foo')
-whatsnew/2.7,,:Sunday,'2009:4:Sunday'
-whatsnew/2.7,,:Cookie,"export PYTHONWARNINGS=all,error:::Cookie:0"
-whatsnew/2.7,,::,"export PYTHONWARNINGS=all,error:::Cookie:0"
-whatsnew/3.2,,:affe,"netloc='[dead:beef:cafe:5417:affe:8FA3:deaf:feed]',"
-whatsnew/3.2,,:affe,>>> urllib.parse.urlparse('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]/foo/')
-whatsnew/3.2,,:beef,"netloc='[dead:beef:cafe:5417:affe:8FA3:deaf:feed]',"
-whatsnew/3.2,,:beef,>>> urllib.parse.urlparse('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]/foo/')
-whatsnew/3.2,,:cafe,"netloc='[dead:beef:cafe:5417:affe:8FA3:deaf:feed]',"
-whatsnew/3.2,,:cafe,>>> urllib.parse.urlparse('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]/foo/')
-whatsnew/3.2,,:deaf,"netloc='[dead:beef:cafe:5417:affe:8FA3:deaf:feed]',"
-whatsnew/3.2,,:deaf,>>> urllib.parse.urlparse('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]/foo/')
-whatsnew/3.2,,:directory,${buildout:directory}/downloads/dist
-whatsnew/3.2,,::,"$ export PYTHONWARNINGS='ignore::RuntimeWarning::,once::UnicodeWarning::'"
-whatsnew/3.2,,:feed,"netloc='[dead:beef:cafe:5417:affe:8FA3:deaf:feed]',"
-whatsnew/3.2,,:feed,>>> urllib.parse.urlparse('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]/foo/')
-whatsnew/3.2,,:gz,">>> with tarfile.open(name='myarchive.tar.gz', mode='w:gz') as tf:"
-whatsnew/3.2,,:location,zope9-location = ${zope9:location}
-whatsnew/3.2,,:prefix,zope-conf = ${custom:prefix}/etc/zope.conf
-library/re,,`,!#$%&'*+-.^_`|~:
-library/re,,`,!\#\$%\&'\*\+\-\.\^_`\|\~:
-library/tarfile,,:xz,'x:xz'
-library/warnings,,:message,action:message:category:module:line
-library/warnings,,:category,action:message:category:module:line
-library/warnings,,:module,action:message:category:module:line
-library/warnings,,:line,action:message:category:module:line
-library/warnings,,::,error::ResourceWarning
-library/warnings,,::,default::DeprecationWarning
-library/warnings,,::,default:::mymodule
-library/warnings,,:mymodule,default:::mymodule
-library/warnings,,::,error:::mymodule
-library/warnings,,:mymodule,error:::mymodule
-library/warnings,,::,ignore::DeprecationWarning
-library/warnings,,::,ignore::PendingDeprecationWarning
-library/warnings,,::,ignore::ImportWarning
-library/warnings,,::,ignore::ResourceWarning
-library/xml.etree.elementtree,,:sometag,prefix:sometag
-library/xml.etree.elementtree,,:fictional,"<actors xmlns:fictional=""http://characters.example.com"""
-library/xml.etree.elementtree,,:character,<fictional:character>Lancelot</fictional:character>
-library/xml.etree.elementtree,,:character,<fictional:character>Archie Leach</fictional:character>
-library/xml.etree.elementtree,,:character,<fictional:character>Sir Robin</fictional:character>
-library/xml.etree.elementtree,,:character,<fictional:character>Gunther</fictional:character>
-library/xml.etree.elementtree,,:character,<fictional:character>Commander Clement</fictional:character>
-library/xml.etree.elementtree,,:actor,"for actor in root.findall('real_person:actor', ns):"
-library/xml.etree.elementtree,,:name,"name = actor.find('real_person:name', ns)"
-library/xml.etree.elementtree,,:character,"for char in actor.findall('role:character', ns):"
-library/xml.etree.elementtree,,:xi,<document xmlns:xi="http://www.w3.org/2001/XInclude">
-library/xml.etree.elementtree,,:include,  <xi:include href="source.xml" parse="xml" />
-library/xml.etree.elementtree,,:include,  Copyright (c) <xi:include href="year.txt" parse="text" />.
-library/zipapp,,:main,"$ python -m zipapp myapp -m ""myapp:main"""
-library/zipapp,,:fn,"pkg.mod:fn"
-library/zipapp,,:callable,"pkg.module:callable"
-library/stdtypes,,::,>>> m[::2].tolist()
-whatsnew/3.5,,:root,'WARNING:root:warning\n'
-whatsnew/3.5,,:warning,'WARNING:root:warning\n'
-whatsnew/3.5,,::,>>> addr6 = ipaddress.IPv6Address('::1')
-whatsnew/3.5,,:root,ERROR:root:exception
-whatsnew/3.5,,:exception,ERROR:root:exception
-whatsnew/changelog,,`,'`'
-whatsnew/changelog,,:end,str[start:end]
-library/binascii,,`,'`'
-library/uu,,`,'`'
-whatsnew/3.7,,`,'`'
-whatsnew/3.7,,::,error::BytesWarning
-whatsnew/changelog,,::,error::BytesWarning
-whatsnew/changelog,,::,default::BytesWarning
-whatsnew/changelog,,::,default::DeprecationWarning
-library/importlib.metadata,,:main,"EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')"
-library/importlib.metadata,,`,loading the metadata for packages for the indicated ``context``.
-library/re,,`,"`"
-library/typing,,`,# Type of ``val`` is narrowed to ``str``
-library/typing,,`,"# Else, type of ``val`` is narrowed to ``float``."
-library/typing,,`,# Type of ``val`` is narrowed to ``list[str]``.
-library/typing,,`,# Type of ``val`` remains as ``list[object]``.
-library/tkinter,,::,ttk::frame .frm -padding 10
-library/tkinter,,::,"grid [ttk::label .frm.lbl -text ""Hello World!""] -column 0 -row 0"
-library/tkinter,,::,"grid [ttk::button .frm.btn -text ""Quit"" -command ""destroy .""] -column 1 -row 0"
-library/tkinter,,::,ttk::frame
-library/tkinter,,::,ttk::button
-library/tkinter,,::,ttk::widget
-reference/compound_stmts,,:exc,subclass of :exc:`BaseExceptionGroup`. It is not possible to mix except
-reference/compound_stmts,,`,subclass of :exc:`BaseExceptionGroup`. It is not possible to mix except
-reference/compound_stmts,,:keyword,"and except* in the same :keyword:`try`. :keyword:`break`,"
-reference/compound_stmts,,`,"and except* in the same :keyword:`try`. :keyword:`break`,"
-reference/compound_stmts,,:keyword,:keyword:`continue` and :keyword:`return` cannot appear in an except*
-reference/compound_stmts,,`,:keyword:`continue` and :keyword:`return` cannot appear in an except*
-whatsnew/changelog,,:CON,": os.path.abspath(“C:CON”) is now fixed to return “\.CON”, not"
-whatsnew/changelog,,::,Lib/email/mime/nonmultipart.py::MIMENonMultipart
-library/typing,,`,"assert_type(name, str)  # OK, inferred type of `name` is `str`"
-library/typing,,`,# after which we hope the inferred type will be `int`
-whatsnew/changelog,,:company,-V:company/tag
-library/typing,,`,# are located in the `typing_extensions` backports package.
-library/dis,490,:TOS,TOS = TOS2[TOS1:TOS]
-library/dis,497,:TOS,TOS2[TOS1:TOS] = TOS3
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index f8122ed1dc44..ebc490691e30 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -468,6 +468,11 @@ Removed
 * ``importlib.util.set_package`` has been removed.
   (Contributed by Brett Cannon in :gh:`65961`.)
 
+* Removed the ``suspicious`` rule from the documentation Makefile, and
+  removed ``Doc/tools/rstlint.py``, both in favor of `sphinx-lint
+  <https://github.com/sphinx-contrib/sphinx-lint>`_.
+  (Contributed by Julien Palard in :gh:`98179`.)
+
 
 Porting to Python 3.12
 ======================
diff --git a/Misc/NEWS.d/next/Documentation/2022-10-11-09-40-50.gh-issue-86404.dEAb8W.rst b/Misc/NEWS.d/next/Documentation/2022-10-11-09-40-50.gh-issue-86404.dEAb8W.rst
new file mode 100644
index 000000000000..de7b09216711
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2022-10-11-09-40-50.gh-issue-86404.dEAb8W.rst
@@ -0,0 +1,3 @@
+Deprecated tools ``make suspicious`` and ``rstlint.py`` are now removed.
+They have been replaced by `spinx-lint
+<https://pypi.org/project/sphinx-lint/>`_.



More information about the Python-checkins mailing list