Diagnostics¶
Simplemark never refuses to render. Everything below is reported by check or simplemark --check, and nothing stops output being produced.
There are three severities, and the split is the whole point:
| Severity | Meaning | Reported by default | Fails --check |
|---|---|---|---|
| error | Simplemark itself is ambiguous, or the author almost certainly erred | yes | yes |
| commonmark | A Markdown renderer would read this differently | yes | no |
| legacy | reStructuredText, or an older docstring convention | no: --compat only |
no |
Legacy notes are migration advice, not problems. A codebase that has never seen reStructuredText should never hear about them; a codebase migrating away from it wants every one. That is what the two modes are for, and they are the same parser with a filter on the output.
--strict promotes every reported diagnostic to a failure. It does not change what is reported.
Errors¶
Always reported.
E001: unclosed code span¶
A backtick run with no matching close.
python - <<'EOF'
import simplemark
for d in simplemark.check("A command `foo is dispatched."):
print(d)
EOF
Fix: close it, or escape the backtick as \`.
E002: doctest is not indented¶
A paragraph beginning with >>> at column 0. It would be reflowed as prose, destroying the example.
Fix: indent by four. 206 standard library docstrings have this: mostly module docstrings whose doctests sit flush left, or are indented by only two.
CommonMark notes¶
Always reported. These say: a Markdown renderer would disagree with simplemark here. If your docstrings are read on GitHub or in an IDE hover, they matter.
CM001: delimiters CommonMark reads as emphasis¶
The important one, and the reason for simplemark's single narrowed inline rule.
python - <<'EOF'
import simplemark
for d in simplemark.check("x = xc*10**xe and y = yc*10**ye, compute x**y."):
print(d)
EOF
Simplemark leaves the arithmetic alone, because emphasis cannot start inside a word. CommonMark pairs the ** runs and puts <strong> through the middle of the formula. Same for __init__ and __del__ in one paragraph.
Fix: nothing is needed for simplemark. To render correctly on GitHub too, wrap the expression in a code span: `x**y`.
This note is deliberately conservative: it fires when the text could be mangled, without embedding a CommonMark parser to know for certain. _pydecimal.py produces a lot of them, and that is correct: those docstrings already render wrong on GitHub today.
CM002: a marker that would interrupt the paragraph¶
python - <<'EOF'
import simplemark
text = "My favourite number is probably the number\n1. It is the smallest natural number."
for d in simplemark.check(text):
print(d)
EOF
Simplemark keeps it as one paragraph, because no block may interrupt a paragraph. CommonMark starts an ordered list.
Fix: reword, or escape the marker: 1\. It is….
Legacy notes¶
Reported only with --compat. These say: this looks like reStructuredText, or a convention that predates the format. Run them once when adopting simplemark on an existing codebase; ignore them forever after.
python - <<'EOF'
import simplemark
legacy = "Example::\n\nSee :mod:`typing` and\n\n.. note:: careful\n"
print("default:", simplemark.check(legacy))
for d in simplemark.check(legacy, compat=True):
print("compat: ", d)
EOF
default: []
compat: 1:8: L001 trailing '::' is reStructuredText; write ':'
compat: 3:5: L002 ':role:' is reStructuredText
compat: 5:1: L003 '..' directive or comment is reStructuredText
L001: trailing ::¶
In reStructuredText, :: at the end of a paragraph introduces a literal block and renders as a single colon. Simplemark does not need it (a blank line and four spaces is enough), so both colons are shown.
Fix: write Example:. 64 standard library docstrings have this.
L002: :role:¶
:mod:`typing`, :class:`Foo` and friends. Usually copied from module documentation without cleanup; this is one of the two problems that started this project.
Fix: `typing`. 32 standard library docstrings.
L003: .. directive or comment¶
.. note::, .. versionadded::, and reST comments.
Fix: ordinary prose, or a bullet list. 4 standard library docstrings.
L004: definition-list shape¶
A short line followed by an indented body. reStructuredText reads it as a definition list; simplemark reads one paragraph.
python - <<'EOF'
import simplemark
for d in simplemark.check("module_relative\n If true, paths are module-relative.\n", compat=True):
print(d)
EOF
The most-reported note, 386 docstrings, and most are false alarms in spirit: Args: headers and hand-aligned prose that merely look like definition lists. See why the construct was left out.
Fix: if you meant a list, use bullets. If you did not, ignore it.
L005: legacy `quoted' text¶
A code span whose content looks like the old GNU quoting style, `foo'.
python - <<'EOF'
import simplemark
for d in simplemark.check("A command `foo' is dispatched to `do_foo'.", compat=True):
print(d)
EOF
This one is worth taking seriously: with an even number of such quotes the backticks pair with each other, swallowing the prose between them into a single code span. The output is silently wrong. See the worked example.
Fix: `foo`. 28 standard library docstrings.
What the corpus produces¶
Every docstring in the Python 3.12 standard library, 7272 of them:
| docstrings | share | |
|---|---|---|
| clean in default mode | 6762 | 93.0% |
| containing an error | 260 | 3.6% |
| Code | Severity | docstrings | share |
|---|---|---|---|
L004 |
legacy | 386 | 5.3% |
CM001 |
commonmark | 219 | 3.0% |
E002 |
error | 206 | 2.8% |
L001 |
legacy | 64 | 0.9% |
E001 |
error | 54 | 0.7% |
CM002 |
commonmark | 48 | 0.7% |
L002 |
legacy | 32 | 0.4% |
L005 |
legacy | 28 | 0.4% |
L003 |
legacy | 4 | 0.1% |
Regenerate with uv run python tools/corpus_report.py.