Skip to content

standalone-comment-formatting (PCF000)

Added in 1.1.0 Usually fix By default Module and Fragment

Part of the PCF rule category.

View source · View documentation source · Search issues

Fix is usually available.

What it does

Checks ordinary standalone comments for canonical marker spacing, trailing whitespace, and wrapping at line-length. A standalone comment is a physical comment line that is not trailing code on the same line.

Canonical ordinary output uses one syntactic #, one following space for non-empty content, and normalized content without surrounding whitespace except for a recognized hard-break suffix. Additional hashes are content and are retained, so ##Heading becomes # #Heading unless heading preservation protects that line. Before run formatting, a regular standalone comment with a nonempty payload containing only ASCII space, tab, or form feed is normalized to a bare #. An already bare #, multi-hash separators, and comments containing other whitespace remain unchanged. Long words and hyphenated words are never split.

PCF000 operates on physical runs of consecutive, same-indent, regular, non-empty standalone comments. Empty comments, hash-only separators, protected comments, blank lines, code lines, and indentation changes end a run. By default, each ordinary physical prose line is normalized and wrapped independently; consecutive ordinary lines are joined into paragraphs only when comment-join-standalone-lines is enabled. Joined paragraphs do not cross standalone colon-ended label lines, while lowercase colon-ended lines can complete unfinished preceding prose.

Whitespace-only normalization happens before these runs are formatted, but empty and hash-only comments remain run boundaries. The narrow replacement preserves indentation, the original line ending, final-newline state, and all surrounding source. PCF000 alone owns this standalone normalization; PCF001 remains responsible for ordinary trailing-comment whitespace.

Within a run, PCF000 first identifies enabled preserved structures, then applies enabled code detectors to the remaining semantic text, and finally formats the remaining list items, block quotes, paragraphs, or physical lines. Lines inside an explicitly preserved structure are excluded from code detection, so code in a fenced or directive region does not prevent adjacent prose from formatting. Valid reStructuredText directive types use Unicode word-character components separated by isolated -, _, +, ., or : characters, so namespaced types such as py:function are preserved. One optional ASCII space may precede the two-colon delimiter, which must be followed by whitespace or the end of the line; text glued directly to it remains ordinary prose. If any code detector matches another non-preserved line or multiline candidate, the entire physical standalone run remains unchanged.

When comment-task-marker-mode is no-wrap, recognized task markers such as TODO:, FIXME:, and HACK: are formatted as independent units but are not wrapped. When it is hanging, recognized task-marker units use hanging continuation indentation. Existing continuation lines belong to the same unit only when they have the same base indentation and exactly enough spaces after the comment marker to align with the task-marker payload. Code-like task-marker payloads in hanging mode are normalized but not wrapped according to the enabled code-detection settings. Set comment-task-marker-mode to none for no task-marker-specific handling.

When indentation leaves no positive wrapping width, PCF000 still canonicalizes spacing but keeps the content on one line. It preserves the source's final-newline state and untouched mixed line endings. Destination-bearing tokens remain indivisible regardless of url-aware-wrapping; enabling the setting allows balanced rather than greedy line selection around them.

PCF000 always treats the same conservative inline-markup subset as PDF101 as indivisible: non-empty same-line backtick spans with equal-length delimiter runs; inline and full or collapsed reference-style Markdown links and images; CommonMark-style URI and email autolinks; and boundary-valid reStructuredText interpreted text, roles, phrase or anonymous references, embedded targets, inline literals, and substitutions. It supports escaped or nested link labels, empty inline components, link titles delimited by "...", '...', or (...), destination parentheses nested up to three levels, and reStructuredText role names made from alphanumeric components separated by isolated -, _, +, :, or . characters. Emphasis, raw HTML or XML, shortcut references, definitions, and multiline constructs remain ordinary or protected structure text. A line-leading run of at least three backticks or tildes is ordinary fence-like text rather than ambiguous inline markup when comment-preserve-code-fences is disabled, including when an info string follows the opener. url-aware-wrapping controls balanced line selection around destination-bearing constructs, not recognition.

Space hard breaks of two or more final ASCII spaces and odd terminal backslash hard breaks are preserved when the physical comment line has a newline. For a space break, tabs immediately before the final space run are removed while preceding spaces remain. PCF000 never joins across either hard break and applies the current unit's continuation prefix after the boundary, so list and task markers are not repeated while block-quote prefixes remain present. A suffix on the final physical line without a newline is ordinary trailing whitespace rather than a hard break.

Strong evidence of incomplete or over-bounded markup makes the current formatter unit ambiguous. Generic unmatched delimiters without a supported-markup prefix remain ordinary prose. The protected formatter unit is one physical line when joining is disabled, or the complete paragraph, list, quote, or task-marker unit when joining is enabled. PCF000 reports the unit only when canonical output would differ and does not reflow its semantic body. It may still attach a marker-only partial fix that inserts canonical separation after # while preserving every body character and original inter-line ending; a later pass then reports the stable remaining ambiguity without a fix.

Why is this useful?

The conservative ordinary-prose default corrects clear spacing and line-length issues without merging separately authored lines. The structure and protection settings let projects reflow common comment prose while keeping doctests, code fences, tables, directives, headings, disabled code, and tool directives stable.

Ruff compatibility

Ruff can report overlong comment lines and general whitespace issues, but does not provide equivalent configurable standalone-comment reflow. PCF000 complements those checks and should not be used to rewrite Ruff, type-checker, formatter, or security directives, which the PCF category protects.

Examples

The canonical case normalizes marker spacing and wraps an ordinary standalone comment:

line-length = 40
#bad spacing
# This standalone comment has enough words to wrap neatly around the configured limit.
# bad spacing
# This standalone comment has enough
# words to wrap neatly around the
# configured limit.

A regular standalone comment containing only ASCII horizontal whitespace becomes a bare marker without disturbing indentation or surrounding code:

if ready:
    #   
    commit()
if ready:
    #
    commit()

Ordinary physical prose lines are wrapped independently by default rather than joined with the next line:

line-length = 40
# First physical line with enough words to require wrapping by itself.
# Second physical line stays separate.
# First physical line with enough words
# to require wrapping by itself.
# Second physical line stays separate.

When standalone-line joining is enabled, consecutive ordinary prose lines in one run become one paragraph before wrapping:

line-length = 35
comment-join-standalone-lines = true
# First prose line.
# Second prose line with more words.
# First prose line. Second prose
# line with more words.

Colon-ended label lines stop standalone-line joining before adjacent prose:

line-length = 80
comment-join-standalone-lines = true
# Summary.
# Accepted values:
# pending, active, disabled.

A lowercase colon-ended line can still complete unfinished preceding prose, but following prose starts a separate unit:

line-length = 80
comment-join-standalone-lines = true
# This sentence has been split
# with a colon:
# following prose continues here.
# This sentence has been split with a colon:
# following prose continues here.

List-item and block-quote formatting retain structural prefixes and align wrapped lines:

line-length = 34
# - A list item with enough words to wrap using hanging indentation.
#   Its continuation is joined to the same item.
# > A quoted paragraph with enough words
# > to wrap while retaining its prefix.
# - A list item with enough words
#   to wrap using hanging
#   indentation. Its continuation
#   is joined to the same item.
# > A quoted paragraph with enough
# > words to wrap while retaining
# > its prefix.

Task-marker comments use no-wrap mode by default. The marker spacing is normalized, but the payload is not wrapped even when it exceeds line-length:

line-length = 30
#TODO: alpha beta gamma delta epsilon zeta eta theta
# TODO: alpha beta gamma delta epsilon zeta eta theta

Set comment-task-marker-mode to hanging to reflow task-marker payloads with marker-width hanging indentation so continuation lines remain visually associated with the marker:

line-length = 30
comment-task-marker-mode = "hanging"
#TODO: alpha beta gamma delta epsilon zeta eta theta
# TODO: alpha beta gamma delta
#       epsilon zeta eta theta

Set comment-task-marker-mode to none to treat task markers as ordinary comment text, so wrapping uses ordinary continuation indentation:

line-length = 30
comment-task-marker-mode = "none"
#TODO: alpha beta gamma delta epsilon zeta eta theta
# TODO: alpha beta gamma delta
# epsilon zeta eta theta

Preserved regions stay unchanged while adjacent prose still formats:

line-length = 40
# Prose before a fence with enough words that it must wrap onto another line.
# ```python
#     value = compute()
# ```
# Prose before a fence with enough words
# that it must wrap onto another line.
# ```python
#     value = compute()
# ```

Recognized inline markup remains indivisible, and a backslash hard break prevents paragraph joining:

line-length = 32
comment-join-standalone-lines = true
# Read [the complete label](target) before continuing.\
# Start a new semantic segment here.
# Read
# [the complete label](target)
# before continuing.\
# Start a new semantic segment
# here.

Ambiguous supported-markup prefixes are not reflowed. PCF000 can still fix marker spacing without changing the semantic body when that is the only canonical difference, while an independently overlong canonical line retains an unfixable finding until the ambiguity is resolved:

line-length = 28
#Before [label](missing.
# Second [label](missing destination words that require wrapping.
# Before [label](missing.
# Second [label](missing destination words that require wrapping.
PCF000: Line 2: Standalone comment needs formatting

Enabled statement detection protects the whole consecutive standalone run, including prose in that run:

line-length = 30
# value = compute()
# prose that would otherwise wrap onto another line

Standalone-line joining does not cross code, blank lines, protected comments, hash-only separators, or indentation boundaries:

line-length = 80
comment-join-standalone-lines = true
# first line
value = 1
# second line

# third line
# noqa
# fourth line
###
# fifth line
if value:
    # sixth line
    pass
# seventh line

Namespaced reStructuredText directives and their indented bodies are preserved. A one-colon introducer is not a valid directive: its colon-ended opener remains a structural boundary, but its following text is ordinary prose that can be reflowed:

line-length = 24
comment-detect-statements = false
# .. py:function :: signature
#    preserved directive body that may exceed the line length
# .. note:
#    ordinary malformed directive body words
# .. py:function :: signature
#    preserved directive body that may exceed the line length
# .. note:
# ordinary malformed
# directive body words

Options