rubocop 1.70.0 → 1.71.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +17 -0
  4. data/lib/rubocop/cli/command/show_cops.rb +24 -2
  5. data/lib/rubocop/comment_config.rb +1 -1
  6. data/lib/rubocop/cop/internal_affairs/location_expression.rb +2 -1
  7. data/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb +3 -2
  8. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +90 -0
  9. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +2 -1
  10. data/lib/rubocop/cop/internal_affairs/single_line_comparison.rb +5 -4
  11. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  12. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  13. data/lib/rubocop/cop/layout/class_structure.rb +7 -7
  14. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +1 -1
  15. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -1
  16. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +1 -1
  17. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +1 -1
  18. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +1 -0
  19. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -0
  20. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -0
  21. data/lib/rubocop/cop/lint/array_literal_in_regexp.rb +119 -0
  22. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +1 -1
  23. data/lib/rubocop/cop/lint/duplicate_set_element.rb +1 -1
  24. data/lib/rubocop/cop/lint/float_comparison.rb +5 -2
  25. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  26. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +12 -2
  27. data/lib/rubocop/cop/lint/mixed_case_range.rb +1 -1
  28. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -1
  29. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +12 -17
  30. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +2 -1
  31. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  32. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +2 -2
  33. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -1
  34. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
  35. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -3
  36. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  37. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +2 -1
  38. data/lib/rubocop/cop/metrics/collection_literal_length.rb +7 -0
  39. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
  40. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  41. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  42. data/lib/rubocop/cop/mixin/hash_subset.rb +170 -0
  43. data/lib/rubocop/cop/mixin/statement_modifier.rb +7 -2
  44. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -2
  45. data/lib/rubocop/cop/security/compound_hash.rb +1 -0
  46. data/lib/rubocop/cop/style/array_first_last.rb +18 -2
  47. data/lib/rubocop/cop/style/block_delimiters.rb +6 -19
  48. data/lib/rubocop/cop/style/collection_methods.rb +1 -1
  49. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  50. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  51. data/lib/rubocop/cop/style/exact_regexp_match.rb +1 -1
  52. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  53. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  54. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
  55. data/lib/rubocop/cop/style/hash_except.rb +5 -131
  56. data/lib/rubocop/cop/style/hash_slice.rb +65 -0
  57. data/lib/rubocop/cop/style/map_to_set.rb +3 -2
  58. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -1
  59. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  60. data/lib/rubocop/cop/style/open_struct_use.rb +4 -4
  61. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  62. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  63. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  64. data/lib/rubocop/cop/style/redundant_line_continuation.rb +27 -10
  65. data/lib/rubocop/cop/style/redundant_parentheses.rb +3 -0
  66. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  67. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  68. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  69. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -1
  70. data/lib/rubocop/cop/style/string_methods.rb +1 -1
  71. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -1
  72. data/lib/rubocop/cops_documentation_generator.rb +13 -13
  73. data/lib/rubocop/directive_comment.rb +9 -8
  74. data/lib/rubocop/options.rb +2 -1
  75. data/lib/rubocop/result_cache.rb +13 -13
  76. data/lib/rubocop/rspec/expect_offense.rb +6 -2
  77. data/lib/rubocop/target_finder.rb +1 -0
  78. data/lib/rubocop/version.rb +1 -1
  79. data/lib/rubocop.rb +3 -0
  80. metadata +9 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55382302106a1f2c44166213aaa0c23bfccc3c0ebac5b1af95fa9073ddf59aa0
4
- data.tar.gz: bb1a6e491c7750b3f4aff90fbe2590f289d5f85ca9bbc2d71e01bd35d420b38c
3
+ metadata.gz: 0f4339f6b223b2a7fd5f68764dd3030e5cb83925c0605414459ed0f3d2543510
4
+ data.tar.gz: 525042279be387c345b3efd57ed7ca976e3eda5b15ba9723485b72f3a083816e
5
5
  SHA512:
6
- metadata.gz: 6cff98184e513e6a57e5a82234f57a8130ef2e242eb3df675b677cfe5be8920d3cb732ce1f615385641731c328253fb65829922d371621d5e5148601f309ee51
7
- data.tar.gz: 3c3d8e5fc22db6f9b1a1ca6f9e31a8a29467c4df358e12da099fcc528884832d00bc18842d846ee76300791c1cb76122a22b6eb079b5a713c6f7b827fbea71dc
6
+ metadata.gz: 404c792e4624474f907576ef9c73fc3ee09a5736c63becf75197fe40d5e9fdefb7c2cb6bc22326857d3b5065f50badd1a6a4d8f7cecd219fb6ec7280bb95a97a
7
+ data.tar.gz: 6e3071938d0f6d7850a732c41ce55890c6a2ba8bcd352df28203c136453cf249fd21a10c9c3d1ea7762376466a669a287efb56c8b7505877d8b17cacdd5dd059
data/README.md CHANGED
@@ -52,7 +52,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
52
52
  in your `Gemfile`:
53
53
 
54
54
  ```rb
55
- gem 'rubocop', '~> 1.70', require: false
55
+ gem 'rubocop', '~> 1.71', require: false
56
56
  ```
57
57
 
58
58
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -1616,6 +1616,12 @@ Lint/AmbiguousRegexpLiteral:
1616
1616
  VersionAdded: '0.17'
1617
1617
  VersionChanged: '0.83'
1618
1618
 
1619
+ Lint/ArrayLiteralInRegexp:
1620
+ Description: 'Checks for an array literal interpolated inside a regexp.'
1621
+ Enabled: pending
1622
+ VersionAdded: '1.71'
1623
+ SafeAutoCorrect: false
1624
+
1619
1625
  Lint/AssignmentInCondition:
1620
1626
  Description: "Don't use assignment in conditions."
1621
1627
  StyleGuide: '#safe-assignment-in-condition'
@@ -4063,6 +4069,9 @@ Style/FrozenStringLiteralComment:
4063
4069
  # exist in a file.
4064
4070
  - never
4065
4071
  SafeAutoCorrect: false
4072
+ Exclude:
4073
+ # Prevent the Ruby warning: `'frozen_string_literal' is ignored after any tokens` when using Active Admin.
4074
+ - '**/*.arb'
4066
4075
 
4067
4076
  Style/GlobalStdStream:
4068
4077
  Description: 'Enforces the use of `$stdout/$stderr/$stdin` instead of `STDOUT/STDERR/STDIN`.'
@@ -4141,6 +4150,14 @@ Style/HashLikeCase:
4141
4150
  # to trigger this cop
4142
4151
  MinBranchesCount: 3
4143
4152
 
4153
+ Style/HashSlice:
4154
+ Description: >-
4155
+ Checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
4156
+ that can be replaced with `Hash#slice` method.
4157
+ Enabled: pending
4158
+ Safe: false
4159
+ VersionAdded: '1.71'
4160
+
4144
4161
  Style/HashSyntax:
4145
4162
  Description: >-
4146
4163
  Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
@@ -9,11 +9,31 @@ module RuboCop
9
9
  class ShowCops < Base
10
10
  self.command_name = :show_cops
11
11
 
12
+ ExactMatcher = Struct.new(:pattern) do
13
+ def match?(name)
14
+ name == pattern
15
+ end
16
+ end
17
+
18
+ WildcardMatcher = Struct.new(:pattern) do
19
+ def match?(name)
20
+ File.fnmatch(pattern, name, File::FNM_PATHNAME)
21
+ end
22
+ end
23
+
12
24
  def initialize(env)
13
25
  super
14
26
 
15
27
  # Load the configs so the require()s are done for custom cops
16
28
  @config = @config_store.for(Dir.pwd)
29
+
30
+ @cop_matchers = @options[:show_cops].map do |pattern|
31
+ if pattern.include?('*')
32
+ WildcardMatcher.new(pattern)
33
+ else
34
+ ExactMatcher.new(pattern)
35
+ end
36
+ end
17
37
  end
18
38
 
19
39
  def run
@@ -24,7 +44,7 @@ module RuboCop
24
44
 
25
45
  def print_available_cops
26
46
  registry = Cop::Registry.global
27
- show_all = @options[:show_cops].empty?
47
+ show_all = @cop_matchers.empty?
28
48
 
29
49
  puts "# Available cops (#{registry.length}) + config for #{Dir.pwd}: " if show_all
30
50
 
@@ -56,7 +76,9 @@ module RuboCop
56
76
 
57
77
  def selected_cops_of_department(cops, department)
58
78
  cops_of_department(cops, department).select do |cop|
59
- @options[:show_cops].include?(cop.cop_name)
79
+ @cop_matchers.any? do |matcher|
80
+ matcher.match?(cop.cop_name)
81
+ end
60
82
  end
61
83
  end
62
84
 
@@ -87,7 +87,7 @@ module RuboCop
87
87
  next unless directive.enabled?
88
88
  next if directive.all_cops?
89
89
 
90
- cops.merge(directive.cop_names)
90
+ cops.merge(directive.raw_cop_names)
91
91
  end
92
92
  cops
93
93
  end
@@ -22,7 +22,7 @@ module RuboCop
22
22
 
23
23
  def on_send(node)
24
24
  return unless (parent = node.parent)
25
- return unless parent.send_type? && parent.method?(:expression)
25
+ return unless parent.call_type? && parent.method?(:expression)
26
26
  return unless parent.receiver.receiver
27
27
 
28
28
  offense = node.loc.selector.join(parent.source_range.end)
@@ -31,6 +31,7 @@ module RuboCop
31
31
  corrector.replace(offense, 'source_range')
32
32
  end
33
33
  end
34
+ alias on_csend on_send
34
35
  end
35
36
  end
36
37
  end
@@ -27,8 +27,8 @@ module RuboCop
27
27
  # @!method arguments_first_or_last?(node)
28
28
  def_node_matcher :arguments_first_or_last?, <<~PATTERN
29
29
  {
30
- (send (send !nil? :arguments) ${:first :last})
31
- (send (send !nil? :arguments) :[] (int ${0 -1}))
30
+ (call (call !nil? :arguments) ${:first :last})
31
+ (call (call !nil? :arguments) :[] (int ${0 -1}))
32
32
  }
33
33
  PATTERN
34
34
 
@@ -47,6 +47,7 @@ module RuboCop
47
47
  end
48
48
  end
49
49
  end
50
+ alias on_csend on_send
50
51
  end
51
52
  end
52
53
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Checks for cops that define `on_send` without define `on_csend`.
7
+ #
8
+ # Although in some cases it can be predetermined that safe navigation
9
+ # will never be used with the code checked by a specific cop, in general
10
+ # it is good practice to handle safe navigation methods if handling any
11
+ # `send` node.
12
+ #
13
+ # NOTE: It is expected to disable this cop for cops that check for method calls
14
+ # on receivers that cannot be nil (`self`, a literal, a constant), and
15
+ # method calls that will never have a receiver (ruby keywords like `raise`,
16
+ # macros like `attr_reader`, DSL methods, etc.), and other checks that wouldn't
17
+ # make sense to support safe navigation.
18
+ #
19
+ # @example
20
+ # # bad
21
+ # class MyCop < RuboCop::Cop:Base
22
+ # def on_send(node)
23
+ # # ...
24
+ # end
25
+ # end
26
+ #
27
+ # # good - explicit method definition
28
+ # class MyCop < RuboCop::Cop:Base
29
+ # def on_send(node)
30
+ # # ...
31
+ # end
32
+ #
33
+ # def on_csend(node)
34
+ # # ...
35
+ # end
36
+ # end
37
+ #
38
+ # # good - alias
39
+ # class MyCop < RuboCop::Cop:Base
40
+ # def on_send(node)
41
+ # # ...
42
+ # end
43
+ # alias on_csend on_send
44
+ # end
45
+ #
46
+ # # good - alias_method
47
+ # class MyCop < RuboCop::Cop:Base
48
+ # def on_send(node)
49
+ # # ...
50
+ # end
51
+ # alias_method :on_csend, :on_send
52
+ # end
53
+ class OnSendWithoutOnCSend < Base
54
+ RESTRICT_ON_SEND = %i[alias_method].freeze
55
+ MSG = 'Cop defines `on_send` but not `on_csend`.'
56
+
57
+ def on_new_investigation
58
+ @on_send_definition = nil
59
+ @on_csend_definition = nil
60
+ end
61
+
62
+ def on_investigation_end
63
+ return unless @on_send_definition && !@on_csend_definition
64
+
65
+ add_offense(@on_send_definition)
66
+ end
67
+
68
+ def on_def(node)
69
+ @on_send_definition = node if node.method?(:on_send)
70
+ @on_csend_definition = node if node.method?(:on_csend)
71
+ end
72
+
73
+ def on_alias(node)
74
+ @on_send_definition = node if node.new_identifier.value == :on_send
75
+ @on_csend_definition = node if node.new_identifier.value == :on_csend
76
+ end
77
+
78
+ def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
79
+ new_identifier = node.first_argument
80
+ return unless new_identifier.basic_literal?
81
+
82
+ new_identifier = new_identifier.value
83
+
84
+ @on_send_definition = node if new_identifier == :on_send
85
+ @on_csend_definition = node if new_identifier == :on_csend
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -47,7 +47,7 @@ module RuboCop
47
47
  # @!method redundant_source_range(node)
48
48
  def_node_matcher :redundant_source_range, <<~PATTERN
49
49
  {
50
- (send $(send _ :source_range) :source)
50
+ (call $(call _ :source_range) :source)
51
51
  (send nil? :add_offense $(send _ :source_range) ...)
52
52
  (send _ {
53
53
  :replace :insert_before :insert_before_multi :insert_after :insert_after_multi
@@ -67,6 +67,7 @@ module RuboCop
67
67
  corrector.remove(source_range.loc.dot.join(selector))
68
68
  end
69
69
  end
70
+ alias on_csend on_send
70
71
  end
71
72
  end
72
73
  end
@@ -34,8 +34,8 @@ module RuboCop
34
34
  # @!method single_line_comparison(node)
35
35
  def_node_matcher :single_line_comparison, <<~PATTERN
36
36
  {
37
- (send (send $_receiver {:line :first_line}) {:== :!=} (send _receiver :last_line))
38
- (send (send $_receiver :last_line) {:== :!=} (send _receiver {:line :first_line}))
37
+ (send (call $_receiver {:line :first_line}) {:== :!=} (call _receiver :last_line))
38
+ (send (call $_receiver :last_line) {:== :!=} (call _receiver {:line :first_line}))
39
39
  }
40
40
  PATTERN
41
41
 
@@ -43,7 +43,8 @@ module RuboCop
43
43
  return unless (receiver = single_line_comparison(node))
44
44
 
45
45
  bang = node.method?(:!=) ? '!' : ''
46
- preferred = "#{bang}#{extract_receiver(receiver)}.single_line?"
46
+ dot = receiver.parent.loc.dot.source
47
+ preferred = "#{bang}#{extract_receiver(receiver)}#{dot}single_line?"
47
48
 
48
49
  add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
49
50
  corrector.replace(node, preferred)
@@ -53,7 +54,7 @@ module RuboCop
53
54
  private
54
55
 
55
56
  def extract_receiver(node)
56
- node = node.receiver if node.send_type? && %i[loc source_range].include?(node.method_name)
57
+ node = node.receiver if node.call_type? && %i[loc source_range].include?(node.method_name)
57
58
  node.source
58
59
  end
59
60
  end
@@ -18,6 +18,7 @@ require_relative 'internal_affairs/node_matcher_directive'
18
18
  require_relative 'internal_affairs/node_type_predicate'
19
19
  require_relative 'internal_affairs/numblock_handler'
20
20
  require_relative 'internal_affairs/offense_location_keyword'
21
+ require_relative 'internal_affairs/on_send_without_on_csend'
21
22
  require_relative 'internal_affairs/operator_keyword'
22
23
  require_relative 'internal_affairs/processed_source_buffer_name'
23
24
  require_relative 'internal_affairs/redundant_context_config_parameter'
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Check that the arguments on a multi-line method definition are aligned.
6
+ # Check that the arguments on a multi-line method call are aligned.
7
7
  #
8
8
  # @example EnforcedStyle: with_first_argument (default)
9
9
  # # good
@@ -3,23 +3,23 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Checks if the code style follows the ExpectedOrder configuration:
6
+ # Checks if the code style follows the `ExpectedOrder` configuration:
7
7
  #
8
8
  # `Categories` allows us to map macro names into a category.
9
9
  #
10
10
  # Consider an example of code style that covers the following order:
11
11
  #
12
- # * Module inclusion (include, prepend, extend)
12
+ # * Module inclusion (`include`, `prepend`, `extend`)
13
13
  # * Constants
14
- # * Associations (has_one, has_many)
15
- # * Public attribute macros (attr_accessor, attr_writer, attr_reader)
16
- # * Other macros (validates, validate)
14
+ # * Associations (`has_one`, `has_many`)
15
+ # * Public attribute macros (`attr_accessor`, `attr_writer`, `attr_reader`)
16
+ # * Other macros (`validates`, `validate`)
17
17
  # * Public class methods
18
18
  # * Initializer
19
19
  # * Public instance methods
20
- # * Protected attribute macros (attr_accessor, attr_writer, attr_reader)
20
+ # * Protected attribute macros (`attr_accessor`, `attr_writer`, `attr_reader`)
21
21
  # * Protected instance methods
22
- # * Private attribute macros (attr_accessor, attr_writer, attr_reader)
22
+ # * Private attribute macros (`attr_accessor`, `attr_writer`, `attr_reader`)
23
23
  # * Private instance methods
24
24
  #
25
25
  # You can configure the following order:
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Checks if empty lines exist around the bodies of `begin`
7
7
  # sections. This cop doesn't check empty lines at `begin` body
8
8
  # beginning/end and around method definition body.
9
- # `Style/EmptyLinesAroundBeginBody` or `Style/EmptyLinesAroundMethodBody`
9
+ # `Layout/EmptyLinesAroundBeginBody` or `Layout/EmptyLinesAroundMethodBody`
10
10
  # can be used for this purpose.
11
11
  #
12
12
  # @example
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # where the opening brace and the first key are on separate lines. The
8
8
  # other keys' indentations are handled by the HashAlignment cop.
9
9
  #
10
- # By default, Hash literals that are arguments in a method call with
10
+ # By default, `Hash` literals that are arguments in a method call with
11
11
  # parentheses, and where the opening curly brace of the hash is on the
12
12
  # same line as the opening parenthesis of the method call, shall have
13
13
  # their first key indented one step (two spaces) more than the position
@@ -51,7 +51,7 @@ module RuboCop
51
51
 
52
52
  def on_hash(node)
53
53
  # node.loc.begin tells us whether the hash opens with a {
54
- # If it doesn't, Style/FirstMethodArgumentLineBreak will handle it
54
+ # If it doesn't, Layout/FirstMethodArgumentLineBreak will handle it
55
55
  return unless node.loc.begin
56
56
 
57
57
  check_children_line_break(node, node.children, ignore_last: ignore_last_element?)
@@ -52,7 +52,7 @@ module RuboCop
52
52
  def on_hash(node)
53
53
  # This cop only deals with hashes wrapped by a set of curly
54
54
  # braces like {foo: 1}. That is, not a kwargs hashes.
55
- # Style/MultilineMethodArgumentLineBreaks handles those.
55
+ # Layout/MultilineMethodArgumentLineBreaks handles those.
56
56
  return unless starts_with_curly_brace?(node)
57
57
  return unless node.loc.begin
58
58
 
@@ -99,6 +99,7 @@ module RuboCop
99
99
 
100
100
  check_line_breaks(node, args, ignore_last: ignore_last_element?)
101
101
  end
102
+ alias on_csend on_send
102
103
 
103
104
  private
104
105
 
@@ -109,6 +109,7 @@ module RuboCop
109
109
  def on_send(node)
110
110
  check_brace_layout(node)
111
111
  end
112
+ alias on_csend on_send
112
113
 
113
114
  private
114
115
 
@@ -36,6 +36,7 @@ module RuboCop
36
36
  ACCEPT_LEFT_PAREN = %w[break defined? next not rescue return super yield].freeze
37
37
  ACCEPT_LEFT_SQUARE_BRACKET = %w[super yield].freeze
38
38
  ACCEPT_NAMESPACE_OPERATOR = 'super'
39
+ RESTRICT_ON_SEND = %i[!].freeze
39
40
 
40
41
  def on_and(node)
41
42
  check(node, [:operator].freeze) if node.keyword?
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for an array literal interpolated inside a regexp.
7
+ #
8
+ # When interpolating an array literal, it is converted to a string. This means
9
+ # that when inside a regexp, it acts as a character class but with additional
10
+ # quotes, spaces and commas that are likely not intended. For example,
11
+ # `/#{%w[a b c]}/` parses as `/["a", "b", "c"]/` (or `/["a, bc]/` without
12
+ # repeated characters).
13
+ #
14
+ # The cop can autocorrect to a character class (if all items in the array are a
15
+ # single character) or alternation (if the array contains longer items).
16
+ #
17
+ # NOTE: This only considers interpolated arrays that contain only strings, symbols,
18
+ # integers, and floats. Any other type is not easily convertible to a character class
19
+ # or regexp alternation.
20
+ #
21
+ # @safety
22
+ # Autocorrection is unsafe because it will change the regexp pattern, by
23
+ # removing the additional quotes, spaces and commas from the character class.
24
+ #
25
+ # @example
26
+ # # bad
27
+ # /#{%w[a b c]}/
28
+ #
29
+ # # good
30
+ # /[abc]/
31
+ #
32
+ # # bad
33
+ # /#{%w[foo bar baz]}/
34
+ #
35
+ # # good
36
+ # /(?:foo|bar|baz)/
37
+ #
38
+ # # bad - construct a regexp rather than interpolate an array of identifiers
39
+ # /#{[foo, bar]}/
40
+ #
41
+ class ArrayLiteralInRegexp < Base
42
+ include Interpolation
43
+ extend AutoCorrector
44
+
45
+ LITERAL_TYPES = %i[str sym int float true false nil].freeze
46
+ private_constant :LITERAL_TYPES
47
+
48
+ MSG_CHARACTER_CLASS = 'Use a character class instead of interpolating an array in a regexp.'
49
+ MSG_ALTERNATION = 'Use alternation instead of interpolating an array in a regexp.'
50
+ MSG_UNKNOWN = 'Use alternation or a character class instead of interpolating an array ' \
51
+ 'in a regexp.'
52
+
53
+ def on_interpolation(begin_node)
54
+ final_node = begin_node.children.last
55
+
56
+ return unless begin_node.parent.regexp_type?
57
+ return unless final_node.array_type?
58
+
59
+ if array_of_literal_values?(final_node)
60
+ register_array_of_literal_values(begin_node, final_node)
61
+ else
62
+ register_array_of_nonliteral_values(begin_node)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def array_of_literal_values?(node)
69
+ node.each_value.all? { |value| value.type?(*LITERAL_TYPES) }
70
+ end
71
+
72
+ def register_array_of_literal_values(begin_node, node)
73
+ array_values = array_values(node)
74
+
75
+ if character_class?(array_values)
76
+ message = MSG_CHARACTER_CLASS
77
+ replacement = character_class_for(array_values)
78
+ else
79
+ message = MSG_ALTERNATION
80
+ replacement = alternation_for(array_values)
81
+ end
82
+
83
+ add_offense(begin_node, message: message) do |corrector|
84
+ corrector.replace(begin_node, replacement)
85
+ end
86
+ end
87
+
88
+ def register_array_of_nonliteral_values(node)
89
+ # Add offense but do not correct if the array contains any nonliteral values.
90
+ add_offense(node, message: MSG_UNKNOWN)
91
+ end
92
+
93
+ def array_values(node)
94
+ node.each_value.map do |value|
95
+ value.respond_to?(:value) ? value.value : value.source
96
+ end
97
+ end
98
+
99
+ def character_class?(values)
100
+ values.all? { |v| v.to_s.length == 1 }
101
+ end
102
+
103
+ def character_class_for(values)
104
+ "[#{escape_values(values).join}]"
105
+ end
106
+
107
+ def alternation_for(values)
108
+ "(?:#{escape_values(values).join('|')})"
109
+ end
110
+
111
+ def escape_values(values)
112
+ # This may add extraneous escape characters, but they can be cleaned up
113
+ # by `Style/RedundantRegexpEscape`.
114
+ values.map { |value| Regexp.escape(value.to_s) }
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for duplicate elements in Regexp character classes.
6
+ # Checks for duplicate elements in `Regexp` character classes.
7
7
  #
8
8
  # @example
9
9
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for duplicate literal, constant, or variable elements in Set and SortedSet.
6
+ # Checks for duplicate literal, constant, or variable elements in `Set` and `SortedSet`.
7
7
  #
8
8
  # @example
9
9
  #
@@ -36,7 +36,8 @@ module RuboCop
36
36
  # # https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/
37
37
  #
38
38
  class FloatComparison < Base
39
- MSG = 'Avoid (in)equality comparisons of floats as they are unreliable.'
39
+ MSG_EQUALITY = 'Avoid equality comparisons of floats as they are unreliable.'
40
+ MSG_INEQUALITY = 'Avoid inequality comparisons of floats as they are unreliable.'
40
41
 
41
42
  EQUALITY_METHODS = %i[== != eql? equal?].freeze
42
43
  FLOAT_RETURNING_METHODS = %i[to_f Float fdiv].freeze
@@ -52,8 +53,10 @@ module RuboCop
52
53
 
53
54
  return if literal_safe?(lhs) || literal_safe?(rhs)
54
55
 
55
- add_offense(node) if float?(lhs) || float?(rhs)
56
+ message = node.method?(:!=) ? MSG_INEQUALITY : MSG_EQUALITY
57
+ add_offense(node, message: message) if float?(lhs) || float?(rhs)
56
58
  end
59
+ alias on_csend on_send
57
60
 
58
61
  private
59
62
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Identifies Float literals which are, like, really really really
6
+ # Identifies `Float` literals which are, like, really really really
7
7
  # really really really really really big. Too big. No-one needs Floats
8
8
  # that big. If you need a float that big, something is wrong with you.
9
9
  #
@@ -5,6 +5,9 @@ module RuboCop
5
5
  module Lint
6
6
  # Checks for interpolated literals.
7
7
  #
8
+ # NOTE: Array literals interpolated in regexps are not handled by this cop, but
9
+ # by `Lint/ArrayLiteralInRegexp` instead.
10
+ #
8
11
  # @example
9
12
  #
10
13
  # # bad
@@ -55,8 +58,10 @@ module RuboCop
55
58
  node &&
56
59
  !special_keyword?(node) &&
57
60
  prints_as_self?(node) &&
58
- # Special case for Layout/TrailingWhitespace
59
- !(space_literal?(node) && ends_heredoc_line?(node))
61
+ # Special case for `Layout/TrailingWhitespace`
62
+ !(space_literal?(node) && ends_heredoc_line?(node)) &&
63
+ # Handled by `Lint/ArrayLiteralInRegexp`
64
+ !array_in_regexp?(node)
60
65
  end
61
66
 
62
67
  def special_keyword?(node)
@@ -64,6 +69,11 @@ module RuboCop
64
69
  (node.str_type? && !node.loc.respond_to?(:begin)) || node.source_range.is?('__LINE__')
65
70
  end
66
71
 
72
+ def array_in_regexp?(node)
73
+ grandparent = node.parent.parent
74
+ node.array_type? && grandparent.regexp_type?
75
+ end
76
+
67
77
  # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
68
78
  def autocorrected_value(node)
69
79
  case node.type
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # Offenses are registered for regexp character classes like `/[A-z]/`
9
9
  # as well as range objects like `('A'..'z')`.
10
10
  #
11
- # NOTE: Range objects cannot be autocorrected.
11
+ # NOTE: `Range` objects cannot be autocorrected.
12
12
  #
13
13
  # @safety
14
14
  # The cop autocorrects regexp character classes
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Do not mix named captures and numbered captures in a Regexp literal
6
+ # Do not mix named captures and numbered captures in a `Regexp` literal
7
7
  # because numbered capture is ignored if they're mixed.
8
8
  # Replace numbered captures with non-capturing groupings or
9
9
  # named captures.