rubocop 1.69.1 → 1.70.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +19 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +3 -3
  6. data/lib/rubocop/config.rb +13 -4
  7. data/lib/rubocop/config_loader.rb +4 -0
  8. data/lib/rubocop/config_loader_resolver.rb +14 -3
  9. data/lib/rubocop/config_validator.rb +18 -8
  10. data/lib/rubocop/cop/autocorrect_logic.rb +31 -34
  11. data/lib/rubocop/cop/base.rb +6 -0
  12. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  13. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  14. data/lib/rubocop/cop/internal_affairs/cop_enabled.rb +85 -0
  15. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +4 -3
  16. data/lib/rubocop/cop/internal_affairs/operator_keyword.rb +4 -2
  17. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  18. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -7
  19. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  20. data/lib/rubocop/cop/layout/empty_lines_around_begin_body.rb +5 -6
  21. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
  22. data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -7
  23. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -7
  24. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -6
  25. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -4
  26. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -0
  27. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +11 -2
  28. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
  29. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  30. data/lib/rubocop/cop/layout/line_length.rb +1 -0
  31. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +24 -0
  32. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  33. data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -1
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -3
  35. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -3
  36. data/lib/rubocop/cop/lint/constant_reassignment.rb +152 -0
  37. data/lib/rubocop/cop/lint/duplicate_set_element.rb +20 -7
  38. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +11 -3
  39. data/lib/rubocop/cop/lint/nested_method_definition.rb +5 -1
  40. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +4 -2
  41. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +6 -14
  42. data/lib/rubocop/cop/lint/shared_mutable_default.rb +65 -0
  43. data/lib/rubocop/cop/lint/syntax.rb +4 -1
  44. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -4
  45. data/lib/rubocop/cop/lint/void.rb +3 -2
  46. data/lib/rubocop/cop/metrics/class_length.rb +9 -9
  47. data/lib/rubocop/cop/metrics/method_length.rb +8 -1
  48. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -7
  49. data/lib/rubocop/cop/mixin/comments_help.rb +6 -1
  50. data/lib/rubocop/cop/mixin/dig_help.rb +1 -1
  51. data/lib/rubocop/cop/mixin/line_length_help.rb +5 -4
  52. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +46 -22
  53. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  54. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  55. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  56. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  57. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  58. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -1
  59. data/lib/rubocop/cop/style/and_or.rb +1 -1
  60. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -4
  61. data/lib/rubocop/cop/style/block_delimiters.rb +8 -1
  62. data/lib/rubocop/cop/style/class_and_module_children.rb +5 -2
  63. data/lib/rubocop/cop/style/each_for_simple_loop.rb +3 -6
  64. data/lib/rubocop/cop/style/empty_else.rb +4 -2
  65. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  66. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  67. data/lib/rubocop/cop/style/exact_regexp_match.rb +1 -2
  68. data/lib/rubocop/cop/style/exponential_notation.rb +1 -1
  69. data/lib/rubocop/cop/style/file_null.rb +20 -4
  70. data/lib/rubocop/cop/style/float_division.rb +8 -4
  71. data/lib/rubocop/cop/style/hash_except.rb +54 -67
  72. data/lib/rubocop/cop/style/hash_syntax.rb +5 -2
  73. data/lib/rubocop/cop/style/if_with_semicolon.rb +6 -4
  74. data/lib/rubocop/cop/style/it_assignment.rb +36 -0
  75. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +11 -1
  76. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -0
  77. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -1
  78. data/lib/rubocop/cop/style/missing_else.rb +2 -0
  79. data/lib/rubocop/cop/style/multiple_comparison.rb +34 -22
  80. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  81. data/lib/rubocop/cop/style/object_then.rb +13 -15
  82. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  83. data/lib/rubocop/cop/style/raise_args.rb +5 -3
  84. data/lib/rubocop/cop/style/random_with_offset.rb +3 -3
  85. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +2 -1
  86. data/lib/rubocop/cop/style/redundant_initialize.rb +12 -3
  87. data/lib/rubocop/cop/style/redundant_line_continuation.rb +12 -9
  88. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -4
  89. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +3 -0
  90. data/lib/rubocop/cop/style/redundant_self_assignment.rb +6 -5
  91. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  92. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +2 -1
  93. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  94. data/lib/rubocop/cop/style/single_line_methods.rb +2 -3
  95. data/lib/rubocop/cop/style/slicing_with_range.rb +40 -11
  96. data/lib/rubocop/cop/style/super_arguments.rb +63 -15
  97. data/lib/rubocop/cop/style/yoda_condition.rb +8 -4
  98. data/lib/rubocop/cop/style/yoda_expression.rb +1 -0
  99. data/lib/rubocop/cop/util.rb +9 -2
  100. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  101. data/lib/rubocop/lsp/diagnostic.rb +189 -0
  102. data/lib/rubocop/lsp/logger.rb +2 -2
  103. data/lib/rubocop/lsp/routes.rb +7 -23
  104. data/lib/rubocop/lsp/runtime.rb +15 -49
  105. data/lib/rubocop/lsp/stdin_runner.rb +83 -0
  106. data/lib/rubocop/magic_comment.rb +3 -3
  107. data/lib/rubocop/path_util.rb +11 -8
  108. data/lib/rubocop/rspec/shared_contexts.rb +4 -1
  109. data/lib/rubocop/runner.rb +5 -6
  110. data/lib/rubocop/target_ruby.rb +15 -0
  111. data/lib/rubocop/version.rb +1 -1
  112. data/lib/rubocop.rb +3 -0
  113. data/lib/ruby_lsp/rubocop/addon.rb +78 -0
  114. data/lib/ruby_lsp/rubocop/wraps_built_in_lsp_runtime.rb +50 -0
  115. metadata +16 -8
@@ -202,6 +202,7 @@ module RuboCop
202
202
 
203
203
  ignore_node(last_argument)
204
204
  end
205
+ alias on_csend on_send
205
206
  alias on_super on_send
206
207
  alias on_yield on_send
207
208
 
@@ -227,6 +228,7 @@ module RuboCop
227
228
  left_sibling = argument_before_hash(node)
228
229
  parent_loc = node.parent.loc
229
230
  selector = left_sibling || parent_loc.selector || parent_loc.expression
231
+
230
232
  same_line?(selector, node.pairs.first)
231
233
  end
232
234
 
@@ -382,13 +384,13 @@ module RuboCop
382
384
  end
383
385
 
384
386
  def enforce_first_argument_with_fixed_indentation?
385
- return false unless argument_alignment_config['Enabled']
386
-
387
+ argument_alignment_config = config.for_enabled_cop('Layout/ArgumentAlignment')
387
388
  argument_alignment_config['EnforcedStyle'] == 'with_fixed_indentation'
388
389
  end
389
390
 
390
- def argument_alignment_config
391
- config.for_cop('Layout/ArgumentAlignment')
391
+ def same_line?(node1, node2)
392
+ # Override `Util#same_line?`
393
+ super || node1.last_line == line(node2)
392
394
  end
393
395
  end
394
396
  end
@@ -75,6 +75,7 @@ module RuboCop
75
75
  autocorrect(corrector, outermost_send)
76
76
  end
77
77
  end
78
+ alias on_csend on_send
78
79
 
79
80
  private
80
81
 
@@ -51,6 +51,15 @@ module RuboCop
51
51
  private_constant :LINE_1_ENDING, :LINE_2_BEGINNING,
52
52
  :LEADING_STYLE_OFFENSE, :TRAILING_STYLE_OFFENSE
53
53
 
54
+ # When both cops are activated and run in the same iteration of the correction loop,
55
+ # `Style/StringLiterals` undoes the moving of spaces that
56
+ # `Layout/LineContinuationLeadingSpace` performs. This is because `Style/StringLiterals`
57
+ # takes the original string content and transforms it, rather than just modifying the
58
+ # delimiters, in order to handle escaping for quotes within the string.
59
+ def self.autocorrect_incompatible_with
60
+ [Style::StringLiterals]
61
+ end
62
+
54
63
  def on_dstr(node)
55
64
  # Quick check if we possibly have line continuations.
56
65
  return unless node.source.include?('\\')
@@ -129,9 +138,9 @@ module RuboCop
129
138
 
130
139
  def message(_range)
131
140
  if enforced_style_leading?
132
- 'Move trailing spaces to the start of next line.'
141
+ 'Move trailing spaces to the start of the next line.'
133
142
  else
134
- 'Move leading spaces to the end of previous line.'
143
+ 'Move leading spaces to the end of the previous line.'
135
144
  end
136
145
  end
137
146
 
@@ -101,7 +101,7 @@ module RuboCop
101
101
  ranges << loc.expression
102
102
  elsif literal.heredoc?
103
103
  ranges << loc.heredoc_body
104
- elsif loc.respond_to?(:begin) && loc.begin
104
+ elsif (loc.respond_to?(:begin) && loc.begin) || ignored_parent?(literal)
105
105
  ranges << loc.expression
106
106
  end
107
107
  end
@@ -127,6 +127,12 @@ module RuboCop
127
127
  comment_ranges(processed_source.comments)
128
128
  end
129
129
 
130
+ def ignored_parent?(node)
131
+ return false unless node.parent
132
+
133
+ node.parent.type?(:regexp, :xstr)
134
+ end
135
+
130
136
  def no_space_style?
131
137
  cop_config['EnforcedStyle'] == 'no_space'
132
138
  end
@@ -127,7 +127,7 @@ module RuboCop
127
127
 
128
128
  def base_column(child)
129
129
  grandparent = child.parent.parent
130
- if grandparent&.type == :pair
130
+ if grandparent&.pair_type?
131
131
  grandparent.loc.column
132
132
  else
133
133
  child.source_range.source_line =~ /\S/
@@ -90,6 +90,7 @@ module RuboCop
90
90
  alias on_array on_potential_breakable_node
91
91
  alias on_hash on_potential_breakable_node
92
92
  alias on_send on_potential_breakable_node
93
+ alias on_csend on_potential_breakable_node
93
94
  alias on_def on_potential_breakable_node
94
95
 
95
96
  def on_new_investigation
@@ -33,6 +33,18 @@ module RuboCop
33
33
  #
34
34
  # @example AllowMultilineFinalElement: false (default)
35
35
  #
36
+ # # bad
37
+ # foo(a, b,
38
+ # c
39
+ # )
40
+ #
41
+ # # bad
42
+ # foo(
43
+ # a, b, {
44
+ # foo: "bar",
45
+ # }
46
+ # )
47
+ #
36
48
  # # good
37
49
  # foo(
38
50
  # a,
@@ -44,6 +56,18 @@ module RuboCop
44
56
  #
45
57
  # @example AllowMultilineFinalElement: true
46
58
  #
59
+ # # bad
60
+ # foo(a, b,
61
+ # c
62
+ # )
63
+ #
64
+ # # good
65
+ # foo(
66
+ # a, b, {
67
+ # foo: "bar",
68
+ # }
69
+ # )
70
+ #
47
71
  # # good
48
72
  # foo(
49
73
  # a,
@@ -115,7 +115,7 @@ module RuboCop
115
115
  end
116
116
 
117
117
  def single_line_block_chain_enabled?
118
- @config.for_cop('Layout/SingleLineBlockChain')['Enabled']
118
+ @config.cop_enabled?('Layout/SingleLineBlockChain')
119
119
  end
120
120
 
121
121
  def convertible_block?(node)
@@ -22,7 +22,7 @@ module RuboCop
22
22
 
23
23
  def on_def(node)
24
24
  args = node.arguments
25
- return unless args.loc.begin&.is?('(')
25
+ return unless args.parenthesized_call?
26
26
 
27
27
  expr = args.source_range
28
28
  pos_before_left_paren = range_between(expr.begin_pos - 1, expr.begin_pos)
@@ -242,12 +242,12 @@ module RuboCop
242
242
  return !aligned_with_operator?(operator) unless type == :assignment
243
243
 
244
244
  token = Token.new(operator, nil, operator.source)
245
- align_preceding = aligned_with_preceding_assignment(token)
245
+ align_preceding = aligned_with_preceding_equals_operator(token)
246
246
 
247
247
  return false if align_preceding == :yes ||
248
- aligned_with_subsequent_assignment(token) == :none
248
+ aligned_with_subsequent_equals_operator(token) == :none
249
249
 
250
- aligned_with_subsequent_assignment(token) != :yes
250
+ aligned_with_subsequent_equals_operator(token) != :yes
251
251
  end
252
252
 
253
253
  def excess_trailing_space?(right_operand, with_space)
@@ -48,7 +48,7 @@ module RuboCop
48
48
 
49
49
  def on_new_investigation
50
50
  processed_source.lines.each_with_index do |line, index|
51
- next unless line.end_with?(' ', "\t")
51
+ next unless line.match?(/[[:blank:]]\z/)
52
52
 
53
53
  process_line(line, index + 1)
54
54
  end
@@ -84,7 +84,7 @@ module RuboCop
84
84
  end
85
85
 
86
86
  def whitespace_is_indentation?(range, level)
87
- range.source[/[ \t]+/].length <= level
87
+ range.source[/[[:blank:]]+/].length <= level
88
88
  end
89
89
 
90
90
  def whitespace_only?(range)
@@ -123,7 +123,9 @@ module RuboCop
123
123
  end
124
124
 
125
125
  def offense_range(lineno, line)
126
- source_range(processed_source.buffer, lineno, (line.rstrip.length)...(line.length))
126
+ source_range(
127
+ processed_source.buffer, lineno, (line.sub(/[[:blank:]]+\z/, '').length)...(line.length)
128
+ )
127
129
  end
128
130
  end
129
131
  end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for constant reassignments.
7
+ #
8
+ # Emulates Ruby's runtime warning "already initialized constant X"
9
+ # when a constant is reassigned in the same file and namespace using the
10
+ # `NAME = value` syntax.
11
+ #
12
+ # The cop cannot catch all offenses, like, for example, when a constant
13
+ # is reassigned in another file, or when using metaprogramming (`Module#const_set`).
14
+ #
15
+ # The cop only takes into account constants assigned in a "simple" way: directly
16
+ # inside class/module definition, or within another constant. Other type of assignments
17
+ # (e.g., inside a conditional) are disregarded.
18
+ #
19
+ # The cop also tracks constant removal using `Module#remove_const` with symbol
20
+ # or string argument.
21
+ #
22
+ # @example
23
+ # # bad
24
+ # X = :foo
25
+ # X = :bar
26
+ #
27
+ # # bad
28
+ # class A
29
+ # X = :foo
30
+ # X = :bar
31
+ # end
32
+ #
33
+ # # bad
34
+ # module A
35
+ # X = :foo
36
+ # X = :bar
37
+ # end
38
+ #
39
+ # # good - keep only one assignment
40
+ # X = :bar
41
+ #
42
+ # class A
43
+ # X = :bar
44
+ # end
45
+ #
46
+ # module A
47
+ # X = :bar
48
+ # end
49
+ #
50
+ # # good - use OR assignment
51
+ # X = :foo
52
+ # X ||= :bar
53
+ #
54
+ # # good - use conditional assignment
55
+ # X = :foo
56
+ # X = :bar unless defined?(X)
57
+ #
58
+ # # good - remove the assigned constant first
59
+ # class A
60
+ # X = :foo
61
+ # remove_const :X
62
+ # X = :bar
63
+ # end
64
+ #
65
+ class ConstantReassignment < Base
66
+ MSG = 'Constant `%<constant>s` is already assigned in this namespace.'
67
+
68
+ RESTRICT_ON_SEND = %i[remove_const].freeze
69
+
70
+ # @!method remove_constant(node)
71
+ def_node_matcher :remove_constant, <<~PATTERN
72
+ (send _ :remove_const
73
+ ({sym str} $_))
74
+ PATTERN
75
+
76
+ def on_casgn(node)
77
+ return unless fixed_constant_path?(node)
78
+ return unless simple_assignment?(node)
79
+ return if constant_names.add?(fully_qualified_constant_name(node))
80
+
81
+ add_offense(node, message: format(MSG, constant: node.name))
82
+ end
83
+
84
+ def on_send(node)
85
+ constant = remove_constant(node)
86
+
87
+ return unless constant
88
+
89
+ namespaces = ancestor_namespaces(node)
90
+
91
+ return if namespaces.none?
92
+
93
+ constant_names.delete(fully_qualified_name_for(namespaces, constant))
94
+ end
95
+
96
+ private
97
+
98
+ def fixed_constant_path?(node)
99
+ node.each_path.all? { |path| path.cbase_type? || path.const_type? || path.self_type? }
100
+ end
101
+
102
+ def simple_assignment?(node)
103
+ node.ancestors.all? do |ancestor|
104
+ return true if namespace_definition?(ancestor)
105
+
106
+ ancestor.begin_type? || ancestor.literal? || ancestor.casgn_type? ||
107
+ freeze_method?(ancestor)
108
+ end
109
+ end
110
+
111
+ def namespace_definition?(node)
112
+ node.module_type? || node.class_type?
113
+ end
114
+
115
+ def freeze_method?(node)
116
+ node.send_type? && node.method?(:freeze)
117
+ end
118
+
119
+ def fully_qualified_constant_name(node)
120
+ if node.absolute?
121
+ namespace = node.namespace.const_type? ? node.namespace.source : nil
122
+
123
+ "#{namespace}::#{node.name}"
124
+ else
125
+ constant_namespaces = ancestor_namespaces(node) + constant_namespaces(node)
126
+
127
+ fully_qualified_name_for(constant_namespaces, node.name)
128
+ end
129
+ end
130
+
131
+ def fully_qualified_name_for(namespaces, constant)
132
+ ['', *namespaces, constant].join('::')
133
+ end
134
+
135
+ def constant_namespaces(node)
136
+ node.each_path.select(&:const_type?).map(&:short_name)
137
+ end
138
+
139
+ def ancestor_namespaces(node)
140
+ node
141
+ .each_ancestor(:class, :module)
142
+ .map { |ancestor| ancestor.identifier.short_name }
143
+ .reverse
144
+ end
145
+
146
+ def constant_names
147
+ @constant_names ||= Set.new
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for duplicate literal, constant, or variable elements in Set.
6
+ # Checks for duplicate literal, constant, or variable elements in Set and SortedSet.
7
7
  #
8
8
  # @example
9
9
  #
@@ -25,17 +25,28 @@ module RuboCop
25
25
  # # good
26
26
  # [:foo, :bar].to_set
27
27
  #
28
+ # # bad
29
+ # SortedSet[:foo, :bar, :foo]
30
+ #
31
+ # # good
32
+ # SortedSet[:foo, :bar]
33
+ #
34
+ # # bad
35
+ # SortedSet.new([:foo, :bar, :foo])
36
+ #
37
+ # # good
38
+ # SortedSet.new([:foo, :bar])
28
39
  class DuplicateSetElement < Base
29
40
  extend AutoCorrector
30
41
 
31
- MSG = 'Remove the duplicate element in Set.'
42
+ MSG = 'Remove the duplicate element in %<class_name>s.'
32
43
  RESTRICT_ON_SEND = %i[\[\] new to_set].freeze
33
44
 
34
45
  # @!method set_init_elements(node)
35
46
  def_node_matcher :set_init_elements, <<~PATTERN
36
47
  {
37
- (send (const {nil? cbase} :Set) :[] $...)
38
- (send (const {nil? cbase} :Set) :new (array $...))
48
+ (send (const {nil? cbase} {:Set :SortedSet}) :[] $...)
49
+ (send (const {nil? cbase} {:Set :SortedSet}) :new (array $...))
39
50
  (call (array $...) :to_set)
40
51
  }
41
52
  PATTERN
@@ -51,7 +62,7 @@ module RuboCop
51
62
  next if !set_element.literal? && !set_element.const_type? && !set_element.variable?
52
63
 
53
64
  if seen_elements.include?(set_element)
54
- register_offense(set_element, set_elements[index - 1])
65
+ register_offense(set_element, set_elements[index - 1], node)
55
66
  else
56
67
  seen_elements << set_element
57
68
  end
@@ -61,8 +72,10 @@ module RuboCop
61
72
 
62
73
  private
63
74
 
64
- def register_offense(current_element, prev_element)
65
- add_offense(current_element) do |corrector|
75
+ def register_offense(current_element, prev_element, node)
76
+ class_name = node.receiver.const_type? ? node.receiver.const_name : 'Set'
77
+
78
+ add_offense(current_element, message: format(MSG, class_name: class_name)) do |corrector|
66
79
  range = prev_element.source_range.end.join(current_element.source_range.end)
67
80
 
68
81
  corrector.remove(range)
@@ -21,6 +21,7 @@ module RuboCop
21
21
  MSG = 'Literal interpolation detected.'
22
22
  COMPOSITE = %i[array hash pair irange erange].freeze
23
23
 
24
+ # rubocop:disable Metrics/AbcSize
24
25
  def on_interpolation(begin_node)
25
26
  final_node = begin_node.children.last
26
27
  return unless offending?(final_node)
@@ -35,11 +36,18 @@ module RuboCop
35
36
  return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
36
37
 
37
38
  add_offense(final_node) do |corrector|
38
- return if final_node.dstr_type? # nested, fixed in next iteration
39
+ next if final_node.dstr_type? # nested, fixed in next iteration
39
40
 
40
- corrector.replace(final_node.parent, expanded_value)
41
+ replacement = if final_node.str_type? && !final_node.value.valid_encoding?
42
+ final_node.source.delete_prefix('"').delete_suffix('"')
43
+ else
44
+ expanded_value
45
+ end
46
+
47
+ corrector.replace(final_node.parent, replacement)
41
48
  end
42
49
  end
50
+ # rubocop:enable Metrics/AbcSize
43
51
 
44
52
  private
45
53
 
@@ -168,7 +176,7 @@ module RuboCop
168
176
  end
169
177
 
170
178
  def space_literal?(node)
171
- node.str_type? && node.value.blank?
179
+ node.str_type? && node.value.valid_encoding? && node.value.blank?
172
180
  end
173
181
 
174
182
  def ends_heredoc_line?(node)
@@ -96,7 +96,7 @@ module RuboCop
96
96
 
97
97
  def on_def(node)
98
98
  subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring
99
- return if node.defs_type? && subject.variable?
99
+ return if node.defs_type? && allowed_subject_type?(subject)
100
100
 
101
101
  def_ancestor = node.each_ancestor(:def, :defs).first
102
102
  return unless def_ancestor
@@ -117,6 +117,10 @@ module RuboCop
117
117
  child.class_constructor? || allowed_method_name?(child)
118
118
  end
119
119
 
120
+ def allowed_subject_type?(subject)
121
+ subject.variable? || subject.const_type? || subject.call_type?
122
+ end
123
+
120
124
  def allowed_method_name?(node)
121
125
  name = node.method_name
122
126
 
@@ -59,12 +59,12 @@ module RuboCop
59
59
 
60
60
  # @!method send_exist_node(node)
61
61
  def_node_search :send_exist_node, <<~PATTERN
62
- $(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
62
+ $(send (const {cbase nil?} {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
63
63
  PATTERN
64
64
 
65
65
  # @!method receiver_and_method_name(node)
66
66
  def_node_matcher :receiver_and_method_name, <<~PATTERN
67
- (send (const nil? $_) $_ ...)
67
+ (send (const {cbase nil?} $_) $_ ...)
68
68
  PATTERN
69
69
 
70
70
  # @!method force?(node)
@@ -78,6 +78,7 @@ module RuboCop
78
78
  PATTERN
79
79
 
80
80
  def on_send(node)
81
+ return unless node.receiver&.const_type?
81
82
  return unless if_node_child?(node)
82
83
  return if explicit_not_force?(node)
83
84
  return unless (exist_node = send_exist_node(node.parent).first)
@@ -115,6 +116,7 @@ module RuboCop
115
116
 
116
117
  def message_remove_file_exist_check(node)
117
118
  receiver, method_name = receiver_and_method_name(node)
119
+
118
120
  format(MSG_REMOVE_FILE_EXIST_CHECK, receiver: receiver, method_name: method_name)
119
121
  end
120
122
 
@@ -4,30 +4,27 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Certain numeric operations have a constant result, usually 0 or 1.
7
- # Subtracting a number from itself or multiplying it by 0 will always return 0.
8
- # Additionally, a variable modulo 0 or itself will always return 0.
7
+ # Multiplying a number by 0 will always return 0.
9
8
  # Dividing a number by itself or raising it to the power of 0 will always return 1.
10
9
  # As such, they can be replaced with that result.
11
10
  # These are probably leftover from debugging, or are mistakes.
12
11
  # Other numeric operations that are similarly leftover from debugging or mistakes
13
12
  # are handled by Lint/UselessNumericOperation.
14
13
  #
14
+ # NOTE: This cop doesn't detect offenses for the `-` and `%` operator because it
15
+ # can't determine the type of `x`. If `x` is an Array or String, it doesn't perform
16
+ # a numeric operation.
17
+ #
15
18
  # @example
16
19
  #
17
20
  # # bad
18
- # x - x
19
21
  # x * 0
20
- # x % 1
21
- # x % x
22
22
  #
23
23
  # # good
24
24
  # 0
25
25
  #
26
26
  # # bad
27
- # x -= x
28
27
  # x *= 0
29
- # x %= 1
30
- # x %= x
31
28
  #
32
29
  # # good
33
30
  # x = 0
@@ -49,7 +46,7 @@ module RuboCop
49
46
  class NumericOperationWithConstantResult < Base
50
47
  extend AutoCorrector
51
48
  MSG = 'Numeric operation with a constant result detected.'
52
- RESTRICT_ON_SEND = %i[- * / % **].freeze
49
+ RESTRICT_ON_SEND = %i[* / **].freeze
53
50
 
54
51
  # @!method operation_with_constant_result?(node)
55
52
  def_node_matcher :operation_with_constant_result?,
@@ -85,21 +82,16 @@ module RuboCop
85
82
 
86
83
  private
87
84
 
88
- # rubocop :disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
89
85
  def constant_result?(variable, operation, number)
90
86
  if number.to_s == '0'
91
87
  return 0 if operation == :*
92
88
  return 1 if operation == :**
93
- elsif number.to_s == '1'
94
- return 0 if operation == :%
95
89
  elsif number == variable
96
- return 0 if %i[- %].include?(operation)
97
90
  return 1 if operation == :/
98
91
  end
99
92
  # If we weren't able to find any matches, return false so we can bail out.
100
93
  false
101
94
  end
102
- # rubocop :enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
103
95
  end
104
96
  end
105
97
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for Hash creation with a mutable default value.
7
+ # Creating a Hash in such a way will share the default value
8
+ # across all keys, causing unexpected behavior when modifying it.
9
+ #
10
+ # For example, when the Hash was created with an Array as the argument,
11
+ # calling `hash[:foo] << 'bar'` will also change the value of all
12
+ # other keys that have not been explicitly assigned to.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # Hash.new([])
17
+ # Hash.new({})
18
+ # Hash.new(Array.new)
19
+ # Hash.new(Hash.new)
20
+ #
21
+ # # okay -- In rare cases that intentionally have this behavior,
22
+ # # without disabling the cop, you can set the default explicitly.
23
+ # h = Hash.new
24
+ # h.default = []
25
+ # h[:a] << 1
26
+ # h[:b] << 2
27
+ # h # => {:a => [1, 2], :b => [1, 2]}
28
+ #
29
+ # # okay -- beware this will discard mutations and only remember assignments
30
+ # Hash.new { Array.new }
31
+ # Hash.new { Hash.new }
32
+ # Hash.new { {} }
33
+ # Hash.new { [] }
34
+ #
35
+ # # good - frozen solution will raise an error when mutation attempted
36
+ # Hash.new([].freeze)
37
+ # Hash.new({}.freeze)
38
+ #
39
+ # # good - using a proc will create a new object for each key
40
+ # h = Hash.new
41
+ # h.default_proc = ->(h, k) { [] }
42
+ # h.default_proc = ->(h, k) { {} }
43
+ #
44
+ # # good - using a block will create a new object for each key
45
+ # Hash.new { |h, k| h[k] = [] }
46
+ # Hash.new { |h, k| h[k] = {} }
47
+ class SharedMutableDefault < Base
48
+ MSG = 'Do not create a Hash with a mutable default value ' \
49
+ 'as the default value can accidentally be changed.'
50
+ RESTRICT_ON_SEND = %i[new].freeze
51
+
52
+ # @!method hash_initialized_with_mutable_shared_object?(node)
53
+ def_node_matcher :hash_initialized_with_mutable_shared_object?, <<~PATTERN
54
+ (send (const {nil? cbase} :Hash) :new {array hash (send (const {nil? cbase} {:Array :Hash}) :new)})
55
+ PATTERN
56
+
57
+ def on_send(node)
58
+ return unless hash_initialized_with_mutable_shared_object?(node)
59
+
60
+ add_offense(node)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -6,9 +6,12 @@ module RuboCop
6
6
  # Repacks Parser's diagnostics/errors
7
7
  # into RuboCop's offenses.
8
8
  class Syntax < Base
9
+ LEVELS = %i[error fatal].freeze
10
+
9
11
  def on_other_file
10
12
  add_offense_from_error(processed_source.parser_error) if processed_source.parser_error
11
- processed_source.diagnostics.each do |diagnostic|
13
+ syntax_errors = processed_source.diagnostics.select { |d| LEVELS.include?(d.level) }
14
+ syntax_errors.each do |diagnostic|
12
15
  add_offense_from_diagnostic(diagnostic, processed_source.ruby_version)
13
16
  end
14
17
  super