rubocop 1.0.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -16
  3. data/config/default.yml +165 -19
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +17 -0
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  7. data/lib/rubocop/cli/command/execute_runner.rb +26 -11
  8. data/lib/rubocop/comment_config.rb +1 -1
  9. data/lib/rubocop/config_loader.rb +14 -5
  10. data/lib/rubocop/config_regeneration.rb +1 -1
  11. data/lib/rubocop/cop/bundler/duplicated_gem.rb +26 -6
  12. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  13. data/lib/rubocop/cop/commissioner.rb +10 -10
  14. data/lib/rubocop/cop/corrector.rb +3 -1
  15. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  16. data/lib/rubocop/cop/force.rb +1 -1
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  18. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  19. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  20. data/lib/rubocop/cop/generator.rb +2 -9
  21. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  22. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +1 -1
  23. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  24. data/lib/rubocop/cop/layout/class_structure.rb +15 -3
  25. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  26. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  27. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +77 -7
  28. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  29. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  32. data/lib/rubocop/cop/layout/line_length.rb +8 -1
  33. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  34. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  35. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  36. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  37. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +2 -1
  38. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +26 -2
  39. data/lib/rubocop/cop/lint/debugger.rb +17 -28
  40. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  41. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  42. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  43. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  44. data/lib/rubocop/cop/lint/empty_block.rb +82 -0
  45. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  46. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  47. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +39 -7
  48. data/lib/rubocop/cop/lint/loop.rb +4 -4
  49. data/lib/rubocop/cop/lint/missing_super.rb +7 -4
  50. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  51. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  52. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  53. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  54. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
  55. data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +86 -0
  57. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +194 -0
  58. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  59. data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
  60. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  61. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  62. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  63. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  64. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  65. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  66. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  67. data/lib/rubocop/cop/mixin/visibility_help.rb +1 -3
  68. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +11 -1
  69. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  70. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  71. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  72. data/lib/rubocop/cop/naming/variable_number.rb +98 -8
  73. data/lib/rubocop/cop/offense.rb +3 -3
  74. data/lib/rubocop/cop/style/and_or.rb +1 -3
  75. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  76. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  77. data/lib/rubocop/cop/style/case_like_if.rb +0 -4
  78. data/lib/rubocop/cop/style/collection_compact.rb +91 -0
  79. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +169 -0
  80. data/lib/rubocop/cop/style/documentation.rb +12 -1
  81. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  82. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  83. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  84. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  85. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
  86. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  87. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  88. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  89. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  90. data/lib/rubocop/cop/style/multiple_comparison.rb +55 -7
  91. data/lib/rubocop/cop/style/negated_if_else_condition.rb +106 -0
  92. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  93. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  94. data/lib/rubocop/cop/style/redundant_argument.rb +73 -0
  95. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  96. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  97. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  98. data/lib/rubocop/cop/style/static_class.rb +97 -0
  99. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  100. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  101. data/lib/rubocop/cop/team.rb +6 -1
  102. data/lib/rubocop/cop/util.rb +6 -2
  103. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  104. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  105. data/lib/rubocop/ext/regexp_node.rb +17 -9
  106. data/lib/rubocop/ext/regexp_parser.rb +84 -0
  107. data/lib/rubocop/formatter/disabled_config_formatter.rb +21 -6
  108. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  109. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  110. data/lib/rubocop/magic_comment.rb +2 -2
  111. data/lib/rubocop/options.rb +7 -0
  112. data/lib/rubocop/rake_task.rb +2 -2
  113. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  114. data/lib/rubocop/runner.rb +1 -1
  115. data/lib/rubocop/target_finder.rb +1 -1
  116. data/lib/rubocop/target_ruby.rb +65 -1
  117. data/lib/rubocop/version.rb +1 -1
  118. metadata +22 -10
  119. data/assets/logo.png +0 -0
  120. data/assets/output.html.erb +0 -261
  121. data/bin/console +0 -10
  122. data/bin/rubocop-profile +0 -32
  123. data/bin/setup +0 -7
@@ -99,7 +99,12 @@ module RuboCop
99
99
  def self.forces_for(cops)
100
100
  needed = Hash.new { |h, k| h[k] = [] }
101
101
  cops.each do |cop|
102
- Array(cop.class.joining_forces).each { |force| needed[force] << cop }
102
+ forces = cop.class.joining_forces
103
+ if forces.is_a?(Array)
104
+ forces.each { |force| needed[force] << cop }
105
+ elsif forces
106
+ needed[forces] << cop
107
+ end
103
108
  end
104
109
 
105
110
  needed.map do |force_class, joining_cops|
@@ -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)
@@ -62,7 +62,7 @@ module RuboCop
62
62
  end
63
63
 
64
64
  def begins_its_line?(range)
65
- (range.source_line =~ /\S/) == range.column
65
+ range.source_line.index(/\S/) == range.column
66
66
  end
67
67
 
68
68
  # Returns, for example, a bare `if` node if the given node is an `if`
@@ -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)
@@ -10,18 +10,26 @@ module RuboCop
10
10
  end
11
11
  private_constant :ANY
12
12
 
13
- class << self
14
- attr_reader :parsed_cache
15
- end
16
- @parsed_cache = {}
17
-
18
13
  # @return [Regexp::Expression::Root, nil]
19
- def parsed_tree
14
+ # Note: we extend Regexp nodes to provide `loc` and `expression`
15
+ # see `ext/regexp_parser`.
16
+ attr_reader :parsed_tree
17
+
18
+ def assign_properties(*)
19
+ super
20
+
20
21
  str = with_interpolations_blanked
21
- Ext::RegexpNode.parsed_cache[str] ||= begin
22
- Regexp::Parser.parse(str, options: options)
22
+ begin
23
+ @parsed_tree = Regexp::Parser.parse(str, options: options)
23
24
  rescue StandardError
24
- 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
25
33
  end
26
34
  end
27
35
 
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Ext
5
+ # Extensions for `regexp_parser` gem
6
+ module RegexpParser
7
+ # Source map for RegexpParser nodes
8
+ class Map < ::Parser::Source::Map
9
+ attr_reader :body, :quantifier, :begin, :end
10
+
11
+ def initialize(expression, body:, quantifier: nil, begin_l: nil, end_l: nil)
12
+ @begin = begin_l
13
+ @end = end_l
14
+ @body = body
15
+ @quantifier = quantifier
16
+ super(expression)
17
+ end
18
+ end
19
+
20
+ module Expression
21
+ # Add `expression` and `loc` to all `regexp_parser` nodes
22
+ module Base
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
29
+
30
+ # Shortcut to `loc.expression`
31
+ def expression
32
+ @expression ||= begin
33
+ origin.adjust(begin_pos: start_index, end_pos: start_index + full_length)
34
+ end
35
+ end
36
+
37
+ # @returns a location map like `parser` does, with:
38
+ # - expression: complete expression
39
+ # - quantifier: for `+`, `{1,2}`, etc.
40
+ # - begin/end: for `[` and `]` (only CharacterSet for now)
41
+ #
42
+ # E.g.
43
+ # [a-z]{2,}
44
+ # ^^^^^^^^^ expression
45
+ # ^^^^ quantifier
46
+ # ^^^^^ body
47
+ # ^ begin
48
+ # ^ end
49
+ #
50
+ # Please open issue if you need other locations
51
+ def loc
52
+ @loc ||= begin
53
+ Map.new(expression, **build_location)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def build_location
60
+ return { body: expression } unless (q = quantifier)
61
+
62
+ body = expression.adjust(end_pos: -q.text.length)
63
+ q_loc = expression.with(begin_pos: body.end_pos)
64
+ { body: body, quantifier: q_loc }
65
+ end
66
+ end
67
+
68
+ # Provide `CharacterSet` with `begin` and `end` locations.
69
+ module CharacterSet
70
+ def build_location
71
+ h = super
72
+ body = h[:body]
73
+ h.merge!(
74
+ begin_l: body.with(end_pos: body.begin_pos + 1),
75
+ end_l: body.with(begin_pos: body.end_pos - 1)
76
+ )
77
+ end
78
+ end
79
+ end
80
+ ::Regexp::Expression::Base.include Expression::Base
81
+ ::Regexp::Expression::CharacterSet.include Expression::CharacterSet
82
+ end
83
+ end
84
+ end
@@ -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,
@@ -30,7 +31,7 @@ module RuboCop
30
31
 
31
32
  FORMATTER_APIS.each do |method_name|
32
33
  define_method(method_name) do |*args|
33
- each { |f| f.send(method_name, *args) }
34
+ each { |f| f.public_send(method_name, *args) }
34
35
  end
35
36
  end
36
37
 
@@ -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
@@ -194,7 +194,7 @@ module RuboCop
194
194
  class SimpleComment < MagicComment
195
195
  # Match `encoding` or `coding`
196
196
  def encoding
197
- extract(/\A\s*\#.*\b(?:en)?coding: (#{TOKEN})/i)
197
+ extract(/\A\s*\#.*\b(?:en)?coding: (#{TOKEN})/io)
198
198
  end
199
199
 
200
200
  private
@@ -207,7 +207,7 @@ module RuboCop
207
207
  # Case-insensitive and dashes/underscores are acceptable.
208
208
  # @see https://git.io/vM7Mg
209
209
  def extract_frozen_string_literal
210
- extract(/\A\s*#\s*frozen[_-]string[_-]literal:\s*(#{TOKEN})\s*\z/i)
210
+ extract(/\A\s*#\s*frozen[_-]string[_-]literal:\s*(#{TOKEN})\s*\z/io)
211
211
  end
212
212
  end
213
213
  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)
@@ -138,3 +138,7 @@ end
138
138
  RSpec.shared_context 'ruby 2.7', :ruby27 do
139
139
  let(:ruby_version) { 2.7 }
140
140
  end
141
+
142
+ RSpec.shared_context 'ruby 3.0', :ruby30 do
143
+ let(:ruby_version) { 3.0 }
144
+ end
@@ -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)