rubocop 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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