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

Add restrictive_version_specifiers to Bundler/GemComment #9358

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
@@ -0,0 +1 @@
* [#9358](https://github.com/rubocop/rubocop/pull/9358): Support `restrictive_version_specificiers` option in `Bundler/GemComment` cop. ([@RobinDaugherty][])
47 changes: 41 additions & 6 deletions lib/rubocop/cop/bundler/gem_comment.rb
Expand Up @@ -3,15 +3,24 @@
module RuboCop
module Cop
module Bundler
# Add a comment describing each gem in your Gemfile.
# Each gem in the Gemfile should have a comment explaining
# its purpose in the project, or the reason for its version
# or source.
#
# Optionally, the "OnlyFor" configuration
# The optional "OnlyFor" configuration array
# can be used to only register offenses when the gems
# use certain options or have version specifiers.
# Add "version_specifiers" and/or the gem option names
# you want to check.
#
# A useful use-case is to enforce a comment when using
# When "version_specifiers" is included, a comment
# will be enforced if the gem has any version specifier.
#
# When "restrictive_version_specifiers" is included, a comment
# will be enforced if the gem has a version specifier that
# holds back the version of the gem.
#
# For any other value in the array, a comment will be enforced for
# a gem if an option by the same name is present.
# A useful use case is to enforce a comment when using
# options that change the source of a gem:
#
# - `bitbucket`
Expand All @@ -21,7 +30,8 @@ module Bundler
# - `source`
#
# For a full list of options supported by bundler,
# you can check the https://bundler.io/man/gemfile.5.html[official documentation].
# see https://bundler.io/man/gemfile.5.html
# .
#
# @example OnlyFor: [] (default)
# # bad
Expand All @@ -43,6 +53,18 @@ module Bundler
# # Version 2.1 introduces breaking change baz
# gem 'foo', '< 2.1'
#
# @example OnlyFor: ['restrictive_version_specifiers']
# # bad
#
# gem 'foo', '< 2.1'
#
# # good
#
# gem 'foo', '>= 1.0'
#
# # Version 2.1 introduces breaking change baz
# gem 'foo', '< 2.1'
#
# @example OnlyFor: ['version_specifiers', 'github']
# # bad
#
Expand All @@ -64,6 +86,8 @@ class GemComment < Base
MSG = 'Missing gem description comment.'
CHECKED_OPTIONS_CONFIG = 'OnlyFor'
VERSION_SPECIFIERS_OPTION = 'version_specifiers'
RESTRICTIVE_VERSION_SPECIFIERS_OPTION = 'restrictive_version_specifiers'
RESTRICTIVE_VERSION_PATTERN = /<|~>/.freeze
RESTRICT_ON_SEND = %i[gem].freeze

# @!method gem_declaration?(node)
Expand Down Expand Up @@ -113,6 +137,8 @@ def ignored_gem?(node)
def checked_options_present?(node)
(cop_config[CHECKED_OPTIONS_CONFIG].include?(VERSION_SPECIFIERS_OPTION) &&
version_specified_gem?(node)) ||
(cop_config[CHECKED_OPTIONS_CONFIG].include?(RESTRICTIVE_VERSION_SPECIFIERS_OPTION) &&
restrictive_version_specified_gem?(node)) ||
contains_checked_options?(node)
end

Expand All @@ -123,6 +149,15 @@ def version_specified_gem?(node)
node.arguments[1]&.str_type?
end

# Version specifications that restrict all updates going forward. This excludes versions
# like ">= 1.0" or "!= 2.0.3".
def restrictive_version_specified_gem?(node)
return unless version_specified_gem?(node)

node.arguments
.any? { |arg| arg&.str_type? && RESTRICTIVE_VERSION_PATTERN.match?(arg.to_s) }
end

def contains_checked_options?(node)
(Array(cop_config[CHECKED_OPTIONS_CONFIG]) & gem_options(node).map(&:to_s)).any?
end
Expand Down
78 changes: 71 additions & 7 deletions spec/rubocop/cop/bundler/gem_comment_spec.rb
Expand Up @@ -74,7 +74,7 @@
context 'when the "OnlyFor" option is set' do
before { cop_config['OnlyFor'] = checked_options }

context 'when the version specifiers are checked' do
context 'including "version_specifiers"' do
let(:checked_options) { ['version_specifiers'] }

context 'when a gem is commented' do
Expand All @@ -86,7 +86,7 @@
end
end

context 'when a gem is uncommented and has no extra options' do
context 'when a gem is uncommented and has no version specified' do
it 'does not register an offense' do
expect_no_offenses(<<-GEM, 'Gemfile')
gem 'rubocop'
Expand Down Expand Up @@ -120,7 +120,7 @@
end
end

context 'when a gem is uncommented and has a version specifier along with unrelated options' do
context 'when a gem is uncommented and has a version specifier along with other options' do
it 'registers an offense' do
expect_offense(<<-GEM, 'Gemfile')
gem 'rubocop', '~> 12.0', required: true
Expand All @@ -130,10 +130,74 @@
end
end

context 'and some other options are checked' do
context 'including "restrictive_version_specifiers"' do
let(:checked_options) { ['restrictive_version_specifiers'] }

context 'when a gem is commented' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY, 'Gemfile')
# Style-guide enforcer.
gem 'rubocop'
RUBY
end
end

context 'when a gem is uncommented and has no version specified' do
it 'does not register an offense' do
expect_no_offenses(<<-GEM, 'Gemfile')
gem 'rubocop'
GEM
end
end

context 'when a gem is uncommented and has options but no version specifiers' do
it 'does not register an offense' do
expect_no_offenses(<<-GEM, 'Gemfile')
gem 'rubocop', group: development
GEM
end
end

context 'when a gem is uncommented and has only a minimum version specifier' do
it 'does not register an offense' do
expect_no_offenses(<<-GEM, 'Gemfile')
gem 'rubocop', '>= 12.0'
GEM
end
end

context 'when a gem is uncommented and has a version specifier' do
it 'registers an offense' do
expect_offense(<<-GEM, 'Gemfile')
gem 'rubocop', '~> 12.0'
^^^^^^^^^^^^^^^^^^^^^^^^ Missing gem description comment.
GEM
end
end

context 'when a gem is uncommented and has both minimum and non-minimum version specifier' do
it 'registers an offense' do
expect_offense(<<-GEM, 'Gemfile')
gem 'rubocop', '~> 12.0', '>= 11.0'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Missing gem description comment.
GEM
end
end

context 'when a gem is uncommented and has a version specifier along with other options' do
it 'registers an offense' do
expect_offense(<<-GEM, 'Gemfile')
gem 'rubocop', '~> 12.0', required: true
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Missing gem description comment.
GEM
end
end
end

context 'including one or more option names but not "version_specifiers"' do
let(:checked_options) { %w[github required] }

context 'when a gem is uncommented and has one of the checked options' do
context 'when a gem is uncommented and has one of the specified options' do
it 'registers an offense' do
expect_offense(<<-GEM, 'Gemfile')
gem 'rubocop', github: 'some_user/some_fork'
Expand All @@ -142,15 +206,15 @@
end
end

context 'when a gem is uncommented and has a version specifier but no other options' do
context 'when a gem is uncommented and has a version specifier but none of the specified options' do
it 'does not register an offense' do
expect_no_offenses(<<-GEM, 'Gemfile')
gem 'rubocop', '~> 12.0'
GEM
end
end

context 'when a gem is uncommented and only unchecked options' do
context 'when a gem is uncommented and containts only options not specified' do
it 'does not register an offense' do
expect_no_offenses(<<-GEM, 'Gemfile')
gem 'rubocop', group: development
Expand Down