rubocop 1.52.1 → 1.53.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) 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/void.rb +1 -1
  30. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  31. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  32. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  33. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  34. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  35. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  36. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  37. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  38. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  39. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  40. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  41. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  42. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  43. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  44. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  45. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  46. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  47. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  48. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  49. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +86 -0
  50. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  51. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  52. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  53. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  54. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  55. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  56. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  57. data/lib/rubocop/cop/util.rb +1 -1
  58. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  59. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  60. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  61. data/lib/rubocop/lsp/logger.rb +22 -0
  62. data/lib/rubocop/lsp/routes.rb +223 -0
  63. data/lib/rubocop/lsp/runtime.rb +79 -0
  64. data/lib/rubocop/lsp/server.rb +62 -0
  65. data/lib/rubocop/lsp/severity.rb +27 -0
  66. data/lib/rubocop/options.rb +11 -1
  67. data/lib/rubocop/version.rb +1 -1
  68. data/lib/rubocop.rb +8 -0
  69. metadata +34 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84bada99408af7ceb29d5c6bb4e188f61b27a78cb52639bcbbb144a289ebbc92
4
- data.tar.gz: 930174d41acdfd3e0f7da8ef80c496ce7e5b68e5b00312d692a2cd93605fa70a
3
+ metadata.gz: 63ade8f6d7d93161d739cd557220bea89fc0320a2f8923f41cc092015dbcadef
4
+ data.tar.gz: bb608b076e84d18f317b1690a6f5ad0b0ecea1232977107ebca57b04d32b6abb
5
5
  SHA512:
6
- metadata.gz: 9c1f475617dc4826cef3c543ed35e65117bd93ff6f80605adc2096a06ddb38f9171a95800685a92103924600158ac306c27c9168cea787d5f11dc0616bd1a9ba
7
- data.tar.gz: 9aaba3f7148469ce255ee22950f7c11542ca1e8480388493ab90c728831e47eef306372c42769a535beaba95d2d715c4b59161852689468cbd3fcd2c2236a535
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
@@ -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