rubocop 0.91.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -5
  3. data/config/default.yml +143 -56
  4. data/lib/rubocop.rb +17 -5
  5. data/lib/rubocop/cached_data.rb +2 -1
  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 +19 -2
  11. data/lib/rubocop/config_loader_resolver.rb +7 -5
  12. data/lib/rubocop/config_regeneration.rb +33 -0
  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/array_alignment.rb +1 -0
  24. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  25. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  26. data/lib/rubocop/cop/layout/dot_position.rb +6 -9
  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/extra_spacing.rb +1 -2
  30. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +2 -11
  31. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  32. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +0 -4
  33. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  34. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -0
  35. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +18 -1
  36. data/lib/rubocop/cop/lint/boolean_symbol.rb +3 -0
  37. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  38. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  39. data/lib/rubocop/cop/lint/empty_block.rb +46 -0
  40. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  41. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +37 -0
  42. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  43. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -0
  44. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  45. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  46. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  47. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +78 -0
  48. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  49. data/lib/rubocop/cop/lint/to_json.rb +1 -1
  50. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  51. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  52. data/lib/rubocop/cop/metrics/block_length.rb +3 -1
  53. data/lib/rubocop/cop/metrics/class_length.rb +14 -6
  54. data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
  55. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  56. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  57. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  58. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  59. data/lib/rubocop/cop/offense.rb +18 -5
  60. data/lib/rubocop/cop/security/open.rb +12 -10
  61. data/lib/rubocop/cop/style/access_modifier_declarations.rb +6 -2
  62. data/lib/rubocop/cop/style/accessor_grouping.rb +3 -0
  63. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  64. data/lib/rubocop/cop/style/array_coercion.rb +4 -0
  65. data/lib/rubocop/cop/style/case_like_if.rb +20 -4
  66. data/lib/rubocop/cop/style/class_equality_comparison.rb +64 -0
  67. data/lib/rubocop/cop/style/combinable_loops.rb +8 -1
  68. data/lib/rubocop/cop/style/comment_annotation.rb +6 -0
  69. data/lib/rubocop/cop/style/date_time.rb +12 -1
  70. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  71. data/lib/rubocop/cop/style/explicit_block_argument.rb +6 -2
  72. data/lib/rubocop/cop/style/for.rb +0 -4
  73. data/lib/rubocop/cop/style/format_string_token.rb +48 -3
  74. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  75. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
  76. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  77. data/lib/rubocop/cop/style/method_def_parentheses.rb +0 -4
  78. data/lib/rubocop/cop/style/mixin_usage.rb +7 -27
  79. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  80. data/lib/rubocop/cop/style/nested_ternary_operator.rb +2 -0
  81. data/lib/rubocop/cop/style/optional_boolean_parameter.rb +11 -3
  82. data/lib/rubocop/cop/style/raise_args.rb +0 -3
  83. data/lib/rubocop/cop/style/redundant_begin.rb +36 -8
  84. data/lib/rubocop/cop/style/redundant_condition.rb +5 -1
  85. data/lib/rubocop/cop/style/redundant_interpolation.rb +6 -1
  86. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  87. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +45 -24
  88. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -15
  89. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  90. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  91. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  92. data/lib/rubocop/cop/style/string_concatenation.rb +14 -2
  93. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  94. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  95. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  96. data/lib/rubocop/cop/team.rb +6 -1
  97. data/lib/rubocop/cop/util.rb +1 -1
  98. data/lib/rubocop/cop/variable_force/branch.rb +0 -4
  99. data/lib/rubocop/ext/regexp_node.rb +29 -10
  100. data/lib/rubocop/ext/regexp_parser.rb +77 -0
  101. data/lib/rubocop/formatter/disabled_config_formatter.rb +12 -5
  102. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  103. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  104. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  105. data/lib/rubocop/magic_comment.rb +2 -2
  106. data/lib/rubocop/options.rb +22 -17
  107. data/lib/rubocop/result_cache.rb +8 -2
  108. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  109. data/lib/rubocop/rspec/expect_offense.rb +5 -5
  110. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  111. data/lib/rubocop/runner.rb +9 -5
  112. data/lib/rubocop/target_finder.rb +27 -26
  113. data/lib/rubocop/target_ruby.rb +1 -1
  114. data/lib/rubocop/version.rb +61 -6
  115. metadata +21 -16
  116. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +0 -43
@@ -9,6 +9,8 @@ module RuboCop
9
9
  # The nodes modified by the corrections should be part of the
10
10
  # AST of the source_buffer.
11
11
  class Corrector < ::Parser::Source::TreeRewriter
12
+ NOOP_CONSUMER = ->(diagnostic) {} # noop
13
+
12
14
  # @param source [Parser::Source::Buffer, or anything
13
15
  # leading to one via `(processed_source.)buffer`]
14
16
  #
@@ -23,7 +25,7 @@ module RuboCop
23
25
  )
24
26
 
25
27
  # Don't print warnings to stderr if corrections conflict with each other
26
- diagnostics.consumer = ->(diagnostic) {}
28
+ diagnostics.consumer = NOOP_CONSUMER
27
29
  end
28
30
 
29
31
  alias rewrite process # Legacy
@@ -52,8 +52,8 @@ module RuboCop
52
52
  end
53
53
 
54
54
  def semicolon(node)
55
- @semicolon ||= {}
56
- @semicolon[node.object_id] ||= processed_source.tokens_within(node).find(&:semicolon?)
55
+ @semicolon ||= {}.compare_by_identity
56
+ @semicolon[node] ||= processed_source.tokens_within(node).find(&:semicolon?)
57
57
  end
58
58
  end
59
59
  end
@@ -89,7 +89,7 @@ module RuboCop
89
89
  begin_line_num = previous_line_num - base_line_num + 1
90
90
  end_line_num = node.first_line - base_line_num + 1
91
91
  lines = source_in_lines[begin_line_num...end_line_num]
92
- "\n#{(lines.join("\n").split(node.source).first || '')}"
92
+ "\n#{lines.join("\n").split(node.source).first || ''}"
93
93
  end
94
94
 
95
95
  def fix_escaped_content(word_node, escape, delimiters)
@@ -31,7 +31,7 @@ module RuboCop
31
31
  cops.each do |cop|
32
32
  next unless cop.respond_to?(method_name)
33
33
 
34
- cop.send(method_name, *args)
34
+ cop.public_send(method_name, *args)
35
35
  end
36
36
  end
37
37
 
@@ -58,24 +58,22 @@ module RuboCop
58
58
  (send _ :required_ruby_version= $_)
59
59
  PATTERN
60
60
 
61
- def_node_matcher :string_version?, <<~PATTERN
62
- {(str _) (array (str _))}
61
+ def_node_matcher :defined_ruby_version, <<~PATTERN
62
+ {$(str _) $(array (str _) (str _))
63
+ (send (const (const nil? :Gem) :Requirement) :new $(str _))}
63
64
  PATTERN
64
65
 
65
66
  # rubocop:disable Metrics/AbcSize
66
67
  def investigate(processed_source)
67
- version = required_ruby_version(processed_source.ast).first
68
+ version_def = required_ruby_version(processed_source.ast).first
68
69
 
69
- if version
70
- return unless string_version?(version)
71
-
72
- ruby_version = extract_ruby_version(version)
73
-
74
- return if ruby_version == target_ruby_version.to_s
70
+ if version_def
71
+ ruby_version = extract_ruby_version(defined_ruby_version(version_def))
72
+ return if !ruby_version || ruby_version == target_ruby_version.to_s
75
73
 
76
74
  add_offense(
77
75
  processed_source.ast,
78
- location: version.loc.expression,
76
+ location: version_def.loc.expression,
79
77
  message: not_equal_message(ruby_version, target_ruby_version)
80
78
  )
81
79
  else
@@ -88,6 +86,8 @@ module RuboCop
88
86
  private
89
87
 
90
88
  def extract_ruby_version(required_ruby_version)
89
+ return unless required_ruby_version
90
+
91
91
  if required_ruby_version.array_type?
92
92
  required_ruby_version = required_ruby_version.children.detect do |v|
93
93
  /[>=]/.match?(v.str_content)
@@ -44,6 +44,7 @@ module RuboCop
44
44
 
45
45
  def on_array(node)
46
46
  return if node.children.size < 2
47
+ return if node.parent&.masgn_type?
47
48
 
48
49
  check_alignment(node.children, base_column(node, node.children))
49
50
  end
@@ -265,6 +265,9 @@ module RuboCop
265
265
  end
266
266
 
267
267
  def end_position_for(node)
268
+ heredoc = find_heredoc(node)
269
+ return heredoc.location.heredoc_end.end_pos + 1 if heredoc
270
+
268
271
  end_line = buffer.line_for_position(node.loc.expression.end_pos)
269
272
  buffer.line_range(end_line).end_pos
270
273
  end
@@ -284,6 +287,10 @@ module RuboCop
284
287
  buffer.line_range(node.loc.line).begin_pos - 1
285
288
  end
286
289
 
290
+ def find_heredoc(node)
291
+ node.each_node(:str, :dstr, :xstr).find(&:heredoc?)
292
+ end
293
+
287
294
  def buffer
288
295
  processed_source.buffer
289
296
  end
@@ -46,7 +46,7 @@ module RuboCop
46
46
  alias on_defs on_def
47
47
 
48
48
  def on_send(node)
49
- return if !node.def_modifier? || node.method?(:using)
49
+ return unless node.def_modifier?
50
50
 
51
51
  method_def = node.each_descendant(:def, :defs).first
52
52
  expr = node.source_range
@@ -29,17 +29,14 @@ module RuboCop
29
29
  def on_send(node)
30
30
  return unless node.dot? || ampersand_dot?(node)
31
31
 
32
- if proper_dot_position?(node)
33
- correct_style_detected
34
- else
35
- return unless opposite_style_detected
32
+ return correct_style_detected if proper_dot_position?(node)
36
33
 
37
- dot = node.loc.dot
38
- message = message(dot)
34
+ opposite_style_detected
35
+ dot = node.loc.dot
36
+ message = message(dot)
39
37
 
40
- add_offense(dot, message: message) do |corrector|
41
- autocorrect(corrector, dot, node)
42
- end
38
+ add_offense(dot, message: message) do |corrector|
39
+ autocorrect(corrector, dot, node)
43
40
  end
44
41
  end
45
42
  alias on_csend on_send
@@ -84,7 +84,8 @@ module RuboCop
84
84
  end
85
85
 
86
86
  def on_send(node)
87
- return unless register_offense?(node)
87
+ return unless node.bare_access_modifier? && !node.parent&.block_type?
88
+ return if expected_empty_lines?(node)
88
89
 
89
90
  message = message(node)
90
91
  add_offense(node, message: message) do |corrector|
@@ -98,17 +99,15 @@ module RuboCop
98
99
 
99
100
  private
100
101
 
101
- def register_offense?(node)
102
- return false unless node.bare_access_modifier? && !node.parent.block_type?
103
-
102
+ def expected_empty_lines?(node)
104
103
  case style
105
104
  when :around
106
- return false if empty_lines_around?(node)
105
+ return true if empty_lines_around?(node)
107
106
  when :only_before
108
- return false if allowed_only_before_style?(node)
107
+ return true if allowed_only_before_style?(node)
109
108
  end
110
109
 
111
- true
110
+ false
112
111
  end
113
112
 
114
113
  def allowed_only_before_style?(node)
@@ -142,6 +141,7 @@ module RuboCop
142
141
  def previous_line_empty?(send_line)
143
142
  previous_line = previous_line_ignoring_comments(processed_source,
144
143
  send_line)
144
+ return true unless previous_line
145
145
 
146
146
  block_start?(send_line) ||
147
147
  class_def?(send_line) ||
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
 
90
90
  def require_empty_line?(node)
91
- return false unless node&.respond_to?(:type)
91
+ return false unless node.respond_to?(:type)
92
92
 
93
93
  !allow_alias?(node) && !attribute_or_allowed_method?(node)
94
94
  end
@@ -56,8 +56,7 @@ module RuboCop
56
56
  aligned = Set[locs.first.line, locs.last.line]
57
57
  locs.each_cons(3) do |before, loc, after|
58
58
  col = loc.column
59
- aligned << loc.line if col == before.column || # rubocop:disable Style/MultipleComparison
60
- col == after.column
59
+ aligned << loc.line if col == before.column || col == after.column
61
60
  end
62
61
  aligned
63
62
  end
@@ -51,22 +51,13 @@ module RuboCop
51
51
  style == :no_space && no_surrounding_space
52
52
  correct_style_detected
53
53
  else
54
- incorrect_style_detected(arg, value, space_on_both_sides,
55
- no_surrounding_space)
54
+ incorrect_style_detected(arg, value)
56
55
  end
57
56
  end
58
57
 
59
- def incorrect_style_detected(arg, value, space_on_both_sides,
60
- no_surrounding_space)
58
+ def incorrect_style_detected(arg, value)
61
59
  range = range_between(arg.end_pos, value.begin_pos)
62
60
 
63
- if style == :space && no_surrounding_space ||
64
- style == :no_space && space_on_both_sides
65
- return unless opposite_style_detected
66
- else
67
- return unless unrecognized_style_detected
68
- end
69
-
70
61
  add_offense(range) do |corrector|
71
62
  autocorrect(corrector, range)
72
63
  end
@@ -5,6 +5,8 @@ module RuboCop
5
5
  module Layout
6
6
  # Checks that operators have space around them, except for ** which
7
7
  # should or shouldn't have surrounding space depending on configuration.
8
+ # It allows vertical alignment consisting of one or more whitespace
9
+ # around operators.
8
10
  #
9
11
  # This cop has `AllowForAlignment` option. When `true`, allows most
10
12
  # uses of extra spacing if the intent is to align with an operator on
@@ -207,7 +209,8 @@ module RuboCop
207
209
  token = Token.new(operator, nil, operator.source)
208
210
  align_preceding = aligned_with_preceding_assignment(token)
209
211
 
210
- return align_preceding == :no unless align_preceding == :none
212
+ return false if align_preceding == :yes ||
213
+ aligned_with_subsequent_assignment(token) == :none
211
214
 
212
215
  aligned_with_subsequent_assignment(token) != :yes
213
216
  end
@@ -206,8 +206,6 @@ module RuboCop
206
206
 
207
207
  def no_space(begin_pos, end_pos, msg)
208
208
  if style == :space
209
- return unless opposite_style_detected
210
-
211
209
  offense(begin_pos, end_pos, msg)
212
210
  else
213
211
  correct_style_detected
@@ -216,8 +214,6 @@ module RuboCop
216
214
 
217
215
  def space(begin_pos, end_pos, msg)
218
216
  if style == :no_space
219
- return unless opposite_style_detected
220
-
221
217
  offense(begin_pos, end_pos, msg)
222
218
  else
223
219
  correct_style_detected
@@ -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
 
@@ -15,6 +15,8 @@ module RuboCop
15
15
  #
16
16
  # # good
17
17
  # # With parentheses, there's no ambiguity.
18
+ # some_method(a { |val| puts val })
19
+ # # or (different meaning)
18
20
  # some_method(a) { |val| puts val }
19
21
  #
20
22
  # # good
@@ -47,7 +47,24 @@ module RuboCop
47
47
  regexp_node.source_range.begin_pos == diagnostic.location.begin_pos
48
48
  end
49
49
 
50
- node.parent
50
+ find_offense_node(node.parent, node)
51
+ end
52
+
53
+ def find_offense_node(node, regexp_receiver)
54
+ return node unless node.parent
55
+
56
+ if node.parent.send_type? || method_chain_to_regexp_receiver?(node, regexp_receiver)
57
+ node = find_offense_node(node.parent, regexp_receiver)
58
+ end
59
+
60
+ node
61
+ end
62
+
63
+ def method_chain_to_regexp_receiver?(node, regexp_receiver)
64
+ return false unless (parent = node.parent)
65
+ return false unless (parent_receiver = parent.receiver)
66
+
67
+ parent.parent && parent_receiver.receiver == regexp_receiver
51
68
  end
52
69
  end
53
70
  end
@@ -32,6 +32,9 @@ module RuboCop
32
32
  def on_sym(node)
33
33
  return unless boolean_symbol?(node)
34
34
 
35
+ parent = node.parent
36
+ return if parent&.array_type? && parent&.percent_literal?(:symbol)
37
+
35
38
  add_offense(node, message: format(MSG, boolean: node.value)) do |corrector|
36
39
  autocorrect(corrector, node)
37
40
  end
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  RESTRICT_ON_SEND = %i[
39
39
  debugger byebug remote_byebug pry remote_pry pry_remote console rescue
40
- save_and_open_page save_and_open_screenshot save_screenshot irb
40
+ save_and_open_page save_and_open_screenshot irb
41
41
  ].freeze
42
42
 
43
43
  def_node_matcher :kernel?, <<~PATTERN
@@ -53,8 +53,7 @@ module RuboCop
53
53
  {:pry :remote_pry :pry_remote :console} ...)
54
54
  (send (const {nil? (cbase)} :Pry) :rescue ...)
55
55
  (send nil? {:save_and_open_page
56
- :save_and_open_screenshot
57
- :save_screenshot} ...)}
56
+ :save_and_open_screenshot} ...)}
58
57
  PATTERN
59
58
 
60
59
  def_node_matcher :binding_irb_call?, <<~PATTERN
@@ -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