Commit 53ed766d authored by Doug Stull's avatar Doug Stull

Merge branch 'pl-rubocop-todo-directory3' into 'master'

Enhance `rake rubocop:todo:generate` to split TODOs into separate files

See merge request gitlab-org/gitlab!82100
parents 56234668 0af3cba5
...@@ -159,25 +159,22 @@ When the number of RuboCop exceptions exceed the default [`exclude-limit` of 15] ...@@ -159,25 +159,22 @@ When the number of RuboCop exceptions exceed the default [`exclude-limit` of 15]
we may want to resolve exceptions over multiple commits. To minimize confusion, we may want to resolve exceptions over multiple commits. To minimize confusion,
we should track our progress through the exception list. we should track our progress through the exception list.
When auto-generating the `.rubocop_todo.yml` exception list for a particular Cop, The preferred way to [generate the initial list or a list for specific RuboCop rules](../rake_tasks.md#generate-initial-rubocop-todo-list)
and more than 15 files are affected, we should add the exception list to is to run the Rake task `rubocop:todo:generate`:
a different file in the directory `.rubocop_todo/`. For example, the configuration for the cop
`Gitlab/NamespacedClass` is in `.rubocop_todo/gitlab/namespaced_class.yml`.
This ensures that our list isn't mistakenly removed by another auto generation of
the `.rubocop_todo.yml`. This also allows us greater visibility into the exceptions
which are currently being resolved.
One way to generate the initial list is to run the Rake task `rubocop:todo:generate`:
```shell ```shell
# Initial list
bundle exec rake rubocop:todo:generate bundle exec rake rubocop:todo:generate
# List for specific RuboCop rules
bundle exec rake 'rubocop:todo:generate[Gitlab/NamespacedClass,Lint/Syntax]'
``` ```
You can then move the list from the freshly generated `.rubocop_todo.yml` for the Cop being actively This Rake task creates or updates the exception list in `.rubocop_todo/`. For
resolved and place it in the directory `.rubocop_todo/`. In this scenario, do not commit example, the configuration for the RuboCop rule `Gitlab/NamespacedClass` is
auto-generated changes to the `.rubocop_todo.yml`, as an `exclude limit` that is higher than 15 located in `.rubocop_todo/gitlab/namespaced_class.yml`.
makes the `.rubocop_todo.yml` hard to parse.
Make sure to commit any changes in `.rubocop_todo/` after running the Rake task.
### Reveal existing RuboCop exceptions ### Reveal existing RuboCop exceptions
......
...@@ -196,6 +196,16 @@ One way to generate the initial list is to run the Rake task `rubocop:todo:gener ...@@ -196,6 +196,16 @@ One way to generate the initial list is to run the Rake task `rubocop:todo:gener
bundle exec rake rubocop:todo:generate bundle exec rake rubocop:todo:generate
``` ```
To generate TODO list for specific RuboCop rules, pass them comma-seperated as
argument to the Rake task:
```shell
bundle exec rake 'rubocop:todo:generate[Gitlab/NamespacedClass,Lint/Syntax]'
bundle exec rake rubocop:todo:generate\[Gitlab/NamespacedClass,Lint/Syntax\]
```
Some shells require brackets to be escaped or quoted.
See [Resolving RuboCop exceptions](contributing/style_guides.md#resolving-rubocop-exceptions) See [Resolving RuboCop exceptions](contributing/style_guides.md#resolving-rubocop-exceptions)
on how to proceed from here. on how to proceed from here.
......
# frozen_string_literal: true # frozen_string_literal: true
# rubocop:disable Rails/RakeEnvironment
unless Rails.env.production? unless Rails.env.production?
require 'rubocop/rake_task' require 'rubocop/rake_task'
...@@ -8,18 +9,59 @@ unless Rails.env.production? ...@@ -8,18 +9,59 @@ unless Rails.env.production?
namespace :rubocop do namespace :rubocop do
namespace :todo do namespace :todo do
desc 'Generate RuboCop todos' desc 'Generate RuboCop todos'
task :generate do # rubocop:disable Rails/RakeEnvironment task :generate do |_task, args|
require 'rubocop' require 'rubocop'
require 'active_support/inflector/inflections'
require_relative '../../rubocop/todo_dir'
require_relative '../../rubocop/formatter/todo_formatter'
# Reveal all pending TODOs so RuboCop can pick them up and report
# during scan.
ENV['REVEAL_RUBOCOP_TODO'] = '1'
# Save cop configuration like `RSpec/ContextWording` into
# `rspec/context_wording.yml` and not into
# `r_spec/context_wording.yml`.
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'RSpec'
inflect.acronym 'GraphQL'
end
options = %w[ options = %w[
--auto-gen-config --parallel
--auto-gen-only-exclude --format RuboCop::Formatter::TodoFormatter
--exclude-limit=100000
--no-offense-counts
] ]
# Convert from Rake::TaskArguments into an Array to make `any?` work as
# expected.
cop_names = args.to_a
todo_dir = RuboCop::TodoDir.new(RuboCop::TodoDir::DEFAULT_TODO_DIR)
if cop_names.any?
# We are sorting the cop names to benefit from RuboCop cache which
# also takes passed parameters into account.
list = cop_names.sort.join(',')
options.concat ['--only', list]
cop_names.each { |cop_name| todo_dir.inspect(cop_name) }
else
todo_dir.inspect_all
end
puts <<~MSG
Generating RuboCop TODOs with:
rubocop #{options.join(' ')}
This might take a while...
MSG
RuboCop::CLI.new.run(options) RuboCop::CLI.new.run(options)
todo_dir.delete_inspected
end end
end end
end end
end end
# rubocop:enable Rails/RakeEnvironment
# frozen_string_literal: true
require 'set'
require 'rubocop'
require 'yaml'
require_relative '../todo_dir'
module RuboCop
module Formatter
# This formatter dumps a YAML configuration file per cop rule
# into `.rubocop_todo/**/*.yml` which contains detected offenses.
#
# For example, this formatter stores offenses for `RSpec/VariableName`
# in `.rubocop_todo/rspec/variable_name.yml`.
class TodoFormatter < BaseFormatter
# Disable a cop which exceeds this limit. This way we ensure that we
# don't enable a cop by accident when moving it from
# .rubocop_todo.yml to .rubocop_todo/.
# We keep the cop disabled if it has been disabled previously explicitly
# via `Enabled: false` in .rubocop_todo.yml or .rubocop_todo/.
MAX_OFFENSE_COUNT = 15
Todo = Struct.new(:cop_name, :files, :offense_count) do
def initialize(cop_name)
super(cop_name, Set.new, 0)
@cop_class = RuboCop::Cop::Registry.global.find_by_cop_name(cop_name)
end
def record(file, offense_count)
files << file
self.offense_count += offense_count
end
def autocorrectable?
@cop_class&.support_autocorrect?
end
end
def initialize(output, options = {})
directory = options.delete(:rubocop_todo_dir) || TodoDir::DEFAULT_TODO_DIR
@todos = Hash.new { |hash, cop_name| hash[cop_name] = Todo.new(cop_name) }
@todo_dir = TodoDir.new(directory)
@config_inspect_todo_dir = load_config_inspect_todo_dir(directory)
@config_old_todo_yml = load_config_old_todo_yml(directory)
check_multiple_configurations!
super
end
def file_finished(file, offenses)
return if offenses.empty?
file = relative_path(file)
offenses.map(&:cop_name).tally.each do |cop_name, offense_count|
@todos[cop_name].record(file, offense_count)
end
end
def finished(_inspected_files)
@todos.values.sort_by(&:cop_name).each do |todo|
yaml = to_yaml(todo)
path = @todo_dir.write(todo.cop_name, yaml)
output.puts "Written to #{relative_path(path)}\n"
end
end
private
def relative_path(path)
parent = File.expand_path('..', @todo_dir.directory)
path.delete_prefix("#{parent}/")
end
def to_yaml(todo)
yaml = []
yaml << '---'
yaml << '# Cop supports --auto-correct.' if todo.autocorrectable?
yaml << "#{todo.cop_name}:"
if previously_disabled?(todo) && offense_count_exceeded?(todo)
yaml << " # Offense count: #{todo.offense_count}"
yaml << ' # Temporarily disabled due to too many offenses'
yaml << ' Enabled: false'
end
yaml << ' Exclude:'
files = todo.files.sort.map { |file| " - '#{file}'" }
yaml.concat files
yaml << ''
yaml.join("\n")
end
def offense_count_exceeded?(todo)
todo.offense_count > MAX_OFFENSE_COUNT
end
def check_multiple_configurations!
cop_names = @config_inspect_todo_dir.keys & @config_old_todo_yml.keys
return if cop_names.empty?
list = cop_names.sort.map { |cop_name| "- #{cop_name}" }.join("\n")
raise "Multiple configurations found for cops:\n#{list}\n"
end
def previously_disabled?(todo)
cop_name = todo.cop_name
config = @config_old_todo_yml[cop_name] ||
@config_inspect_todo_dir[cop_name] || {}
return false if config.empty?
config['Enabled'] == false
end
def load_config_inspect_todo_dir(directory)
@todo_dir.list_inspect.each_with_object({}) do |path, combined|
config = YAML.load_file(path)
combined.update(config) if Hash === config
end
end
# Load YAML configuration from `.rubocop_todo.yml`.
# We consider this file already old, obsolete, and to be removed soon.
def load_config_old_todo_yml(directory)
path = File.expand_path(File.join(directory, '../.rubocop_todo.yml'))
config = YAML.load_file(path) if File.exist?(path)
config || {}
end
end
end
end
# frozen_string_literal: true
require 'fileutils'
require 'active_support/inflector/inflections'
module RuboCop
# Helper class to manage file access to RuboCop TODOs in .rubocop_todo directory.
class TodoDir
DEFAULT_TODO_DIR = File.expand_path('../.rubocop_todo', __dir__)
# Suffix to indicate TODOs being inspected right now.
SUFFIX_INSPECT = '.inspect'
attr_reader :directory
def initialize(directory, inflector: ActiveSupport::Inflector)
@directory = directory
@inflector = inflector
end
def read(cop_name, suffix = nil)
read_suffixed(cop_name)
end
def write(cop_name, content)
path = path_for(cop_name)
FileUtils.mkdir_p(File.dirname(path))
File.write(path, content)
path
end
def inspect(cop_name)
path = path_for(cop_name)
if File.exist?(path)
FileUtils.mv(path, "#{path}#{SUFFIX_INSPECT}")
true
else
false
end
end
def inspect_all
pattern = File.join(@directory, '**/*.yml')
Dir.glob(pattern).count do |path|
FileUtils.mv(path, "#{path}#{SUFFIX_INSPECT}")
end
end
def list_inspect
pattern = File.join(@directory, "**/*.yml.inspect")
Dir.glob(pattern)
end
def delete_inspected
pattern = File.join(@directory, '**/*.yml.inspect')
Dir.glob(pattern).count do |path|
File.delete(path)
end
end
private
def read_suffixed(cop_name, suffix = nil)
path = path_for(cop_name, suffix)
File.read(path) if File.exist?(path)
end
def path_for(cop_name, suffix = nil)
todo_path = "#{@inflector.underscore(cop_name)}.yml#{suffix}"
File.join(@directory, todo_path)
end
end
end
# frozen_string_literal: true
# rubocop:disable RSpec/VerifiedDoubles
require 'fast_spec_helper'
require 'stringio'
require 'fileutils'
require_relative '../../../rubocop/formatter/todo_formatter'
require_relative '../../../rubocop/todo_dir'
RSpec.describe RuboCop::Formatter::TodoFormatter do
let(:stdout) { StringIO.new }
let(:tmp_dir) { Dir.mktmpdir }
let(:real_tmp_dir) { File.join(tmp_dir, 'real') }
let(:symlink_tmp_dir) { File.join(tmp_dir, 'symlink') }
let(:rubocop_todo_dir) { "#{symlink_tmp_dir}/.rubocop_todo" }
let(:options) { { rubocop_todo_dir: rubocop_todo_dir } }
let(:todo_dir) { RuboCop::TodoDir.new(rubocop_todo_dir) }
subject(:formatter) { described_class.new(stdout, options) }
around do |example|
FileUtils.mkdir(real_tmp_dir)
FileUtils.symlink(real_tmp_dir, symlink_tmp_dir)
Dir.chdir(symlink_tmp_dir) do
example.run
end
end
after do
FileUtils.remove_entry(tmp_dir)
end
context 'with offenses detected' do
let(:offense) { fake_offense('A/Offense') }
let(:offense_too_many) { fake_offense('B/TooManyOffenses') }
let(:offense_autocorrect) { fake_offense('B/AutoCorrect') }
before do
stub_const("#{described_class}::MAX_OFFENSE_COUNT", 1)
stub_rubocop_registry(
'A/Offense' => { autocorrectable: false },
'B/AutoCorrect' => { autocorrectable: true }
)
end
def run_formatter
formatter.started(%w[a.rb b.rb c.rb d.rb])
formatter.file_finished('c.rb', [offense_too_many])
formatter.file_finished('a.rb', [offense_too_many, offense, offense_too_many])
formatter.file_finished('b.rb', [])
formatter.file_finished('d.rb', [offense_autocorrect])
formatter.finished(%w[a.rb b.rb c.rb d.rb])
end
it 'outputs its actions' do
run_formatter
expect(stdout.string).to eq(<<~OUTPUT)
Written to .rubocop_todo/a/offense.yml
Written to .rubocop_todo/b/auto_correct.yml
Written to .rubocop_todo/b/too_many_offenses.yml
OUTPUT
end
it 'creates YAML files', :aggregate_failures do
run_formatter
expect(rubocop_todo_dir_listing).to contain_exactly(
'a/offense.yml', 'b/auto_correct.yml', 'b/too_many_offenses.yml'
)
expect(todo_yml('A/Offense')).to eq(<<~YAML)
---
A/Offense:
Exclude:
- 'a.rb'
YAML
expect(todo_yml('B/AutoCorrect')).to eq(<<~YAML)
---
# Cop supports --auto-correct.
B/AutoCorrect:
Exclude:
- 'd.rb'
YAML
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
---
B/TooManyOffenses:
Exclude:
- 'a.rb'
- 'c.rb'
YAML
end
context 'when cop previously not explicitly disabled' do
before do
todo_dir.write('B/TooManyOffenses', <<~YAML)
---
B/TooManyOffenses:
Exclude:
- 'x.rb'
YAML
end
it 'does not disable cop' do
run_formatter
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
---
B/TooManyOffenses:
Exclude:
- 'a.rb'
- 'c.rb'
YAML
end
end
context 'when cop previously explicitly disabled in rubocop_todo/' do
before do
todo_dir.write('B/TooManyOffenses', <<~YAML)
---
B/TooManyOffenses:
Enabled: false
Exclude:
- 'x.rb'
YAML
todo_dir.inspect_all
end
it 'keeps cop disabled' do
run_formatter
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
---
B/TooManyOffenses:
# Offense count: 3
# Temporarily disabled due to too many offenses
Enabled: false
Exclude:
- 'a.rb'
- 'c.rb'
YAML
end
end
context 'when cop previously explicitly disabled in rubocop_todo.yml' do
before do
File.write('.rubocop_todo.yml', <<~YAML)
---
B/TooManyOffenses:
Enabled: false
Exclude:
- 'x.rb'
YAML
end
it 'keeps cop disabled' do
run_formatter
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
---
B/TooManyOffenses:
# Offense count: 3
# Temporarily disabled due to too many offenses
Enabled: false
Exclude:
- 'a.rb'
- 'c.rb'
YAML
end
end
context 'with cop configuration in both .rubocop_todo/ and .rubocop_todo.yml' do
before do
todo_dir.write('B/TooManyOffenses', <<~YAML)
---
B/TooManyOffenses:
Exclude:
- 'a.rb'
YAML
todo_dir.write('A/Offense', <<~YAML)
---
A/Offense:
Exclude:
- 'a.rb'
YAML
todo_dir.inspect_all
File.write('.rubocop_todo.yml', <<~YAML)
---
B/TooManyOffenses:
Exclude:
- 'x.rb'
A/Offense:
Exclude:
- 'y.rb'
YAML
end
it 'raises an error' do
expect { run_formatter }.to raise_error(RuntimeError, <<~TXT)
Multiple configurations found for cops:
- A/Offense
- B/TooManyOffenses
TXT
end
end
end
context 'without offenses detected' do
before do
formatter.started(%w[a.rb b.rb])
formatter.file_finished('a.rb', [])
formatter.file_finished('b.rb', [])
formatter.finished(%w[a.rb b.rb])
end
it 'does not output anything' do
expect(stdout.string).to eq('')
end
it 'does not write any YAML files' do
expect(rubocop_todo_dir_listing).to be_empty
end
end
context 'without files to inspect' do
before do
formatter.started([])
formatter.finished([])
end
it 'does not output anything' do
expect(stdout.string).to eq('')
end
it 'does not write any YAML files' do
expect(rubocop_todo_dir_listing).to be_empty
end
end
private
def rubocop_todo_dir_listing
Dir.glob("#{rubocop_todo_dir}/**/*")
.select { |path| File.file?(path) }
.map { |path| path.delete_prefix("#{rubocop_todo_dir}/") }
end
def todo_yml(cop_name)
todo_dir.read(cop_name)
end
def fake_offense(cop_name)
double(:offense, cop_name: cop_name)
end
def stub_rubocop_registry(**cops)
rubocop_registry = double(:rubocop_registry)
allow(RuboCop::Cop::Registry).to receive(:global).and_return(rubocop_registry)
allow(rubocop_registry).to receive(:find_by_cop_name)
.with(String).and_return(nil)
cops.each do |cop_name, attributes|
allow(rubocop_registry).to receive(:find_by_cop_name)
.with(cop_name).and_return(fake_cop(**attributes))
end
end
def fake_cop(autocorrectable:)
double(:cop, support_autocorrect?: autocorrectable)
end
end
# rubocop:enable RSpec/VerifiedDoubles
# frozen_string_literal: true
require 'fast_spec_helper'
require 'fileutils'
require 'active_support/inflector/inflections'
require_relative '../../rubocop/todo_dir'
RSpec.describe RuboCop::TodoDir do
let(:todo_dir) { described_class.new(directory) }
let(:directory) { Dir.mktmpdir }
let(:cop_name) { 'RSpec/VariableInstance' }
let(:cop_name_underscore) { ActiveSupport::Inflector.underscore(cop_name) }
let(:yaml_path) { "#{File.join(directory, cop_name_underscore)}.yml" }
around do |example|
Dir.chdir(directory) do
example.run
end
end
after do
FileUtils.remove_entry(directory)
end
describe '#initialize' do
context 'when passing inflector' do
let(:fake_inflector) { double(:inflector) } # rubocop:disable RSpec/VerifiedDoubles
let(:todo_dir) { described_class.new(directory, inflector: fake_inflector) }
before do
allow(fake_inflector).to receive(:underscore)
.with(cop_name)
.and_return(cop_name_underscore)
end
it 'calls .underscore' do
todo_dir.write(cop_name, 'a')
expect(fake_inflector).to have_received(:underscore)
end
end
end
describe '#directory' do
subject { todo_dir.directory }
it { is_expected.to eq(directory) }
end
describe '#read' do
let(:content) { 'a' }
subject { todo_dir.read(cop_name) }
context 'when file exists' do
before do
todo_dir.write(cop_name, content)
end
it { is_expected.to eq(content) }
end
context 'when file is missing' do
it { is_expected.to be_nil }
end
end
describe '#write' do
let(:content) { 'a' }
subject { todo_dir.write(cop_name, content) }
it { is_expected.to eq(yaml_path) }
it 'writes content to YAML file' do
subject
expect(File.read(yaml_path)).to eq(content)
end
end
describe '#inspect' do
subject { todo_dir.inspect(cop_name) }
context 'with existing YAML file' do
before do
todo_dir.write(cop_name, 'a')
end
it { is_expected.to eq(true) }
it 'moves YAML file to .inspect' do
subject
expect(File).not_to exist(yaml_path)
expect(File).to exist("#{yaml_path}.inspect")
end
end
context 'with missing YAML file' do
it { is_expected.to eq(false) }
end
end
describe '#inspect_all' do
subject { todo_dir.inspect_all }
context 'with YAML files' do
before do
todo_dir.write(cop_name, 'a')
todo_dir.write('Other/Rule', 'a')
todo_dir.write('Very/Nested/Rule', 'a')
end
it { is_expected.to eq(3) }
it 'moves all YAML files to .inspect' do
subject
expect(Dir.glob('**/*.yml')).to be_empty
expect(Dir.glob('**/*.yml.inspect').size).to eq(3)
end
end
context 'with non-YAML files' do
before do
File.write('file', 'a')
File.write('file.txt', 'a')
File.write('file.yaml', 'a') # not .yml
end
it { is_expected.to eq(0) }
it 'does not move non-YAML files' do
subject
expect(Dir.glob('**/*'))
.to contain_exactly('file', 'file.txt', 'file.yaml')
end
end
context 'without files' do
it { is_expected.to eq(0) }
end
end
describe '#list_inspect' do
let(:content) { 'a' }
subject { todo_dir.list_inspect }
context 'when file exists and is being inspected' do
before do
todo_dir.write(cop_name, content)
todo_dir.inspect_all
end
it do
is_expected.to contain_exactly("#{yaml_path}.inspect")
end
end
context 'when file exists but not being inspected' do
before do
todo_dir.write(cop_name, content)
end
it { is_expected.to be_empty }
end
context 'when file is missing' do
it { is_expected.to be_empty }
end
end
describe '#delete_inspected' do
subject { todo_dir.delete_inspected }
context 'with YAML files' do
before do
todo_dir.write(cop_name, 'a')
todo_dir.write('Other/Rule', 'a')
todo_dir.write('Very/Nested/Rule', 'a')
todo_dir.inspect_all
end
it { is_expected.to eq(3) }
it 'deletes all .inspected YAML files' do
subject
expect(Dir.glob('**/*.yml.inspect')).to be_empty
end
end
context 'with non-YAML files' do
before do
File.write('file.inspect', 'a')
File.write('file.txt.inspect', 'a')
File.write('file.yaml.inspect', 'a') # not .yml
end
it { is_expected.to eq(0) }
it 'does not delete non-YAML files' do
subject
expect(Dir.glob('**/*')).to contain_exactly(
'file.inspect', 'file.txt.inspect', 'file.yaml.inspect')
end
end
context 'without files' do
it { is_expected.to eq(0) }
end
end
end
...@@ -458,11 +458,6 @@ RSpec.configure do |config| ...@@ -458,11 +458,6 @@ RSpec.configure do |config|
end end
end end
# Allows stdout to be redirected to reduce noise
config.before(:each, :silence_stdout) do
$stdout = StringIO.new
end
# Makes diffs show entire non-truncated values. # Makes diffs show entire non-truncated values.
config.before(:each, unlimited_max_formatted_output_length: true) do |_example| config.before(:each, unlimited_max_formatted_output_length: true) do |_example|
config.expect_with :rspec do |c| config.expect_with :rspec do |c|
...@@ -475,10 +470,6 @@ RSpec.configure do |config| ...@@ -475,10 +470,6 @@ RSpec.configure do |config|
allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" }) allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" })
end end
config.after(:each, :silence_stdout) do
$stdout = STDOUT
end
config.disable_monkey_patching! config.disable_monkey_patching!
end end
......
# frozen_string_literal: true
RSpec.configure do |config|
# Allows stdout to be redirected to reduce noise
config.before(:each, :silence_stdout) do
$stdout = StringIO.new
end
config.after(:each, :silence_stdout) do
$stdout = STDOUT
end
end
# frozen_string_literal: true
# rubocop:disable RSpec/VerifiedDoubles
require 'fast_spec_helper'
require 'rake'
require 'fileutils'
require_relative '../support/silence_stdout'
require_relative '../support/helpers/next_instance_of'
require_relative '../support/helpers/rake_helpers'
require_relative '../../rubocop/todo_dir'
RSpec.describe 'rubocop rake tasks', :silence_stdout do
include RakeHelpers
before do
stub_const('Rails', double(:rails_env))
allow(Rails).to receive(:env).and_return(double(production?: false))
stub_const('ENV', ENV.to_hash.dup)
Rake.application.rake_require 'tasks/rubocop'
end
describe 'todo:generate', :aggregate_failures do
let(:tmp_dir) { Dir.mktmpdir }
let(:rubocop_todo_dir) { File.join(tmp_dir, '.rubocop_todo') }
let(:todo_dir) { RuboCop::TodoDir.new(rubocop_todo_dir) }
around do |example|
Dir.chdir(tmp_dir) do
with_inflections do
example.run
end
end
end
before do
allow(RuboCop::TodoDir).to receive(:new).and_return(todo_dir)
# This Ruby file will trigger the following 3 offenses.
File.write('a.rb', <<~RUBY)
a+b
RUBY
# Mimic GitLab's .rubocop_todo.yml avoids relying on RuboCop's
# default.yml configuration.
File.write('.rubocop.yml', <<~YAML)
<% unless ENV['REVEAL_RUBOCOP_TODO'] == '1' %>
<% Dir.glob('.rubocop_todo/**/*.yml').each do |rubocop_todo_yaml| %>
- '<%= rubocop_todo_yaml %>'
<% end %>
- '.rubocop_todo.yml'
<% end %>
AllCops:
NewCops: enable # Avoiding RuboCop warnings
Layout/SpaceAroundOperators:
Enabled: true
Layout/TrailingEmptyLines:
Enabled: true
Lint/Syntax:
Enabled: true
Style/FrozenStringLiteralComment:
Enabled: true
YAML
# Required to verify that we are revealing all TODOs via
# ENV['REVEAL_RUBOCOP_TODO'] = '1'.
# This file can be removed from specs after we've moved all offenses from
# .rubocop_todo.yml to .rubocop_todo/**/*.yml.
File.write('.rubocop_todo.yml', <<~YAML)
# Too many offenses
Layout/SpaceAroundOperators:
Enabled: false
YAML
# Previous offense now fixed.
todo_dir.write('Lint/Syntax', '')
end
after do
FileUtils.remove_entry(tmp_dir)
end
context 'without arguments' do
let(:run_task) { run_rake_task('rubocop:todo:generate') }
it 'generates TODOs for all RuboCop rules' do
expect { run_task }.to output(<<~OUTPUT).to_stdout
Generating RuboCop TODOs with:
rubocop --parallel --format RuboCop::Formatter::TodoFormatter
This might take a while...
Written to .rubocop_todo/layout/space_around_operators.yml
Written to .rubocop_todo/layout/trailing_empty_lines.yml
Written to .rubocop_todo/style/frozen_string_literal_comment.yml
OUTPUT
expect(rubocop_todo_dir_listing).to contain_exactly(
'layout/space_around_operators.yml',
'layout/trailing_empty_lines.yml',
'style/frozen_string_literal_comment.yml'
)
end
it 'sets acronyms for inflections' do
run_task
expect(ActiveSupport::Inflector.inflections.acronyms).to include(
'rspec' => 'RSpec',
'graphql' => 'GraphQL'
)
end
end
context 'with cop names as arguments' do
let(:run_task) do
cop_names = %w[
Style/FrozenStringLiteralComment Layout/TrailingEmptyLines
Lint/Syntax
]
run_rake_task('rubocop:todo:generate', cop_names)
end
it 'generates TODOs for given RuboCop cops' do
expect { run_task }.to output(<<~OUTPUT).to_stdout
Generating RuboCop TODOs with:
rubocop --parallel --format RuboCop::Formatter::TodoFormatter --only Layout/TrailingEmptyLines,Lint/Syntax,Style/FrozenStringLiteralComment
This might take a while...
Written to .rubocop_todo/layout/trailing_empty_lines.yml
Written to .rubocop_todo/style/frozen_string_literal_comment.yml
OUTPUT
expect(rubocop_todo_dir_listing).to contain_exactly(
'layout/trailing_empty_lines.yml',
'style/frozen_string_literal_comment.yml'
)
end
end
private
def rubocop_todo_dir_listing
Dir.glob("#{rubocop_todo_dir}/**/*")
.select { |path| File.file?(path) }
.map { |path| path.delete_prefix("#{rubocop_todo_dir}/") }
end
def with_inflections
original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
yield
ensure
ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
end
end
end
# rubocop:enable RSpec/VerifiedDoubles
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment