diff --git a/CHANGELOG.md b/CHANGELOG.md index a53fc1330a4..871a2ce58e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +* [#8778](https://github.com/rubocop-hq/rubocop/pull/8778): Add command line option `--regenerate-todo`. ([@dvandersluis][]) * [#8790](https://github.com/rubocop-hq/rubocop/pull/8790): Add `AllowedMethods` option to `Style/OptionalBooleanParameter` cop. ([@fatkodima][]) * [#8738](https://github.com/rubocop-hq/rubocop/issues/8738): Add autocorrection to `Style/DateTime`. ([@dvandersluis][]) diff --git a/docs/modules/ROOT/pages/configuration.adoc b/docs/modules/ROOT/pages/configuration.adoc index b79e94f548f..58d31335e55 100644 --- a/docs/modules/ROOT/pages/configuration.adoc +++ b/docs/modules/ROOT/pages/configuration.adoc @@ -586,7 +586,8 @@ behavior of a cop instead of disabling it completely. Then you can start removing the entries in the generated `.rubocop_todo.yml` file one by one as you work through all the offenses -in the code. +in the code. You can also regenerate your `.rubocop_todo.yml` using +the same options by running `rubocop --regenerate-todo`. Another way of silencing offense reports, aside from configuration, is through source code comments. These can be added manually or diff --git a/docs/modules/ROOT/pages/usage/basic_usage.adoc b/docs/modules/ROOT/pages/usage/basic_usage.adoc index 6df7803e4af..90178c49b41 100644 --- a/docs/modules/ROOT/pages/usage/basic_usage.adoc +++ b/docs/modules/ROOT/pages/usage/basic_usage.adoc @@ -199,11 +199,14 @@ $ rubocop -h | `-L/--list-target-files` | List all files RuboCop will inspect. -| `--no-auto-gen-timestamp` -| Don't include the date and time when --auto-gen-config was run in the config file it generates +| `--[no-]auto-gen-only-exclude` +| Generate only `Exclude` parameters and not `Max` when running `--auto-gen-config`, except if the number of files with offenses is bigger than `exclude-limit`. Default is false -| `--no-offense-counts` -| Don't show offense counts in config file generated by --auto-gen-config +| `--[no-]auto-gen-timestamp` +| Include the date and time when `--auto-gen-config` was run in the config file it generates. Default is true. + +| `--[no-]offense-counts` +| Show offense counts in config file generated by `--auto-gen-config`. Default is true. | `--only` | Run only the specified cop(s) and/or cops in the specified departments. @@ -217,6 +220,9 @@ $ rubocop -h | `-r/--require` | Require Ruby file (see xref:extensions.adoc#loading-extensions[Loading Extensions]). +| `--regenerate-todo` +| Regenerate the TODO list using the same options as the last time it was generated with `--auto-gen-config` (generation options can be overridden). + | `--safe` | Run only safe cops. diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 005bf21be7d..87973677172 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -629,6 +629,7 @@ require_relative 'rubocop/cli/command/init_dotfile' require_relative 'rubocop/cli/command/show_cops' require_relative 'rubocop/cli/command/version' +require_relative 'rubocop/config_regeneration' require_relative 'rubocop/options' require_relative 'rubocop/remote_config' require_relative 'rubocop/target_ruby' diff --git a/lib/rubocop/config_regeneration.rb b/lib/rubocop/config_regeneration.rb new file mode 100644 index 00000000000..bae43fea9be --- /dev/null +++ b/lib/rubocop/config_regeneration.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module RuboCop + # This class handles collecting the options for regenerating a TODO file. + # @api private + class ConfigRegeneration + AUTO_GENERATED_FILE = RuboCop::CLI::Command::AutoGenerateConfig::AUTO_GENERATED_FILE + COMMAND_REGEX = /(?<=`rubocop )(.*?)(?=`)/.freeze + DEFAULT_OPTIONS = { auto_gen_config: true }.freeze + + # Get options from the comment in the TODO file, and parse them as options + def options + # If there's no existing TODO file, generate one + return DEFAULT_OPTIONS unless todo_exists? + + match = generation_command.match(COMMAND_REGEX) + return DEFAULT_OPTIONS unless match + + options = match[1].split(' ') + Options.new.parse(options).first + end + + private + + def todo_exists? + File.exist?(AUTO_GENERATED_FILE) && !File.empty?(AUTO_GENERATED_FILE) + end + + def generation_command + File.foreach(AUTO_GENERATED_FILE).take(2).last + end + end +end diff --git a/lib/rubocop/formatter/disabled_config_formatter.rb b/lib/rubocop/formatter/disabled_config_formatter.rb index 840fb2744a7..5d8f7f4b7c3 100644 --- a/lib/rubocop/formatter/disabled_config_formatter.rb +++ b/lib/rubocop/formatter/disabled_config_formatter.rb @@ -32,7 +32,6 @@ def file_started(_file, _file_info) @exclude_limit_option = @options[:exclude_limit] @exclude_limit = Integer(@exclude_limit_option || RuboCop::Options::DEFAULT_MAXIMUM_EXCLUSION_ITEMS) - @show_offense_counts = !@options[:no_offense_counts] end def file_finished(file, offenses) @@ -56,6 +55,14 @@ def finished(_inspected_files) private + def show_timestamp? + @options.fetch(:auto_gen_timestamp, true) + end + + def show_offense_counts? + @options.fetch(:offense_counts, true) + end + def command command = 'rubocop --auto-gen-config' @@ -66,15 +73,15 @@ def command format(' --exclude-limit %d', limit: Integer(@exclude_limit_option)) end - command += ' --no-offense-counts' if @options[:no_offense_counts] + command += ' --no-offense-counts' unless show_offense_counts? - command += ' --no-auto-gen-timestamp' if @options[:no_auto_gen_timestamp] + command += ' --no-auto-gen-timestamp' unless show_timestamp? command end def timestamp - @options[:no_auto_gen_timestamp] ? '' : "on #{Time.now.utc} " + show_timestamp? ? "on #{Time.now.utc} " : '' end def output_offenses @@ -112,7 +119,7 @@ def set_max(cfg, cop_name) end def output_cop_comments(output_buffer, cfg, cop_name, offense_count) - output_buffer.puts "# Offense count: #{offense_count}" if @show_offense_counts + output_buffer.puts "# Offense count: #{offense_count}" if show_offense_counts? cop_class = Cop::Registry.global.find_by_cop_name(cop_name) output_buffer.puts '# Cop supports --auto-correct.' if cop_class&.support_autocorrect? diff --git a/lib/rubocop/options.rb b/lib/rubocop/options.rb index cecfd6ac64b..12e8ce9ead8 100644 --- a/lib/rubocop/options.rb +++ b/lib/rubocop/options.rb @@ -114,20 +114,19 @@ def add_configuration_options(opts) def add_auto_gen_options(opts) option(opts, '--auto-gen-config') + option(opts, '--regenerate-todo') do + @options.replace(ConfigRegeneration.new.options.merge(@options)) + end + option(opts, '--exclude-limit COUNT') do @validator.validate_exclude_limit_option end option(opts, '--disable-uncorrectable') - option(opts, '--no-offense-counts') do - @options[:no_offense_counts] = true - end - - option(opts, '--auto-gen-only-exclude') - option(opts, '--no-auto-gen-timestamp') do - @options[:no_auto_gen_timestamp] = true - end + option(opts, '--[no-]offense-counts') + option(opts, '--[no-]auto-gen-only-exclude') + option(opts, '--[no-]auto-gen-timestamp') option(opts, '--init') end @@ -318,7 +317,7 @@ def validate_auto_gen_config message = '--%s can only be used together with --auto-gen-config.' - %i[exclude_limit no_offense_counts no_auto_gen_timestamp + %i[exclude_limit offense_counts auto_gen_timestamp auto_gen_only_exclude].each do |option| if @options.key?(option) raise OptionArgumentError, @@ -423,17 +422,20 @@ module OptionsHelp config: 'Specify configuration file.', auto_gen_config: ['Generate a configuration file acting as a', 'TODO list.'], - no_offense_counts: ['Do not include offense counts in configuration', - 'file generated by --auto-gen-config.'], - no_auto_gen_timestamp: - ['Do not include the date and time when', - 'the --auto-gen-config was run in the file it', - 'generates.'], + regenerate_todo: ['Regenerate the TODO configuration file using', + 'the last configuration. If there is no existing', + 'TODO file, acts like --auto-gen-config.'], + offense_counts: ['Include offense counts in configuration', + 'file generated by --auto-gen-config.', + 'Default is true.'], + auto_gen_timestamp: + ['Include the date and time when the --auto-gen-config', + 'was run in the file it generates. Default is true.'], auto_gen_only_exclude: ['Generate only Exclude parameters and not Max', 'when running --auto-gen-config, except if the', 'number of files with offenses is bigger than', - 'exclude-limit.'], + 'exclude-limit. Default is false.'], exclude_limit: ['Used together with --auto-gen-config to', 'set the limit for how many Exclude', "properties to generate. Default is #{MAX_EXCL}."], diff --git a/spec/rubocop/config_regeneration_spec.rb b/spec/rubocop/config_regeneration_spec.rb new file mode 100644 index 00000000000..bae4e9f4d08 --- /dev/null +++ b/spec/rubocop/config_regeneration_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::ConfigRegeneration, :isolated_environment do + include FileHelper + + subject(:config_regeneration) { described_class.new } + + describe '#options' do + subject { config_regeneration.options } + + context 'when no todo file exists' do + it { is_expected.to eq(auto_gen_config: true) } + end + + context 'when there is a blank todo file' do + before { create_file('.rubocop_todo.yml', nil) } + + it { is_expected.to eq(auto_gen_config: true) } + end + + context 'when the todo file is malformed' do + before { create_file('.rubocop_todo.yml', 'todo') } + + it { is_expected.to eq(auto_gen_config: true) } + end + + context 'it parses options from the generation comment' do + let(:header) do + <<~HEADER + # This configuration was generated by + # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 100 --no-offense-counts --no-auto-gen-timestamp` + # on 2020-06-12 17:42:47 UTC using RuboCop version 0.85.1. + HEADER + end + + let(:expected_options) do + { + auto_gen_config: true, + auto_gen_only_exclude: true, + exclude_limit: '100', + offense_counts: false, + auto_gen_timestamp: false + } + end + + before { create_file('.rubocop_todo.yml', header) } + + it { is_expected.to eq(expected_options) } + end + end +end diff --git a/spec/rubocop/options_spec.rb b/spec/rubocop/options_spec.rb index 3b5bed02115..c32e408cb02 100644 --- a/spec/rubocop/options_spec.rb +++ b/spec/rubocop/options_spec.rb @@ -52,21 +52,24 @@ def abs(path) files are present in the directory tree. --auto-gen-config Generate a configuration file acting as a TODO list. + --regenerate-todo Regenerate the TODO configuration file using + the last configuration. If there is no existing + TODO file, acts like --auto-gen-config. --exclude-limit COUNT Used together with --auto-gen-config to set the limit for how many Exclude properties to generate. Default is 15. --disable-uncorrectable Used with --auto-correct to annotate any offenses that do not support autocorrect with `rubocop:todo` comments. - --no-offense-counts Do not include offense counts in configuration + --[no-]offense-counts Include offense counts in configuration file generated by --auto-gen-config. - --auto-gen-only-exclude Generate only Exclude parameters and not Max + Default is true. + --[no-]auto-gen-only-exclude Generate only Exclude parameters and not Max when running --auto-gen-config, except if the number of files with offenses is bigger than - exclude-limit. - --no-auto-gen-timestamp Do not include the date and time when - the --auto-gen-config was run in the file it - generates. + exclude-limit. Default is false. + --[no-]auto-gen-timestamp Include the date and time when the --auto-gen-config + was run in the file it generates. Default is true. --init Generate a .rubocop.yml file in the current directory. -f, --format FORMATTER Choose an output formatter. This option can be specified multiple times to enable @@ -370,6 +373,75 @@ def abs(path) end end + describe '--regenerate-todo' do + subject(:parsed_options) { options.parse(command_line_options).first } + + let(:config_regeneration) do + instance_double(RuboCop::ConfigRegeneration, options: todo_options) + end + let(:todo_options) { { auto_gen_config: true, exclude_limit: '100', offense_counts: false } } + + before do + allow(RuboCop::ConfigRegeneration).to receive(:new).and_return(config_regeneration) + end + + context 'when no other options are given' do + let(:command_line_options) { %w[--regenerate-todo] } + let(:expected_options) do + { + auto_gen_config: true, + exclude_limit: '100', + offense_counts: false, + regenerate_todo: true + } + end + + it { is_expected.to eq(expected_options) } + end + + context 'when todo options are overridden before --regenerate-todo' do + let(:command_line_options) { %w[--exclude-limit 50 --regenerate-todo] } + let(:expected_options) do + { + auto_gen_config: true, + exclude_limit: '50', + offense_counts: false, + regenerate_todo: true + } + end + + it { is_expected.to eq(expected_options) } + end + + context 'when todo options are overridden after --regenerate-todo' do + let(:command_line_options) { %w[--regenerate-todo --exclude-limit 50] } + let(:expected_options) do + { + auto_gen_config: true, + exclude_limit: '50', + offense_counts: false, + regenerate_todo: true + } + end + + it { is_expected.to eq(expected_options) } + end + + context 'when disabled options are overridden to be enabled' do + let(:command_line_options) { %w[--regenerate-todo --offense-counts] } + let(:expected_options) do + { + auto_gen_config: true, + exclude_limit: '100', + offense_counts: true, + regenerate_todo: true + } + end + + it { is_expected.to eq(expected_options) } + end + end + describe '-s/--stdin' do before do $stdin = StringIO.new