Skip to content

Commit

Permalink
[Fix rubocop#10570] Add new Gemspec/DependencyVersion cop
Browse files Browse the repository at this point in the history
Resolve: rubocop#10570.

This cop requires/forbids version specifications or a commit reference for gem dependency in gemspec.

## example

```ruby
# EnforcedStyle: required (default)

# bad
Gem::Specification.new do |spec|
  spec.add_dependency 'rubocop'
end

# good
Gem::Specification.new do |spec|
  spec.add_dependency 'rubocop', '~> 1.28'
end
```

```ruby
# EnforcedStyle: forbidden

# bad
Gem::Specification.new do |spec|
  spec.add_dependency 'rubocop', '~> 1.28'
end

# good
Gem::Specification.new do |spec|
  spec.add_dependency 'rubocop'
end
```
  • Loading branch information
nobuyo authored and bbatsov committed Apr 29, 2022
1 parent b2edf74 commit 5f526f7
Show file tree
Hide file tree
Showing 7 changed files with 878 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,6 @@ InternalAffairs/UndefinedConfig:
InternalAffairs/StyleDetectedApiUse:
Exclude:
- 'lib/rubocop/cop/mixin/percent_array.rb'

Gemspec/DependencyVersion:
Enabled: true
1 change: 1 addition & 0 deletions changelog/new_add_gemspec_dependency_version_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#10570](https://github.com/rubocop/rubocop/issues/10570): Add new `Gemspec/DependencyVersion` cop. ([@nobuyo][])
12 changes: 12 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ Gemspec/DateAssignment:
Include:
- '**/*.gemspec'

Gemspec/DependencyVersion:
Description: 'Requires or forbids specifying gem dependency versions.'
Enabled: false
VersionAdded: '<<next>>'
EnforcedStyle: 'required'
SupportedStyles:
- 'required'
- 'forbidden'
Include:
- '**/*.gemspec'
AllowedGems: []

Gemspec/DuplicatedAssignment:
Description: 'An attribute assignment method calls should be listed only once in a gemspec.'
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
require_relative 'rubocop/cop/bundler/ordered_gems'

require_relative 'rubocop/cop/gemspec/date_assignment'
require_relative 'rubocop/cop/gemspec/dependency_version'
require_relative 'rubocop/cop/gemspec/duplicated_assignment'
require_relative 'rubocop/cop/gemspec/ordered_dependencies'
require_relative 'rubocop/cop/gemspec/require_mfa'
Expand Down
156 changes: 156 additions & 0 deletions lib/rubocop/cop/gemspec/dependency_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Gemspec
# Enforce that gem dependency version specifications or a commit reference (branch,
# ref, or tag) are either required or forbidden.
#
# @example EnforcedStyle: required (default)
#
# # bad
# Gem::Specification.new do |spec|
# spec.add_dependency 'parser'
# end
#
# # bad
# Gem::Specification.new do |spec|
# spec.add_development_dependency 'parser'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.add_dependency 'parser', '>= 2.3.3.1', '< 3.0'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.add_development_dependency 'parser', '>= 2.3.3.1', '< 3.0'
# end
#
# @example EnforcedStyle: forbidden
#
# # bad
# Gem::Specification.new do |spec|
# spec.add_dependency 'parser', '>= 2.3.3.1', '< 3.0'
# end
#
# # bad
# Gem::Specification.new do |spec|
# spec.add_development_dependency 'parser', '>= 2.3.3.1', '< 3.0'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.add_dependency 'parser'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.add_development_dependency 'parser'
# end
#
class DependencyVersion < Base
include ConfigurableEnforcedStyle
include GemspecHelp

REQUIRED_MSG = 'Dependency version specification is required.'
FORBIDDEN_MSG = 'Dependency version specification is forbidden.'
VERSION_SPECIFICATION_REGEX = /^\s*[~<>=]*\s*[0-9.]+/.freeze

# @!method add_dependency_method_declarations(node)
def_node_search :add_dependency_method_declarations, <<~PATTERN
(send
(lvar #match_block_variable_name?) #add_dependency_method? ...)
PATTERN

# @!method includes_version_specification?(node)
def_node_matcher :includes_version_specification?, <<~PATTERN
(send _ #add_dependency_method? <(str #version_specification?) ...>)
PATTERN

# @!method includes_commit_reference?(node)
def_node_matcher :includes_commit_reference?, <<~PATTERN
(send _ #add_dependency_method? <(hash <(pair (sym {:branch :ref :tag}) (str _)) ...>) ...>)
PATTERN

def on_new_investigation
return if processed_source.blank?

add_dependency_method_nodes.each do |node|
next if allowed_gem?(node)

if offense?(node)
add_offense(node)
opposite_style_detected
else
correct_style_detected
end
end
end

private

def allowed_gem?(node)
allowed_gems.include?(node.first_argument.value)
end

def allowed_gems
Array(cop_config['AllowedGems'])
end

def message(range)
gem_specification = range.source

if required_style?
format(REQUIRED_MSG, gem_specification: gem_specification)
elsif forbidden_style?
format(FORBIDDEN_MSG, gem_specification: gem_specification)
end
end

def match_block_variable_name?(receiver_name)
gem_specification(processed_source.ast) do |block_variable_name|
return block_variable_name == receiver_name
end
end

def add_dependency_method?(method_name)
method_name.to_s.end_with?('_dependency')
end

def add_dependency_method_nodes
add_dependency_method_declarations(processed_source.ast)
end

def offense?(node)
required_offense?(node) || forbidden_offense?(node)
end

def required_offense?(node)
return unless required_style?

!includes_version_specification?(node) && !includes_commit_reference?(node)
end

def forbidden_offense?(node)
return unless forbidden_style?

includes_version_specification?(node) || includes_commit_reference?(node)
end

def forbidden_style?
style == :forbidden
end

def required_style?
style == :required
end

def version_specification?(expression)
expression.match?(VERSION_SPECIFICATION_REGEX)
end
end
end
end
end
2 changes: 1 addition & 1 deletion rubocop.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency('parser', '>= 3.1.0.0')
s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 4.0')
s.add_runtime_dependency('regexp_parser', '>= 1.8', '< 3.0')
s.add_runtime_dependency('rexml')
s.add_runtime_dependency('rexml', '~> 3.2', '>= 3.2.5')
s.add_runtime_dependency('rubocop-ast', '>= 1.17.0', '< 2.0')
s.add_runtime_dependency('ruby-progressbar', '~> 1.7')
s.add_runtime_dependency('unicode-display_width', '>= 1.4.0', '< 3.0')
Expand Down

0 comments on commit 5f526f7

Please sign in to comment.