rubocop 0.93.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -16
  3. data/config/default.yml +207 -72
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +16 -2
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  7. data/lib/rubocop/cli/command/version.rb +1 -1
  8. data/lib/rubocop/comment_config.rb +1 -1
  9. data/lib/rubocop/config.rb +4 -0
  10. data/lib/rubocop/config_loader.rb +33 -7
  11. data/lib/rubocop/config_loader_resolver.rb +7 -5
  12. data/lib/rubocop/config_validator.rb +7 -6
  13. data/lib/rubocop/cop/badge.rb +9 -24
  14. data/lib/rubocop/cop/base.rb +16 -1
  15. data/lib/rubocop/cop/bundler/duplicated_gem.rb +26 -6
  16. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  17. data/lib/rubocop/cop/commissioner.rb +37 -23
  18. data/lib/rubocop/cop/corrector.rb +3 -1
  19. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  20. data/lib/rubocop/cop/force.rb +1 -1
  21. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  22. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  23. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  24. data/lib/rubocop/cop/generator.rb +2 -9
  25. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  26. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  27. data/lib/rubocop/cop/layout/class_structure.rb +22 -3
  28. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  29. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  30. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  31. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  32. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  33. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  34. data/lib/rubocop/cop/layout/line_length.rb +8 -1
  35. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  36. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  37. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  38. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  39. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +26 -2
  40. data/lib/rubocop/cop/lint/debugger.rb +17 -28
  41. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  42. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  43. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  44. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  45. data/lib/rubocop/cop/lint/empty_block.rb +82 -0
  46. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  47. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  48. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +38 -6
  49. data/lib/rubocop/cop/lint/loop.rb +4 -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/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_method_definition.rb +2 -4
  61. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  62. data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
  63. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  64. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  65. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  66. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  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 +98 -8
  72. data/lib/rubocop/cop/offense.rb +3 -3
  73. data/lib/rubocop/cop/security/open.rb +12 -10
  74. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -1
  75. data/lib/rubocop/cop/style/and_or.rb +1 -3
  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 +0 -4
  79. data/lib/rubocop/cop/style/collection_compact.rb +91 -0
  80. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +169 -0
  81. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  82. data/lib/rubocop/cop/style/format_string_token.rb +47 -2
  83. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  84. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  85. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  86. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
  87. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  88. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  89. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
  91. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  92. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  93. data/lib/rubocop/cop/style/multiple_comparison.rb +55 -7
  94. data/lib/rubocop/cop/style/negated_if_else_condition.rb +106 -0
  95. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  96. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  97. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  98. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  99. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  100. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  101. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  102. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  103. data/lib/rubocop/cop/style/static_class.rb +97 -0
  104. data/lib/rubocop/cop/style/string_concatenation.rb +13 -1
  105. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  106. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  107. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  108. data/lib/rubocop/cop/team.rb +6 -1
  109. data/lib/rubocop/cop/util.rb +5 -1
  110. data/lib/rubocop/ext/regexp_node.rb +17 -9
  111. data/lib/rubocop/ext/regexp_parser.rb +84 -0
  112. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  113. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  114. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  115. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  116. data/lib/rubocop/magic_comment.rb +2 -2
  117. data/lib/rubocop/options.rb +6 -1
  118. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  119. data/lib/rubocop/target_finder.rb +1 -1
  120. data/lib/rubocop/target_ruby.rb +65 -1
  121. data/lib/rubocop/version.rb +56 -6
  122. metadata +22 -6
@@ -11,10 +11,12 @@ module RuboCop
11
11
  # # bad
12
12
  # f( 3)
13
13
  # g = (a + 3 )
14
+ # f( )
14
15
  #
15
16
  # # good
16
17
  # f(3)
17
18
  # g = (a + 3)
19
+ # f()
18
20
  #
19
21
  # @example EnforcedStyle: space
20
22
  # # The `space` style enforces that parentheses have a space at the
@@ -44,11 +46,7 @@ module RuboCop
44
46
  @processed_source = processed_source
45
47
 
46
48
  if style == :space
47
- each_missing_space(processed_source.tokens) do |range|
48
- add_offense(range, message: MSG_SPACE) do |corrector|
49
- corrector.insert_before(range, ' ')
50
- end
51
- end
49
+ process_with_space_style(processed_source)
52
50
  else
53
51
  each_extraneous_space(processed_source.tokens) do |range|
54
52
  add_offense(range) do |corrector|
@@ -60,6 +58,21 @@ module RuboCop
60
58
 
61
59
  private
62
60
 
61
+ def process_with_space_style(processed_source)
62
+ processed_source.tokens.each_cons(2) do |token1, token2|
63
+ each_extraneous_space_in_empty_parens(token1, token2) do |range|
64
+ add_offense(range) do |corrector|
65
+ corrector.remove(range)
66
+ end
67
+ end
68
+ each_missing_space(token1, token2) do |range|
69
+ add_offense(range, message: MSG_SPACE) do |corrector|
70
+ corrector.insert_before(range, ' ')
71
+ end
72
+ end
73
+ end
74
+ end
75
+
63
76
  def each_extraneous_space(tokens)
64
77
  tokens.each_cons(2) do |token1, token2|
65
78
  next unless parens?(token1, token2)
@@ -73,15 +86,21 @@ module RuboCop
73
86
  end
74
87
  end
75
88
 
76
- def each_missing_space(tokens)
77
- tokens.each_cons(2) do |token1, token2|
78
- next if can_be_ignored?(token1, token2)
89
+ def each_extraneous_space_in_empty_parens(token1, token2)
90
+ return unless token1.left_parens? && token2.right_parens?
79
91
 
80
- if token1.left_parens?
81
- yield range_between(token2.begin_pos, token2.begin_pos + 1)
82
- elsif token2.right_parens?
83
- yield range_between(token2.begin_pos, token2.end_pos)
84
- end
92
+ return if range_between(token1.begin_pos, token2.end_pos).source == '()'
93
+
94
+ yield range_between(token1.end_pos, token2.begin_pos)
95
+ end
96
+
97
+ def each_missing_space(token1, token2)
98
+ return if can_be_ignored?(token1, token2)
99
+
100
+ if token1.left_parens?
101
+ yield range_between(token2.begin_pos, token2.begin_pos + 1)
102
+ elsif token2.right_parens?
103
+ yield range_between(token2.begin_pos, token2.end_pos)
85
104
  end
86
105
  end
87
106
 
@@ -96,6 +115,9 @@ module RuboCop
96
115
  def can_be_ignored?(token1, token2)
97
116
  return true unless parens?(token1, token2)
98
117
 
118
+ # Ignore empty parentheses.
119
+ return true if range_between(token1.begin_pos, token2.end_pos).source == '()'
120
+
99
121
  # If the second token is a comment, that means that a line break
100
122
  # follows, and that the rules for space inside don't apply.
101
123
  return true if token2.comment?
@@ -14,14 +14,25 @@ module RuboCop
14
14
  # # good
15
15
  # x = 0
16
16
  #
17
- # @example AllowInHeredoc: false
17
+ # @example AllowInHeredoc: false (default)
18
18
  # # The line in this example contains spaces after the 0.
19
19
  # # bad
20
20
  # code = <<~RUBY
21
21
  # x = 0
22
22
  # RUBY
23
23
  #
24
- # @example AllowInHeredoc: true (default)
24
+ # # ok
25
+ # code = <<~RUBY
26
+ # x = 0 #{}
27
+ # RUBY
28
+ #
29
+ # # good
30
+ # trailing_whitespace = ' '
31
+ # code = <<~RUBY
32
+ # x = 0#{trailing_whitespace}
33
+ # RUBY
34
+ #
35
+ # @example AllowInHeredoc: true
25
36
  # # The line in this example contains spaces after the 0.
26
37
  # # good
27
38
  # code = <<~RUBY
@@ -35,36 +46,49 @@ module RuboCop
35
46
  MSG = 'Trailing whitespace detected.'
36
47
 
37
48
  def on_new_investigation
38
- heredoc_ranges = extract_heredoc_ranges(processed_source.ast)
49
+ @heredocs = extract_heredocs(processed_source.ast)
39
50
  processed_source.lines.each_with_index do |line, index|
40
- lineno = index + 1
41
-
42
51
  next unless line.end_with?(' ', "\t")
43
- next if skip_heredoc? && inside_heredoc?(heredoc_ranges, lineno)
44
52
 
45
- range = offense_range(lineno, line)
46
- add_offense(range) do |corrector|
53
+ process_line(line, index + 1)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def process_line(line, lineno)
60
+ heredoc = find_heredoc(lineno)
61
+ return if skip_heredoc? && heredoc
62
+
63
+ range = offense_range(lineno, line)
64
+ add_offense(range) do |corrector|
65
+ if heredoc
66
+ corrector.wrap(range, "\#{'", "'}") unless static?(heredoc)
67
+ else
47
68
  corrector.remove(range)
48
69
  end
49
70
  end
50
71
  end
51
72
 
52
- private
73
+ def static?(heredoc)
74
+ heredoc.loc.expression.source.end_with? "'"
75
+ end
53
76
 
54
77
  def skip_heredoc?
55
78
  cop_config.fetch('AllowInHeredoc', false)
56
79
  end
57
80
 
58
- def inside_heredoc?(heredoc_ranges, line_number)
59
- heredoc_ranges.any? { |r| r.include?(line_number) }
81
+ def find_heredoc(line_number)
82
+ @heredocs.each { |node, r| return node if r.include?(line_number) }
83
+ nil
60
84
  end
61
85
 
62
- def extract_heredoc_ranges(ast)
86
+ def extract_heredocs(ast)
63
87
  return [] unless ast
64
88
 
65
89
  ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
66
90
  body = node.location.heredoc_body
67
- (body.first_line...body.last_line)
91
+ [node, body.first_line...body.last_line]
68
92
  end
69
93
  end
70
94
 
@@ -49,7 +49,21 @@ module RuboCop
49
49
  # const_set(:LIST, [])
50
50
  # end
51
51
  # end
52
+ #
53
+ # @example AllowedMethods: ['enums'] (default)
54
+ # # good
55
+ #
56
+ # # `enums` for Typed Enums via `T::Enum` in Sorbet.
57
+ # # https://sorbet.org/docs/tenum
58
+ # class TestEnum < T::Enum
59
+ # enums do
60
+ # Foo = new("foo")
61
+ # end
62
+ # end
63
+ #
52
64
  class ConstantDefinitionInBlock < Base
65
+ include AllowedMethods
66
+
53
67
  MSG = 'Do not define constants this way within a block.'
54
68
 
55
69
  def_node_matcher :constant_assigned_in_block?, <<~PATTERN
@@ -61,13 +75,23 @@ module RuboCop
61
75
  PATTERN
62
76
 
63
77
  def on_casgn(node)
64
- add_offense(node) if constant_assigned_in_block?(node)
78
+ return if !constant_assigned_in_block?(node) || allowed_method?(method_name(node))
79
+
80
+ add_offense(node)
65
81
  end
66
82
 
67
83
  def on_class(node)
68
- add_offense(node) if module_defined_in_block?(node)
84
+ return if !module_defined_in_block?(node) || allowed_method?(method_name(node))
85
+
86
+ add_offense(node)
69
87
  end
70
88
  alias on_module on_class
89
+
90
+ private
91
+
92
+ def method_name(node)
93
+ node.ancestors.find(&:block_type?).send_node.method_name
94
+ end
71
95
  end
72
96
  end
73
97
  end
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # This cop checks for calls to debugger or pry.
7
+ # The cop can be configured to define which methods and receivers must be fixed.
7
8
  #
8
9
  # @example
9
10
  #
@@ -35,34 +36,11 @@ module RuboCop
35
36
  class Debugger < Base
36
37
  MSG = 'Remove debugger entry point `%<source>s`.'
37
38
 
38
- RESTRICT_ON_SEND = %i[
39
- debugger byebug remote_byebug pry remote_pry pry_remote console rescue
40
- save_and_open_page save_and_open_screenshot save_screenshot irb
41
- ].freeze
42
-
43
- def_node_matcher :kernel?, <<~PATTERN
44
- {
45
- (const nil? :Kernel)
46
- (const (cbase) :Kernel)
47
- }
48
- PATTERN
49
-
50
- def_node_matcher :debugger_call?, <<~PATTERN
51
- {(send {nil? #kernel?} {:debugger :byebug :remote_byebug} ...)
52
- (send (send {#kernel? nil?} :binding)
53
- {:pry :remote_pry :pry_remote :console} ...)
54
- (send (const {nil? (cbase)} :Pry) :rescue ...)
55
- (send nil? {:save_and_open_page
56
- :save_and_open_screenshot
57
- :save_screenshot} ...)}
58
- PATTERN
59
-
60
- def_node_matcher :binding_irb_call?, <<~PATTERN
61
- (send (send {#kernel? nil?} :binding) :irb ...)
62
- PATTERN
39
+ RESTRICT_ON_SEND = [].freeze
63
40
 
64
41
  def on_send(node)
65
- return unless debugger_call?(node) || binding_irb?(node)
42
+ return unless debugger_method?(node.method_name)
43
+ return if !node.receiver.nil? && !debugger_receiver?(node)
66
44
 
67
45
  add_offense(node)
68
46
  end
@@ -73,8 +51,19 @@ module RuboCop
73
51
  format(MSG, source: node.source)
74
52
  end
75
53
 
76
- def binding_irb?(node)
77
- binding_irb_call?(node)
54
+ def debugger_method?(name)
55
+ cop_config.fetch('DebuggerMethods', []).include?(name.to_s)
56
+ end
57
+
58
+ def debugger_receiver?(node)
59
+ receiver = case node.receiver
60
+ when RuboCop::AST::SendNode
61
+ node.receiver.method_name
62
+ when RuboCop::AST::ConstNode
63
+ node.receiver.const_name
64
+ end
65
+
66
+ cop_config.fetch('DebuggerReceivers', []).include?(receiver.to_s)
78
67
  end
79
68
  end
80
69
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks that there are no repeated bodies
7
+ # within `if/unless`, `case-when` and `rescue` constructs.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # if foo
12
+ # do_foo
13
+ # do_something_else
14
+ # elsif bar
15
+ # do_foo
16
+ # do_something_else
17
+ # end
18
+ #
19
+ # # good
20
+ # if foo || bar
21
+ # do_foo
22
+ # do_something_else
23
+ # end
24
+ #
25
+ # # bad
26
+ # case x
27
+ # when foo
28
+ # do_foo
29
+ # when bar
30
+ # do_foo
31
+ # else
32
+ # do_something_else
33
+ # end
34
+ #
35
+ # # good
36
+ # case x
37
+ # when foo, bar
38
+ # do_foo
39
+ # else
40
+ # do_something_else
41
+ # end
42
+ #
43
+ # # bad
44
+ # begin
45
+ # do_something
46
+ # rescue FooError
47
+ # handle_error
48
+ # rescue BarError
49
+ # handle_error
50
+ # end
51
+ #
52
+ # # good
53
+ # begin
54
+ # do_something
55
+ # rescue FooError, BarError
56
+ # handle_error
57
+ # end
58
+ #
59
+ class DuplicateBranch < Base
60
+ include RescueNode
61
+
62
+ MSG = 'Duplicate branch body detected.'
63
+
64
+ def on_branching_statement(node)
65
+ branches = node.branches.compact
66
+ branches.each_with_object(Set.new) do |branch, previous|
67
+ add_offense(offense_range(branch)) unless previous.add?(branch)
68
+ end
69
+ end
70
+ alias on_if on_branching_statement
71
+ alias on_case on_branching_statement
72
+ alias on_rescue on_branching_statement
73
+
74
+ private
75
+
76
+ def offense_range(duplicate_branch)
77
+ parent = duplicate_branch.parent
78
+
79
+ if parent.respond_to?(:else_branch) &&
80
+ parent.else_branch.equal?(duplicate_branch)
81
+ if parent.if_type? && parent.ternary?
82
+ duplicate_branch.source_range
83
+ else
84
+ parent.loc.else
85
+ end
86
+ else
87
+ parent.source_range
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -31,22 +31,12 @@ module RuboCop
31
31
  MSG = 'Duplicate `when` condition detected.'
32
32
 
33
33
  def on_case(case_node)
34
- case_node.when_branches.each_with_object([]) do |when_node, previous|
34
+ case_node.when_branches.each_with_object(Set.new) do |when_node, previous|
35
35
  when_node.each_condition do |condition|
36
- next unless repeated_condition?(previous, condition)
37
-
38
- add_offense(condition)
36
+ add_offense(condition) unless previous.add?(condition)
39
37
  end
40
-
41
- previous.push(when_node.conditions)
42
38
  end
43
39
  end
44
-
45
- private
46
-
47
- def repeated_condition?(previous, condition)
48
- previous.any? { |c| c.include?(condition) }
49
- end
50
40
  end
51
41
  end
52
42
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for duplicate elements in Regexp character classes.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # r = /[xyx]/
12
+ #
13
+ # # bad
14
+ # r = /[0-9x0-9]/
15
+ #
16
+ # # good
17
+ # r = /[xy]/
18
+ #
19
+ # # good
20
+ # r = /[0-9x]/
21
+ class DuplicateRegexpCharacterClassElement < Base
22
+ include RangeHelp
23
+ extend AutoCorrector
24
+
25
+ MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
26
+
27
+ def on_regexp(node)
28
+ each_repeated_character_class_element_loc(node) do |loc|
29
+ add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
30
+ corrector.remove(loc)
31
+ end
32
+ end
33
+ end
34
+
35
+ def each_repeated_character_class_element_loc(node)
36
+ node.parsed_tree&.each_expression do |expr|
37
+ next if expr.type != :set || expr.token == :intersection
38
+
39
+ seen = Set.new
40
+
41
+ expr.expressions.each do |child|
42
+ next if within_interpolation?(node, child)
43
+
44
+ child_source = child.to_s
45
+
46
+ yield child.expression if seen.include?(child_source)
47
+
48
+ seen << child_source
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Since we blank interpolations with a space for every char of the interpolation, we would
56
+ # mark every space (except the first) as duplicate if we do not skip regexp_parser nodes
57
+ # that are within an interpolation.
58
+ def within_interpolation?(node, child)
59
+ parse_tree_child_loc = child.expression
60
+
61
+ interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
62
+ end
63
+
64
+ def interpolation_locs(node)
65
+ @interpolation_locs ||= {}
66
+
67
+ # Cache by loc, not by regexp content, as content can be repeated in multiple patterns
68
+ key = node.loc
69
+
70
+ @interpolation_locs[key] ||= node.children.select(&:begin_type?).map do |interpolation|
71
+ interpolation.loc.expression
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end