skip to content

In Ruby code checked by RuboCop, how do rubocop:disable, rubocop:enable and rubocop:todo comments work, and how do you keep them tightly scoped?

level: middleimportance: must knowfreq 65%

answer

  1. end-of-line disable for one line
  2. disable/enable pair for a region
  3. todo is an alias that means revisit
  4. -- adds the reason
  5. disable-next covers one statement

basics

~20 s

A # rubocop:disable Cop comment at the end of a line silences that line; on its own line it silences until # rubocop:enable Cop. rubocop:todo is an alias marking a suppression to revisit. -- reason documents why.

solid answer

~40 s

RuboCop reads **directive comments** in the source. An end-of-line `# rubocop:disable Style/For` silences that cop on that line only. On its own line, `# rubocop:disable Metrics/AbcSize` opens a region that lasts until `# rubocop:enable Metrics/AbcSize`, or to the end of the file if you forget, which `Lint/MissingCopEnableDirective` reports. You can list several cops, a whole department (`Metrics`) or `all`. `rubocop:todo` behaves exactly like `disable` but marks a suppression you mean to revisit; `--disable-uncorrectable` writes those. Add a reason after `--`: `# rubocop:disable Style/For -- generated DSL`. RuboCop 1.90 added `# rubocop:disable-next Cop`, which covers exactly the next statement, however many lines it spans. `Lint/RedundantCopDisableDirective` flags a directive that no longer suppresses anything, and its autocorrection deletes it.

code

ruby · 14 lines
ruby
def legacy_report(rows)
  puts "Total: " + rows.size.to_s # rubocop:disable Style/StringConcatenation -- matches the vendor log format
end

# rubocop:disable-next Metrics/MethodLength -- mirrors the vendor API, see #412
def build_payload(order)
  # ... forty lines ...
end

# rubocop:todo Style/For
for i in 0..2
  puts i
end
# rubocop:enable Style/For

go deeper

for a junior

Recall the end-of-line form, the disable/enable pair and the -- reason, and always use the qualified Department/Cop name.

for a middle

Explain how far each form reaches, why todo is only an alias, and which Lint cops catch missing enables, stale directives and malformed comments.

for a senior

Keep suppressions auditable: prefer the tightest form, require reasons, and use --display-suppressed or --ignore-disable-comments to measure what is hidden.

for a principal

Decide whether the team allows directives at all and on what terms, using Style/DisableCopsWithinSourceCodeDirective with AllowWithReason as the enforcement.

## Directives are comments RuboCop obeys A **directive** is a Ruby comment starting with `rubocop:` that changes which cops report on part of a file, without touching `.rubocop.yml`. They are for local exceptions: a line that must stay long, a method that is deliberately complex, generated code inside a hand-written file. ## The forms, from tightest to widest | Form | Where it goes | What it covers | |---|---|---| | `code # rubocop:disable Cop` | end of a code line | that line only | | `# rubocop:disable-next Cop` | own line, above a statement | the whole next statement (RuboCop 1.90+) | | `# rubocop:disable Cop` ... `# rubocop:enable Cop` | own lines | every line between them | | `# rubocop:push` ... `# rubocop:pop` | own lines | a region, restoring the previous state exactly | - **Names**: one or more cops separated by commas, a department name such as `Metrics`, or `all`. - **Reasons**: anything after ` -- ` is a justification: `# rubocop:disable Layout/LineLength -- URL cannot be wrapped`. - **`todo`**: `rubocop:todo` (and `todo-next`) are aliases of `disable` (and `disable-next`). They behave identically; the word tells readers this is debt, not a decision. - **Enabling**: `# rubocop:enable Style/AsciiComments` can also switch on a cop that the configuration disables, for part of a file. It has no effect in a file that `.rubocop.yml` or `.rubocop_todo.yml` excludes. RuboCop 1.91 added `enable-next` and a `next` form with `+Cop`/`-Cop` arguments for toggling several cops on one statement. ## push and pop `# rubocop:push` saves the current state of every cop, and `# rubocop:pop` restores it. Between them you can change several cops at once, either with ordinary `disable`/`enable` directives or with inline arguments: `# rubocop:push -Style/GuardClause +Style/For` disables one cop and enables another. Unlike an `enable` line, `pop` cannot restore the wrong state, because it never names one; it returns to whatever was saved, which matters in nested regions. Most code never needs this; it exists for generated or unusual sections where several cops must change together. ## Pitfalls 1. **A missing `enable`.** A block `disable` without its `enable` runs to the end of the file. `Lint/MissingCopEnableDirective` (on by default) reports it; its `MaxRangeSize` option can also cap how long a region may be. 2. **A stale directive.** After someone fixes the offense, the comment stays and hides the next one. `Lint/RedundantCopDisableDirective` reports directives that suppress nothing, and its autocorrection removes them with their `--` reason. It is not silenced by `rubocop:disable all` or by disabling the `Lint` department. 3. **A malformed directive.** A misspelled mode, a missing comma or a cop name that does not exist makes the comment suppress nothing, silently. `Lint/CopDirectiveSyntax` reports those. 4. **An unqualified name.** `# rubocop:disable LineLength` is flagged by `Migration/DepartmentName`; write `Layout/LineLength`. 5. **A long end-of-line comment.** The directive itself can push a line past `Layout/LineLength`; `disable-next` on the line above avoids that. ## Seeing and policing what is suppressed - `rubocop --display-suppressed` (1.90+) lists suppressed offenses tagged `[Suppressed]`, with their justification, without changing the exit status. - `rubocop --ignore-disable-comments` runs as if no directive existed, which shows how much is being hidden. - `Style/DisableCopsWithinSourceCodeDirective`, disabled by default, forbids directives or allows only some: `AllowedCops`, `DisallowedCops`, `AllowWithReason: true` to require a `--` reason, and `AllowedDirectives` to exempt forms such as `todo`. - `Style/DirectiveScope` (pending) converts a `disable`/`enable` pair around a single statement into `disable-next`. ## Directives or configuration? - One place in one file: a directive, with a reason. - A whole file or directory: `Exclude` for that cop in `.rubocop.yml`. - Many existing offenses across the codebase: a generated `.rubocop_todo.yml`, or `rubocop -a --disable-uncorrectable`, which inserts `rubocop:todo` comments next to each offense that autocorrect could not fix.

  • Why does a rubocop:disable comment sometimes keep hiding problems after the original offense was fixed?
    The directive does not know the offense is gone, so it keeps suppressing whatever that cop finds in its range later. `Lint/RedundantCopDisableDirective` catches this: it reports a directive that suppresses nothing, and `rubocop -a` deletes it along with its `--` reason. It does not run under `--only`, because a partial run cannot tell which directives are redundant.
  • What is the difference between rubocop:todo and rubocop:disable?
    None in behaviour: `rubocop:todo` is an alias of `rubocop:disable`, and `todo-next` of `disable-next`. The difference is intent. `todo` marks debt you plan to fix, such as the comments `rubocop -a --disable-uncorrectable` inserts, while `disable` with a `--` reason records a deliberate, permanent exception.

saying these in an interview costs you the question

  • A rubocop:disable line without a matching enable only affects the next line.
  • rubocop:todo makes RuboCop report the offense as a warning instead of hiding it.
  • A misspelled cop name in a directive makes RuboCop stop with a configuration error.
  • An old rubocop:disable comment is harmless because RuboCop removes it when the offense is fixed.
  • rubocop:disable all also silences Lint/RedundantCopDisableDirective.