Rule selection specification¶
This document specifies how pydocfmt discovers rule definitions and resolves rule-selection, per-file-ignore, and fixability settings, and how rule selection information is exposed via the CLI.
Ruff compatibility deltas¶
- D1: Rule catalog and selector prefixes.
pydocformatter has its own rule catalog and accepts either code selectors with complete pydocformatter prefixes such as
PDForPCF, or exact canonical rule names such asdocstring-reflow. The selectorPdoes not matchPDForPCFrules, and names do not support prefix matching. - D2: Supported settings.
pydocformatter implements
select,ignore,extend-select,per-file-ignores,extend-per-file-ignores,fixable,unfixable, andextend-fixable, and adds the pydocformatter-specificrequire-explicitsetting. Ruff settings that have no pydocformatter equivalent, such as deprecatedextend-ignore, preview-rule settings, fix-safety settings, andexternal, are outside this compatibility surface. - D3: Selector errors. pydocformatter records selector validation problems as operational errors so one run can report all rule-selection issues together. Ruff may fail earlier while parsing or resolving its own configuration.
- D4: Fixability selector validation.
pydocformatter reports an operational error when a
fixableorextend-fixableselector matches only rules whoseFixAvailabilityisNever. Ruff accepts such selectors and simply has no fix to apply for those rules. - D5: Command-line comma-list whitespace.
pydocformatter strips whitespace around entries in dedicated command-line comma-list options, so
--select "PDF200, PDF110"is accepted asPDF200andPDF110. Ruff rejects whitespace-containing selector entries such asF841.
Rule definitions¶
Rules and rule categories live under pydocformatter.rules.definitions. Importing pydocformatter.rules.collection imports each category module before its rule modules and builds the default RULE_COLLECTION.
Each rule category is a RuleCategoryBase subclass in a module named after its rule-code prefix, with adjacent category documentation:
src/pydocformatter/rules/definitions/PDF/PDF.py
src/pydocformatter/rules/definitions/PDF/PDF.md
The class is also named after the prefix and defines RuleCategoryMetadata containing its prefix, user-facing name, and optional url. RuleCategoryBase rejects missing or invalid metadata at class definition time and provides ordered_rules() and ordered_code_class_map() views.
Each implemented rule is collected as one RuleBase subclass, normally in a module grouped by prefix:
src/pydocformatter/rules/definitions/PDF/PDF101_docstring_reflow.py
Rule classes register with their category through @register_rule_to(PDF). Rule selection uses the rule's meta class attribute, a RuleMetadata record containing:
code: ARuleCode, such asPDF101.name: A stable machine-readable name, such asdocstring-reflow.message: The default diagnostic message shown in rule listings and findings.fix_availability: AFixAvailabilityvalue describing whether automatic fixes areAlways,Usually,Sometimes, orNeveravailable at the rule level.Alwaysmeans every finding has a fix.Usuallymeans every semantic violation kind has a correction, although instance-specific safety can prevent a fix.Sometimesmeans at least one semantic violation kind is intentionally diagnostic-only while another has a fix.Nevermeans the rule is diagnostic-only.stable_since: The pydocformatter version in which the rule became stable.setting_effects: Immutable metadata mapping resolved setting fields and triggering values toIgnoredorDisabledselection effects.incompatible_with: An immutable tuple ofRuleCodevalues for rules that cannot be selected together with this rule.check_kind: ARuleCheckKindvalue used to distinguish standard source-check rules from suppression-audit rules during rule collection and execution ordering.source_contexts: A non-empty immutable tuple of uniqueSourceContextvalues declaring whether the rule is semantically applicable to complete modules, standalone fragments, or both. Valid input is normalized to enum declaration order, so equivalent context sets have identical metadata and display output regardless of caller order.allows_directive_self_suppression: Whether a local bracket selector may suppress a finding reported on its own directive line. This is false by default and enabled only for representation policies whose finding is about that selector.
RuleBase rejects subclasses without meta, or with non-RuleMetadata metadata, at class definition time. RuleMetadata rejects non-RuleCode codes, non-FixAvailability fix availability values, non-RuleCheckKind check kinds, empty, duplicate, or invalid source-context tuples, non-Boolean self-suppression capabilities, invalid or empty names, empty messages or stable versions, malformed setting-effect records, malformed incompatibility tuples, and duplicate incompatible codes. It canonicalizes valid source-context tuples before collection validation and public display. Collection construction rejects duplicate names across the complete catalog, exact rule codes that would also act as broad selectors for another collected rule, and mutually incompatible rules with different source-context applicability. Category registration rejects rules whose code prefix differs from the category prefix and rejects duplicate rule codes from different classes.
Rule codes¶
Rule codes are the canonical lookup keys.
A valid rule code:
- Uses one or more uppercase ASCII letters followed by one or more digits.
- Does not start with the reserved selector tag
ALL. - Is parsed into
prefix,number_str, and numericnumber.
Examples:
PDF001is valid with prefixPDF, number string001, and number1.PCF100is valid with prefixPCF, number string100, and number100.bad,001, andALL001are invalid.
Rule names¶
Every rule also has one globally unique canonical name. Names match [a-z][a-z0-9]*(?:-[a-z0-9]+)*, but their uppercase spelling must not be a valid code selector. Names are case-sensitive in configuration and select exactly one registered rule. For example, docstring-reflow selects PDF101 with the same strength as the full code. Names have no prefix, wildcard, fuzzy-match, or alias forms.
Code-shaped spellings such as pdf101, pdf, all, and noop are therefore invalid rule names, including when the current catalog has no matching code prefix. Codes remain uppercase and names remain lowercase.
Collection¶
RuleCollection stores category classes in deterministic prefix order in categories, exposes the same order through category_class, and flattens their rules into deterministic rule-code order in rules and rule_class. rule_class_by_name indexes the same classes by canonical name while preserving collection order.
Collection behavior:
- Every collected object must inherit from
RuleCategoryBase; rules cannot be registered directly with a registry or collection. - Duplicate category prefixes are rejected unless they refer to the same category class.
- Categories expose their rules in rule-code order and allow idempotent re-registration of the same rule class.
matching_rules(selector)returns matching rule classes in collection order.matching_rules_exist(selector)returns whether any collected rule matches the selector.resolve_selector(text)distinguishes registered names, valid code selectors, unknown canonical-looking names, and invalid text.- Every exact collected rule code must select only its own rule; catalogs containing codes such as
PDF1andPDF10together are rejected becausePDF1would be both exact and broad. - Incompatibility declarations must reference collected rules, cannot reference the declaring rule itself, and must be declared mutually by both rules.
Built-in categories use the default registry through @register_rule_category, and their rules use @register_rule_to(category). Tests and custom rule packages can use an isolated RuleRegistry with register_rule_category_to(registry), pass that registry to import_package_rule_categories, then call RuleCollection.from_registry(registry).
Definition package loading validates the complete built-in layout: the definitions package contains only category packages; each category package is flat and contains a matching prefix-named category module and class; each rule module contains exactly one registered local rule whose code matches its filename and category; registered rules originate from and have modules in their category package; and every category and rule has adjacent Markdown documentation with no orphan Markdown files.
With default settings, a custom empty catalog resolves to an empty active ruleset without errors.
Selector grammar¶
Rule selectors are:
ALL: Matches all collected rules.- Full rule code:
PDF101. - Complete prefix:
PDF. - Complete prefix plus leading digits:
PDF10, matching rule codes whose numeric string starts with10. - Exact canonical rule name:
docstring-reflow, matching onlyPDF101.
Selectors are case-sensitive. Code selectors must use complete rule prefixes, so P does not match rules with prefix PDF. Names must use their exact canonical lowercase spelling.
Selectors outside both grammars are operational errors. A canonical-looking name absent from the collection is reported specifically as an unknown rule name. Code selectors that match no collected rule are unknown selectors, except ALL, which may match an empty collection without error. Invalid or unknown selectors resolve to no rules and resolution continues.
Successful names are reduced immediately to the corresponding concrete RuleCode. A code and its name in the same selector group therefore contribute one semantic rule match while preserving exact-match evidence. Broad selectors remain distinct, while collection validation guarantees that a collected full code is never also broad for another rule. Final selection and persistent cache identity use the resolved ordered rule codes, so equivalent code and name spellings do not create distinct analysis identities.
Source priority and specificity¶
Selection conflicts are resolved first by configuration source priority, then by selector specificity. Selector order inside one setting is not significant.
Source priority values:
- Defaults have priority
0. - Auto-discovered or explicit config-file settings have priority
1. - Inline
--config "<KEY> = <VALUE>"settings have priority2. - Dedicated command-line options have priority
3. - In-process field overrides used by tests and callers have priority
4.
Specificity values:
ALLhas specificity0.- A code selector other than
ALLhas specificity equal to its code-selector length. - An exact name resolves to its full rule code and therefore has full-code specificity.
Resolution rule:
- The strongest matching enabling selector is compared with the strongest matching disabling selector.
- A selector with a higher source priority beats a selector with a lower source priority, even if the lower-priority selector is more specific.
- When both sides have the same source priority, the more specific selector wins.
- The disabling side wins equal-priority, equal-specificity ties.
- Lower-priority adjustment settings that do not participate in the final decision are still validated and can still produce operational errors.
Examples:
select = ["PDF14"]andignore = ["PDF1"]enablesPDF142.select = ["PDF1"]andignore = ["PDF14"]disablesPDF142but can still enablePDF150.select = ["PDF14"]andignore = ["PDF14"]disablesPDF142.- With default
select = ["ALL"],extend-select = ["PDF14"]andignore = ["PDF1"]enablesPDF142but disablesPDF150. - A command-line
--select PDF1enablesPDF142even when a lower-priority config file hasignore = ["PDF142"]. - A command-line
--ignore PDF1disablesPDF142even when a lower-priority config file hasselect = ["PDF142"].
Setting effects¶
After normal select and ignore precedence is resolved, each selected rule's metadata is evaluated against the resolved CheckSettings values. Effects from all declared setting fields are combined:
Disabledtakes precedence overIgnored.Ignoredremoves a rule selected throughALL, a category prefix, or a partial selector, but any participating exact rule-code or rule-name selector restores it, including one retained alongside a higher-priority broadextend-select.Disabledalways removes a rule, including one selected by exact code or name.- Convention
Ignoredeffects can encode a profile's choice among antagonistic rules, including a choice to broadly select neither alternative. - Configured
ignoreselectors and matching per-file ignores still suppress an exactly restored ignored rule. - Setting effects do not change effective fixability.
- A metadata field name that is not present on
CheckSettingsis a programming error identifying the rule and unknown field.
Source-context applicability¶
After selector resolution and setting effects, pydocfmt removes rules whose source_contexts metadata does not contain the effective source-context. Applicability is a semantic constraint rather than a preference: exact code or canonical-name selection cannot restore an inapplicable rule.
Rules support both module and fragment by default. Complete-module ownership, public/private module identity, and policies requiring documentation for definitions in a real module make some rules inapplicable to fragments.
The following exhaustive list identifies the built-in module-only rules:
- PDF510, PDF511, PDF513, PDF515, PDF517, PDF520, PDF521, PDF524, PDF525, PDF529
- PDF600, PDF601, PDF602, PDF603, PDF604, PDF605, PDF606, PDF607, PDF608, PDF609, PDF610, PDF611, PDF612, PDF613, PDF614, PDF615
Some rules that could plausibly require a complete module remain applicable to fragments.
The following non-exhaustive list highlights built-in rules that remain applicable to fragments:
- PDF528, PDF616, PDF716, PDF717, PDF718, PDF719
A class defined in an example has a coherent local attribute inventory, while checks concerning existing documentation, signatures, and declarations do not require the fragment to be an importable module.
Global rule selection¶
Defaults:
select = ["ALL"]ignore = []extend-select = []require-explicitdefaults to exact selectors in the runtimeDEFAULT_REQUIRE_EXPLICITsetting.
Global enabled rules are resolved per rule:
- Use the resolved
selectvalue as the active rule-selection basis. The source priority of thisselectvalue is the active select priority. - Keep
extend-selectonly when its source priority is greater than or equal to the active select priority. Lower-priorityextend-selectsettings are ignored. - Keep
ignoreonly when its source priority is greater than or equal to the active select priority. Lower-priorityignoresettings are ignored. - For each rule, track the strongest matching enabling selector by source priority and specificity.
- For each rule, track the strongest matching disabling selector by source priority and specificity.
- Select the rule only when the enabling selector strength is greater than the disabling selector strength.
- Resolve
require-explicitas rule selectors. Any selected rule matched byrequire-explicitis removed unless an exact rule-code or rule-name selector also participated in enabling it.
The output rules tuple preserves deterministic rule-code order after filtering.
require-explicit is intended for otherwise applicable rules that should remain available through exact selection without being enabled by broad selectors such as ALL, PDF, or PCF. For a rule matched by require-explicit, select = ["ALL"] does not enable it, while including either the rule's exact code or canonical name in extend-select enables it. Setting require-explicit = [] removes this gate, but it does not override configured ignores, Ignored or Disabled setting effects, per-file ignores, or incompatibility resolution. Each built-in default entry changes broad selection in at least one settings profile where those independent gates permit the rule.
Rule incompatibilities¶
After normal selection precedence, setting effects, and explicit-selection requirements are resolved, selected rule incompatibilities are resolved once for the complete settings profile:
- Rules are ranked by effective enabling-selector source priority and then selector specificity, with deterministic
RuleCollection.rulesorder breaking equal-strength ties. - The complete incompatibility graph is resolved strongest-first so a rule discarded by a weaker blocker can still be retained when that blocker loses to a later stronger rule.
- A retained stronger rule silently overrides incompatible weaker rules.
- Equal-strength incompatible rules retain the earlier rule in collection order, and each discarded equal-strength rule produces one operational error listing its earlier retained equal-strength conflicts.
- Final selected rules return to deterministic collection order for execution.
- A discarded rule does not prevent a weaker compatible rule from being retained.
- Per-file ignores run later and do not restore rules discarded by incompatibility resolution.
The built-in opposing pairs (e.g. PDF106/PDF107, PDF108/PDF109, and PCF102/PCF103) are mutually incompatible. Convention-specific setting effects keep every broad built-in convention profile conflict-free by selecting at most one rule from each incompatible PDF pair; a profile may select neither alternative. Exact code or name selection can restore an ignored rule and silently override an incompatible rule selected through a weaker broad selector. Selecting both rules exactly at the same source priority retains the lower ordered rule and reports the higher ordered rule as disabled.
docstring-convention = "pep257" is the default named convention profile. It does not parse Google sections, NumPy sections, or reStructuredText fields, and it applies PEP 257/pydocstyle-compatible broad-rule carve-outs. docstring-convention = "none" also avoids convention-specific parsing, but keeps the stricter generic no-convention rule profile for rules that can act without convention parsing.
Per-file ignores¶
Defaults:
per-file-ignores = {}extend-per-file-ignores = {}
Per-file ignores are resolved from per-file-ignores followed by extend-per-file-ignores. Both settings are TOML-style mappings from file patterns to lists of rule selectors.
Each field uses the resolved highest-priority value for that field. For example, a command-line --per-file-ignores value replaces configured per-file-ignores, while a command-line --extend-per-file-ignores value contributes through the separate extension field and does not replace configured per-file-ignores.
Each per-file ignore entry stores:
pattern: The file pattern exactly as configured.base_path: The setting source base used for matching the pattern.rule_codes: The set of rule codes matched by the entry's selectors.rule_specificities: The strongest selector specificity for each matched rule code.
RuleSelection.for_path(path):
- Converts
pathto a POSIX-style relative path from the per-file-ignore entry'sbase_path. - Matches per-file ignore patterns with
GlobPatternSet.compile((pattern,), match_parent_segments_for_bare=False). - Treats a leading
!in the pattern as negation: the entry applies to files that do not match the pattern after!is removed. - Removes any globally selected rule whose code is matched by a matching per-file-ignore entry.
- Does not compare per-file-ignore selector specificity against the selector that enabled the rule.
Per-file-ignore bases follow file-selection glob bases: auto-discovered config patterns are relative to their config directory, and explicit config, inline config, and CLI patterns are relative to the current working directory.
Examples:
select = ["PDF14"]withper-file-ignores = {"tests/*.py" = ["PDF1"]}removesPDF142for matching files.select = ["PDF1"]withper-file-ignores = {"tests/*.py" = ["PDF14"]}removesPDF142for matching files and keepsPDF150.select = ["PDF14"]withper-file-ignores = {"tests/*.py" = ["PDF14"]}removesPDF142for matching files.select = ["PDF"]withper-file-ignores = {"!src/*.py" = ["PDF101"]}removesPDF101everywhere except files matchingsrc/*.py.- A lower-priority config-file
per-file-ignoresentry still suppresses a rule selected by a higher-priority command-line--select; per-file ignores apply after global rule selection.
pydocfmt check --show-rules prints the global active rules and does not apply per-file ignores.
Per-file settings boundary¶
Language-aware Markdown defaults and per-file settings are specified in Settings specification. They apply after global selector resolution and do not re-run selector matching, setting effects, explicit-selection gates, or incompatibility resolution. The effective path-specific source-context applies hard source-context metadata to the globally resolved candidate rules before per-file ignores, so it can remove inapplicable rules or retain module-only rules that were filtered from the global display context. pydocfmt check --show-rules remains global/cwd-oriented and does not apply path-specific language defaults or per-file setting overrides.
CLI list options¶
Rule-selection CLI list options accept comma-separated selector values per option occurrence:
pydocfmt check --select PDF200,PDF110 --ignore PDF203
Exact names can be mixed with code selectors:
pydocfmt check --select docstring-reflow,PCF --ignore unused-suppression
Whitespace around command-line comma-list entries is stripped before validation. This is a pydocformatter CLI parsing delta from Ruff, which treats the whitespace as part of the selector.
Repeated list option occurrences append values within the command-line layer:
pydocfmt check --select PDF200 --select PDF110
Repeated TOML-map option occurrences merge entries within the command-line layer. If the same pattern appears more than once in repeated --per-file-ignores or --extend-per-file-ignores values, the selector lists are appended:
pydocfmt check --per-file-ignores '{"tests/*.py" = ["PDF200"]}' --per-file-ignores '{"tests/*.py" = ["PDF110"]}'
As with other settings, the resolved command-line value for a field replaces lower-priority values for the same field.
Fixability¶
Defaults:
fixable = ["ALL"]unfixable = []extend-fixable = []
Effective fixability uses the same source-priority and specificity model as rule selection:
- Use the resolved
fixablevalue as the active fixability basis. The source priority of thisfixablevalue is the active fixable priority. - Keep
extend-fixableonly when its source priority is greater than or equal to the active fixable priority. Lower-priorityextend-fixablesettings are ignored. - Keep
unfixableonly when its source priority is greater than or equal to the active fixable priority. Lower-priorityunfixablesettings are ignored. - For each rule, track the strongest matching fixable selector by source priority and specificity.
- For each rule, track the strongest matching unfixable selector by source priority and specificity.
- Treat the rule as configured-fixable only when the fixable selector strength is greater than the unfixable selector strength.
- Intersect configured fixability with the rule's
RuleMetadata.fix_availability.
Settings cannot make a rule with fix_availability = FixAvailability.NEVER fixable. Rules with FixAvailability.ALWAYS, FixAvailability.USUALLY, and FixAvailability.SOMETIMES all have available fixes for selector purposes.
A selector in fixable or extend-fixable that matches only rules with no available fixes is an operational error unless the selector is ALL. This means the default fixable = ["ALL"] does not warn merely because some collected rules have FixAvailability.NEVER.
Examples:
fixable = ["PDF14"]andunfixable = ["PDF1"]makesPDF142configured-fixable.fixable = ["PDF1"]andunfixable = ["PDF14"]makesPDF142configured-unfixable but can leavePDF150configured-fixable.fixable = ["PDF14"]andunfixable = ["PDF14"]makesPDF142configured-unfixable.- A command-line
--fixable PDF1makesPDF142configured-fixable even when a lower-priority config file hasunfixable = ["PDF142"]. - A command-line
--unfixable PDF1makesPDF142configured-unfixable even when a lower-priority config file hasfixable = ["PDF142"].
Operational errors¶
Rule selection is tolerant of selector errors. select_rules() accumulates nonfatal error strings and continues with invalid or unknown selectors resolving to no rules.
Current error wording:
- Invalid selector:
"{context} contains invalid selector: {selector}" - Unknown rule name:
"{context} contains unknown rule name: {selector}" - Unknown selector:
"{context} contains unknown selector: {selector}" - Fixability selector that only matches rules with no available fixes:
"{context} selector {selector!r} only matches rules with no available fixes" - Equal-strength incompatible selected rule:
"Selected rule {rule} is incompatible with earlier selected rule[s] {conflicts}; {rule} has been disabled"
Contexts include:
rule selectionignored rulesfixable rulesunfixable rulesper-file ignores for {pattern!r}
When pydocfmt check --show-rules sees rule-selection errors, it prints them before the rule list and exits with status 1. Normal check execution includes these errors in grouped output handling.
Resolved selection API¶
select_rules(settings, collection=None) resolves a CheckSettings object against a RuleCollection. If no collection is passed, it uses pydocformatter.rules.collection.RULE_COLLECTION. Callers can pass a SettingsProfile to preserve source bases and source priorities from configuration loading; when no profile or explicit field priorities are provided, all selectors are treated as coming from the same source priority.
It returns RuleSelection:
rules: A tuple ofSelectedRuleobjects in rule-code order.per_file_ignores: A tuple of resolvedPerFileRuleIgnoreobjects.errors: A tuple of nonfatal operational error strings.collection: The collection used for resolution.
SelectedRule contains:
rule: The rule'sRuleMetadata.fixable: The effective fixability after configured and inherent fixability are combined.enabled_priority: The source priority of the selector that enabled the rule.enabled_specificity: The global selector specificity that enabled the rule.
PerFileRuleIgnore contains:
pattern: The configured path pattern.rule_codes: A frozenset of matched rule codes.rule_specificities: A sorted tuple of(RuleCode, specificity)pairs.
RuleSelection.for_path(path) returns the selected rules after applying matching per-file ignores to the normalized path.
CLI behavior¶
pydocfmt check --show-rules:
- Resolves rule selection using the loaded settings.
- Prints operational errors first, prefixed with
ERROR:. - Prints each globally active rule as
{code}{* if fixable} {name} ({message}). - Prints
No active rules.when no global rules are selected. - Exits with status
1if rule-selection errors were reported, otherwise0.
Only global rule selection is displayed. Per-file ignores are intentionally not reflected in --show-rules output because they depend on a path.
CLI rule explanations¶
pydocfmt rule [RULE|--all] exposes Ruff-style rule explanations.
RULEmust be an exact uppercase rule code, such asPDF101; rule names and lowercase codes are invalid.--allprints all collected rules in rule-code order and cannot be combined withRULE.--output-format textprints the adjacent Markdown rule document directly.--output-format jsonprints Ruff-style metadata for one rule, or a list of metadata objects with--all; itsexplanationfield omits the Markdown title and fixability paragraph.
Rule documents live next to their rule definition modules with the same basename and a .md extension. For example, src/pydocformatter/rules/definitions/PDF/PDF101_docstring_reflow.py is documented by src/pydocformatter/rules/definitions/PDF/PDF101_docstring_reflow.md.
CLI linter listing¶
pydocfmt linter exposes Ruff-style linter metadata translated from collected rule categories. The public command retains Ruff's terminology even though the internal model uses categories.
- Text output prints a right-aligned prefix/name table.
- JSON output prints a list of objects with
prefix,name, andurlwhen a URL is configured. - The command reports metadata from
RuleCollection.categoriesand does not resolve project configuration.