rubocop 1.52.0 → 1.53.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +38 -1
  4. data/lib/rubocop/cli/command/lsp.rb +19 -0
  5. data/lib/rubocop/cli.rb +3 -0
  6. data/lib/rubocop/config_loader_resolver.rb +4 -3
  7. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  8. data/lib/rubocop/cop/bundler/gem_version.rb +2 -2
  9. data/lib/rubocop/cop/gemspec/dependency_version.rb +2 -2
  10. data/lib/rubocop/cop/internal_affairs/cop_description.rb +32 -8
  11. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +3 -3
  12. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  13. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -2
  14. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
  16. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  17. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  18. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  19. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
  20. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -1
  21. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  22. data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
  23. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
  24. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -2
  25. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
  26. data/lib/rubocop/cop/lint/identity_comparison.rb +0 -1
  27. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +1 -2
  28. data/lib/rubocop/cop/lint/inherit_exception.rb +3 -1
  29. data/lib/rubocop/cop/lint/missing_super.rb +31 -5
  30. data/lib/rubocop/cop/lint/mixed_case_range.rb +109 -0
  31. data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
  32. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +0 -1
  33. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +120 -0
  34. data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
  35. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +2 -2
  36. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -2
  37. data/lib/rubocop/cop/lint/shadowed_exception.rb +5 -11
  38. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  39. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -1
  40. data/lib/rubocop/cop/lint/void.rb +1 -1
  41. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -2
  42. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  43. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  44. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  45. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  46. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  47. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  48. data/lib/rubocop/cop/style/begin_block.rb +1 -2
  49. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  50. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  51. data/lib/rubocop/cop/style/class_equality_comparison.rb +17 -39
  52. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  53. data/lib/rubocop/cop/style/dir.rb +1 -1
  54. data/lib/rubocop/cop/style/dir_empty.rb +8 -14
  55. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  56. data/lib/rubocop/cop/style/eval_with_location.rb +3 -3
  57. data/lib/rubocop/cop/style/file_read.rb +2 -2
  58. data/lib/rubocop/cop/style/hash_transform_keys.rb +2 -2
  59. data/lib/rubocop/cop/style/hash_transform_values.rb +2 -2
  60. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  61. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  62. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  63. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  64. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  65. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  66. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  67. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  68. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  69. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +86 -0
  70. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  71. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  72. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  73. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  74. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  75. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  76. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  77. data/lib/rubocop/cop/style/special_global_vars.rb +1 -2
  78. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  79. data/lib/rubocop/cop/util.rb +1 -1
  80. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  81. data/lib/rubocop/cop/variable_force/assignment.rb +14 -1
  82. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  83. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  84. data/lib/rubocop/lsp/logger.rb +22 -0
  85. data/lib/rubocop/lsp/routes.rb +223 -0
  86. data/lib/rubocop/lsp/runtime.rb +79 -0
  87. data/lib/rubocop/lsp/server.rb +62 -0
  88. data/lib/rubocop/lsp/severity.rb +27 -0
  89. data/lib/rubocop/options.rb +11 -1
  90. data/lib/rubocop/version.rb +1 -1
  91. data/lib/rubocop.rb +8 -0
  92. metadata +36 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5301525d37bb844594d465a40a1e5643646f2b85a3bbd7258ce92af20c1d82bd
4
- data.tar.gz: 3a1ba98652dc97a41f5ee5828160a9a74b59c806b3da618742e879a92d5ba596
3
+ metadata.gz: 63ade8f6d7d93161d739cd557220bea89fc0320a2f8923f41cc092015dbcadef
4
+ data.tar.gz: bb608b076e84d18f317b1690a6f5ad0b0ecea1232977107ebca57b04d32b6abb
5
5
  SHA512:
6
- metadata.gz: 939d1acac3b8dd67e286c2a10c2cf4f81d2c7029eda506b186546198dbdaa1726fe9d70c32bb6ea09d22de3dcdc4224b4e4e061b590f5b0bced0e7f693fa29fe
7
- data.tar.gz: 1b449beeac25c4475ff5deb915b045349283e35f1aff9bd526e9dda73a957a44df8e7ba7ec3c8a1b3c360a5bbc05120ca4cdbda62f15be0c4c0b45f46882bc9e
6
+ metadata.gz: '0178518d0c9ab5eb1425395c4047f603f92e9d17ae9ef23d7e52ef9c9cfc51e8dda9abd0b08d39b84014c6f59f238673b5fe73de3c2eb649cd166c39cfc58081'
7
+ data.tar.gz: 752ee645c95115d20848ff2f00731597bd6106738bb6bded350a3348b392a471890d6f607e475245f81aae7746c23389ffd302e7884b2cba8476ca66d4d07ec7
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.52', require: false
56
+ gem 'rubocop', '~> 1.53', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -467,7 +467,9 @@ Layout/ClassStructure:
467
467
  Description: 'Enforces a configured order of definitions within a class body.'
468
468
  StyleGuide: '#consistent-classes'
469
469
  Enabled: false
470
+ SafeAutoCorrect: false
470
471
  VersionAdded: '0.52'
472
+ VersionChanged: '1.53'
471
473
  Categories:
472
474
  module_inclusion:
473
475
  - include
@@ -1530,7 +1532,6 @@ Lint/AmbiguousBlockAssociation:
1530
1532
  Description: >-
1531
1533
  Checks for ambiguous block association with method when param passed without
1532
1534
  parentheses.
1533
- StyleGuide: '#syntax'
1534
1535
  Enabled: true
1535
1536
  VersionAdded: '0.48'
1536
1537
  VersionChanged: '1.13'
@@ -1988,9 +1989,16 @@ Lint/MissingSuper:
1988
1989
  Checks for the presence of constructors and lifecycle callbacks
1989
1990
  without calls to `super`.
1990
1991
  Enabled: true
1992
+ AllowedParentClasses: []
1991
1993
  VersionAdded: '0.89'
1992
1994
  VersionChanged: '1.4'
1993
1995
 
1996
+ Lint/MixedCaseRange:
1997
+ Description: 'Checks for mixed-case character ranges since they include likely unintended characters.'
1998
+ Enabled: pending
1999
+ SafeAutoCorrect: false
2000
+ VersionAdded: '1.53'
2001
+
1994
2002
  Lint/MixedRegexpCaptureTypes:
1995
2003
  Description: 'Do not mix named captures and numbered captures in a Regexp literal.'
1996
2004
  Enabled: true
@@ -2140,6 +2148,11 @@ Lint/RedundantDirGlobSort:
2140
2148
  VersionChanged: '1.26'
2141
2149
  SafeAutoCorrect: false
2142
2150
 
2151
+ Lint/RedundantRegexpQuantifiers:
2152
+ Description: 'Checks for redundant quantifiers in Regexps.'
2153
+ Enabled: pending
2154
+ VersionAdded: '1.53'
2155
+
2143
2156
  Lint/RedundantRequireStatement:
2144
2157
  Description: 'Checks for unnecessary `require` statement.'
2145
2158
  Enabled: true
@@ -4843,6 +4856,11 @@ Style/RedundantConstantBase:
4843
4856
  Enabled: pending
4844
4857
  VersionAdded: '1.40'
4845
4858
 
4859
+ Style/RedundantCurrentDirectoryInPath:
4860
+ Description: 'Checks for uses a redundant current directory in path.'
4861
+ Enabled: pending
4862
+ VersionAdded: '1.53'
4863
+
4846
4864
  Style/RedundantDoubleSplatHashBraces:
4847
4865
  Description: 'Checks for redundant uses of double splat hash braces.'
4848
4866
  Enabled: pending
@@ -4931,6 +4949,11 @@ Style/RedundantPercentQ:
4931
4949
  Enabled: true
4932
4950
  VersionAdded: '0.76'
4933
4951
 
4952
+ Style/RedundantRegexpArgument:
4953
+ Description: 'Identifies places where argument can be replaced from a deterministic regexp to a string.'
4954
+ Enabled: pending
4955
+ VersionAdded: '1.53'
4956
+
4934
4957
  Style/RedundantRegexpCharacterClass:
4935
4958
  Description: 'Checks for unnecessary single-element Regexp character classes.'
4936
4959
  Enabled: true
@@ -5043,6 +5066,15 @@ Style/ReturnNil:
5043
5066
  - return_nil
5044
5067
  VersionAdded: '0.50'
5045
5068
 
5069
+ Style/ReturnNilInPredicateMethodDefinition:
5070
+ Description: 'Checks if uses of `return` or `return nil` in predicate method definition.'
5071
+ StyleGuide: '#bool-methods-qmark'
5072
+ Enabled: pending
5073
+ SafeAutoCorrect: false
5074
+ AllowedMethods: []
5075
+ AllowedPatterns: []
5076
+ VersionAdded: '1.53'
5077
+
5046
5078
  Style/SafeNavigation:
5047
5079
  Description: >-
5048
5080
  Transforms usages of a method call safeguarded by
@@ -5527,6 +5559,11 @@ Style/WordArray:
5527
5559
  # The regular expression `WordRegex` decides what is considered a word.
5528
5560
  WordRegex: !ruby/regexp '/\A(?:\p{Word}|\p{Word}-\p{Word}|\n|\t)+\z/'
5529
5561
 
5562
+ Style/YAMLFileRead:
5563
+ Description: 'Checks for the use of `YAML.load`, `YAML.safe_load`, and `YAML.parse` with `File.read` argument.'
5564
+ Enabled: pending
5565
+ VersionAdded: '1.53'
5566
+
5530
5567
  Style/YodaCondition:
5531
5568
  Description: 'Forbid or enforce yoda conditions.'
5532
5569
  Reference: 'https://en.wikipedia.org/wiki/Yoda_conditions'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lsp/server'
4
+
5
+ module RuboCop
6
+ class CLI
7
+ module Command
8
+ # Start Language Server Protocol of RuboCop.
9
+ # @api private
10
+ class Lsp < Base
11
+ self.command_name = :lsp
12
+
13
+ def run
14
+ RuboCop::Lsp::Server.new(@config_store).start
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/rubocop/cli.rb CHANGED
@@ -174,14 +174,17 @@ module RuboCop
174
174
  ConfigLoader.ignore_unrecognized_cops = @options[:ignore_unrecognized_cops]
175
175
  end
176
176
 
177
+ # rubocop:disable Metrics/CyclomaticComplexity
177
178
  def handle_exiting_options
178
179
  return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }
179
180
 
180
181
  run_command(:version) if @options[:version] || @options[:verbose_version]
181
182
  run_command(:show_cops) if @options[:show_cops]
182
183
  run_command(:show_docs_url) if @options[:show_docs_url]
184
+ run_command(:lsp) if @options[:lsp]
183
185
  raise Finished
184
186
  end
187
+ # rubocop:enable Metrics/CyclomaticComplexity
185
188
 
186
189
  def apply_default_formatter
187
190
  # This must be done after the options have already been processed,
@@ -33,7 +33,7 @@ module RuboCop
33
33
  inherit_mode: determine_inherit_mode(hash, k))
34
34
  end
35
35
  hash[k] = v
36
- fix_include_paths(base_config.loaded_path, hash, k, v) if v.key?('Include')
36
+ fix_include_paths(base_config.loaded_path, hash, path, k, v) if v.key?('Include')
37
37
  end
38
38
  end
39
39
  end
@@ -42,12 +42,13 @@ module RuboCop
42
42
  # base configuration are relative to the directory where the base configuration file is. For the
43
43
  # derived configuration, we need to make those paths relative to where the derived configuration
44
44
  # file is.
45
- def fix_include_paths(base_config_path, hash, key, value)
45
+ def fix_include_paths(base_config_path, hash, path, key, value)
46
46
  return unless File.basename(base_config_path).start_with?('.rubocop')
47
47
 
48
48
  base_dir = File.dirname(base_config_path)
49
+ derived_dir = File.dirname(path)
49
50
  hash[key]['Include'] = value['Include'].map do |include_path|
50
- PathUtil.relative_path(File.join(base_dir, include_path), Dir.pwd)
51
+ PathUtil.relative_path(File.join(base_dir, include_path), derived_dir)
51
52
  end
52
53
  end
53
54
 
@@ -150,7 +150,7 @@ module RuboCop
150
150
  # Version specifications that restrict all updates going forward. This excludes versions
151
151
  # like ">= 1.0" or "!= 2.0.3".
152
152
  def restrictive_version_specified_gem?(node)
153
- return unless version_specified_gem?(node)
153
+ return false unless version_specified_gem?(node)
154
154
 
155
155
  node.arguments[1..]
156
156
  .any? { |arg| arg&.str_type? && RESTRICTIVE_VERSION_PATTERN.match?(arg.value) }
@@ -105,13 +105,13 @@ module RuboCop
105
105
  end
106
106
 
107
107
  def required_offense?(node)
108
- return unless required_style?
108
+ return false unless required_style?
109
109
 
110
110
  !includes_version_specification?(node) && !includes_commit_reference?(node)
111
111
  end
112
112
 
113
113
  def forbidden_offense?(node)
114
- return unless forbidden_style?
114
+ return false unless forbidden_style?
115
115
 
116
116
  includes_version_specification?(node) || includes_commit_reference?(node)
117
117
  end
@@ -126,13 +126,13 @@ module RuboCop
126
126
  end
127
127
 
128
128
  def required_offense?(node)
129
- return unless required_style?
129
+ return false unless required_style?
130
130
 
131
131
  !includes_version_specification?(node) && !includes_commit_reference?(node)
132
132
  end
133
133
 
134
134
  def forbidden_offense?(node)
135
- return unless forbidden_style?
135
+ return false unless forbidden_style?
136
136
 
137
137
  includes_version_specification?(node) || includes_commit_reference?(node)
138
138
  end
@@ -12,6 +12,13 @@ module RuboCop
12
12
  # ....
13
13
  # end
14
14
  #
15
+ # # bad
16
+ # #
17
+ # # Checks ...
18
+ # class SomeCop < Base
19
+ # ...
20
+ # end
21
+ #
15
22
  # # good
16
23
  # # Checks ...
17
24
  # class SomeCop < Base
@@ -21,27 +28,47 @@ module RuboCop
21
28
  class CopDescription < Base
22
29
  extend AutoCorrector
23
30
 
24
- MSG = 'Description should be started with %<suggestion>s instead of `This cop ...`.'
31
+ MSG_STARTS_WITH_WRONG_WORD =
32
+ 'Description should be started with %<suggestion>s instead of `This cop ...`.'
33
+ MSG_STARTS_WITH_EMPTY_COMMENT_LINE =
34
+ 'Description should not start with an empty comment line.'
25
35
 
26
36
  SPECIAL_WORDS = %w[is can could should will would must may].freeze
27
37
  COP_DESC_OFFENSE_REGEX =
28
38
  /^\s+# This cop (?<special>#{SPECIAL_WORDS.join('|')})?\s*(?<word>.+?) .*/.freeze
29
39
  REPLACEMENT_REGEX = /^\s+# This cop (#{SPECIAL_WORDS.join('|')})?\s*(.+?) /.freeze
40
+ EMPTY_COMMENT_LINE_REGEX = /\A\s*#\s*\n\z/.freeze
30
41
 
31
- # rubocop:disable Metrics/CyclomaticComplexity
32
42
  def on_class(node)
33
43
  return unless (module_node = node.parent) && node.parent_class
34
44
 
35
45
  description_beginning = first_comment_line(module_node)
36
46
  return unless description_beginning
37
47
 
38
- start_with_subject = description_beginning.match(COP_DESC_OFFENSE_REGEX)
39
- return unless start_with_subject
48
+ if description_beginning.match?(EMPTY_COMMENT_LINE_REGEX)
49
+ register_offense_for_empty_comment_line(module_node, description_beginning)
50
+ else
51
+ start_with_subject = description_beginning.match(COP_DESC_OFFENSE_REGEX)
52
+ return unless start_with_subject
53
+
54
+ register_offense_for_wrong_word(module_node, description_beginning, start_with_subject)
55
+ end
56
+ end
57
+
58
+ private
40
59
 
60
+ def register_offense_for_empty_comment_line(module_node, description_beginning)
61
+ range = range(module_node, description_beginning)
62
+ add_offense(range, message: MSG_STARTS_WITH_EMPTY_COMMENT_LINE) do |corrector|
63
+ corrector.remove(range)
64
+ end
65
+ end
66
+
67
+ def register_offense_for_wrong_word(module_node, description_beginning, start_with_subject)
41
68
  suggestion = start_with_subject['word']&.capitalize
42
69
  range = range(module_node, description_beginning)
43
70
  suggestion_for_message = suggestion_for_message(suggestion, start_with_subject)
44
- message = format(MSG, suggestion: suggestion_for_message)
71
+ message = format(MSG_STARTS_WITH_WRONG_WORD, suggestion: suggestion_for_message)
45
72
 
46
73
  add_offense(range, message: message) do |corrector|
47
74
  if suggestion && !start_with_subject['special']
@@ -49,9 +76,6 @@ module RuboCop
49
76
  end
50
77
  end
51
78
  end
52
- # rubocop:enable Metrics/CyclomaticComplexity
53
-
54
- private
55
79
 
56
80
  def replace_with_suggestion(corrector, range, suggestion, description_beginning)
57
81
  replacement = description_beginning.gsub(REPLACEMENT_REGEX, "#{suggestion} ")
@@ -117,11 +117,11 @@ module RuboCop
117
117
  def add_newline?(node)
118
118
  # Determine if a blank line should be inserted before the new directive
119
119
  # in order to spread out pattern matchers
120
- return if node.sibling_index&.zero?
121
- return unless node.parent
120
+ return false if node.sibling_index&.zero?
121
+ return false unless node.parent
122
122
 
123
123
  prev_sibling = node.parent.child_nodes[node.sibling_index - 1]
124
- return unless prev_sibling && pattern_matcher?(prev_sibling)
124
+ return false unless prev_sibling && pattern_matcher?(prev_sibling)
125
125
 
126
126
  node.loc.line == last_line(prev_sibling) + 1
127
127
  end
@@ -68,6 +68,13 @@ module RuboCop
68
68
  # - extend
69
69
  # ----
70
70
  #
71
+ # @safety
72
+ # Autocorrection is unsafe because class methods and module inclusion
73
+ # can behave differently, based on which methods or constants have
74
+ # already been defined.
75
+ #
76
+ # Constants will only be moved when they are assigned with literals.
77
+ #
71
78
  # @example
72
79
  # # bad
73
80
  # # Expect extend be before constant
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- #
7
6
  # Checks the indentation of here document closings.
8
7
  #
9
8
  # @example
@@ -73,7 +72,7 @@ module RuboCop
73
72
  end
74
73
 
75
74
  def argument_indentation_correct?(node)
76
- return unless node.argument? || node.chained?
75
+ return false unless node.argument? || node.chained?
77
76
 
78
77
  opening_indentation(
79
78
  find_node_used_heredoc_argument(node.parent)
@@ -160,7 +160,7 @@ module RuboCop
160
160
  private
161
161
 
162
162
  def candidate?(node)
163
- return unless node
163
+ return false unless node
164
164
 
165
165
  method_candidate?(node) || class_candidate?(node) || module_candidate?(node)
166
166
  end
@@ -68,6 +68,8 @@ module RuboCop
68
68
  check_body(node.body, node.loc.line)
69
69
  end
70
70
  alias on_defs on_def
71
+ alias on_block on_def
72
+ alias on_numblock on_def
71
73
 
72
74
  def on_kwbegin(node)
73
75
  body, = *node
@@ -76,7 +76,7 @@ module RuboCop
76
76
 
77
77
  def autocorrect_lambda_for_tabs(corrector, range)
78
78
  spaces = ' ' * configured_indentation_width
79
- corrector.replace(range, range.source.gsub(/\t/, spaces))
79
+ corrector.replace(range, range.source.gsub("\t", spaces))
80
80
  end
81
81
 
82
82
  def autocorrect_lambda_for_spaces(corrector, range)
@@ -366,10 +366,10 @@ module RuboCop
366
366
  end
367
367
 
368
368
  def starts_with_access_modifier?(body_node)
369
- return unless body_node.begin_type?
369
+ return false unless body_node.begin_type?
370
370
 
371
371
  starting_node = body_node.children.first
372
- return unless starting_node
372
+ return false unless starting_node
373
373
 
374
374
  starting_node.send_type? && starting_node.bare_access_modifier?
375
375
  end
@@ -99,7 +99,7 @@ module RuboCop
99
99
  def suitable_as_single_line?(node)
100
100
  !comment_within?(node) &&
101
101
  node.each_descendant(:if, :case, :kwbegin, :def).none? &&
102
- node.each_descendant(:dstr, :str).none?(&:heredoc?) &&
102
+ node.each_descendant(:dstr, :str).none? { |n| n.heredoc? || n.value.include?("\n") } &&
103
103
  node.each_descendant(:begin).none? { |b| !b.single_line? }
104
104
  end
105
105
 
@@ -35,7 +35,7 @@ module RuboCop
35
35
  def check(node)
36
36
  expression = node.source
37
37
  op = node.loc.operator.source
38
- escaped_op = op.gsub(/\./, '\.')
38
+ escaped_op = op.gsub('.', '\.')
39
39
 
40
40
  # account for multiline range literals
41
41
  expression.sub!(/#{escaped_op}\n\s*/, op)
@@ -97,7 +97,8 @@ module RuboCop
97
97
  def wrap_in_parentheses(corrector, node)
98
98
  range = node.loc.selector.end.join(node.first_argument.source_range.begin)
99
99
 
100
- corrector.replace(range, '(')
100
+ corrector.remove(range)
101
+ corrector.insert_before(range, '(')
101
102
  corrector.insert_after(node.last_argument, ')')
102
103
  end
103
104
  end
@@ -90,7 +90,7 @@ module RuboCop
90
90
  end
91
91
 
92
92
  def debugger_method?(send_node)
93
- return if send_node.parent&.send_type? && send_node.parent.receiver == send_node
93
+ return false if send_node.parent&.send_type? && send_node.parent.receiver == send_node
94
94
 
95
95
  debugger_methods.include?(chained_method_name(send_node))
96
96
  end
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Checks for duplicated keys in hash literals.
7
+ # This cop considers both primitive types and constants for the hash keys.
7
8
  #
8
9
  # This cop mirrors a warning in Ruby 2.2.
9
10
  #
@@ -24,7 +25,7 @@ module RuboCop
24
25
  MSG = 'Duplicated key in hash literal.'
25
26
 
26
27
  def on_hash(node)
27
- keys = node.keys.select(&:recursive_basic_literal?)
28
+ keys = node.keys.select { |key| key.recursive_basic_literal? || key.const_type? }
28
29
 
29
30
  return unless duplicates?(keys)
30
31
 
@@ -24,6 +24,8 @@ module RuboCop
24
24
 
25
25
  MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
26
26
 
27
+ OCTAL_DIGITS_AFTER_ESCAPE = 2
28
+
27
29
  def on_regexp(node)
28
30
  each_repeated_character_class_element_loc(node) do |loc|
29
31
  add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
@@ -32,35 +34,57 @@ module RuboCop
32
34
  end
33
35
  end
34
36
 
35
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
36
37
  def each_repeated_character_class_element_loc(node)
37
38
  node.parsed_tree&.each_expression do |expr|
38
39
  next if skip_expression?(expr)
39
40
 
40
41
  seen = Set.new
41
- enum = expr.expressions.to_enum
42
- expression_count = expr.expressions.count
42
+ group_expressions(node, expr.expressions) do |group|
43
+ group_source = group.map(&:to_s).join
43
44
 
44
- expression_count.times do |current_number|
45
- current_child = enum.next
46
- next if within_interpolation?(node, current_child)
45
+ yield source_range(group) if seen.include?(group_source)
47
46
 
48
- current_child_source = current_child.to_s
49
- next_child = enum.peek if current_number + 1 < expression_count
47
+ seen << group_source
48
+ end
49
+ end
50
+ end
50
51
 
51
- if seen.include?(current_child_source)
52
- next if start_with_escaped_zero_number?(current_child_source, next_child.to_s)
52
+ private
53
53
 
54
- yield current_child.expression
55
- end
54
+ def group_expressions(node, expressions)
55
+ # Create a mutable list to simplify state tracking while we iterate.
56
+ expressions = expressions.to_a
56
57
 
57
- seen << current_child_source
58
- end
58
+ until expressions.empty?
59
+ # With we may need to compose a group of multiple expressions.
60
+ group = [expressions.shift]
61
+ next if within_interpolation?(node, group.first)
62
+
63
+ # With regexp_parser < 2.7 escaped octal sequences may be up to 3
64
+ # separate expressions ("\\0", "0", "1").
65
+ pop_octal_digits(group, expressions) if escaped_octal?(group.first.to_s)
66
+
67
+ yield(group)
59
68
  end
60
69
  end
61
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
62
70
 
63
- private
71
+ def pop_octal_digits(current_child, expressions)
72
+ OCTAL_DIGITS_AFTER_ESCAPE.times do
73
+ next_child = expressions.first
74
+ break unless octal?(next_child.to_s)
75
+
76
+ current_child << expressions.shift
77
+ end
78
+ end
79
+
80
+ def source_range(children)
81
+ return children.first.expression if children.size == 1
82
+
83
+ range_between(
84
+ children.first.expression.begin_pos,
85
+ children.last.expression.begin_pos + children.last.to_s.length
86
+ )
87
+ end
64
88
 
65
89
  def skip_expression?(expr)
66
90
  expr.type != :set || expr.token == :intersection
@@ -75,9 +99,12 @@ module RuboCop
75
99
  interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
76
100
  end
77
101
 
78
- def start_with_escaped_zero_number?(current_child, next_child)
79
- # Represents escaped code from `"\00"` (`"\u0000"`) to `"\07"` (`"\a"`).
80
- current_child == '\\0' && next_child.match?(/[0-7]/)
102
+ def escaped_octal?(string)
103
+ string.length == 2 && string[0] == '\\' && octal?(string[1])
104
+ end
105
+
106
+ def octal?(char)
107
+ ('0'..'7').cover?(char)
81
108
  end
82
109
 
83
110
  def interpolation_locs(node)
@@ -3,8 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
- # This cop emulates the following Ruby warnings in Ruby 2.6.
6
+ # Emulates the following Ruby warnings in Ruby 2.6.
8
7
  #
9
8
  # [source,console]
10
9
  # ----
@@ -65,7 +65,7 @@ module RuboCop
65
65
  end
66
66
 
67
67
  def send_node?(node)
68
- return nil unless node
68
+ return false unless node
69
69
 
70
70
  node.call_type?
71
71
  end
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
6
  # Prefer `equal?` over `==` when comparing `object_id`.
8
7
  #
9
8
  # `Object#equal?` is provided to compare objects for identity, and in contrast
@@ -3,8 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
- # This cop checks for `IO.select` that is incompatible with Fiber Scheduler since Ruby 3.0.
6
+ # Checks for `IO.select` that is incompatible with Fiber Scheduler since Ruby 3.0.
8
7
  #
9
8
  # When an array of IO objects waiting for an exception (the third argument of `IO.select`)
10
9
  # is used as an argument, there is no alternative API, so offenses are not registered.
@@ -91,7 +91,9 @@ module RuboCop
91
91
  def inherit_exception_class_with_omitted_namespace?(class_node)
92
92
  return false if class_node.parent_class.namespace&.cbase_type?
93
93
 
94
- class_node.left_siblings.any? { |sibling| exception_class?(sibling.identifier) }
94
+ class_node.left_siblings.any? do |sibling|
95
+ sibling.respond_to?(:identifier) && exception_class?(sibling.identifier)
96
+ end
95
97
  end
96
98
 
97
99
  def preferred_base_class