Skip to content

comment-directive-normalization (PCF003)

Added in 1.0.0 Always fix By default

Part of the PCF rule category.

View source · View documentation source · Search issues

Fix is always available.

What it does

Normalizes safe marker spacing and machine-readable syntax in recognized directive comments. For trailing directives, the code before the directive is preserved exactly so PCF002 remains the owner of code-to-# delimiter spacing. For standalone directives, indentation is preserved. In both cases, directive content starts after one marker space.

PCF003 handles comments already classified as type comments or known tool directives by the PCF category. It normalizes recognized directive heads to lowercase, removes space before directive introducer colons, adds one space after directive colons where a value follows, and normalizes safe comma-separated lists for type: ignore[...], ty: ignore[...], noqa, pydocfmt: noqa, pydocfmt: ignore[...], pydocfmt: file-ignore[...], ruff: noqa, flake8: noqa, pylint enable/disable directives, PyCharm noinspection directives, and Ruff bracket directives such as ruff: ignore[...], ruff: disable[...], ruff: enable[...], and ruff: file-ignore[...]. Ruff-prefixed isort action comments such as ruff: isort: skip_file are normalized as nested directives. PyCharm language= injection comments normalize only the directive head and preserve the language ID and optional prefix=/suffix= payload. PyCharm @formatter:on and @formatter:off marker comments are normalized as individual directive lines only; they do not disable pydocformatter for a range of code.

For pydocfmt directives, this rule normalizes only the supported suppression forms: pydocfmt: noqa, pydocfmt: ignore[...], and pydocfmt: file-ignore[...]. It does not add support for range-style pydocfmt: disable[...] or pydocfmt: enable[...] comments.

Unknown or ambiguous payload text is preserved after safe prefix cleanup. Ordinary comments remain outside this rule.

Why is this useful?

Tool directives are sensitive, so ordinary comment reflow should not rewrite them. A separate comment-directive-normalization rule lets projects canonicalize recognized directive syntax without enabling broader trailing-comment extraction or prose formatting.

Ruff compatibility

Ruff may normalize some comment spacing through its formatter, but pydocformatter keeps this as an independently selectable PCF rule so directive syntax normalization can be controlled separately from ordinary comment formatting.

Examples

Compact known directives get canonical marker spacing and directive-head spelling:

value = compute()#noqa
other = compute() #   nosec
typed = compute() #TYPE : ignore[assignment]
ty_typed = compute() #TY : ignore[invalid-argument-type]
value = compute()# noqa
other = compute() # nosec
typed = compute() # type: ignore[assignment]
ty_typed = compute() # ty: ignore[invalid-argument-type]

Standalone directives are normalized without changing indentation:

#ruff: noqa
#PYDOCFMT : ignore [ pdf101, pcf001, ]
#ruff:ignore[F401,E501]
    # ruff : isort : SKIP_FILE
    #fmt : off
#noinspection PyTypeChecker,PyUnresolvedReferences
# LANGUAGE = SQL prefix=SELECT suffix=FROM table
# @formatter : OFF
#   pylint : disable-next = missing-docstring,unused-argument
# ruff: noqa
# pydocfmt: ignore[PDF101, PCF001]
# ruff: ignore[F401, E501]
    # ruff: isort: skip_file
    # fmt: off
# noinspection PyTypeChecker, PyUnresolvedReferences
# language=SQL prefix=SELECT suffix=FROM table
# @formatter:off
# pylint: disable-next=missing-docstring, unused-argument

Pydocfmt suppression directives use pydocfmt selector casing and safe list normalization:

#PYDOCFMT : noqa : pdf101,pcf001
#PYDOCFMT : ignore [ pdf101, pcf001, ]  # reason
#PYDOCFMT : file-ignore [ pdf, pcf006, ]
#PYDOCFMT : disable [ pdf101 ]
# pydocfmt: noqa: PDF101, PCF001
# pydocfmt: ignore[PDF101, PCF001]  # reason
# pydocfmt: file-ignore[PDF, PCF006]
# PYDOCFMT : disable [ pdf101 ]

Safe machine-readable payloads are normalized, while additional # payload text is preserved:

typed = compute()#TYPE : ignore[assignment,arg-type]
mixed = compute()#TYPE : ignore[arg-type,ty:invalid-argument-type]
ty_typed = compute()#TY : ignore[invalid-argument-type,unresolved-import]
ruffed = compute() # ruff : noqa : ruf100, f401
ruff_range = compute()#ruff : enable [ E741, F841, ]  # local reason
pycharm = compute()#NoInspection PyTypeChecker,PyUnresolvedReferences
linted = compute()#   PyLiNt : disable = missing-docstring,unused-argument  # local reason
typed = compute()# type: ignore[assignment, arg-type]
mixed = compute()# type: ignore[arg-type, ty:invalid-argument-type]
ty_typed = compute()# ty: ignore[invalid-argument-type, unresolved-import]
ruffed = compute() # ruff: noqa: RUF100, F401
ruff_range = compute()# ruff: enable[E741, F841]  # local reason
pycharm = compute()# noinspection PyTypeChecker, PyUnresolvedReferences
linted = compute()# pylint: disable=missing-docstring, unused-argument  # local reason

Directive normalization is independent of syntax-aware trailing-comment extraction. PCF003 still normalizes directives attached to decorators, arguments, and compound statement headers, but it does not move or wrap them:

line-length = 8
@decorator#noqa
def function(
    value,# type: ignore[arg-type]
):
    if enabled:#nosec
        pass
@decorator# noqa
def function(
    value,# type: ignore[arg-type]
):
    if enabled:# nosec
        pass

Unknown directive-like comments are handled by ordinary comment formatting, not PCF003:

value = compute() #not-a-known-directive
# unknown-directive

Options

None.