/
required_ruby_version.rb
90 lines (81 loc) · 2.72 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
# frozen_string_literal: true
module RuboCop
module Cop
module Gemspec
# Checks that `required_ruby_version` of gemspec and `TargetRubyVersion`
# of .rubocop.yml are equal.
# 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|
# 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
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.'
def_node_search :required_ruby_version, <<~PATTERN
(send _ :required_ruby_version= ${(str _) (array (str _))})
PATTERN
def investigate(processed_source)
required_ruby_version(processed_source.ast) do |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)
)
end
end
private
def extract_ruby_version(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 message(required_ruby_version, target_ruby_version)
format(
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