rubocop 0.92.0 → 1.2.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -7
  3. data/config/default.yml +169 -59
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +15 -3
  6. data/lib/rubocop/cached_data.rb +2 -1
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  8. data/lib/rubocop/cli/command/version.rb +1 -1
  9. data/lib/rubocop/comment_config.rb +1 -1
  10. data/lib/rubocop/config.rb +4 -0
  11. data/lib/rubocop/config_loader.rb +19 -2
  12. data/lib/rubocop/config_loader_resolver.rb +7 -5
  13. data/lib/rubocop/config_validator.rb +7 -6
  14. data/lib/rubocop/cop/badge.rb +9 -24
  15. data/lib/rubocop/cop/base.rb +16 -1
  16. data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
  17. data/lib/rubocop/cop/commissioner.rb +36 -22
  18. data/lib/rubocop/cop/corrector.rb +3 -1
  19. data/lib/rubocop/cop/correctors/line_break_corrector.rb +2 -2
  20. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  21. data/lib/rubocop/cop/force.rb +1 -1
  22. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -10
  23. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  24. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  25. data/lib/rubocop/cop/layout/dot_position.rb +6 -9
  26. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  27. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -7
  28. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  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/space_around_block_parameters.rb +24 -18
  33. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +2 -11
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  35. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +0 -4
  36. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  37. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  38. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -0
  39. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +18 -1
  40. data/lib/rubocop/cop/lint/boolean_symbol.rb +3 -0
  41. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  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 +59 -0
  45. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  46. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +37 -0
  47. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  48. data/lib/rubocop/cop/lint/loop.rb +0 -4
  49. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -0
  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/parentheses_as_grouped_expression.rb +1 -1
  55. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +78 -0
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  57. data/lib/rubocop/cop/lint/to_json.rb +1 -1
  58. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  59. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  60. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  61. data/lib/rubocop/cop/metrics/block_length.rb +3 -1
  62. data/lib/rubocop/cop/metrics/class_length.rb +14 -6
  63. data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
  64. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  65. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  66. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  67. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +12 -2
  68. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  69. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  70. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  71. data/lib/rubocop/cop/naming/variable_number.rb +82 -8
  72. data/lib/rubocop/cop/offense.rb +18 -5
  73. data/lib/rubocop/cop/security/open.rb +12 -10
  74. data/lib/rubocop/cop/style/access_modifier_declarations.rb +6 -2
  75. data/lib/rubocop/cop/style/accessor_grouping.rb +3 -0
  76. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  77. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  78. data/lib/rubocop/cop/style/case_like_if.rb +18 -6
  79. data/lib/rubocop/cop/style/class_equality_comparison.rb +64 -0
  80. data/lib/rubocop/cop/style/collection_compact.rb +85 -0
  81. data/lib/rubocop/cop/style/combinable_loops.rb +8 -1
  82. data/lib/rubocop/cop/style/comment_annotation.rb +6 -0
  83. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  84. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  85. data/lib/rubocop/cop/style/explicit_block_argument.rb +6 -2
  86. data/lib/rubocop/cop/style/for.rb +0 -4
  87. data/lib/rubocop/cop/style/format_string_token.rb +48 -3
  88. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  89. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  91. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
  92. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  93. data/lib/rubocop/cop/style/method_def_parentheses.rb +0 -4
  94. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  95. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  96. data/lib/rubocop/cop/style/negated_if_else_condition.rb +99 -0
  97. data/lib/rubocop/cop/style/nested_ternary_operator.rb +2 -0
  98. data/lib/rubocop/cop/style/raise_args.rb +21 -9
  99. data/lib/rubocop/cop/style/redundant_begin.rb +36 -8
  100. data/lib/rubocop/cop/style/redundant_condition.rb +5 -1
  101. data/lib/rubocop/cop/style/redundant_interpolation.rb +6 -1
  102. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  103. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +45 -24
  104. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -15
  105. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  106. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  107. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  108. data/lib/rubocop/cop/style/string_concatenation.rb +14 -2
  109. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  110. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  111. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  112. data/lib/rubocop/cop/team.rb +6 -1
  113. data/lib/rubocop/cop/util.rb +5 -1
  114. data/lib/rubocop/cop/variable_force/branch.rb +0 -4
  115. data/lib/rubocop/ext/regexp_node.rb +35 -11
  116. data/lib/rubocop/ext/regexp_parser.rb +84 -0
  117. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  118. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  119. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  120. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  121. data/lib/rubocop/magic_comment.rb +2 -2
  122. data/lib/rubocop/options.rb +6 -1
  123. data/lib/rubocop/result_cache.rb +8 -2
  124. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  125. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  126. data/lib/rubocop/runner.rb +4 -4
  127. data/lib/rubocop/target_finder.rb +23 -25
  128. data/lib/rubocop/version.rb +56 -6
  129. metadata +22 -8
  130. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +0 -43
@@ -20,8 +20,6 @@ module RuboCop
20
20
  nil
21
21
  end
22
22
 
23
- # rubocop:disable Metrics/BlockLength
24
-
25
23
  # Abstract base class for branch classes.
26
24
  # A branch represents a conditional branch in a scope.
27
25
  #
@@ -42,8 +40,6 @@ module RuboCop
42
40
  # do_something # no branch
43
41
  # end
44
42
  Base = Struct.new(:child_node, :scope) do
45
- # rubocop:enable Metrics/BlockLength
46
-
47
43
  def self.classes
48
44
  @classes ||= []
49
45
  end
@@ -10,20 +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
20
- return if interpolation?
14
+ # Note: we extend Regexp nodes to provide `loc` and `expression`
15
+ # see `ext/regexp_parser`.
16
+ attr_reader :parsed_tree
21
17
 
22
- str = content
23
- Ext::RegexpNode.parsed_cache[str] ||= begin
24
- Regexp::Parser.parse(str)
18
+ def assign_properties(*)
19
+ super
20
+
21
+ str = with_interpolations_blanked
22
+ begin
23
+ @parsed_tree = Regexp::Parser.parse(str, options: options)
25
24
  rescue StandardError
26
- 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
27
33
  end
28
34
  end
29
35
 
@@ -40,6 +46,24 @@ module RuboCop
40
46
  self
41
47
  end
42
48
 
49
+ private
50
+
51
+ def with_interpolations_blanked
52
+ # Ignore the trailing regopt node
53
+ children[0...-1].map do |child|
54
+ source = child.source
55
+
56
+ # We don't want to consider the contents of interpolations as part of the pattern source,
57
+ # but need to preserve their width, to allow offsets to correctly line up with the
58
+ # original source: spaces have no effect, and preserve width.
59
+ if child.begin_type?
60
+ ' ' * source.length
61
+ else
62
+ source
63
+ end
64
+ end.join
65
+ end
66
+
43
67
  AST::RegexpNode.include self
44
68
  end
45
69
  end
@@ -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
@@ -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
@@ -67,7 +67,7 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def total_offense_count(offense_counts)
70
- offense_counts.values.inject(0, :+)
70
+ offense_counts.values.sum
71
71
  end
72
72
  end
73
73
  end
@@ -55,7 +55,7 @@ module RuboCop
55
55
  end
56
56
 
57
57
  def total_offense_count(offense_counts)
58
- offense_counts.values.inject(0, :+)
58
+ offense_counts.values.sum
59
59
  end
60
60
  end
61
61
  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
@@ -145,6 +145,7 @@ module RuboCop
145
145
  end
146
146
  end
147
147
 
148
+ option(opts, '--display-time')
148
149
  option(opts, '--display-only-failed')
149
150
  end
150
151
 
@@ -242,6 +243,9 @@ module RuboCop
242
243
  # @api private
243
244
  class OptionsValidator
244
245
  class << self
246
+ SYNTAX_DEPARTMENTS = %w[Syntax Lint/Syntax].freeze
247
+ private_constant :SYNTAX_DEPARTMENTS
248
+
245
249
  # Cop name validation must be done later than option parsing, so it's not
246
250
  # called from within Options.
247
251
  def validate_cop_list(names)
@@ -253,7 +257,7 @@ module RuboCop
253
257
  names.each do |name|
254
258
  next if cop_names.include?(name)
255
259
  next if departments.include?(name)
256
- next if %w[Syntax Lint/Syntax].include?(name)
260
+ next if SYNTAX_DEPARTMENTS.include?(name)
257
261
 
258
262
  raise IncorrectCopNameError, format_message_from(name, cop_names)
259
263
  end
@@ -466,6 +470,7 @@ module RuboCop
466
470
  'if no format is specified.'],
467
471
  fail_level: ['Minimum severity (A/R/C/W/E/F) for exit',
468
472
  'with error code.'],
473
+ display_time: 'Display elapsed time in seconds.',
469
474
  display_only_failed: ['Only output offense messages. Omit passing',
470
475
  'cops. Only valid for --format junit.'],
471
476
  display_only_fail_level_offenses:
@@ -95,6 +95,11 @@ module RuboCop
95
95
  context_checksum(team, options),
96
96
  file_checksum(file, config_store))
97
97
  @cached_data = CachedData.new(file)
98
+ @debug = options[:debug]
99
+ end
100
+
101
+ def debug?
102
+ @debug
98
103
  end
99
104
 
100
105
  def valid?
@@ -102,6 +107,7 @@ module RuboCop
102
107
  end
103
108
 
104
109
  def load
110
+ puts "Loading cache from #{@path}" if debug?
105
111
  @cached_data.from_json(IO.read(@path, encoding: Encoding::UTF_8))
106
112
  end
107
113
 
@@ -209,8 +215,8 @@ module RuboCop
209
215
  # The external dependency checksums are cached per RuboCop team so that
210
216
  # the checksums don't need to be recomputed for each file.
211
217
  def team_checksum(team)
212
- @checksum_by_team ||= {}
213
- @checksum_by_team[team.object_id] ||= team.external_dependency_checksum
218
+ @checksum_by_team ||= {}.compare_by_identity
219
+ @checksum_by_team[team] ||= team.external_dependency_checksum
214
220
  end
215
221
 
216
222
  # We combine team and options into a single "context" checksum to avoid
@@ -26,7 +26,7 @@ module CopHelper
26
26
  end
27
27
 
28
28
  def parse_source(source, file = nil)
29
- if file&.respond_to?(:write)
29
+ if file.respond_to?(:write)
30
30
  file.write(source)
31
31
  file.rewind
32
32
  file = file.path
@@ -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
@@ -324,8 +324,8 @@ module RuboCop
324
324
  end
325
325
 
326
326
  def mobilized_cop_classes(config)
327
- @mobilized_cop_classes ||= {}
328
- @mobilized_cop_classes[config.object_id] ||= begin
327
+ @mobilized_cop_classes ||= {}.compare_by_identity
328
+ @mobilized_cop_classes[config] ||= begin
329
329
  cop_classes = Cop::Registry.all
330
330
 
331
331
  OptionsValidator.new(@options).validate_cop_options
@@ -399,8 +399,8 @@ module RuboCop
399
399
  # otherwise dormant team that can be used for config- and option-
400
400
  # level caching in ResultCache.
401
401
  def standby_team(config)
402
- @team_by_config ||= {}
403
- @team_by_config[config.object_id] ||=
402
+ @team_by_config ||= {}.compare_by_identity
403
+ @team_by_config[config] ||=
404
404
  Cop::Team.mobilize(mobilized_cop_classes(config), config, @options)
405
405
  end
406
406
  end
@@ -81,34 +81,32 @@ module RuboCop
81
81
  # the top level directories that are excluded in configuration in the
82
82
  # normal way (dir/**/*).
83
83
  def find_files(base_dir, flags)
84
- wanted_toplevel_dirs = toplevel_dirs(base_dir, flags) -
85
- excluded_dirs(base_dir)
86
- wanted_toplevel_dirs.map! { |dir| dir << '/**/*' }
87
-
88
- pattern = if wanted_toplevel_dirs.empty?
89
- # We need this special case to avoid creating the pattern
90
- # /**/* which searches the entire file system.
91
- ["#{base_dir}/**/*"]
92
- else
93
- # Search the non-excluded top directories, but also add files
94
- # on the top level, which would otherwise not be found.
95
- wanted_toplevel_dirs.unshift("#{base_dir}/*")
84
+ # get all wanted directories first to improve speed of finding all files
85
+ exclude_pattern = combined_exclude_glob_patterns(base_dir)
86
+ dir_flags = flags | File::FNM_PATHNAME | File::FNM_EXTGLOB
87
+ patterns = wanted_dir_patterns(base_dir, exclude_pattern, dir_flags)
88
+ patterns.map! { |dir| File.join(dir, '*') }
89
+ # We need this special case to avoid creating the pattern
90
+ # /**/* which searches the entire file system.
91
+ patterns = [File.join(dir, '**/*')] if patterns.empty?
92
+
93
+ Dir.glob(patterns, flags).select { |path| FileTest.file?(path) }
94
+ end
95
+
96
+ def wanted_dir_patterns(base_dir, exclude_pattern, flags)
97
+ dirs = Dir.glob(File.join(base_dir, '*/'), flags)
98
+ .reject do |dir|
99
+ dir.end_with?('/./', '/../') || File.fnmatch?(exclude_pattern, dir, flags)
96
100
  end
97
- Dir.glob(pattern, flags).select { |path| FileTest.file?(path) }
101
+ dirs.flat_map { |dir| wanted_dir_patterns(dir, exclude_pattern, flags) }
102
+ .unshift(base_dir)
98
103
  end
99
104
 
100
- def toplevel_dirs(base_dir, flags)
101
- Dir.glob(File.join(base_dir, '*'), flags).select do |dir|
102
- File.directory?(dir) && !dir.end_with?('/.', '/..')
103
- end
104
- end
105
-
106
- def excluded_dirs(base_dir)
107
- all_cops_config = @config_store.for(base_dir).for_all_cops
108
- dir_tree_excludes = all_cops_config['Exclude'].select do |pattern|
109
- pattern.is_a?(String) && pattern.end_with?('/**/*')
110
- end
111
- dir_tree_excludes.map { |pattern| pattern.sub(%r{/\*\*/\*$}, '') }
105
+ def combined_exclude_glob_patterns(base_dir)
106
+ exclude = @config_store.for(base_dir).for_all_cops['Exclude']
107
+ patterns = exclude.select { |pattern| pattern.is_a?(String) && pattern.end_with?('/**/*') }
108
+ .map { |pattern| pattern.sub("#{base_dir}/", '') }
109
+ "#{base_dir}/{#{patterns.join(',')}}"
112
110
  end
113
111
 
114
112
  def ruby_extension?(file)
@@ -3,24 +3,74 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '0.92.0'
6
+ STRING = '1.2.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, '\
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
10
10
  'running on %<ruby_engine>s %<ruby_version>s %<ruby_platform>s)'
11
11
 
12
+ CANONICAL_FEATURE_NAMES = { 'Rspec' => 'RSpec' }.freeze
13
+
12
14
  # @api private
13
- def self.version(debug: false)
15
+ def self.version(debug: false, env: nil)
14
16
  if debug
15
- format(MSG, version: STRING, parser_version: Parser::VERSION,
16
- rubocop_ast_version: RuboCop::AST::Version::STRING,
17
- ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
18
- ruby_platform: RUBY_PLATFORM)
17
+ verbose_version = format(MSG, version: STRING, parser_version: Parser::VERSION,
18
+ rubocop_ast_version: RuboCop::AST::Version::STRING,
19
+ ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
20
+ ruby_platform: RUBY_PLATFORM)
21
+ return verbose_version unless env
22
+
23
+ extension_versions = extension_versions(env)
24
+ return verbose_version if extension_versions.empty?
25
+
26
+ <<~VERSIONS
27
+ #{verbose_version}
28
+ #{extension_versions.join("\n")}
29
+ VERSIONS
19
30
  else
20
31
  STRING
21
32
  end
22
33
  end
23
34
 
35
+ # @api private
36
+ def self.extension_versions(env)
37
+ env.config_store.for_pwd.loaded_features.sort.map do |loaded_feature|
38
+ next unless (match = loaded_feature.match(/rubocop-(?<feature>.*)/))
39
+
40
+ feature = match[:feature]
41
+ begin
42
+ require "rubocop/#{feature}/version"
43
+ rescue LoadError
44
+ # Not worth mentioning libs that are not installed
45
+ else
46
+ next unless (feature_version = feature_version(feature))
47
+
48
+ " - #{loaded_feature} #{feature_version}"
49
+ end
50
+ end.compact
51
+ end
52
+
53
+ # Returns feature version in one of two ways:
54
+ #
55
+ # * Find by RuboCop core version style (e.g. rubocop-performance, rubocop-rspec)
56
+ # * Find by `bundle gem` version style (e.g. rubocop-rake)
57
+ #
58
+ # @api private
59
+ def self.feature_version(feature)
60
+ capitalized_feature = feature.capitalize
61
+ extension_name = CANONICAL_FEATURE_NAMES.fetch(capitalized_feature, capitalized_feature)
62
+
63
+ # Find by RuboCop core version style (e.g. rubocop-performance, rubocop-rspec)
64
+ RuboCop.const_get(extension_name)::Version::STRING
65
+ rescue NameError
66
+ begin
67
+ # Find by `bundle gem` version style (e.g. rubocop-rake, rubocop-packaging)
68
+ RuboCop.const_get(extension_name)::VERSION
69
+ rescue NameError
70
+ # noop
71
+ end
72
+ end
73
+
24
74
  # @api private
25
75
  def self.document_version
26
76
  STRING.match('\d+\.\d+').to_s