rubocop 1.71.2 → 1.74.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/config/default.yml +86 -15
  4. data/config/internal_affairs.yml +20 -0
  5. data/lib/rubocop/cli/command/suggest_extensions.rb +7 -1
  6. data/lib/rubocop/comment_config.rb +1 -1
  7. data/lib/rubocop/config.rb +4 -0
  8. data/lib/rubocop/config_loader.rb +44 -9
  9. data/lib/rubocop/config_loader_resolver.rb +24 -9
  10. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -3
  11. data/lib/rubocop/config_obsoletion.rb +1 -1
  12. data/lib/rubocop/config_validator.rb +1 -1
  13. data/lib/rubocop/cop/internal_affairs/example_description.rb +7 -3
  14. data/lib/rubocop/cop/internal_affairs/location_exists.rb +116 -0
  15. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -1
  16. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +91 -0
  17. data/lib/rubocop/cop/internal_affairs/plugin.rb +33 -0
  18. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +7 -1
  19. data/lib/rubocop/cop/internal_affairs.rb +2 -16
  20. data/lib/rubocop/cop/layout/block_alignment.rb +2 -0
  21. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +4 -4
  22. data/lib/rubocop/cop/layout/else_alignment.rb +1 -1
  23. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +26 -1
  25. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +22 -2
  26. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  27. data/lib/rubocop/cop/layout/line_length.rb +3 -3
  28. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  29. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  30. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -0
  31. data/lib/rubocop/cop/lint/duplicate_methods.rb +0 -14
  32. data/lib/rubocop/cop/lint/empty_conditional_body.rb +14 -64
  33. data/lib/rubocop/cop/lint/erb_new_arguments.rb +0 -6
  34. data/lib/rubocop/cop/lint/float_comparison.rb +1 -6
  35. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  36. data/lib/rubocop/cop/lint/literal_as_condition.rb +103 -9
  37. data/lib/rubocop/cop/lint/mixed_case_range.rb +2 -2
  38. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +2 -2
  39. data/lib/rubocop/cop/lint/redundant_require_statement.rb +0 -21
  40. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +258 -0
  41. data/lib/rubocop/cop/lint/return_in_void_context.rb +4 -11
  42. data/lib/rubocop/cop/lint/shared_mutable_default.rb +12 -1
  43. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +111 -0
  44. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +71 -0
  45. data/lib/rubocop/cop/lint/void.rb +6 -0
  46. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +7 -7
  47. data/lib/rubocop/cop/mixin/alignment.rb +2 -2
  48. data/lib/rubocop/cop/mixin/allowed_pattern.rb +4 -4
  49. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
  50. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  51. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +18 -18
  52. data/lib/rubocop/cop/mixin/hash_subset.rb +19 -4
  53. data/lib/rubocop/cop/mixin/hash_transform_method.rb +74 -74
  54. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  55. data/lib/rubocop/cop/mixin/range_help.rb +15 -3
  56. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  57. data/lib/rubocop/cop/mixin/target_ruby_version.rb +1 -1
  58. data/lib/rubocop/cop/mixin/trailing_comma.rb +12 -0
  59. data/lib/rubocop/cop/naming/block_forwarding.rb +3 -3
  60. data/lib/rubocop/cop/naming/predicate_name.rb +44 -0
  61. data/lib/rubocop/cop/naming/variable_name.rb +64 -6
  62. data/lib/rubocop/cop/style/accessor_grouping.rb +19 -5
  63. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -3
  64. data/lib/rubocop/cop/style/class_and_module_children.rb +29 -7
  65. data/lib/rubocop/cop/style/commented_keyword.rb +10 -3
  66. data/lib/rubocop/cop/style/comparable_between.rb +75 -0
  67. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  68. data/lib/rubocop/cop/style/endless_method.rb +163 -18
  69. data/lib/rubocop/cop/style/expand_path_arguments.rb +2 -7
  70. data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
  71. data/lib/rubocop/cop/style/format_string_token.rb +38 -11
  72. data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -2
  73. data/lib/rubocop/cop/style/inverse_methods.rb +8 -5
  74. data/lib/rubocop/cop/style/keyword_parameters_order.rb +13 -7
  75. data/lib/rubocop/cop/style/line_end_concatenation.rb +10 -4
  76. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -3
  77. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +1 -1
  78. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  79. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -9
  80. data/lib/rubocop/cop/style/redundant_condition.rb +45 -0
  81. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +14 -4
  82. data/lib/rubocop/cop/style/redundant_format.rb +250 -0
  83. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  84. data/lib/rubocop/cop/style/redundant_parentheses.rb +18 -4
  85. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -1
  86. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -0
  87. data/lib/rubocop/cop/style/single_line_methods.rb +3 -3
  88. data/lib/rubocop/cop/style/sole_nested_conditional.rb +0 -6
  89. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  90. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +47 -6
  91. data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +48 -6
  92. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  93. data/lib/rubocop/cop/util.rb +1 -1
  94. data/lib/rubocop/cop/utils/format_string.rb +10 -5
  95. data/lib/rubocop/cops_documentation_generator.rb +12 -1
  96. data/lib/rubocop/directive_comment.rb +36 -3
  97. data/lib/rubocop/ext/regexp_node.rb +0 -1
  98. data/lib/rubocop/lsp/runtime.rb +2 -0
  99. data/lib/rubocop/lsp/server.rb +0 -2
  100. data/lib/rubocop/options.rb +26 -11
  101. data/lib/rubocop/path_util.rb +4 -0
  102. data/lib/rubocop/plugin/configuration_integrator.rb +143 -0
  103. data/lib/rubocop/plugin/load_error.rb +26 -0
  104. data/lib/rubocop/plugin/loader.rb +100 -0
  105. data/lib/rubocop/plugin/not_supported_error.rb +29 -0
  106. data/lib/rubocop/plugin.rb +46 -0
  107. data/lib/rubocop/rake_task.rb +4 -1
  108. data/lib/rubocop/rspec/cop_helper.rb +9 -0
  109. data/lib/rubocop/rspec/shared_contexts.rb +15 -0
  110. data/lib/rubocop/rspec/support.rb +1 -0
  111. data/lib/rubocop/server/cache.rb +35 -2
  112. data/lib/rubocop/server/cli.rb +2 -2
  113. data/lib/rubocop/version.rb +17 -2
  114. data/lib/rubocop.rb +6 -1
  115. data/lib/ruby_lsp/rubocop/addon.rb +7 -10
  116. data/lib/ruby_lsp/rubocop/{wraps_built_in_lsp_runtime.rb → runtime_adapter.rb} +5 -8
  117. metadata +37 -10
  118. data/lib/rubocop/cop/utils/regexp_ranges.rb +0 -113
@@ -9,8 +9,8 @@ module RuboCop
9
9
  # These keywords are: `class`, `module`, `def`, `begin`, `end`.
10
10
  #
11
11
  # Note that some comments
12
- # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
13
- # and RBS::Inline annotation comments are allowed.
12
+ # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`),
13
+ # RBS::Inline annotation, and Steep annotation (`steep:ignore`) are allowed.
14
14
  #
15
15
  # Autocorrection removes comments from `end` keyword and keeps comments
16
16
  # for `class`, `module`, `def` and `begin` above the keyword.
@@ -57,9 +57,11 @@ module RuboCop
57
57
 
58
58
  REGEXP = /(?<keyword>\S+).*#/.freeze
59
59
 
60
- SUBCLASS_DEFINITION = /\A\s*class\s+\w+\s*<\s*\w+/.freeze
60
+ SUBCLASS_DEFINITION = /\A\s*class\s+(\w|::)+\s*<\s*(\w|::)+/.freeze
61
61
  METHOD_DEFINITION = /\A\s*def\s/.freeze
62
62
 
63
+ STEEP_REGEXP = /#\ssteep:ignore(\s|\z)/.freeze
64
+
63
65
  def on_new_investigation
64
66
  processed_source.comments.each do |comment|
65
67
  next unless offensive?(comment) && (match = source_line(comment).match(REGEXP))
@@ -86,6 +88,7 @@ module RuboCop
86
88
  def offensive?(comment)
87
89
  line = source_line(comment)
88
90
  return false if rbs_inline_annotation?(line, comment)
91
+ return false if steep_annotation?(comment)
89
92
 
90
93
  KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
91
94
  ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
@@ -105,6 +108,10 @@ module RuboCop
105
108
  false
106
109
  end
107
110
  end
111
+
112
+ def steep_annotation?(comment)
113
+ comment.text.match?(STEEP_REGEXP)
114
+ end
108
115
  end
109
116
  end
110
117
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for logical comparison which can be replaced with `Comparable#between?`.
7
+ #
8
+ # NOTE: `Comparable#between?` is on average slightly slower than logical comparison,
9
+ # although the difference generally isn't observable. If you require maximum
10
+ # performance, consider using logical comparison.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # x >= min && x <= max
16
+ #
17
+ # # bad
18
+ # x <= max && x >= min
19
+ #
20
+ # # good
21
+ # x.between?(min, max)
22
+ #
23
+ class ComparableBetween < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Prefer `%<prefer>s` over logical comparison.'
27
+
28
+ # @!method logical_comparison_between_by_min_first?(node)
29
+ def_node_matcher :logical_comparison_between_by_min_first?, <<~PATTERN
30
+ (and
31
+ (send
32
+ {$_value :>= $_min | $_min :<= $_value})
33
+ (send
34
+ {$_value :<= $_max | $_max :>= $_value}))
35
+ PATTERN
36
+
37
+ # @!method logical_comparison_between_by_max_first?(node)
38
+ def_node_matcher :logical_comparison_between_by_max_first?, <<~PATTERN
39
+ (and
40
+ (send
41
+ {$_value :<= $_max | $_max :>= $_value})
42
+ (send
43
+ {$_value :>= $_min | $_min :<= $_value}))
44
+ PATTERN
45
+
46
+ def on_and(node)
47
+ logical_comparison_between_by_min_first?(node) do |*args|
48
+ min_and_value, max_and_value = args.each_slice(2).to_a
49
+
50
+ register_offense(node, min_and_value, max_and_value)
51
+ end
52
+
53
+ logical_comparison_between_by_max_first?(node) do |*args|
54
+ max_and_value, min_and_value = args.each_slice(2).to_a
55
+
56
+ register_offense(node, min_and_value, max_and_value)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def register_offense(node, min_and_value, max_and_value)
63
+ value = (min_and_value & max_and_value).first
64
+ min = min_and_value.find { _1 != value }
65
+ max = max_and_value.find { _1 != value }
66
+
67
+ prefer = "#{value.source}.between?(#{min.source}, #{max.source})"
68
+ add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
69
+ corrector.replace(node, prefer)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def define_method?(node)
112
- return false unless node.block_type?
112
+ return false unless node.any_block_type?
113
113
 
114
114
  child = node.child_nodes.first
115
115
  return false unless child.send_type?
@@ -5,8 +5,11 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for endless methods.
7
7
  #
8
- # It can enforce either the use of endless methods definitions
9
- # for single-lined method bodies, or disallow endless methods.
8
+ # It can enforce endless method definitions whenever possible or with single line methods.
9
+ # It can also disallow multiline endless method definitions or all endless definitions.
10
+ #
11
+ # `require_single_line` style enforces endless method definitions for single line methods.
12
+ # `require_always` style enforces endless method definitions for single statement methods.
10
13
  #
11
14
  # Other method definition types are not considered by this cop.
12
15
  #
@@ -15,36 +18,116 @@ module RuboCop
15
18
  # * allow_single_line (default) - only single line endless method definitions are allowed.
16
19
  # * allow_always - all endless method definitions are allowed.
17
20
  # * disallow - all endless method definitions are disallowed.
21
+ # * require_single_line - endless method definitions are required for single line methods.
22
+ # * require_always - all endless method definitions are required.
18
23
  #
19
24
  # NOTE: Incorrect endless method definitions will always be
20
25
  # corrected to a multi-line definition.
21
26
  #
22
27
  # @example EnforcedStyle: allow_single_line (default)
28
+ # # bad, multi-line endless method
29
+ # def my_method = x.foo
30
+ # .bar
31
+ # .baz
32
+ #
23
33
  # # good
24
- # def my_method() = x
34
+ # def my_method
35
+ # x
36
+ # end
25
37
  #
26
- # # bad, multi-line endless method
27
- # def my_method() = x.foo
28
- # .bar
29
- # .baz
38
+ # # good
39
+ # def my_method = x
40
+ #
41
+ # # good
42
+ # def my_method
43
+ # x.foo
44
+ # .bar
45
+ # .baz
46
+ # end
30
47
  #
31
48
  # @example EnforcedStyle: allow_always
32
49
  # # good
33
- # def my_method() = x
50
+ # def my_method
51
+ # x
52
+ # end
34
53
  #
35
54
  # # good
36
- # def my_method() = x.foo
37
- # .bar
38
- # .baz
55
+ # def my_method = x
56
+ #
57
+ # # good
58
+ # def my_method = x.foo
59
+ # .bar
60
+ # .baz
61
+ #
62
+ # # good
63
+ # def my_method
64
+ # x.foo
65
+ # .bar
66
+ # .baz
67
+ # end
39
68
  #
40
69
  # @example EnforcedStyle: disallow
41
70
  # # bad
42
- # def my_method() = x
71
+ # def my_method = x
72
+ #
73
+ # # bad
74
+ # def my_method = x.foo
75
+ # .bar
76
+ # .baz
77
+ #
78
+ # # good
79
+ # def my_method
80
+ # x
81
+ # end
82
+ #
83
+ # # good
84
+ # def my_method
85
+ # x.foo
86
+ # .bar
87
+ # .baz
88
+ # end
89
+ #
90
+ # @example EnforcedStyle: require_single_line
91
+ # # bad
92
+ # def my_method
93
+ # x
94
+ # end
95
+ #
96
+ # # bad
97
+ # def my_method = x.foo
98
+ # .bar
99
+ # .baz
100
+ #
101
+ # # good
102
+ # def my_method = x
103
+ #
104
+ # # good
105
+ # def my_method
106
+ # x.foo
107
+ # .bar
108
+ # .baz
109
+ # end
110
+ #
111
+ # @example EnforcedStyle: require_always
112
+ # # bad
113
+ # def my_method
114
+ # x
115
+ # end
43
116
  #
44
117
  # # bad
45
- # def my_method() = x.foo
46
- # .bar
47
- # .baz
118
+ # def my_method
119
+ # x.foo
120
+ # .bar
121
+ # .baz
122
+ # end
123
+ #
124
+ # # good
125
+ # def my_method = x
126
+ #
127
+ # # good
128
+ # def my_method = x.foo
129
+ # .bar
130
+ # .baz
48
131
  #
49
132
  class EndlessMethod < Base
50
133
  include ConfigurableEnforcedStyle
@@ -57,12 +140,21 @@ module RuboCop
57
140
  CORRECTION_STYLES = %w[multiline single_line].freeze
58
141
  MSG = 'Avoid endless method definitions.'
59
142
  MSG_MULTI_LINE = 'Avoid endless method definitions with multiple lines.'
143
+ MSG_REQUIRE_SINGLE = 'Use endless method definitions for single line methods.'
144
+ MSG_REQUIRE_ALWAYS = 'Use endless method definitions.'
60
145
 
61
146
  def on_def(node)
62
- if style == :disallow
63
- handle_disallow_style(node)
64
- else
147
+ return if node.assignment_method?
148
+
149
+ case style
150
+ when :allow_single_line, :allow_always
65
151
  handle_allow_style(node)
152
+ when :disallow
153
+ handle_disallow_style(node)
154
+ when :require_single_line
155
+ handle_require_single_line_style(node)
156
+ when :require_always
157
+ handle_require_always_style(node)
66
158
  end
67
159
  end
68
160
 
@@ -77,11 +169,64 @@ module RuboCop
77
169
  end
78
170
  end
79
171
 
172
+ def handle_require_single_line_style(node)
173
+ if node.endless? && !node.single_line?
174
+ add_offense(node, message: MSG_MULTI_LINE) do |corrector|
175
+ correct_to_multiline(corrector, node)
176
+ end
177
+ elsif !node.endless? && can_be_made_endless?(node) && node.body.single_line?
178
+ return if too_long_when_made_endless?(node)
179
+
180
+ add_offense(node, message: MSG_REQUIRE_SINGLE) do |corrector|
181
+ corrector.replace(node, endless_replacement(node))
182
+ end
183
+ end
184
+ end
185
+
186
+ def handle_require_always_style(node)
187
+ return if node.endless? || !can_be_made_endless?(node)
188
+ return if too_long_when_made_endless?(node)
189
+
190
+ add_offense(node, message: MSG_REQUIRE_ALWAYS) do |corrector|
191
+ corrector.replace(node, endless_replacement(node))
192
+ end
193
+ end
194
+
80
195
  def handle_disallow_style(node)
81
196
  return unless node.endless?
82
197
 
83
198
  add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
84
199
  end
200
+
201
+ def correct_to_multiline(corrector, node)
202
+ replacement = <<~RUBY.strip
203
+ def #{node.method_name}#{arguments(node)}
204
+ #{node.body.source}
205
+ end
206
+ RUBY
207
+
208
+ corrector.replace(node, replacement)
209
+ end
210
+
211
+ def endless_replacement(node)
212
+ <<~RUBY.strip
213
+ def #{node.method_name}#{arguments(node)} = #{node.body.source}
214
+ RUBY
215
+ end
216
+
217
+ def arguments(node, missing = '')
218
+ node.arguments.any? ? node.arguments.source : missing
219
+ end
220
+
221
+ def can_be_made_endless?(node)
222
+ node.body && !node.body.begin_type? && !node.body.kwbegin_type?
223
+ end
224
+
225
+ def too_long_when_made_endless?(node)
226
+ return false unless config.cop_enabled?('Layout/LineLength')
227
+
228
+ endless_replacement(node).length > config.for_cop('Layout/LineLength')['Max']
229
+ end
85
230
  end
86
231
  end
87
232
  end
@@ -137,11 +137,11 @@ module RuboCop
137
137
 
138
138
  case depth(stripped_current_path)
139
139
  when 0
140
- range = arguments_range(current_path)
140
+ range = arguments_range(current_path.parent)
141
141
 
142
142
  corrector.replace(range, '__FILE__')
143
143
  when 1
144
- range = arguments_range(current_path)
144
+ range = arguments_range(current_path.parent)
145
145
 
146
146
  corrector.replace(range, '__dir__')
147
147
  else
@@ -185,11 +185,6 @@ module RuboCop
185
185
  corrector.remove(node.loc.dot)
186
186
  corrector.remove(node.loc.selector)
187
187
  end
188
-
189
- def arguments_range(node)
190
- range_between(node.parent.first_argument.source_range.begin_pos,
191
- node.parent.last_argument.source_range.end_pos)
192
- end
193
188
  end
194
189
  end
195
190
  end
@@ -60,8 +60,8 @@ module RuboCop
60
60
  class ExponentialNotation < Base
61
61
  include ConfigurableEnforcedStyle
62
62
  MESSAGES = {
63
- scientific: 'Use a mantissa in [1, 10[.',
64
- engineering: 'Use an exponent divisible by 3 and a mantissa in [0.1, 1000[.',
63
+ scientific: 'Use a mantissa >= 1 and < 10.',
64
+ engineering: 'Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000.',
65
65
  integral: 'Use an integer as mantissa, without trailing zero.'
66
66
  }.freeze
67
67
 
@@ -3,16 +3,24 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Use a consistent style for named format string tokens.
6
+ # Use a consistent style for tokens within a format string.
7
7
  #
8
- # NOTE: `unannotated` style cop only works for strings
9
- # which are passed as arguments to those methods:
10
- # `printf`, `sprintf`, `format`, `%`.
11
- # The reason is that _unannotated_ format is very similar
12
- # to encoded URLs or Date/Time formatting strings.
8
+ # By default, all strings are evaluated. In some cases, this may be undesirable,
9
+ # as they could be used as arguments to a method that does not consider
10
+ # them to be tokens, but rather other identifiers or just part of the string.
13
11
  #
14
- # This cop's allowed methods can be customized with `AllowedMethods`.
15
- # By default, there are no allowed methods.
12
+ # `AllowedMethods` or `AllowedPatterns` can be configured with in order to mark specific
13
+ # methods as always allowed, thereby avoiding an offense from the cop. By default, there
14
+ # are no allowed methods.
15
+ #
16
+ # Additionally, the cop can be made conservative by configuring it with
17
+ # `Mode: conservative` (default `aggressive`). In this mode, tokens (regardless
18
+ # of `EnforcedStyle`) are only considered if used in the format string argument to the
19
+ # methods `printf`, `sprintf`, `format` and `%`.
20
+ #
21
+ # NOTE: Tokens in the `unannotated` style (eg. `%s`) are always treated as if
22
+ # configured with `Conservative: true`. This is done in order to prevent false positives,
23
+ # because this format is very similar to encoded URLs or Date/Time formatting strings.
16
24
  #
17
25
  # @example EnforcedStyle: annotated (default)
18
26
  #
@@ -82,6 +90,20 @@ module RuboCop
82
90
  # # good
83
91
  # redirect('foo/%{bar_id}')
84
92
  #
93
+ # @example Mode: conservative, EnforcedStyle: annotated
94
+ # # In `conservative` mode, offenses are only registered for strings
95
+ # # given to a known formatting method.
96
+ #
97
+ # # good
98
+ # "%{greeting}"
99
+ # foo("%{greeting}")
100
+ #
101
+ # # bad
102
+ # format("%{greeting}", greeting: 'Hello')
103
+ # printf("%{greeting}", greeting: 'Hello')
104
+ # sprintf("%{greeting}", greeting: 'Hello')
105
+ # "%{greeting}" % { greeting: 'Hello' }
106
+ #
85
107
  class FormatStringToken < Base
86
108
  include ConfigurableEnforcedStyle
87
109
  include AllowedMethods
@@ -153,8 +175,9 @@ module RuboCop
153
175
  corrector.replace(token_range, correction)
154
176
  end
155
177
 
156
- def unannotated_format?(node, detected_style)
157
- detected_style == :unannotated && !format_string_in_typical_context?(node)
178
+ def allowed_string?(node, detected_style)
179
+ (detected_style == :unannotated || conservative?) &&
180
+ !format_string_in_typical_context?(node)
158
181
  end
159
182
 
160
183
  def message(detected_style)
@@ -203,7 +226,7 @@ module RuboCop
203
226
  def collect_detections(node)
204
227
  detections = []
205
228
  tokens(node) do |detected_sequence, token_range|
206
- unless unannotated_format?(node, detected_sequence.style)
229
+ unless allowed_string?(node, detected_sequence.style)
207
230
  detections << [detected_sequence, token_range]
208
231
  end
209
232
  end
@@ -222,6 +245,10 @@ module RuboCop
222
245
  def max_unannotated_placeholders_allowed
223
246
  cop_config['MaxUnannotatedPlaceholdersAllowed']
224
247
  end
248
+
249
+ def conservative?
250
+ cop_config.fetch('Mode', :aggressive).to_sym == :conservative
251
+ end
225
252
  end
226
253
  end
227
254
  end
@@ -164,8 +164,8 @@ module RuboCop
164
164
 
165
165
  def too_long_due_to_comment_after_modifier?(node, comment)
166
166
  source_length = processed_source.lines[node.first_line - 1].length
167
- source_length >= max_line_length &&
168
- source_length - comment.source_range.length <= max_line_length
167
+
168
+ max_line_length.between?(source_length - comment.source_range.length, source_length)
169
169
  end
170
170
 
171
171
  def allowed_patterns
@@ -46,6 +46,7 @@ module RuboCop
46
46
 
47
47
  MSG = 'Use `%<inverse>s` instead of inverting `%<method>s`.'
48
48
  CLASS_COMPARISON_METHODS = %i[<= >= < >].freeze
49
+ SAFE_NAVIGATION_INCOMPATIBLE_METHODS = (CLASS_COMPARISON_METHODS + %i[any? none?]).freeze
49
50
  EQUALITY_METHODS = %i[== != =~ !~ <= >= < >].freeze
50
51
  NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
51
52
  CAMEL_CASE = /[A-Z]+[a-z]+/.freeze
@@ -77,7 +78,7 @@ module RuboCop
77
78
  def on_send(node)
78
79
  inverse_candidate?(node) do |method_call, lhs, method, rhs|
79
80
  return unless inverse_methods.key?(method)
80
- return if negated?(node) || relational_comparison_with_safe_navigation?(method_call)
81
+ return if negated?(node) || safe_navigation_incompatible?(method_call)
81
82
  return if part_of_ignored_node?(node)
82
83
  return if possible_class_hierarchy_check?(lhs, rhs, method)
83
84
 
@@ -154,10 +155,6 @@ module RuboCop
154
155
  node.parent.respond_to?(:method?) && node.parent.method?(:!)
155
156
  end
156
157
 
157
- def relational_comparison_with_safe_navigation?(node)
158
- node.csend_type? && CLASS_COMPARISON_METHODS.include?(node.method_name)
159
- end
160
-
161
158
  def not_to_receiver(node, method_call)
162
159
  node.loc.selector.begin.join(method_call.source_range.begin)
163
160
  end
@@ -166,6 +163,12 @@ module RuboCop
166
163
  method_call.source_range.end.join(node.source_range.end)
167
164
  end
168
165
 
166
+ def safe_navigation_incompatible?(node)
167
+ return false unless node.csend_type?
168
+
169
+ SAFE_NAVIGATION_INCOMPATIBLE_METHODS.include?(node.method_name)
170
+ end
171
+
169
172
  # When comparing classes, `!(Integer < Numeric)` is not the same as
170
173
  # `Integer > Numeric`.
171
174
  def possible_class_hierarchy_check?(lhs, rhs, method)
@@ -42,19 +42,25 @@ module RuboCop
42
42
  return if kwarg_nodes.empty?
43
43
 
44
44
  add_offense(node) do |corrector|
45
- if node.parent.find(&:kwoptarg_type?) == node
46
- corrector.insert_before(node, "#{kwarg_nodes.map(&:source).join(', ')}, ")
45
+ defining_node = node.each_ancestor(:def, :defs, :block).first
46
+ next if processed_source.contains_comment?(arguments_range(defining_node))
47
+ next unless node.parent.find(&:kwoptarg_type?) == node
47
48
 
48
- arguments = node.each_ancestor(:def, :defs, :block).first.arguments
49
- append_newline_to_last_kwoptarg(arguments, corrector) unless parentheses?(arguments)
50
-
51
- remove_kwargs(kwarg_nodes, corrector)
52
- end
49
+ autocorrect(corrector, node, defining_node, kwarg_nodes)
53
50
  end
54
51
  end
55
52
 
56
53
  private
57
54
 
55
+ def autocorrect(corrector, node, defining_node, kwarg_nodes)
56
+ corrector.insert_before(node, "#{kwarg_nodes.map(&:source).join(', ')}, ")
57
+
58
+ arguments = defining_node.arguments
59
+ append_newline_to_last_kwoptarg(arguments, corrector) unless parentheses?(arguments)
60
+
61
+ remove_kwargs(kwarg_nodes, corrector)
62
+ end
63
+
58
64
  def append_newline_to_last_kwoptarg(arguments, corrector)
59
65
  last_argument = arguments.last
60
66
  return if last_argument.type?(:kwrestarg, :blockarg)
@@ -36,7 +36,7 @@ module RuboCop
36
36
  include RangeHelp
37
37
  extend AutoCorrector
38
38
 
39
- MSG = 'Use `\\` instead of `+` or `<<` to concatenate those strings.'
39
+ MSG = 'Use `\\` instead of `%<operator>s` to concatenate multiline strings.'
40
40
  CONCAT_TOKEN_TYPES = %i[tPLUS tLSHFT].freeze
41
41
  SIMPLE_STRING_TOKEN_TYPE = :tSTRING
42
42
  COMPLEX_STRING_BEGIN_TOKEN = :tSTRING_BEG
@@ -61,14 +61,20 @@ module RuboCop
61
61
  successor = tokens[index + 2]
62
62
 
63
63
  return unless eligible_token_set?(predecessor, operator, successor)
64
-
65
64
  return if same_line?(operator, successor)
66
65
 
67
66
  next_successor = token_after_last_string(successor, index)
68
-
69
67
  return unless eligible_next_successor?(next_successor)
70
68
 
71
- add_offense(operator.pos) { |corrector| autocorrect(corrector, operator.pos) }
69
+ register_offense(operator)
70
+ end
71
+
72
+ def register_offense(operator)
73
+ message = format(MSG, operator: operator.text)
74
+
75
+ add_offense(operator.pos, message: message) do |corrector|
76
+ autocorrect(corrector, operator.pos)
77
+ end
72
78
  end
73
79
 
74
80
  def autocorrect(corrector, operator_range)
@@ -108,7 +108,7 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def call_in_literals?(node)
111
- parent = node.parent&.block_type? ? node.parent.parent : node.parent
111
+ parent = node.parent&.any_block_type? ? node.parent.parent : node.parent
112
112
  return false unless parent
113
113
 
114
114
  parent.type?(:pair, :array, :range) ||
@@ -117,7 +117,7 @@ module RuboCop
117
117
  end
118
118
 
119
119
  def call_in_logical_operators?(node)
120
- parent = node.parent&.block_type? ? node.parent.parent : node.parent
120
+ parent = node.parent&.any_block_type? ? node.parent.parent : node.parent
121
121
  return false unless parent
122
122
 
123
123
  logical_operator?(parent) ||
@@ -153,7 +153,7 @@ module RuboCop
153
153
  end
154
154
 
155
155
  def call_in_argument_with_block?(node)
156
- parent = node.parent&.block_type? && node.parent.parent
156
+ parent = node.parent&.any_block_type? && node.parent.parent
157
157
  return false unless parent
158
158
 
159
159
  parent.type?(:call, :super, :yield)
@@ -41,7 +41,7 @@ module RuboCop
41
41
  return if ignored_node?(node)
42
42
 
43
43
  return unless (receiver = node.receiver)
44
- return unless receiver.any_block_type? && receiver.loc.end.is?('end')
44
+ return unless receiver.any_block_type? && receiver.keywords?
45
45
 
46
46
  range = range_between(receiver.loc.end.begin_pos, node.source_range.end_pos)
47
47
 
@@ -28,7 +28,7 @@ module RuboCop
28
28
  MSG = 'Avoid multi-line chains of blocks.'
29
29
 
30
30
  def on_block(node)
31
- node.send_node.each_node(:send, :csend) do |send_node|
31
+ node.send_node.each_node(:call) do |send_node|
32
32
  receiver = send_node.receiver
33
33
 
34
34
  next unless receiver&.any_block_type? && receiver.multiline?
@@ -50,7 +50,7 @@ module RuboCop
50
50
  corrector.remove(range_by_whole_lines(arguments.loc.end, include_final_newline: true))
51
51
  end
52
52
 
53
- arguments_range = arguments_range(node)
53
+ arguments_range = range_with_surrounding_space(arguments_range(node), side: :left)
54
54
  # If the method name isn't on the same line as def, move it directly after def
55
55
  if arguments_range.first_line != opening_line(node)
56
56
  corrector.remove(node.loc.name)
@@ -66,14 +66,6 @@ module RuboCop
66
66
  processed_source[arguments.last_line - 1].strip
67
67
  end
68
68
 
69
- def arguments_range(node)
70
- range = range_between(
71
- node.first_argument.source_range.begin_pos, node.last_argument.source_range.end_pos
72
- )
73
-
74
- range_with_surrounding_space(range, side: :left)
75
- end
76
-
77
69
  def opening_line(node)
78
70
  node.first_line
79
71
  end