rubocop 1.1.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -12
  3. data/config/default.yml +113 -16
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +9 -0
  6. data/lib/rubocop/cli/command/execute_runner.rb +26 -11
  7. data/lib/rubocop/config_loader.rb +14 -5
  8. data/lib/rubocop/config_regeneration.rb +1 -1
  9. data/lib/rubocop/cop/bundler/duplicated_gem.rb +3 -3
  10. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  11. data/lib/rubocop/cop/commissioner.rb +1 -1
  12. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  13. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  14. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  15. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  16. data/lib/rubocop/cop/generator.rb +2 -9
  17. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +1 -1
  19. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  20. data/lib/rubocop/cop/layout/class_structure.rb +15 -3
  21. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  22. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +77 -7
  23. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  24. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  25. data/lib/rubocop/cop/layout/line_length.rb +8 -1
  26. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  27. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  29. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +2 -1
  30. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +26 -2
  31. data/lib/rubocop/cop/lint/debugger.rb +17 -27
  32. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  33. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  34. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  35. data/lib/rubocop/cop/lint/empty_block.rb +38 -2
  36. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  37. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +22 -4
  38. data/lib/rubocop/cop/lint/loop.rb +4 -4
  39. data/lib/rubocop/cop/lint/missing_super.rb +7 -4
  40. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  41. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  42. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
  43. data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
  44. data/lib/rubocop/cop/lint/to_enum_arguments.rb +6 -15
  45. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +13 -4
  46. data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
  47. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  48. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  49. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  50. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  51. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  52. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  53. data/lib/rubocop/cop/mixin/visibility_help.rb +1 -3
  54. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +11 -1
  55. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  56. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  57. data/lib/rubocop/cop/naming/variable_number.rb +98 -8
  58. data/lib/rubocop/cop/style/and_or.rb +1 -3
  59. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  60. data/lib/rubocop/cop/style/case_like_if.rb +0 -4
  61. data/lib/rubocop/cop/style/collection_compact.rb +91 -0
  62. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +107 -5
  63. data/lib/rubocop/cop/style/documentation.rb +12 -1
  64. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  65. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  66. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  67. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  68. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
  69. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  70. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  71. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  72. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  73. data/lib/rubocop/cop/style/multiple_comparison.rb +3 -2
  74. data/lib/rubocop/cop/style/negated_if_else_condition.rb +106 -0
  75. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  76. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  77. data/lib/rubocop/cop/style/redundant_argument.rb +73 -0
  78. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  79. data/lib/rubocop/cop/style/static_class.rb +97 -0
  80. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  81. data/lib/rubocop/cop/util.rb +5 -1
  82. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  83. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  84. data/lib/rubocop/ext/regexp_node.rb +10 -5
  85. data/lib/rubocop/ext/regexp_parser.rb +9 -2
  86. data/lib/rubocop/formatter/disabled_config_formatter.rb +21 -6
  87. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  88. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  89. data/lib/rubocop/options.rb +7 -0
  90. data/lib/rubocop/rake_task.rb +2 -2
  91. data/lib/rubocop/runner.rb +1 -1
  92. data/lib/rubocop/target_finder.rb +1 -1
  93. data/lib/rubocop/target_ruby.rb +65 -1
  94. data/lib/rubocop/version.rb +1 -1
  95. metadata +14 -8
  96. data/bin/console +0 -10
  97. data/bin/rubocop-profile +0 -32
  98. data/bin/setup +0 -7
@@ -82,7 +82,7 @@ module RuboCop
82
82
 
83
83
  def each_escape(node)
84
84
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
85
- yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
85
+ yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
86
86
 
87
87
  if expr.type == :set
88
88
  char_class_depth + (event == :enter ? 1 : -1)
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for places where classes with only class methods can be
7
+ # replaced with a module. Classes should be used only when it makes sense to create
8
+ # instances out of them.
9
+ #
10
+ # This cop is marked as unsafe, because it is possible that this class is a parent
11
+ # for some other subclass, monkey-patched with instance methods or
12
+ # a dummy instance is instantiated from it somewhere.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # class SomeClass
17
+ # def self.some_method
18
+ # # body omitted
19
+ # end
20
+ #
21
+ # def self.some_other_method
22
+ # # body omitted
23
+ # end
24
+ # end
25
+ #
26
+ # # good
27
+ # module SomeModule
28
+ # module_function
29
+ #
30
+ # def some_method
31
+ # # body omitted
32
+ # end
33
+ #
34
+ # def some_other_method
35
+ # # body omitted
36
+ # end
37
+ # end
38
+ #
39
+ # # good - has instance method
40
+ # class SomeClass
41
+ # def instance_method; end
42
+ # def self.class_method; end
43
+ # end
44
+ #
45
+ class StaticClass < Base
46
+ include VisibilityHelp
47
+
48
+ MSG = 'Prefer modules to classes with only class methods.'
49
+
50
+ def on_class(class_node)
51
+ return if class_node.parent_class
52
+
53
+ add_offense(class_node) if class_convertible_to_module?(class_node)
54
+ end
55
+
56
+ private
57
+
58
+ def class_convertible_to_module?(class_node)
59
+ nodes = class_elements(class_node)
60
+ return false if nodes.empty?
61
+
62
+ nodes.all? do |node|
63
+ node_visibility(node) == :public &&
64
+ node.defs_type? ||
65
+ sclass_convertible_to_module?(node) ||
66
+ node.equals_asgn? ||
67
+ extend_call?(node)
68
+ end
69
+ end
70
+
71
+ def extend_call?(node)
72
+ node.send_type? && node.method?(:extend)
73
+ end
74
+
75
+ def sclass_convertible_to_module?(node)
76
+ return false unless node.sclass_type?
77
+
78
+ class_elements(node).all? do |child|
79
+ node_visibility(child) == :public && (child.def_type? || child.equals_asgn?)
80
+ end
81
+ end
82
+
83
+ def class_elements(class_node)
84
+ class_def = class_node.body
85
+
86
+ if !class_def
87
+ []
88
+ elsif class_def.begin_type?
89
+ class_def.children
90
+ else
91
+ [class_def]
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -24,6 +24,15 @@ module RuboCop
24
24
  #
25
25
  # # good
26
26
  # x += 1 until x > 10
27
+ #
28
+ # @example
29
+ # # bad
30
+ # x += 100 while x < 500 # a long comment that makes code too long if it were a single line
31
+ #
32
+ # # good
33
+ # while x < 500 # a long comment that makes code too long if it were a single line
34
+ # x += 100
35
+ # end
27
36
  class WhileUntilModifier < Base
28
37
  include StatementModifier
29
38
  extend AutoCorrector
@@ -53,7 +53,7 @@ module RuboCop
53
53
  end
54
54
 
55
55
  def on_node(syms, sexp, excludes = [], &block)
56
- return to_enum(:on_node, syms, sexp, excludes) unless block_given?
56
+ return to_enum(:on_node, syms, sexp, excludes) unless block
57
57
 
58
58
  yield sexp if Array(syms).include?(sexp.type)
59
59
  return if Array(excludes).include?(sexp.type)
@@ -123,6 +123,10 @@ module RuboCop
123
123
  node1.loc.line == node2.loc.line
124
124
  end
125
125
 
126
+ def indent(node)
127
+ ' ' * node.loc.column
128
+ end
129
+
126
130
  def to_supported_styles(enforced_style)
127
131
  enforced_style
128
132
  .sub(/^Enforced/, 'Supported')
@@ -78,7 +78,7 @@ module RuboCop
78
78
  end
79
79
 
80
80
  def each_ancestor(include_self: false, &block)
81
- return to_enum(__method__, include_self: include_self) unless block_given?
81
+ return to_enum(__method__, include_self: include_self) unless block
82
82
 
83
83
  yield self if include_self
84
84
  scan_ancestors(&block)
@@ -61,7 +61,7 @@ module RuboCop
61
61
  end
62
62
 
63
63
  def each_node(&block)
64
- return to_enum(__method__) unless block_given?
64
+ return to_enum(__method__) unless block
65
65
 
66
66
  yield node if naked_top_level?
67
67
  scan_node(node, &block)
@@ -19,13 +19,18 @@ module RuboCop
19
19
  super
20
20
 
21
21
  str = with_interpolations_blanked
22
- @parsed_tree = begin
23
- Regexp::Parser.parse(str, options: options)
22
+ begin
23
+ @parsed_tree = Regexp::Parser.parse(str, options: options)
24
24
  rescue StandardError
25
- nil
25
+ @parsed_tree = nil
26
+ else
27
+ origin = loc.begin.end
28
+ source = @parsed_tree.to_s
29
+ @parsed_tree.each_expression(true) do |e|
30
+ e.origin = origin
31
+ e.source = source
32
+ end
26
33
  end
27
- origin = loc.begin.end
28
- @parsed_tree&.each_expression(true) { |e| e.origin = origin }
29
34
  end
30
35
 
31
36
  def each_capture(named: ANY)
@@ -20,11 +20,18 @@ module RuboCop
20
20
  module Expression
21
21
  # Add `expression` and `loc` to all `regexp_parser` nodes
22
22
  module Base
23
- attr_accessor :origin
23
+ attr_accessor :origin, :source
24
+
25
+ def start_index
26
+ # ts is a byte index; convert it to a character index
27
+ @start_index ||= source.byteslice(0, ts).length
28
+ end
24
29
 
25
30
  # Shortcut to `loc.expression`
26
31
  def expression
27
- @expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length)
32
+ @expression ||= begin
33
+ origin.adjust(begin_pos: start_index, end_pos: start_index + full_length)
34
+ end
28
35
  end
29
36
 
30
37
  # @returns a location map like `parser` does, with:
@@ -5,6 +5,8 @@ module RuboCop
5
5
  # This formatter displays a YAML configuration file where all cops that
6
6
  # detected any offenses are configured to not detect the offense.
7
7
  class DisabledConfigFormatter < BaseFormatter
8
+ include PathUtil
9
+
8
10
  HEADING = <<~COMMENTS
9
11
  # This configuration was generated by
10
12
  # `%<command>s`
@@ -195,15 +197,28 @@ module RuboCop
195
197
  end
196
198
 
197
199
  def excludes(offending_files, cop_name, parent)
198
- # Exclude properties in .rubocop_todo.yml override default ones, as well
199
- # as any custom excludes in .rubocop.yml, so in order to retain those
200
- # excludes we must copy them.
201
- # There can be multiple .rubocop.yml files in subdirectories, but we
202
- # just look at the current working directory
200
+ # Exclude properties in .rubocop_todo.yml override default ones, as well as any custom
201
+ # excludes in .rubocop.yml, so in order to retain those excludes we must copy them.
202
+ # There can be multiple .rubocop.yml files in subdirectories, but we just look at the
203
+ # current working directory.
203
204
  config = ConfigStore.new.for(parent)
204
205
  cfg = config[cop_name] || {}
205
206
 
206
- ((cfg['Exclude'] || []) + offending_files).uniq
207
+ if merge_mode_for_exclude?(config) || merge_mode_for_exclude?(cfg)
208
+ offending_files
209
+ else
210
+ cop_exclude = cfg['Exclude']
211
+ if cop_exclude && cop_exclude != default_config(cop_name)['Exclude']
212
+ warn "`#{cop_name}: Exclude` in `#{smart_path(config.loaded_path)}` overrides a " \
213
+ 'generated `Exclude` in `.rubocop_todo.yml`. Either remove ' \
214
+ "`#{cop_name}: Exclude` or add `inherit_mode: merge: - Exclude`."
215
+ end
216
+ ((cop_exclude || []) + offending_files).uniq
217
+ end
218
+ end
219
+
220
+ def merge_mode_for_exclude?(cfg)
221
+ Array(cfg.to_h.dig('inherit_mode', 'merge')).include?('Exclude')
207
222
  end
208
223
 
209
224
  def output_exclude_path(output_buffer, exclude_path, parent)
@@ -14,6 +14,7 @@ module RuboCop
14
14
  '[e]macs' => EmacsStyleFormatter,
15
15
  '[fi]les' => FileListFormatter,
16
16
  '[fu]ubar' => FuubarStyleFormatter,
17
+ '[g]ithub' => GitHubActionsFormatter,
17
18
  '[h]tml' => HTMLFormatter,
18
19
  '[j]son' => JSONFormatter,
19
20
  '[ju]nit' => JUnitFormatter,
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Formatter
5
+ # This formatter formats report data as GitHub Workflow commands resulting
6
+ # in GitHub check annotations when run within GitHub Actions.
7
+ class GitHubActionsFormatter < BaseFormatter
8
+ ESCAPE_MAP = {
9
+ '%' => '%25',
10
+ "\n" => '%0A',
11
+ "\r" => '%0D'
12
+ }.freeze
13
+
14
+ def file_finished(file, offenses)
15
+ offenses.each { |offense| report_offense(file, offense) }
16
+ end
17
+
18
+ private
19
+
20
+ def github_escape(string)
21
+ string.gsub(Regexp.union(ESCAPE_MAP.keys), ESCAPE_MAP)
22
+ end
23
+
24
+ def minimum_severity_to_fail
25
+ @minimum_severity_to_fail ||= begin
26
+ name = options.fetch(:fail_level, :refactor)
27
+ RuboCop::Cop::Severity.new(name)
28
+ end
29
+ end
30
+
31
+ def github_severity(offense)
32
+ offense.severity < minimum_severity_to_fail ? 'warning' : 'error'
33
+ end
34
+
35
+ def report_offense(file, offense)
36
+ output.printf(
37
+ "\n::%<severity>s file=%<file>s,line=%<line>d,col=%<column>d::%<message>s\n",
38
+ severity: github_severity(offense),
39
+ file: file,
40
+ line: offense.line,
41
+ column: offense.real_column,
42
+ message: github_escape(offense.message)
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -5,6 +5,7 @@ require 'shellwords'
5
5
 
6
6
  module RuboCop
7
7
  class IncorrectCopNameError < StandardError; end
8
+
8
9
  class OptionArgumentError < StandardError; end
9
10
 
10
11
  # This class handles command line options.
@@ -145,6 +146,7 @@ module RuboCop
145
146
  end
146
147
  end
147
148
 
149
+ option(opts, '--display-time')
148
150
  option(opts, '--display-only-failed')
149
151
  end
150
152
 
@@ -194,6 +196,7 @@ module RuboCop
194
196
 
195
197
  option(opts, '--safe')
196
198
 
199
+ option(opts, '--stderr')
197
200
  option(opts, '--[no-]color')
198
201
 
199
202
  option(opts, '-v', '--version')
@@ -469,6 +472,7 @@ module RuboCop
469
472
  'if no format is specified.'],
470
473
  fail_level: ['Minimum severity (A/R/C/W/E/F) for exit',
471
474
  'with error code.'],
475
+ display_time: 'Display elapsed time in seconds.',
472
476
  display_only_failed: ['Only output offense messages. Omit passing',
473
477
  'cops. Only valid for --format junit.'],
474
478
  display_only_fail_level_offenses:
@@ -496,6 +500,9 @@ module RuboCop
496
500
  extra_details: 'Display extra details in offense messages.',
497
501
  lint: 'Run only lint cops.',
498
502
  safe: 'Run only safe cops.',
503
+ stderr: ['Write all output to stderr except for the',
504
+ 'autocorrected source. This is especially useful',
505
+ 'when combined with --auto-correct and --stdin.'],
499
506
  list_target_files: 'List all files RuboCop will inspect.',
500
507
  auto_correct: 'Auto-correct offenses (only when it\'s safe).',
501
508
  safe_auto_correct: '(same, deprecated)',
@@ -22,7 +22,7 @@ module RuboCop
22
22
 
23
23
  task(name, *args) do |_, task_args|
24
24
  RakeFileUtils.verbose(verbose) do
25
- yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
25
+ yield(*[self, task_args].slice(0, task_block.arity)) if task_block
26
26
  run_cli(verbose, full_options)
27
27
  end
28
28
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
 
67
67
  task(:auto_correct, *args) do |_, task_args|
68
68
  RakeFileUtils.verbose(verbose) do
69
- yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
69
+ yield(*[self, task_args].slice(0, task_block.arity)) if task_block
70
70
  options = full_options.unshift('--auto-correct-all')
71
71
  options.delete('--parallel')
72
72
  run_cli(verbose, options)
@@ -64,7 +64,7 @@ module RuboCop
64
64
  # instances that each inspects its allotted group of files.
65
65
  def warm_cache(target_files)
66
66
  puts 'Running parallel inspection' if @options[:debug]
67
- Parallel.each(target_files, &method(:file_offenses))
67
+ Parallel.each(target_files) { |target_file| file_offenses(target_file) }
68
68
  end
69
69
 
70
70
  def find_target_files(paths)
@@ -94,7 +94,7 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def wanted_dir_patterns(base_dir, exclude_pattern, flags)
97
- dirs = Dir.glob(File.join(base_dir, '*/'), flags)
97
+ dirs = Dir.glob(File.join(base_dir.gsub('/**/', '/\**/'), '*/'), flags)
98
98
  .reject do |dir|
99
99
  dir.end_with?('/./', '/../') || File.fnmatch?(exclude_pattern, dir, flags)
100
100
  end
@@ -112,6 +112,70 @@ module RuboCop
112
112
  end
113
113
  end
114
114
 
115
+ # The target ruby version may be found in a .gemspec file.
116
+ # @api private
117
+ class GemspecFile < Source
118
+ extend NodePattern::Macros
119
+
120
+ GEMSPEC_EXTENSION = '.gemspec'
121
+
122
+ def_node_search :required_ruby_version, <<~PATTERN
123
+ (send _ :required_ruby_version= $_)
124
+ PATTERN
125
+
126
+ def_node_matcher :gem_requirement?, <<~PATTERN
127
+ (send (const(const _ :Gem):Requirement) :new $str)
128
+ PATTERN
129
+
130
+ def name
131
+ "`required_ruby_version` parameter (in #{gemspec_filename})"
132
+ end
133
+
134
+ private
135
+
136
+ def find_version
137
+ file = gemspec_filepath
138
+ return unless file && File.file?(file)
139
+
140
+ version = version_from_gemspec_file(file)
141
+ return if version.nil?
142
+
143
+ requirement = version.children.last
144
+ return version_from_array(version) if version.array_type?
145
+ return version_from_array(requirement) if gem_requirement? version
146
+
147
+ version_from_str(version.str_content)
148
+ end
149
+
150
+ def gemspec_filename
151
+ @gemspec_filename ||= begin
152
+ basename = Pathname.new(@config.base_dir_for_path_parameters).basename.to_s
153
+ "#{basename}#{GEMSPEC_EXTENSION}"
154
+ end
155
+ end
156
+
157
+ def gemspec_filepath
158
+ @gemspec_filepath ||=
159
+ @config.find_file_upwards(gemspec_filename, @config.base_dir_for_path_parameters)
160
+ end
161
+
162
+ def version_from_gemspec_file(file)
163
+ processed_source = ProcessedSource.from_file(file, DEFAULT_VERSION)
164
+ required_ruby_version(processed_source.ast).first
165
+ end
166
+
167
+ def version_from_array(array)
168
+ versions = array.children.map { |v| version_from_str(v.is_a?(String) ? v : v.str_content) }
169
+ versions.compact.min
170
+ end
171
+
172
+ def version_from_str(str)
173
+ str.match(/^(?:>=|<=)?\s*(?<version>\d+(?:\.\d+)*)/) do |md|
174
+ md[:version].to_f
175
+ end
176
+ end
177
+ end
178
+
115
179
  # If all else fails, a default version will be picked.
116
180
  # @api private
117
181
  class Default < Source
@@ -130,7 +194,7 @@ module RuboCop
130
194
  KNOWN_RUBIES
131
195
  end
132
196
 
133
- SOURCES = [RuboCopConfig, RubyVersionFile, BundlerLockFile, Default].freeze
197
+ SOURCES = [RuboCopConfig, RubyVersionFile, BundlerLockFile, GemspecFile, Default].freeze
134
198
  private_constant :SOURCES
135
199
 
136
200
  def initialize(config)