rubocop 1.52.1 → 1.53.1

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 (70) 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/node_matcher_directive.rb +3 -3
  11. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  12. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  14. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
  15. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  16. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  17. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  18. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
  19. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  20. data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
  21. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
  22. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
  23. data/lib/rubocop/cop/lint/missing_super.rb +31 -5
  24. data/lib/rubocop/cop/lint/mixed_case_range.rb +109 -0
  25. data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
  26. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +120 -0
  27. data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
  28. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  29. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  30. data/lib/rubocop/cop/lint/void.rb +1 -1
  31. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  32. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  33. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  34. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  35. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  36. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  37. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  38. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  39. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  40. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  41. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  42. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  43. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  44. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  45. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  46. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  47. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  48. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  49. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  50. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +97 -0
  51. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  52. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  53. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  54. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  55. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  56. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  57. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  58. data/lib/rubocop/cop/util.rb +1 -1
  59. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  60. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  61. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  62. data/lib/rubocop/lsp/logger.rb +22 -0
  63. data/lib/rubocop/lsp/routes.rb +223 -0
  64. data/lib/rubocop/lsp/runtime.rb +79 -0
  65. data/lib/rubocop/lsp/server.rb +62 -0
  66. data/lib/rubocop/lsp/severity.rb +27 -0
  67. data/lib/rubocop/options.rb +11 -1
  68. data/lib/rubocop/version.rb +1 -1
  69. data/lib/rubocop.rb +8 -0
  70. metadata +30 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84bada99408af7ceb29d5c6bb4e188f61b27a78cb52639bcbbb144a289ebbc92
4
- data.tar.gz: 930174d41acdfd3e0f7da8ef80c496ce7e5b68e5b00312d692a2cd93605fa70a
3
+ metadata.gz: d7b38b9b2471ab2c5972c061427da41ddf9820f91b21e7fa5d89330b2647eaa5
4
+ data.tar.gz: 1a48aaee106f9e6c0459c8c5ef0a98dc39455bf06a68174d6b52ac2ea74abb0d
5
5
  SHA512:
6
- metadata.gz: 9c1f475617dc4826cef3c543ed35e65117bd93ff6f80605adc2096a06ddb38f9171a95800685a92103924600158ac306c27c9168cea787d5f11dc0616bd1a9ba
7
- data.tar.gz: 9aaba3f7148469ce255ee22950f7c11542ca1e8480388493ab90c728831e47eef306372c42769a535beaba95d2d715c4b59161852689468cbd3fcd2c2236a535
6
+ metadata.gz: 181bd1ac83ae045a457b3b5b73bf8e18e964f7413cabfa843d594a10cc7435403827e0b001ef4d5574c0065944e942adb908b0e4bfac8766486f2c58dd10e8e1
7
+ data.tar.gz: b14fad843c6eb6ba3aa2e521133fe63da2abfed97fb79b1e45893c055c273180d1f0d0ed25cf53c53885d41859637c4469a2527617bb1c84125331a4eaf24fa3
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
@@ -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
@@ -72,7 +72,7 @@ module RuboCop
72
72
  end
73
73
 
74
74
  def argument_indentation_correct?(node)
75
- return unless node.argument? || node.chained?
75
+ return false unless node.argument? || node.chained?
76
76
 
77
77
  opening_indentation(
78
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)
@@ -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)
@@ -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
@@ -14,6 +14,13 @@ module RuboCop
14
14
  # Autocorrection is not supported because the position of `super` cannot be
15
15
  # determined automatically.
16
16
  #
17
+ # `Object` and `BasicObject` are allowed by this cop because of their
18
+ # stateless nature. However, sometimes you might want to allow other parent
19
+ # classes from this cop, for example in the case of an abstract class that is
20
+ # not meant to be called with `super`. In those cases, you can use the
21
+ # `AllowedParentClasses` option to specify which classes should be allowed
22
+ # *in addition to* `Object` and `BasicObject`.
23
+ #
17
24
  # @example
18
25
  # # bad
19
26
  # class Employee < Person
@@ -60,6 +67,21 @@ module RuboCop
60
67
  # end
61
68
  # end
62
69
  #
70
+ # # good
71
+ # class ClassWithNoParent
72
+ # def initialize
73
+ # do_something
74
+ # end
75
+ # end
76
+ #
77
+ # @example AllowedParentClasses: [MyAbstractClass]
78
+ # # good
79
+ # class MyConcreteClass < MyAbstractClass
80
+ # def initialize
81
+ # do_something
82
+ # end
83
+ # end
84
+ #
63
85
  class MissingSuper < Base
64
86
  CONSTRUCTOR_MSG = 'Call `super` to initialize state of the parent class.'
65
87
  CALLBACK_MSG = 'Call `super` to invoke callback defined in the parent class.'
@@ -103,7 +125,7 @@ module RuboCop
103
125
  end
104
126
 
105
127
  def callback_method_def?(node)
106
- return unless CALLBACKS.include?(node.method_name)
128
+ return false unless CALLBACKS.include?(node.method_name)
107
129
 
108
130
  node.each_ancestor(:class, :sclass, :module).first
109
131
  end
@@ -116,16 +138,20 @@ module RuboCop
116
138
  if (block_node = node.each_ancestor(:block, :numblock).first)
117
139
  return false unless (super_class = class_new_block(block_node))
118
140
 
119
- !stateless_class?(super_class)
141
+ !allowed_class?(super_class)
120
142
  elsif (class_node = node.each_ancestor(:class).first)
121
- class_node.parent_class && !stateless_class?(class_node.parent_class)
143
+ class_node.parent_class && !allowed_class?(class_node.parent_class)
122
144
  else
123
145
  false
124
146
  end
125
147
  end
126
148
 
127
- def stateless_class?(node)
128
- STATELESS_CLASSES.include?(node.const_name)
149
+ def allowed_class?(node)
150
+ allowed_classes.include?(node.const_name)
151
+ end
152
+
153
+ def allowed_classes
154
+ @allowed_classes ||= STATELESS_CLASSES + cop_config.fetch('AllowedParentClasses', [])
129
155
  end
130
156
  end
131
157
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for mixed-case character ranges since they include likely unintended characters.
7
+ #
8
+ # Offenses are registered for regexp character classes like `/[A-z]/`
9
+ # as well as range objects like `('A'..'z')`.
10
+ #
11
+ # NOTE: Range objects cannot be autocorrected.
12
+ #
13
+ # @safety
14
+ # The cop autocorrects regexp character classes
15
+ # by replacing one character range with two: `A-z` becomes `A-Za-z`.
16
+ # In most cases this is probably what was originally intended
17
+ # but it changes the regexp to no longer match symbols it used to include.
18
+ # For this reason, this cop's autocorrect is unsafe (it will
19
+ # change the behavior of the code).
20
+ #
21
+ # @example
22
+ #
23
+ # # bad
24
+ # r = /[A-z]/
25
+ #
26
+ # # good
27
+ # r = /[A-Za-z]/
28
+ class MixedCaseRange < Base
29
+ extend AutoCorrector
30
+ include RangeHelp
31
+
32
+ MSG = 'Ranges from upper to lower case ASCII letters may include unintended ' \
33
+ 'characters. Instead of `A-z` (which also includes several symbols) ' \
34
+ 'specify each range individually: `A-Za-z` and individually specify any symbols.'
35
+ RANGES = [('a'..'z').freeze, ('A'..'Z').freeze].freeze
36
+
37
+ def on_irange(node)
38
+ return unless node.children.compact.all?(&:str_type?)
39
+
40
+ range_start, range_end = node.children
41
+
42
+ return if range_start.nil? || range_end.nil?
43
+
44
+ add_offense(node) if unsafe_range?(range_start.value, range_end.value)
45
+ end
46
+ alias on_erange on_irange
47
+
48
+ def on_regexp(node)
49
+ each_unsafe_regexp_range(node) do |loc|
50
+ add_offense(loc) do |corrector|
51
+ corrector.replace(loc, rewrite_regexp_range(loc.source))
52
+ end
53
+ end
54
+ end
55
+
56
+ def each_unsafe_regexp_range(node)
57
+ node.parsed_tree&.each_expression do |expr|
58
+ next if skip_expression?(expr)
59
+
60
+ range_pairs(expr).reject do |range_start, range_end|
61
+ next if skip_range?(range_start, range_end)
62
+
63
+ next unless unsafe_range?(range_start.text, range_end.text)
64
+
65
+ yield(build_source_range(range_start, range_end))
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def build_source_range(range_start, range_end)
73
+ range_between(range_start.expression.begin_pos, range_end.expression.end_pos)
74
+ end
75
+
76
+ def range_for(char)
77
+ RANGES.detect do |range|
78
+ range.include?(char)
79
+ end
80
+ end
81
+
82
+ def range_pairs(expr)
83
+ RuboCop::Cop::Utils::RegexpRanges.new(expr).pairs
84
+ end
85
+
86
+ def unsafe_range?(range_start, range_end)
87
+ range_for(range_start) != range_for(range_end)
88
+ end
89
+
90
+ def skip_expression?(expr)
91
+ !(expr.type == :set && expr.token == :character)
92
+ end
93
+
94
+ def skip_range?(range_start, range_end)
95
+ [range_start, range_end].any? do |bound|
96
+ bound.type == :escape
97
+ end
98
+ end
99
+
100
+ def rewrite_regexp_range(source)
101
+ open, close = source.split('-')
102
+ first = [open, range_for(open).end]
103
+ second = [range_for(close).begin, close]
104
+ "#{first.uniq.join('-')}#{second.uniq.join('-')}"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -74,6 +74,7 @@ module RuboCop
74
74
  extend AutoCorrector
75
75
  include AllowedMethods
76
76
  include AllowedPattern
77
+ include IgnoredNode
77
78
 
78
79
  CONVERSION_METHOD_CLASS_MAPPING = {
79
80
  to_i: "#{Integer.name}(%<number_object>s, 10)",
@@ -116,7 +117,11 @@ module RuboCop
116
117
  corrected_method: correct_method(node, receiver)
117
118
  )
118
119
  add_offense(node, message: message) do |corrector|
120
+ next if part_of_ignored_node?(node)
121
+
119
122
  corrector.replace(node, correct_method(node, node.receiver))
123
+
124
+ ignore_node(node)
120
125
  end
121
126
  end
122
127
  end