[Fix #15042] Add `DisallowedCops` option to `Style/DisableCopsWithinSourceCodeDirective` by hammadxcm · Pull Request #15050 · rubocop/rubocop · GitHub
Skip to content

[Fix #15042] Add DisallowedCops option to Style/DisableCopsWithinSourceCodeDirective - #15050

Merged
bbatsov merged 1 commit into
rubocop:masterfrom
hammadxcm:add-disallowed-cops-option
Jul 29, 2026
Merged

bbatsov merged 1 commit into
rubocop:masterfrom
hammadxcm:add-disallowed-cops-option

Conversation

@hammadxcm

@hammadxcm hammadxcm commented Mar 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #15042

Summary

  • Adds a DisallowedCops configuration option to Style/DisableCopsWithinSourceCodeDirective, the inverse of the existing AllowedCops.
  • When DisallowedCops is set, only directives for the listed cops (and rubocop:disable all) are flagged — all other cops can be freely disabled.
  • This is useful when you want to protect a small set of critical cops (e.g. Lint/Void) from being disabled rather than allowlisting hundreds of cops.

Context

This addresses a different use case from #14598. Where #14598 is about preventing Style/DisableCopsWithinSourceCodeDirective itself from being disabled, this feature is about selectively protecting a small set of critical cops without needing to allowlist every other cop. See the discussion in #14598 and the prior PR #14705 for background.

Example configuration

Style/DisableCopsWithinSourceCodeDirective:
  Enabled: true
  DisallowedCops:
    - Lint/Void
    - Security/Eval

With this config, # rubocop:disable Lint/Void is flagged, but # rubocop:disable Metrics/AbcSize is allowed.

Test plan

  • Added specs for DisallowedCops: single disallowed cop, non-disallowed cop, mixed directives, multiple disallowed cops, rubocop:enable all, and leading source comments
  • Verified all existing AllowedCops specs still pass (no regressions)
  • bundle exec rake passes (all specs + self-lint + doc syntax check)

Before submitting the PR make sure the following are checked:

  • The PR relates to only one subject with a clear title and description in grammatically correct, complete sentences.
  • Wrote good commit messages.
  • Commit message starts with [Fix #issue-number] (if the related issue exists).
  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Ran bundle exec rake default. It executes all tests and runs RuboCop on its own code.
  • Added an entry (file) to the changelog folder named {change_type}_{change_description}.md if the new code introduces user-observable changes. See changelog entry format for details.

@corsonknowles

Copy link
Copy Markdown
Contributor

@hammadxcm
hammadxcm force-pushed the add-disallowed-cops-option branch from 588a5ad to d19e9bf Compare April 19, 2026 06:46
@hammadxcm

Copy link
Copy Markdown
Contributor Author

@bbatsov Please review when you are available. Some people need this one.

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any recent activity. It will be closed soon if no further activity occurs. Thank you for your contribution and understanding!

@github-actions github-actions Bot added the stale Issues that haven't been active in a while label May 30, 2026
@hammadxcm

Copy link
Copy Markdown
Contributor Author

still waiting on its merge

@github-actions github-actions Bot removed the stale Issues that haven't been active in a while label May 30, 2026
@bbatsov

bbatsov commented May 30, 2026

Copy link
Copy Markdown
Collaborator

I haven't forgotten about this and I'll take a closer look soon.

@corsonknowles

Copy link
Copy Markdown
Contributor

Thank you so much!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest file rename to something like:changelog/new_add_disallowed_cops_option_20260323210952.md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — renamed to changelog/new_add_disallowed_cops_option_20260323210952.md.

#
# Alternatively, specific cops can be disallowed with the `DisallowedCops`
# configuration. When `DisallowedCops` is set, only directives for the listed
# cops (and `rubocop:disable all`) will be flagged. This is useful when you want

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:enable all is also flagged, so let's shorten this accordiingly

Suggested change
# cops (and `rubocop:disable all`) will be flagged. This is useful when you want
# cops (and `all`) will be flagged. This is useful when you want

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

end
end

context 'when enabling all cops' do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest the corresponding disable spec as well, something like

context 'when disabling all cops' do
  it 'registers an offense and corrects' do
    expect_offense(<<~RUBY)
      foo # rubocop:disable all
          ^^^^^^^^^^^^^^^^^^^^^ RuboCop disable/enable directives for `all` are not permitted.
    RUBY

    expect_correction(<<~RUBY)
      foo#{trailing_whitespace}
    RUBY
  end
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — but the suggested spec actually fails: # rubocop:disable all disables Style/DisableCopsWithinSourceCodeDirective itself on that line, so the offense is suppressed by Cop::Base#enabled_line? before it is registered. This is pre-existing behavior on master (it also applies without DisallowedCops/AllowedCops — easy to verify with --force-default-config on a file containing foo # rubocop:disable all), and it is why the existing "disabling all cops" spec at the top of this file uses rubocop:enable all rather than disable all.

Making disable all flaggable would require bypassing directive suppression for this cop, which feels out of scope for this PR. I added a spec documenting the current behavior instead, with a comment explaining the self-suppression.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes a ton of sense, and there's a separate issue about addressing that. Thanks for explaining!

end
else
directive_cops - allowed_cops
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is perfectly clear, but in the interests of making future development easier, we can add a test for it like so:

context 'when both AllowedCops and DisallowedCops are set' do
  let(:cop_config) do
    { 'AllowedCops' => ['Lint/Void'], 'DisallowedCops' => ['Security/Eval'] }
  end

  it 'gives DisallowedCops precedence and ignores AllowedCops' do
    expect_offense(<<~RUBY)
      foo # rubocop:disable Lint/Void, Security/Eval
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RuboCop disable/enable directives for `Security/Eval` are not permitted.
    RUBY

    expect_correction(<<~RUBY)
      foo # rubocop:disable Lint/Void
    RUBY
  end
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added the precedence spec (with the offense carets adjusted to match the actual range length).

@corsonknowles

Copy link
Copy Markdown
Contributor

@hammadxcm thanks so much for submitting this. I added some very minor comments in hope that they speed along acceptance. All non-blocking nits from my perspective.

@hammadxcm
hammadxcm force-pushed the add-disallowed-cops-option branch from d19e9bf to e5c4ee6 Compare June 12, 2026 23:24
@hammadxcm

Copy link
Copy Markdown
Contributor Author

@hammadxcm thanks so much for submitting this. I added some very minor comments in hope that they speed along acceptance. All non-blocking nits from my perspective.

Its done. @corsonknowles Please review.

@corsonknowles

Copy link
Copy Markdown
Contributor

@hammadxcm thanks so much for submitting this. I added some very minor comments in hope that they speed along acceptance. All non-blocking nits from my perspective.

All looks good to me, and thanks for the explanation on the disable all part. I hope we're in good shape for when @bbatsov gets to take a deeper look

@corsonknowles

Copy link
Copy Markdown
Contributor

Just dropping by to say I continue to be excited about this.

@corsonknowles

corsonknowles commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

In order to help validate this for general adoption, we've ported this PR into our Rails monorepo as a local cop to exercise DisallowedCops at scale

Here are the useful findings:

  1. It works on a large, real set of configs
    Our repo has ~1,265 registered cops (RuboCop plus nine plugins and an in-repo plugin) and currently uses AllowedCops: a 271-entry allowlist, so ~1,004 cops may not be disabled inline.

  2. To exercise the new code path in detail, we inverted that allowlist into a 1,004-entry DisallowedCops denylist — The point was that inverting an allowlist works just fine. There were no bugs found, the behavior on runs matches identically.

Across all 6,794 files containing a rubocop:disable|enable|todo directive (~14,000 directive comments), the offense set is identical before and after the switch.

Handling of all, mixed allowed/disallowed autocorrection, and message selection all behaved as the specs describe, with no new edge cases found.

Repeating that he directive_cops.include?('all') guard is a nice catch. It keeps rubocop:disable all flagged in DisallowedCops mode, matching the documented AllowedCops semantics.

For what it's worth, the reason we want this option is exactly the one in your description: an allowlist has to be revisited on every RuboCop or extension upgrade, because newly added cops land outside it and start failing builds. A denylist of genuinely critical cops or already perfectly observed rules makes upgrades a non-event for the config for this Cop, which is very nice for keeping up to date.

The very tiniest of nits:

We had one good finding.
Array cost scales with the size of the denylist or the allowlist. A set is very slightly faster (both are very fast)
But that's separate from the scope of this change, could be its own minor PR.

In summary, "works great, validated at scale"

@hammadxcm

Copy link
Copy Markdown
Contributor Author

@bbatsov
bbatsov merged commit 061235d into rubocop:master Jul 29, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add DisallowedCops configuration option to Style/DisableCopsWithinSourceCodeDirective

3 participants