Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show extension cop versions when using --verbose-version option #8908

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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