Resolve auto-correctable RuboCop offenses (Exclude-only) in .rubocop_todo/**/*.yml

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

❗ COMMUNITY CONTRIBUTORS, PLEASE ONLY PICKUP ONE COP VIOLATION TO ALLOW OTHER NEW CONTRIBUTORS TO LEARN FROM THESE TOO ❗

Problem

.rubocop_todo/**/*.yml contains auto-correctable (RuboCop) offenses (grep -hr auto-correct .rubocop_todo -A 1 | grep "^[A-Z]") which can be fixed automatically.

Implementation guide

Resolve auto-correctable rules which are enabled by letting RuboCop auto-correct them automatically 🎉

  1. Verify that the you are working on is already enabled. See also #369268 (closed)
  2. Pick a offense from The List below e.g. Layout/ClosingHeredocIndentation
  3. Create a branch (e.g. 239356-abc/fix-Layout/ClosingHeredocIndentation)
    • Info: Using this issue ID (239356) in a branch name will add required labels and reference to this issue in the new merge request.
    • Info: Using your username or initials (e.g. abc/ above) in the branch name will help to prevent branch naming clashes with other contributors also working on this issue.
  4. Delete the rule from the corresponding .rubocop_todo/ YAML file (for example https://gitlab.com/gitlab-org/gitlab/-/blob/fdeea9247566ca45fece68605a838ebc659b62db/.rubocop_todo/gitlab/rails/safe_format.yml)
  5. Auto-correct RuboCop offenses via e.g. bundle exec rubocop --autocorrect --only Layout/ClosingHeredocIndentation
  6. Check changed files and adjust if needed
    • Suggestion: In order to keep MRs short one can fix 10-15 files in one go. Depending on the amount of changes in the file.
  7. Commit and create a merge request
  8. Mention this merge request in this issue
  9. 🎉

Burndown chart

gitlab-org/gitlab RuboCop burndown

The List

📣 A list containing all auto-correctable offenses

How to regenerate the list?

  1. Copy+paste the Script from below 👇 and save it as regenerate.rb in the root of your gitlab repository.
  2. Copy The List from above ☝️ in Markdown format. You can include the heading and the :mega: line. The script skips them.
  3. Paste the list into a new file old_list.md in the same directory.
  4. Run ruby regenerate.rb. It reads old_list.md and writes the new list to new_list.md.
  5. Copy+paste the content of new_list.md into The List section.

A cop is ticked only when it has 0 entries left. If a cop still has entries, the script unticks it.

You can also use other file names: ruby regenerate.rb <input_file> <output_file>.

Script

script
#!/usr/bin/env ruby
# frozen_string_literal: true

# From https://gitlab.com/gitlab-org/gitlab/-/issues/239356#the-list
#
# Usage (from the root of the gitlab repo, on an up-to-date master):
#   ruby regenerate.rb [old_list.md] [new_list.md]

require "yaml"

BURNDOWN_CHART_URL = "https://ck3g.gitlab.io/rubocop-burndown/"
BLOB_URL = "https://gitlab.com/gitlab-org/gitlab/-/blob"

input_path = ARGV.fetch(0, "old_list.md")
output_path = ARGV.fetch(1, "new_list.md")

sha = `git rev-parse HEAD`.chomp
abort "Could not read git HEAD. Run this from the gitlab repo." if sha.empty?

def entries_text(count)
  "#{count} #{count == 1 ? 'entry' : 'entries'}"
end

def chart_link(name)
  "[Chart](#{BURNDOWN_CHART_URL}##{name})"
end

# Only bullet lines with a cop name count. Headers and blank lines are ignored.
current = File.readlines(input_path, chomp: true).each_with_object({}) do |line, hash|
  next unless line.start_with?("- [")

  name = line[/`(\S+)`/, 1]
  hash[name] = line if name
end
warn "Read #{current.size} cops from #{input_path}"

todos = Dir.glob(".rubocop_todo/**/*.yml").each_with_object({}) do |path, hash|
  next unless /^# Cop supports --auto-?correct/.match?(File.read(path))

  name, config = YAML.safe_load_file(path).first
  entries = (config && config["Exclude"]) || []
  hash[name] = [path, entries.size]
end

lines = (current.keys | todos.keys).sort.filter_map do |name|
  path, count = todos[name]
  old_line = current[name]

  if path
    # Keep any note (for example an MR link) from the old line.
    note = old_line&.[](/entr(?:y|ies)\)(.*?)\s*\[Chart\]/, 1).to_s
    "- [#{count.zero? ? 'x' : ' '}] [`#{name}`](#{BLOB_URL}/#{sha}/#{path}) " \
      "(#{entries_text(count)})#{note} #{chart_link(name)}"
  elsif old_line
    # The todo file is gone, so the cop is done. Keep the old link to its last version.
    line = old_line.sub("- [ ]", "- [x]").sub(/\d+ entr(?:y|ies)/, entries_text(0))
    line.include?("[Chart](") ? line : "#{line} #{chart_link(name)}"
  end
end

File.write(output_path, "#{lines.join("\n")}\n")
warn "Wrote #{lines.size} cops to #{output_path}"

Edited by Vitali Tatarintev