Skip to content

Commit

Permalink
Add restrictive_version_specifiers to Bundler/GemComment (#9358)
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinDaugherty committed Apr 11, 2021
1 parent 1f1e59c commit aa5f841
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 13 deletions.
@@ -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

0 comments on commit aa5f841

Please sign in to comment.