rubocop 1.69.1 → 1.70.0

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