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

Enhance Gemspec/RequiredRubyVersion cop with check that required_ruby_version is specified #8370

Merged
merged 2 commits into from Aug 5, 2020
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 @@ -5,6 +5,7 @@
### New features

* [#8322](https://github.com/rubocop-hq/rubocop/pull/8322): Support autocorrect for `Style/CaseEquality` cop. ([@fatkodima][])
* [#7876](https://github.com/rubocop-hq/rubocop/issues/7876): Enhance `Gemspec/RequiredRubyVersion` cop with check that `required_ruby_version` is specified. ([@fatkodima][])
* [#8291](https://github.com/rubocop-hq/rubocop/pull/8291): Add new `Lint/SelfAssignment` cop. ([@fatkodima][])
* [#8389](https://github.com/rubocop-hq/rubocop/pull/8389): Add new `Lint/DuplicateRescueException` cop. ([@fatkodima][])
* [#8433](https://github.com/rubocop-hq/rubocop/pull/8433): Add new `Lint/BinaryOperatorWithIdenticalOperands` cop. ([@fatkodima][])
Expand Down
3 changes: 2 additions & 1 deletion config/default.yml
Expand Up @@ -210,9 +210,10 @@ Gemspec/OrderedDependencies:
- '**/*.gemspec'

Gemspec/RequiredRubyVersion:
Description: 'Checks that `required_ruby_version` of gemspec and `TargetRubyVersion` of .rubocop.yml are equal.'
Description: 'Checks that `required_ruby_version` of gemspec is specified and equal to `TargetRubyVersion` of .rubocop.yml.'
Enabled: true
VersionAdded: '0.52'
VersionChanged: '0.89'
Include:
- '**/*.gemspec'

Expand Down
11 changes: 8 additions & 3 deletions docs/modules/ROOT/pages/cops_gemspec.adoc
Expand Up @@ -149,11 +149,11 @@ spec.add_dependency 'rspec'
| Yes
| No
| 0.52
| -
| 0.89
|===

Checks that `required_ruby_version` of gemspec and `TargetRubyVersion`
of .rubocop.yml are equal.
Checks that `required_ruby_version` of gemspec is specified and
equal to `TargetRubyVersion` of .rubocop.yml.
Thereby, RuboCop to perform static analysis working on the version
required by gemspec.

Expand All @@ -163,6 +163,11 @@ required by gemspec.
----
# When `TargetRubyVersion` of .rubocop.yml is `2.5`.

# bad
Gem::Specification.new do |spec|
# no `required_ruby_version` specified
end

# bad
Gem::Specification.new do |spec|
spec.required_ruby_version = '>= 2.4.0'
Expand Down
43 changes: 32 additions & 11 deletions lib/rubocop/cop/gemspec/required_ruby_version.rb
Expand Up @@ -3,8 +3,8 @@
module RuboCop
module Cop
module Gemspec
# Checks that `required_ruby_version` of gemspec and `TargetRubyVersion`
# of .rubocop.yml are equal.
# Checks that `required_ruby_version` of gemspec is specified and
# equal to `TargetRubyVersion` of .rubocop.yml.
# Thereby, RuboCop to perform static analysis working on the version
# required by gemspec.
#
Expand All @@ -13,6 +13,11 @@ module Gemspec
#
# # bad
# Gem::Specification.new do |spec|
# # no `required_ruby_version` specified
# end
#
# # bad
# Gem::Specification.new do |spec|
# spec.required_ruby_version = '>= 2.4.0'
# end
#
Expand Down Expand Up @@ -41,28 +46,44 @@ module Gemspec
# spec.required_ruby_version = '~> 2.5'
# end
class RequiredRubyVersion < Cop
MSG = '`required_ruby_version` (%<required_ruby_version>s, ' \
'declared in %<gemspec_filename>s) and `TargetRubyVersion` ' \
'(%<target_ruby_version>s, which may be specified in ' \
'.rubocop.yml) should be equal.'
include RangeHelp

NOT_EQUAL_MSG = '`required_ruby_version` (%<required_ruby_version>s, ' \
'declared in %<gemspec_filename>s) and `TargetRubyVersion` ' \
'(%<target_ruby_version>s, which may be specified in ' \
'.rubocop.yml) should be equal.'
MISSING_MSG = '`required_ruby_version` should be specified.'

def_node_search :required_ruby_version, <<~PATTERN
(send _ :required_ruby_version= ${(str _) (array (str _))})
(send _ :required_ruby_version= $_)
PATTERN

def_node_matcher :string_version?, <<~PATTERN
{(str _) (array (str _))}
PATTERN

# rubocop:disable Metrics/AbcSize
def investigate(processed_source)
required_ruby_version(processed_source.ast) do |version|
version = required_ruby_version(processed_source.ast).first

if version
return unless string_version?(version)

ruby_version = extract_ruby_version(version)

return if ruby_version == target_ruby_version.to_s

add_offense(
processed_source.ast,
location: version.loc.expression,
message: message(ruby_version, target_ruby_version)
message: not_equal_message(ruby_version, target_ruby_version)
)
else
range = source_range(processed_source.buffer, 1, 0)
add_offense(nil, location: range, message: MISSING_MSG)
end
end
# rubocop:enable Metrics/AbcSize

private

Expand All @@ -76,9 +97,9 @@ def extract_ruby_version(required_ruby_version)
required_ruby_version.str_content.scan(/\d/).first(2).join('.')
end

def message(required_ruby_version, target_ruby_version)
def not_equal_message(required_ruby_version, target_ruby_version)
format(
MSG,
NOT_EQUAL_MSG,
required_ruby_version: required_ruby_version,
gemspec_filename: File.basename(processed_source.file_path),
target_ruby_version: target_ruby_version
Expand Down
8 changes: 8 additions & 0 deletions spec/rubocop/cop/gemspec/required_ruby_version_spec.rb
Expand Up @@ -128,4 +128,12 @@
RUBY
end
end

it 'registers an offense when `required_ruby_version` is not specified' do
expect_offense(<<~RUBY, '/path/to/foo.gemspec')
Gem::Specification.new do |spec|
^ `required_ruby_version` should be specified.
end
RUBY
end
end