Module: DevDoc::Test::Lints::NoFileExcludes

Defined in:
lib/dev_doc/test/lints/no_file_excludes.rb

Overview

Reject literal-file entries in any ‘Exclude:` list under `.rubocop.yml`.

## Rationale A literal-path ‘Exclude:` makes the suppression invisible to anyone reading the excluded file. They see the violating code, see no `# rubocop:disable` annotation near it, and reasonably conclude the pattern is acceptable for new code in the same file. That sets the wrong expectation and lets violations multiply.

Inline ‘# rubocop:disable Cop/Name` at the violation line(s) keeps the suppression visible to anyone reading the code AND scopes it to the specific lines — a fresh violation in the same file still gets flagged. The rationale for the suppression lives next to the code it describes, so a future reader sees it without having to cross-check `.rubocop.yml`.

Globs (‘vendor/*/`, `db/migrate/*/.rb`, `bin/*`) remain fine because they target a category, not a file someone might be reading.

❌ Hides the suppression from readers of the file

DevDoc/Migration/AvoidColumnDefault:
  Exclude:
    - "db/migrate/20260505035728_create_pr_reviews.rb"

✔ Visible inline, scoped to the specific line(s)

# db/migrate/20260505035728_create_pr_reviews.rb
t.jsonb :diff_files, default: [] # rubocop:disable DevDoc/Migration/AvoidColumnDefault

✔ Glob — still acceptable, targets a category not a specific file

AllCops:
  Exclude:
    - "vendor/**/*"

## Usage Include this module in a Minitest test class. To allowlist additional auto-generated literal paths (e.g. a project-specific generated file), redefine ‘ALLOWED_FILE_EXCLUDES` on the test class.

class RubocopConfigTest < ActiveSupport::TestCase
  include DevDoc::Test::Lints::NoFileExcludes
  # ALLOWED_FILE_EXCLUDES = %w[db/schema.rb Gemfile.lock].freeze
end

Constant Summary collapse

ALLOWED_FILE_EXCLUDES =

Default allowlist. Per-project override: redefine the constant on the test class that includes this module.

NoFileExcludesChecker::DEFAULT_ALLOWED_FILES

Instance Method Summary collapse

Instance Method Details

#test_no_file_excludes_in_rubocop_ymlObject



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/dev_doc/test/lints/no_file_excludes.rb', line 105

def test_no_file_excludes_in_rubocop_yml
  result = NoFileExcludesChecker
           .new(rubocop_yml_path, allowed_files: self.class::ALLOWED_FILE_EXCLUDES)
           .offenders

  skip "no #{rubocop_yml_path} found" if result == NoFileExcludesChecker::MissingFile

  assert result.empty?,
         "`.rubocop.yml` Exclude lists must contain only globs " \
         "(category patterns), not literal file paths. A literal-path " \
         "exclude hides the suppression from readers of the file. " \
         "Use `# rubocop:disable Cop/Name` at the violation line(s) " \
         "instead — visible to readers, scoped to the specific lines.\n\n" \
         "Offenders:\n#{result.join("\n")}"
end