/
required_ruby_version.rb
111 lines (99 loc) · 3.54 KB
/
required_ruby_version.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# frozen_string_literal: true
module RuboCop
module Cop
module Gemspec
# 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.
#
# @example
# # 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'
# end
#
# # bad
# Gem::Specification.new do |spec|
# spec.required_ruby_version = '>= 2.6.0'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.required_ruby_version = '>= 2.5.0'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.required_ruby_version = '>= 2.5'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.required_ruby_version = ['>= 2.5.0', '< 2.7.0']
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.required_ruby_version = '~> 2.5'
# end
class RequiredRubyVersion < Cop
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= $_)
PATTERN
def_node_matcher :defined_ruby_version, <<~PATTERN
{$(str _) $(array (str _) (str _))
(send (const (const nil? :Gem) :Requirement) :new $(str _))}
PATTERN
# rubocop:disable Metrics/AbcSize
def investigate(processed_source)
version_def = required_ruby_version(processed_source.ast).first
if version_def
ruby_version = extract_ruby_version(defined_ruby_version(version_def))
return if !ruby_version || ruby_version == target_ruby_version.to_s
add_offense(
processed_source.ast,
location: version_def.loc.expression,
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
def extract_ruby_version(required_ruby_version)
return unless required_ruby_version
if required_ruby_version.array_type?
required_ruby_version = required_ruby_version.children.detect do |v|
/[>=]/.match?(v.str_content)
end
end
required_ruby_version.str_content.scan(/\d/).first(2).join('.')
end
def not_equal_message(required_ruby_version, target_ruby_version)
format(
NOT_EQUAL_MSG,
required_ruby_version: required_ruby_version,
gemspec_filename: File.basename(processed_source.file_path),
target_ruby_version: target_ruby_version
)
end
end
end
end
end