Skip to content

pylint-disable-comment (ODC8502)

Preview (since 0.16.2.3) · Related issues · View source

Derived from the odoo linter.

Fix is sometimes available.

This rule is unstable and in preview. The --preview flag is required for use.

What it does

Checks for # pylint: disable=... comments that suppress checks Ruff now covers, so they can be migrated to Ruff's # ruff: ... suppression comments.

A message is considered covered when it matches a Ruff rule name (most pylint-odoo checks were ported under their original names, and many pylint checks exist in Ruff under the same name, e.g. too-many-branches), a pylint-odoo message code (e.g. E8102), or a known rename — either across linters, as pylint's too-complex becoming mccabe's complex-structure, or within Ruff's own Pylint rules, which keep pylint's code but not always its name (C0415 import-outside-toplevel is PLC0415 import-outside-top-level).

One message can be covered by several rules, because pylint reported as a single message what Ruff splits up: redefined-builtin is builtin-variable-shadowing, builtin-argument-shadowing and builtin-import-shadowing. The suppression then names all of them, since leaving one out would let it report what the pragma used to silence.

The pragma does not have to be alone in its comment. pylint reads a # followed by anything and then pylint:, so a codebase part-way through the migration usually looks like # noqa: F401 pylint: disable=..., and only the directive is rewritten, leaving the rest of the comment untouched. Prose that merely mentions a pragma (# see pylint: disable=x for details) is not one: what tells them apart is that a real pragma is followed by nothing, or by another comment, but never by more words.

Why is this bad?

After migrating from pylint / pylint-odoo to Ruff, # pylint: disable comments have no effect: Ruff does not read them, so the previously silenced diagnostics are reported again despite the suppression comment.

Example

def action_confirm(env):
    env.cr.commit()  # pylint: disable=invalid-commit

Use instead:

def action_confirm(env):
    env.cr.commit()  # ruff: ignore[invalid-commit]

The suppression names the rule rather than its code, because a name is what a reader recognizes and, for every check ported here, it is the very name pylint used. Note that Ruff only resolves a rule name in a suppression comment when preview mode is on; with preview off only codes resolve. That is not a restriction in practice — every OD/OAPP rule is itself a preview rule, so preview is already on wherever these suppressions matter. The exception worth knowing is the handful of messages that map onto stable upstream rules (print-used to print, too-complex to complex-structure): those keep firing with preview off, and a name-based suppression stops covering them.

Fix safety

Each pylint pragma has a Ruff suppression with the same scope, so all three rewrites preserve behavior and are safe:

  • an inline (trailing) disable pragma becomes # ruff: ignore[...] on that same line;
  • a disable-next pragma becomes an own-line # ruff: ignore[...], which applies to the statement below it;
  • a standalone disable pragma governs the rest of the enclosing block, which is what a # ruff: disable[...] / # ruff: enable[...] pair expresses. When the pragma opens a def body it also covers the def header, as pylint does for messages anchored there (missing-return, method-required-super, ...), so the disable is placed above the header;
  • a standalone disable pragma at module level is pylint's global disable, which governs everything to the end of the file. An unclosed # ruff: disable[...] does the same, so no closing # ruff: enable[...] is appended to the file.

One deliberate difference: disable-next applies to the next line in pylint and to the next statement in Ruff, so a multi-line statement ends up fully covered rather than only on its first line. Widening a suppression can hide a later diagnostic but never unsuppresses one.

A standalone pragma sharing its comment with other text is the one case left without a fix: rebuilding it as a pair consumes the whole comment, which would take that text with it.

Messages without a Ruff equivalent are kept in a # pylint: disable comment next to the inserted suppression.