Skip to content

Commit

Permalink
Show extension cop versions when using --verbose-version option
Browse files Browse the repository at this point in the history
## Summary

This PR shows extension cop versions when using `--verbose-version` option.

Before:

```console
% rubocop -V
0.93.1 (using Parser 2.7.2.0, rubocop-ast 0.8.0, running on ruby 2.7.2 x86_64-darwin19)
```

After:

```console
% rubocop -V
0.93.1 (using Parser 2.7.2.0, rubocop-ast 0.8.0, running on ruby 2.7.2 x86_64-darwin19)
  - rubocop-performance 1.8.1
  - rubocop-rspec 1.43.2
```

This information is not shown in `rubocop -v` as it is needed in the bug report, first.

## More Information

It shows extension cop versions named rubocop-feature (e.g. rubocop-performance, rubocop-rspec).
Extention names separated by two or more, such as rubocop-feature-foo, are not
supported by this PR. First of all, this is aimed at supporting the extension cop managed
by RuboCop Headquarters.

This feature searches for version constant in two ways:

- RuboCop core version style (e.g. `RuboCop::Performance::Version::STRING`)
- `bundle gem` version style (e.g. `RuboCop::Rake::VERSION`)

This enriches the information when reporting bugs from users.

It was inspired by RSpec. Thank you.

```console
% bundle exec rspec --version
RSpec 3.9
  - rspec-core 3.9.3
  - rspec-expectations 3.9.2
  - rspec-mocks 3.9.1
  - rspec-support 3.9.3
```
  • Loading branch information
koic committed Oct 20, 2020
1 parent 203d531 commit 96d2809
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -20,6 +20,7 @@
* [#8692](https://github.com/rubocop-hq/rubocop/pull/8692): Default changed to disallow `Layout/TrailingWhitespace` in heredoc. ([@marcandre][])
* [#8894](https://github.com/rubocop-hq/rubocop/issues/8894): Make `Security/Open` aware of `URI.open`. ([@koic][])
* [#8901](https://github.com/rubocop-hq/rubocop/issues/8901): Fix false positive for `Naming/BinaryOperatorParameterName` when defining `=~`. ([@zajn][])
* [#8908](https://github.com/rubocop-hq/rubocop/pull/8908): Show extension cop versions when using `--verbose-version` option. ([@koic][])

## 0.93.1 (2020-10-12)

Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cli/command/version.rb
Expand Up @@ -10,7 +10,7 @@ class Version < Base

def run
puts RuboCop::Version.version(debug: false) if @options[:version]
puts RuboCop::Version.version(debug: true) if @options[:verbose_version]
puts RuboCop::Version.version(debug: true, env: env) if @options[:verbose_version]
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/rubocop/config.rb
Expand Up @@ -37,6 +37,10 @@ def self.create(hash, path)
new(hash, path).check
end

def loaded_features
@loaded_features ||= ConfigLoader.loaded_features
end

def check
deprecation_check do |deprecation_message|
warn("#{loaded_path} - #{deprecation_message}")
Expand Down
21 changes: 19 additions & 2 deletions lib/rubocop/config_loader.rb
Expand Up @@ -35,12 +35,13 @@ def clear_options
end

def load_file(file)
path = File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
path = file_path(file)

hash = load_yaml_configuration(path)

# Resolve requires first in case they define additional cops
resolver.resolve_requires(path, hash)
loaded_features = resolver.resolve_requires(path, hash)
add_loaded_features(loaded_features)

add_missing_namespaces(path, hash)

Expand Down Expand Up @@ -172,8 +173,24 @@ def merge_with_default(config, config_file, unset_nil: true)
resolver.merge_with_default(config, config_file, unset_nil: unset_nil)
end

def loaded_features
@loaded_features.flatten.compact
end

private

def file_path(file)
File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
end

def add_loaded_features(loaded_features)
if instance_variable_defined?(:@loaded_features)
instance_variable_get(:@loaded_features) << loaded_features
else
instance_variable_set(:@loaded_features, [loaded_features])
end
end

def find_project_dotfile(target_dir)
find_file_upwards(DOTFILE, target_dir, project_root)
end
Expand Down
12 changes: 7 additions & 5 deletions lib/rubocop/config_loader_resolver.rb
Expand Up @@ -9,11 +9,13 @@ module RuboCop
class ConfigLoaderResolver
def resolve_requires(path, hash)
config_dir = File.dirname(path)
Array(hash.delete('require')).each do |r|
if r.start_with?('.')
require(File.join(config_dir, r))
else
require(r)
hash.delete('require').tap do |loaded_features|
Array(loaded_features).each do |feature|
if feature.start_with?('.')
require(File.join(config_dir, feature))
else
require(feature)
end
end
end
end
Expand Down
58 changes: 53 additions & 5 deletions lib/rubocop/version.rb
Expand Up @@ -9,18 +9,66 @@ module Version
'rubocop-ast %<rubocop_ast_version>s, ' \
'running on %<ruby_engine>s %<ruby_version>s %<ruby_platform>s)'

CANONICAL_FEATURE_NAMES = { 'Rspec' => 'RSpec' }.freeze

# @api private
def self.version(debug: false)
def self.version(debug: false, env: nil)
if debug
format(MSG, version: STRING, parser_version: Parser::VERSION,
rubocop_ast_version: RuboCop::AST::Version::STRING,
ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
ruby_platform: RUBY_PLATFORM)
verbose_version = format(MSG, version: STRING, parser_version: Parser::VERSION,
rubocop_ast_version: RuboCop::AST::Version::STRING,
ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
ruby_platform: RUBY_PLATFORM)
extension_versions = extension_versions(env)
return verbose_version if extension_versions.empty?

<<~VERSIONS
#{verbose_version}
#{extension_versions.join("\n")}
VERSIONS
else
STRING
end
end

# @api private
def self.extension_versions(env)
env.config_store.for_pwd.loaded_features.sort.map do |loaded_feature|
next unless (match = loaded_feature.match(/rubocop-(?<feature>.*)/))

feature = match[:feature]
begin
require "rubocop/#{feature}/version"
rescue LoadError
# Not worth mentioning libs that are not installed
else
next unless (feature_version = feature_version(feature))

" - #{loaded_feature} #{feature_version}"
end
end.compact
end

# Returns feature version in one of two ways:
#
# * Find by RuboCop core version style (e.g. rubocop-performance, rubocop-rspec)
# * Find by `bundle gem` version style (e.g. rubocop-rake)
#
# @api private
def self.feature_version(feature)
capitalized_feature = feature.capitalize
extension_name = CANONICAL_FEATURE_NAMES.fetch(capitalized_feature, capitalized_feature)

# Find by RuboCop core version style (e.g. rubocop-performance, rubocop-rspec)
RuboCop.const_get(extension_name)::Version::STRING
rescue NameError
begin
# Find by `bundle gem` version style (e.g. rubocop-rake, rubocop-packaging)
RuboCop.const_get(extension_name)::VERSION
rescue NameError
# noop
end
end

# @api private
def self.document_version
STRING.match('\d+\.\d+').to_s
Expand Down
25 changes: 23 additions & 2 deletions spec/rubocop/cli/cli_options_spec.rb
Expand Up @@ -152,8 +152,29 @@
it 'exits cleanly' do
expect(cli.run(['-V'])).to eq(0)
expect($stdout.string).to include(RuboCop::Version::STRING)
expect($stdout.string).to match(/Parser \d\.\d\.\d/)
expect($stdout.string).to match(/rubocop-ast \d\.\d\.\d/)
expect($stdout.string).to match(/Parser \d+\.\d+\.\d+/)
expect($stdout.string).to match(/rubocop-ast \d+\.\d+\.\d+/)
end

context 'when requiring extension cops' do
before do
create_file('.rubocop.yml', <<~YAML)
require:
- rubocop-performance
- rubocop-rspec
YAML
end

it 'shows with version of extension cops' do
# Run in different process that requiring rubocop-perfmance and rubocop-rspec
# does not affect other testing processes.
output = `ruby -I . "#{rubocop}" -V --disable-pending-cops`
expect(output).to include(RuboCop::Version::STRING)
expect(output).to match(/Parser \d+\.\d+\.\d+/)
expect(output).to match(/rubocop-ast \d+\.\d+\.\d+/)
expect(output).to match(/rubocop-performance \d+\.\d+\.\d+/)
expect(output).to match(/rubocop-rspec \d+\.\d+\.\d+/)
end
end
end

Expand Down

0 comments on commit 96d2809

Please sign in to comment.