diff --git a/.rubocop.yml b/.rubocop.yml index 42e2376986b..be4ea6da4d0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,6 +15,7 @@ AllCops: - '.git/**/*' - 'bin/*' TargetRubyVersion: 2.4 + SuggestExtensions: false Naming/PredicateName: # Method define macros for dynamically generated method. diff --git a/changelog/change_added_notification_if_any_gems_are.md b/changelog/change_added_notification_if_any_gems_are.md new file mode 100644 index 00000000000..db04ad8fe4e --- /dev/null +++ b/changelog/change_added_notification_if_any_gems_are.md @@ -0,0 +1 @@ +* [#9122](https://github.com/rubocop-hq/rubocop/issues/9122): Added tip message if any gems are loaded that have RuboCop extensions. ([@dvandersluis][]) diff --git a/config/default.yml b/config/default.yml index e7e4e82178d..6cc753567e4 100644 --- a/config/default.yml +++ b/config/default.yml @@ -139,6 +139,16 @@ AllCops: # from the lock file.) If the Ruby version is still unresolved, RuboCop will # use the oldest officially supported Ruby version (currently Ruby 2.4). TargetRubyVersion: ~ + # Determines if a notification for extension libraries should be shown when + # rubocop is run. Keys are the name of the extension, and values are an array + # of gems in the Gemfile that the extension is suggested for, if not already + # included. + SuggestExtensions: + rubocop-rails: [rails] + rubocop-rspec: [rspec, rspec-rails] + rubocop-minitest: [minitest] + rubocop-sequel: [sequel] + rubocop-rake: [rake] #################### Bundler ############################### diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 639ca51e68a..23c32cf83dc 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -649,6 +649,7 @@ require_relative 'rubocop/cli/command/execute_runner' require_relative 'rubocop/cli/command/init_dotfile' require_relative 'rubocop/cli/command/show_cops' +require_relative 'rubocop/cli/command/suggest_extensions' require_relative 'rubocop/cli/command/version' require_relative 'rubocop/config_regeneration' require_relative 'rubocop/options' diff --git a/lib/rubocop/cli.rb b/lib/rubocop/cli.rb index 646c586a401..d38add9314e 100644 --- a/lib/rubocop/cli.rb +++ b/lib/rubocop/cli.rb @@ -69,10 +69,14 @@ def execute_runners if @options[:auto_gen_config] run_command(:auto_gen_config) else - run_command(:execute_runner) + run_command(:execute_runner).tap { suggest_extensions } end end + def suggest_extensions + run_command(:suggest_extensions) + end + def validate_options_vs_config if @options[:parallel] && !@config_store.for_pwd.for_all_cops['UseCache'] diff --git a/lib/rubocop/cli/command/suggest_extensions.rb b/lib/rubocop/cli/command/suggest_extensions.rb new file mode 100644 index 00000000000..d53e85c09de --- /dev/null +++ b/lib/rubocop/cli/command/suggest_extensions.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module RuboCop + class CLI + module Command + # Run all the selected cops and report the result. + # @api private + class SuggestExtensions < Base + # Combination of short and long formatter names. + INCLUDED_FORMATTERS = %w[p progress fu fuubar pa pacman].freeze + + self.command_name = :suggest_extensions + + def run + return if skip? || extensions.none? + + puts + puts 'Tip: Based on detected gems, the following '\ + 'RuboCop extension libraries might be helpful:' + + extensions.each do |extension| + puts " * #{extension} (http://github.com/rubocop-hq/#{extension})" + end + + puts + puts 'You can opt out of this message by adding the following to your config:' + puts ' AllCops:' + puts ' SuggestExtensions: false' + puts if @options[:display_time] + end + + private + + def skip? + # Disable outputting the notification: + # 1. On CI + # 2. When given RuboCop options that it doesn't make sense for + # 3. For all formatters except specified in `INCLUDED_FORMATTERS'` + ENV['CI'] || + @options[:only] || @options[:debug] || @options[:list_target_files] || @options[:out] || + !INCLUDED_FORMATTERS.include?(current_formatter) + end + + def current_formatter + @options[:format] || @config_store.for_pwd.for_all_cops['DefaultFormatter'] || 'p' + end + + def extensions + extensions = @config_store.for_pwd.for_all_cops['SuggestExtensions'] + return [] unless extensions + + extensions.select { |_, v| (v & dependent_gems).any? }.keys - dependent_gems + end + + def dependent_gems + # This only includes gems in Gemfile, not in lockfile + Bundler.load.dependencies.map(&:name) + end + + def puts(*args) + output = (@options[:stderr] ? $stderr : $stdout) + output.puts(*args) + end + end + end + end +end diff --git a/lib/rubocop/config_loader_resolver.rb b/lib/rubocop/config_loader_resolver.rb index 741d02c01bd..4359669bf8b 100644 --- a/lib/rubocop/config_loader_resolver.rb +++ b/lib/rubocop/config_loader_resolver.rb @@ -92,7 +92,7 @@ def merge(base_hash, derived_hash, **opts) keys_appearing_in_both.each do |key| if opts[:unset_nil] && derived_hash[key].nil? result.delete(key) - elsif base_hash[key].is_a?(Hash) + elsif merge_hashes?(base_hash, derived_hash, key) result[key] = merge(base_hash[key], derived_hash[key], **opts) elsif should_union?(base_hash, key, opts[:inherit_mode]) result[key] = base_hash[key] | derived_hash[key] @@ -164,6 +164,10 @@ def should_union?(base_hash, key, inherit_mode) inherit_mode['merge'].include?(key) end + def merge_hashes?(base_hash, derived_hash, key) + base_hash[key].is_a?(Hash) && derived_hash[key].is_a?(Hash) + end + def base_configs(path, inherit_from, file) configs = Array(inherit_from).compact.map do |f| ConfigLoader.load_file(inherited_file(path, f, file)) diff --git a/spec/rubocop/cli/cli_autocorrect_spec.rb b/spec/rubocop/cli/cli_autocorrect_spec.rb index 532c16bafbb..b10cd1ca8d6 100644 --- a/spec/rubocop/cli/cli_autocorrect_spec.rb +++ b/spec/rubocop/cli/cli_autocorrect_spec.rb @@ -7,6 +7,7 @@ before do RuboCop::ConfigLoader.default_configuration = nil + RuboCop::ConfigLoader.default_configuration.for_all_cops['SuggestExtensions'] = false end it 'does not correct ExtraSpacing in a hash that would be changed back' do diff --git a/spec/rubocop/cli/cli_options_spec.rb b/spec/rubocop/cli/cli_options_spec.rb index e55e43db837..b7024c1901f 100644 --- a/spec/rubocop/cli/cli_options_spec.rb +++ b/spec/rubocop/cli/cli_options_spec.rb @@ -766,6 +766,10 @@ class SomeCop < Cop end describe '-d/--debug' do + before do + RuboCop::ConfigLoader.default_configuration = nil + end + it 'shows config files' do create_file('example1.rb', "\tputs 0") expect(cli.run(['--debug', 'example1.rb'])).to eq(1)