Skip to content

Commit

Permalink
Add Gemspec/DevelopmentDependencies cop
Browse files Browse the repository at this point in the history
This cop checks that development dependencies are specified in Gemfile
rather than gemspec.
  • Loading branch information
sambostock authored and bbatsov committed Jan 23, 2023
1 parent 8750cd6 commit e704d26
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_gemspec_development_dependencies_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#11469](https://github.com/rubocop/rubocop/pull/11469): Add `Gemspec/DevelopmentDependencies` cop. ([@sambostock][])
16 changes: 16 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,22 @@ Gemspec/DeprecatedAttributeAssignment:
Include:
- '**/*.gemspec'

Gemspec/DevelopmentDependencies:
Description: Checks that development dependencies are specified in Gemfile rather than gemspec.
Enabled: pending
VersionAdded: '<<next>>'
EnforcedStyle: Gemfile
SupportedStyles:
- Gemfile
- gems.rb
- gemspec
AllowedGems:
- bundler
Include:
- '**/*.gemspec'
- '**/Gemfile'
- '**/gems.rb'

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 @@ -166,6 +166,7 @@

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

module RuboCop
module Cop
module Gemspec
# Enforce that development dependencies for a gem are specified in
# `Gemfile`, rather than in the `gemspec` using
# `add_development_dependency`. Alternatively, using `EnforcedStyle:
# gemspec`, enforce that all dependencies are specified in `gemspec`,
# rather than in `Gemfile`.
#
# @example EnforcedStyle: Gemfile (default)
# # Specify runtime dependencies in your gemspec,
# # but all other dependencies in your Gemfile.
#
# # bad
# # example.gemspec
# s.add_development_dependency "foo"
#
# # good
# # Gemfile
# gem "foo"
#
# # good
# # gems.rb
# gem "foo"
#
# # good (with AllowedGems: ["bar"])
# # example.gemspec
# s.add_development_dependency "bar"
#
# @example EnforcedStyle: gems.rb
# # Specify runtime dependencies in your gemspec,
# # but all other dependencies in your Gemfile.
# #
# # Identical to `EnforcedStyle: Gemfile`, but with a different error message.
# # Rely on Bundler/GemFilename to enforce the use of `Gemfile` vs `gems.rb`.
#
# # bad
# # example.gemspec
# s.add_development_dependency "foo"
#
# # good
# # Gemfile
# gem "foo"
#
# # good
# # gems.rb
# gem "foo"
#
# # good (with AllowedGems: ["bar"])
# # example.gemspec
# s.add_development_dependency "bar"
#
# @example EnforcedStyle: gemspec
# # Specify all dependencies in your gemspec.
#
# # bad
# # Gemfile
# gem "foo"
#
# # good
# # example.gemspec
# s.add_development_dependency "foo"
#
# # good (with AllowedGems: ["bar"])
# # Gemfile
# gem "bar"
#
class DevelopmentDependencies < Base
include ConfigurableEnforcedStyle

MSG = 'Specify development dependencies in %<preferred>s.'
RESTRICT_ON_SEND = %i[add_development_dependency gem].freeze

# @!method add_development_dependency?(node)
def_node_matcher :add_development_dependency?, <<~PATTERN
(send _ :add_development_dependency (str #forbidden_gem? ...))
PATTERN

# @!method gem?(node)
def_node_matcher :gem?, <<~PATTERN
(send _ :gem (str #forbidden_gem? ...))
PATTERN

def on_send(node)
case style
when :Gemfile, :'gems.rb'
add_offense(node) if add_development_dependency?(node)
when :gemspec
add_offense(node) if gem?(node)
end
end

private

def forbidden_gem?(gem_name)
!cop_config['AllowedGems'].include?(gem_name)
end

def message(_range)
format(MSG, preferred: style)
end
end
end
end
end
79 changes: 79 additions & 0 deletions spec/rubocop/cop/gemspec/development_dependencies_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Gemspec::DevelopmentDependencies, :config do
let(:cop_config) do
{
'Enabled' => true,
'EnforcedStyle' => enforced_style,
'AllowedGems' => [
'allowed'
]
}
end

shared_examples 'prefer gem file' do
it 'registers an offense when using `#add_development_dependency` in a gemspec' do
expect_offense(<<~RUBY, 'example.gemspec', preferred_file: enforced_style)
Gem::Specification.new do |spec|
spec.name = 'example'
spec.add_development_dependency 'foo'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Specify development dependencies in %{preferred_file}.
spec.add_development_dependency 'allowed'
end
RUBY
end

it 'registers no offenses when specifying dependencies in Gemfile' do
expect_no_offenses(<<~RUBY, 'Gemfile')
gem 'example'
RUBY
end

it 'registers no offenses when specifying dependencies in gems.rb' do
expect_no_offenses(<<~RUBY, 'gems.rb')
gem 'example'
RUBY
end
end

context 'with `EnforcedStyle: Gemfile`' do
let(:enforced_style) { 'Gemfile' }

include_examples 'prefer gem file'
end

context 'with `EnforcedStyle: gems.rb`' do
let(:enforced_style) { 'gems.rb' }

include_examples 'prefer gem file'
end

context 'with `EnforcedStyle: gemspec`' do
let(:enforced_style) { 'gemspec' }

it 'registers no offenses when using `#add_development_dependency`' do
expect_no_offenses(<<~RUBY, 'example.gemspec')
Gem::Specification.new do |spec|
spec.name = 'example'
spec.add_development_dependency 'foo'
end
RUBY
end

it 'registers an offense when specifying dependencies in Gemfile' do
expect_offense(<<~RUBY, 'Gemfile')
gem 'example'
^^^^^^^^^^^^^ Specify development dependencies in gemspec.
gem 'allowed'
RUBY
end

it 'registers an offense when specifying dependencies in gems.rb' do
expect_offense(<<~RUBY, 'gems.rb')
gem 'example'
^^^^^^^^^^^^^ Specify development dependencies in gemspec.
gem 'allowed'
RUBY
end
end
end

0 comments on commit e704d26

Please sign in to comment.