rubocop 1.0.0 → 1.1.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -5
  3. data/config/default.yml +52 -3
  4. data/lib/rubocop.rb +8 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  6. data/lib/rubocop/comment_config.rb +1 -1
  7. data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
  8. data/lib/rubocop/cop/commissioner.rb +9 -9
  9. data/lib/rubocop/cop/corrector.rb +3 -1
  10. data/lib/rubocop/cop/force.rb +1 -1
  11. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  12. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  13. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  14. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  15. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  16. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  17. data/lib/rubocop/cop/lint/empty_block.rb +46 -0
  18. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  19. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  20. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  21. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  22. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  23. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  24. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  25. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  26. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  27. data/lib/rubocop/cop/offense.rb +3 -3
  28. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  29. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  30. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  31. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  32. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  33. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  34. data/lib/rubocop/cop/team.rb +6 -1
  35. data/lib/rubocop/cop/util.rb +1 -1
  36. data/lib/rubocop/ext/regexp_node.rb +10 -7
  37. data/lib/rubocop/ext/regexp_parser.rb +77 -0
  38. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  39. data/lib/rubocop/magic_comment.rb +2 -2
  40. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  41. data/lib/rubocop/version.rb +1 -1
  42. metadata +13 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c25c996f99dbf63d4ffad0041dd929fc0f3d11b5bd2abd5d0aea1ac97cd57afb
4
- data.tar.gz: 462f0dcd3120a9911fe100c7fb2d02a6919cf81078e871c7896044ebd5e25358
3
+ metadata.gz: da2c7f3fe5f1a109fce4e8e9e44efd9e438c45c0c0036f79f3d0156a4dacc452
4
+ data.tar.gz: ff29fbdedc13116e5fdbcb95dc3af4a81379794f9d36596983f69a09d69ac575
5
5
  SHA512:
6
- metadata.gz: e3f9afb1fc30fef47c2d3eb64d33f237ca21adcfaf81c64aa24a47eaca9bee840123e29bd07be63e88137544d2e5cf00c4b63110dddfa1f306e0b95a1bc02d45
7
- data.tar.gz: b0b79a254b907beb97ea89c3e330ca0ed8ed9ad7c3e5e51848ad5dfe77f98aa0a3097dcdab06c8eb82d6981d6354fbbd81b4e43a49b2b3d5f1b7be9cc4dc614a
6
+ metadata.gz: 11f024ff53e3eb551b31b02c3a48ab250941c9fd0e642c3bb518a8d635bee89f0fc61dba12e0a66ac40c4536bf368690ade37ce3a6014531ad97ebc0fd9d82f8
7
+ data.tar.gz: e567372e804699a2499b6475cdeb4e86906c5fe8eda16428d292a600648d2afe2d34bacea2443fe52229ce4539b5a09b17f387586fc3a9e2d3034d3718ce4bb3
data/README.md CHANGED
@@ -43,15 +43,18 @@ If you'd rather install RuboCop using `bundler`, add a line for it in your `Gemf
43
43
  gem 'rubocop', require: false
44
44
  ```
45
45
 
46
- RuboCop's development is moving at a very rapid pace and there are
47
- often backward-incompatible changes between minor releases (since we
48
- haven't reached version 1.0 yet). To prevent an unwanted RuboCop update you
49
- might want to use a conservative version lock in your `Gemfile`:
46
+ RuboCop is stable between major versions, both in terms of API and cop configuration.
47
+ We aim the ease the maintenance of RuboCop extensions and the upgrades between RuboCop
48
+ releases. All big changes are reserved for major releases.
49
+ To prevent an unwanted RuboCop update you might want to use a conservative version lock
50
+ in your `Gemfile`:
50
51
 
51
52
  ```rb
52
- gem 'rubocop', '~> 1.0.0', require: false
53
+ gem 'rubocop', '~> 1.1', require: false
53
54
  ```
54
55
 
56
+ See [versioning](https://docs.rubocop.org/rubocop/1.0/versioning.html) for further details.
57
+
55
58
  ## Quickstart
56
59
 
57
60
  Just type `rubocop` in a Ruby project's folder and watch the magic happen.
@@ -1334,7 +1334,7 @@ Layout/TrailingWhitespace:
1334
1334
  StyleGuide: '#no-trailing-whitespace'
1335
1335
  Enabled: true
1336
1336
  VersionAdded: '0.49'
1337
- VersionChanged: '0.83'
1337
+ VersionChanged: '1.0'
1338
1338
  AllowInHeredoc: false
1339
1339
 
1340
1340
  #################### Lint ##################################
@@ -1454,6 +1454,11 @@ Lint/DuplicateMethods:
1454
1454
  Enabled: true
1455
1455
  VersionAdded: '0.29'
1456
1456
 
1457
+ Lint/DuplicateRegexpCharacterClassElement:
1458
+ Description: 'Checks for duplicate elements in Regexp character classes.'
1459
+ Enabled: pending
1460
+ VersionAdded: '1.1'
1461
+
1457
1462
  Lint/DuplicateRequire:
1458
1463
  Description: 'Check for duplicate `require`s and `require_relative`s.'
1459
1464
  Enabled: true
@@ -1474,6 +1479,12 @@ Lint/ElseLayout:
1474
1479
  Enabled: true
1475
1480
  VersionAdded: '0.17'
1476
1481
 
1482
+ Lint/EmptyBlock:
1483
+ Description: 'This cop checks for blocks without a body.'
1484
+ Enabled: pending
1485
+ VersionAdded: '1.1'
1486
+ AllowComments: true
1487
+
1477
1488
  Lint/EmptyConditionalBody:
1478
1489
  Description: 'This cop checks for the presence of `if`, `elsif` and `unless` branches without a body.'
1479
1490
  Enabled: true
@@ -1647,7 +1658,8 @@ Lint/MultipleComparison:
1647
1658
  Description: "Use `&&` operator to compare multiple values."
1648
1659
  Enabled: true
1649
1660
  VersionAdded: '0.47'
1650
- VersionChanged: '0.77'
1661
+ VersionChanged: '1.1'
1662
+ AllowMethodComparison: true
1651
1663
 
1652
1664
  Lint/NestedMethodDefinition:
1653
1665
  Description: 'Do not use nested method definitions.'
@@ -1682,8 +1694,12 @@ Lint/NumberConversion:
1682
1694
  Description: 'Checks unsafe usage of number conversion methods.'
1683
1695
  Enabled: false
1684
1696
  VersionAdded: '0.53'
1685
- VersionChanged: '0.70'
1697
+ VersionChanged: '1.1'
1686
1698
  SafeAutoCorrect: false
1699
+ IgnoredMethods: []
1700
+ IgnoredClasses:
1701
+ - Time
1702
+ - DateTime
1687
1703
 
1688
1704
  Lint/OrderedMagicComments:
1689
1705
  Description: 'Checks the proper ordering of magic comments and whether a magic comment is not placed before a shebang.'
@@ -1910,6 +1926,11 @@ Lint/Syntax:
1910
1926
  VersionAdded: '0.9'
1911
1927
 
1912
1928
 
1929
+ Lint/ToEnumArguments:
1930
+ Description: 'This cop ensures that `to_enum`/`enum_for`, called for the current method, has correct arguments.'
1931
+ Enabled: pending
1932
+ VersionAdded: '1.1'
1933
+
1913
1934
  Lint/ToJSON:
1914
1935
  Description: 'Ensure #to_json includes an optional argument.'
1915
1936
  Enabled: true
@@ -1936,6 +1957,11 @@ Lint/UnifiedInteger:
1936
1957
  Enabled: true
1937
1958
  VersionAdded: '0.43'
1938
1959
 
1960
+ Lint/UnmodifiedReduceAccumulator:
1961
+ Description: Checks for `reduce` or `inject` blocks that do not update the accumulator each iteration.
1962
+ Enabled: pending
1963
+ VersionAdded: '1.1'
1964
+
1939
1965
  Lint/UnreachableCode:
1940
1966
  Description: 'Unreachable code.'
1941
1967
  Enabled: true
@@ -2478,6 +2504,13 @@ Style/AndOr:
2478
2504
  - always
2479
2505
  - conditionals
2480
2506
 
2507
+ Style/ArgumentsForwarding:
2508
+ Description: 'Use arguments forwarding.'
2509
+ StyleGuide: '#arguments-forwarding'
2510
+ Enabled: pending
2511
+ AllowOnlyRestArgument: true
2512
+ VersionAdded: '1.1'
2513
+
2481
2514
  Style/ArrayCoercion:
2482
2515
  Description: >-
2483
2516
  Use Array() instead of explicit Array check or [*var], when dealing
@@ -2918,6 +2951,14 @@ Style/DisableCopsWithinSourceCodeDirective:
2918
2951
  Enabled: false
2919
2952
  VersionAdded: '0.82'
2920
2953
 
2954
+ Style/DocumentDynamicEvalDefinition:
2955
+ Description: >-
2956
+ When using `class_eval` (or other `eval`) with string interpolation,
2957
+ add a comment block showing its appearance if interpolated.
2958
+ StyleGuide: '#eval-comment-docs'
2959
+ Enabled: pending
2960
+ VersionAdded: '1.1'
2961
+
2921
2962
  Style/Documentation:
2922
2963
  Description: 'Document classes and non-namespace modules.'
2923
2964
  Enabled: true
@@ -3535,6 +3576,7 @@ Style/MultipleComparison:
3535
3576
  use Array#include? instead.
3536
3577
  Enabled: true
3537
3578
  VersionAdded: '0.49'
3579
+ VersionChanged: '1.1'
3538
3580
 
3539
3581
  Style/MutableConstant:
3540
3582
  Description: 'Do not assign mutable objects to constants.'
@@ -4226,6 +4268,13 @@ Style/StructInheritance:
4226
4268
  VersionAdded: '0.29'
4227
4269
  VersionChanged: '0.86'
4228
4270
 
4271
+ Style/SwapValues:
4272
+ Description: 'This cop enforces the use of shorthand-style swapping of 2 variables.'
4273
+ StyleGuide: '#values-swapping'
4274
+ Enabled: pending
4275
+ VersionAdded: '1.1'
4276
+ SafeAutoCorrect: false
4277
+
4229
4278
  Style/SymbolArray:
4230
4279
  Description: 'Use %i or %I for arrays of symbols.'
4231
4280
  StyleGuide: '#percent-i'
@@ -15,6 +15,7 @@ require 'rubocop-ast'
15
15
 
16
16
  require_relative 'rubocop/ast_aliases'
17
17
  require_relative 'rubocop/ext/regexp_node'
18
+ require_relative 'rubocop/ext/regexp_parser'
18
19
 
19
20
  require_relative 'rubocop/core_ext/string'
20
21
  require_relative 'rubocop/ext/processed_source'
@@ -262,10 +263,12 @@ require_relative 'rubocop/cop/lint/duplicate_case_condition'
262
263
  require_relative 'rubocop/cop/lint/duplicate_elsif_condition'
263
264
  require_relative 'rubocop/cop/lint/duplicate_hash_key'
264
265
  require_relative 'rubocop/cop/lint/duplicate_methods'
266
+ require_relative 'rubocop/cop/lint/duplicate_regexp_character_class_element'
265
267
  require_relative 'rubocop/cop/lint/duplicate_require'
266
268
  require_relative 'rubocop/cop/lint/duplicate_rescue_exception'
267
269
  require_relative 'rubocop/cop/lint/each_with_object_argument'
268
270
  require_relative 'rubocop/cop/lint/else_layout'
271
+ require_relative 'rubocop/cop/lint/empty_block'
269
272
  require_relative 'rubocop/cop/lint/empty_conditional_body'
270
273
  require_relative 'rubocop/cop/lint/empty_ensure'
271
274
  require_relative 'rubocop/cop/lint/empty_expression'
@@ -330,11 +333,13 @@ require_relative 'rubocop/cop/lint/shadowing_outer_local_variable'
330
333
  require_relative 'rubocop/cop/lint/struct_new_override'
331
334
  require_relative 'rubocop/cop/lint/suppressed_exception'
332
335
  require_relative 'rubocop/cop/lint/syntax'
336
+ require_relative 'rubocop/cop/lint/to_enum_arguments'
333
337
  require_relative 'rubocop/cop/lint/to_json'
334
338
  require_relative 'rubocop/cop/lint/top_level_return_with_argument'
335
339
  require_relative 'rubocop/cop/lint/trailing_comma_in_attribute_declaration'
336
340
  require_relative 'rubocop/cop/lint/underscore_prefixed_variable_name'
337
341
  require_relative 'rubocop/cop/lint/unified_integer'
342
+ require_relative 'rubocop/cop/lint/unmodified_reduce_accumulator'
338
343
  require_relative 'rubocop/cop/lint/unreachable_code'
339
344
  require_relative 'rubocop/cop/lint/unreachable_loop'
340
345
  require_relative 'rubocop/cop/lint/unused_block_argument'
@@ -384,6 +389,7 @@ require_relative 'rubocop/cop/style/access_modifier_declarations'
384
389
  require_relative 'rubocop/cop/style/accessor_grouping'
385
390
  require_relative 'rubocop/cop/style/alias'
386
391
  require_relative 'rubocop/cop/style/and_or'
392
+ require_relative 'rubocop/cop/style/arguments_forwarding'
387
393
  require_relative 'rubocop/cop/style/array_coercion'
388
394
  require_relative 'rubocop/cop/style/array_join'
389
395
  require_relative 'rubocop/cop/style/ascii_comments'
@@ -419,6 +425,7 @@ require_relative 'rubocop/cop/style/dir'
419
425
  require_relative 'rubocop/cop/style/disable_cops_within_source_code_directive'
420
426
  require_relative 'rubocop/cop/style/documentation_method'
421
427
  require_relative 'rubocop/cop/style/documentation'
428
+ require_relative 'rubocop/cop/style/document_dynamic_eval_definition'
422
429
  require_relative 'rubocop/cop/style/double_cop_disable_directive'
423
430
  require_relative 'rubocop/cop/style/double_negation'
424
431
  require_relative 'rubocop/cop/style/each_for_simple_loop'
@@ -554,6 +561,7 @@ require_relative 'rubocop/cop/style/string_literals_in_interpolation'
554
561
  require_relative 'rubocop/cop/style/string_methods'
555
562
  require_relative 'rubocop/cop/style/strip'
556
563
  require_relative 'rubocop/cop/style/struct_inheritance'
564
+ require_relative 'rubocop/cop/style/swap_values'
557
565
  require_relative 'rubocop/cop/style/symbol_array'
558
566
  require_relative 'rubocop/cop/style/symbol_literal'
559
567
  require_relative 'rubocop/cop/style/symbol_proc'
@@ -90,7 +90,7 @@ module RuboCop
90
90
  def reset_config_and_auto_gen_file
91
91
  @config_store = ConfigStore.new
92
92
  @config_store.options_config = @options[:config] if @options[:config]
93
- File.open(AUTO_GENERATED_FILE, 'w') {}
93
+ File.open(AUTO_GENERATED_FILE, 'w') {} # create or truncate if exists
94
94
  add_inheritance_from_auto_generated_file(@options[:config])
95
95
  end
96
96
 
@@ -8,7 +8,7 @@ module RuboCop
8
8
  REDUNDANT_DISABLE = 'Lint/RedundantCopDisableDirective'
9
9
 
10
10
  # @api private
11
- COP_NAME_PATTERN = '([A-Z]\w+/)?(?:[A-Z]\w+)'
11
+ COP_NAME_PATTERN = '([A-Z]\w+/)*(?:[A-Z]\w+)'
12
12
  # @api private
13
13
  COP_NAMES_PATTERN = "(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}"
14
14
  # @api private
@@ -25,6 +25,16 @@ module RuboCop
25
25
  #
26
26
  # # good
27
27
  # gem 'rubocop', groups: [:development, :test]
28
+ #
29
+ # # good - conditional declaration
30
+ # if Dir.exist?(local)
31
+ # gem 'rubocop', path: local
32
+ # elsif ENV['RUBOCOP_VERSION'] == 'master'
33
+ # gem 'rubocop', git: 'https://github.com/rubocop-hq/rubocop.git'
34
+ # else
35
+ # gem 'rubocop', '~> 0.90.0'
36
+ # end
37
+ #
28
38
  class DuplicatedGem < Cop
29
39
  include RangeHelp
30
40
 
@@ -53,11 +63,21 @@ module RuboCop
53
63
  gem_declarations(processed_source.ast)
54
64
  .group_by(&:first_argument)
55
65
  .values
56
- .select { |nodes| nodes.size > 1 && !condition?(nodes) }
66
+ .select { |nodes| nodes.size > 1 && !conditional_declaration?(nodes) }
57
67
  end
58
68
 
59
- def condition?(nodes)
60
- nodes[0].parent&.if_type? && nodes[0].parent == nodes[1].parent
69
+ def conditional_declaration?(nodes)
70
+ parent = nodes[0].parent
71
+ return false unless parent&.if_type? || parent&.when_type?
72
+
73
+ root_conditional_node = parent.if_type? ? parent : parent.parent
74
+ nodes.all? { |node| within_conditional?(node, root_conditional_node) }
75
+ end
76
+
77
+ def within_conditional?(node, conditional_node)
78
+ conditional_node.branches.any? do |branch|
79
+ branch == node || branch.child_nodes.include?(node)
80
+ end
61
81
  end
62
82
 
63
83
  def register_offense(node, gem_name, line_of_first_occurrence)
@@ -65,13 +65,13 @@ module RuboCop
65
65
  c = '#' if NO_CHILD_NODES.include?(node_type) # has Children?
66
66
 
67
67
  class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
68
- def on_#{node_type}(node)
69
- trigger_responding_cops(:on_#{node_type}, node)
70
- #{r} trigger_restricted_cops(:on_#{node_type}, node)
71
- #{c} super(node)
72
- #{c} trigger_responding_cops(:after_#{node_type}, node)
73
- #{c}#{r} trigger_restricted_cops(:after_#{node_type}, node)
74
- end
68
+ def on_#{node_type}(node) # def on_send(node)
69
+ trigger_responding_cops(:on_#{node_type}, node) # trigger_responding_cops(:on_send, node)
70
+ #{r} trigger_restricted_cops(:on_#{node_type}, node) # trigger_restricted_cops(:on_send, node)
71
+ #{c} super(node) # super(node)
72
+ #{c} trigger_responding_cops(:after_#{node_type}, node) # trigger_responding_cops(:after_send, node)
73
+ #{c}#{r} trigger_restricted_cops(:after_#{node_type}, node) # trigger_restricted_cops(:after_send, node)
74
+ end # end
75
75
  RUBY
76
76
  end
77
77
 
@@ -97,7 +97,7 @@ module RuboCop
97
97
  def trigger_responding_cops(callback, node)
98
98
  @callbacks[callback]&.each do |cop|
99
99
  with_cop_error_handling(cop, node) do
100
- cop.send(callback, node)
100
+ cop.public_send(callback, node)
101
101
  end
102
102
  end
103
103
  end
@@ -133,7 +133,7 @@ module RuboCop
133
133
  name = node.method_name
134
134
  @restricted_map[event][name]&.each do |cop|
135
135
  with_cop_error_handling(cop, node) do
136
- cop.send(event, node)
136
+ cop.public_send(event, node)
137
137
  end
138
138
  end
139
139
  end
@@ -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
@@ -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
 
@@ -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
@@ -141,6 +141,7 @@ module RuboCop
141
141
  def previous_line_empty?(send_line)
142
142
  previous_line = previous_line_ignoring_comments(processed_source,
143
143
  send_line)
144
+ return true unless previous_line
144
145
 
145
146
  block_start?(send_line) ||
146
147
  class_def?(send_line) ||
@@ -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
@@ -63,7 +63,7 @@ module RuboCop
63
63
  range = offense_range(lineno, line)
64
64
  add_offense(range) do |corrector|
65
65
  if heredoc
66
- corrector.insert_after(range, '#{}') unless static?(heredoc) # rubocop:disable Lint/InterpolationCheck
66
+ corrector.wrap(range, "\#{'", "'}") unless static?(heredoc)
67
67
  else
68
68
  corrector.remove(range)
69
69
  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