rubocop 0.74.0 → 0.75.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/config/default.yml +27 -3
  4. data/lib/rubocop.rb +6 -1
  5. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +1 -12
  6. data/lib/rubocop/comment_config.rb +3 -2
  7. data/lib/rubocop/config.rb +4 -0
  8. data/lib/rubocop/config_loader.rb +20 -2
  9. data/lib/rubocop/config_loader_resolver.rb +2 -2
  10. data/lib/rubocop/config_obsoletion.rb +12 -0
  11. data/lib/rubocop/cop/autocorrect_logic.rb +2 -2
  12. data/lib/rubocop/cop/cop.rb +4 -3
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +43 -17
  14. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +2 -2
  15. data/lib/rubocop/cop/generator.rb +3 -3
  16. data/lib/rubocop/cop/generator/configuration_injector.rb +9 -4
  17. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  18. data/lib/rubocop/cop/layout/block_alignment.rb +2 -2
  19. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  20. data/lib/rubocop/cop/layout/extra_spacing.rb +0 -6
  21. data/lib/rubocop/cop/layout/indent_assignment.rb +9 -1
  22. data/lib/rubocop/cop/layout/indent_heredoc.rb +1 -1
  23. data/lib/rubocop/cop/layout/multiline_block_layout.rb +24 -2
  24. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +5 -1
  25. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -1
  26. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +7 -0
  27. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +2 -0
  28. data/lib/rubocop/cop/lint/assignment_in_condition.rb +17 -4
  29. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  30. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +10 -36
  31. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  32. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +91 -0
  33. data/lib/rubocop/cop/lint/unneeded_cop_disable_directive.rb +1 -1
  34. data/lib/rubocop/cop/message_annotator.rb +16 -7
  35. data/lib/rubocop/cop/migration/department_name.rb +44 -0
  36. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  37. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  38. data/lib/rubocop/cop/mixin/safe_mode.rb +2 -0
  39. data/lib/rubocop/cop/naming/method_name.rb +12 -1
  40. data/lib/rubocop/cop/naming/variable_name.rb +1 -0
  41. data/lib/rubocop/cop/offense.rb +18 -7
  42. data/lib/rubocop/cop/registry.rb +22 -1
  43. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -0
  44. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  45. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +29 -10
  46. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  47. data/lib/rubocop/cop/style/commented_keyword.rb +8 -2
  48. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -4
  49. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +8 -2
  50. data/lib/rubocop/cop/style/format_string_token.rb +10 -40
  51. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +18 -33
  52. data/lib/rubocop/cop/style/if_unless_modifier.rb +51 -15
  53. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  54. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +5 -5
  55. data/lib/rubocop/cop/style/mixin_usage.rb +11 -1
  56. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  57. data/lib/rubocop/cop/style/nested_modifier.rb +18 -2
  58. data/lib/rubocop/cop/style/or_assignment.rb +6 -1
  59. data/lib/rubocop/cop/style/parentheses_around_condition.rb +14 -0
  60. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -4
  61. data/lib/rubocop/cop/style/redundant_self.rb +18 -1
  62. data/lib/rubocop/cop/style/rescue_modifier.rb +24 -0
  63. data/lib/rubocop/cop/style/safe_navigation.rb +9 -0
  64. data/lib/rubocop/cop/style/single_line_methods.rb +8 -1
  65. data/lib/rubocop/cop/style/ternary_parentheses.rb +19 -0
  66. data/lib/rubocop/cop/utils/format_string.rb +128 -0
  67. data/lib/rubocop/cop/variable_force/variable.rb +15 -2
  68. data/lib/rubocop/core_ext/string.rb +0 -24
  69. data/lib/rubocop/formatter/emacs_style_formatter.rb +8 -5
  70. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  71. data/lib/rubocop/formatter/pacman_formatter.rb +80 -0
  72. data/lib/rubocop/formatter/simple_text_formatter.rb +9 -1
  73. data/lib/rubocop/formatter/tap_formatter.rb +9 -1
  74. data/lib/rubocop/magic_comment.rb +4 -0
  75. data/lib/rubocop/options.rb +2 -1
  76. data/lib/rubocop/runner.rb +14 -8
  77. data/lib/rubocop/version.rb +1 -1
  78. metadata +6 -3
  79. data/lib/rubocop/cop/mixin/ignored_method_patterns.rb +0 -19
@@ -3,11 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop is designed to help upgrade to after Ruby 3.0. It will add the
7
- # comment `# frozen_string_literal: true` to the top of files to
8
- # enable frozen string literals. Frozen string literals may be default
9
- # after Ruby 3.0. The comment will be added below a shebang and encoding
10
- # comment. The frozen string literal comment is only valid in Ruby 2.3+.
6
+ # This cop is designed to help you transition from mutable string literals
7
+ # to frozen string literals.
8
+ # It will add the comment `# frozen_string_literal: true` to the top of
9
+ # files to enable frozen string literals. Frozen string literals may be
10
+ # default in future Ruby. The comment will be added below a shebang and
11
+ # encoding comment. The frozen string literal comment is only valid in
12
+ # Ruby 2.3+.
11
13
  #
12
14
  # @example EnforcedStyle: always (default)
13
15
  # # The `always` style will always add the frozen string literal comment
@@ -121,42 +123,25 @@ module RuboCop
121
123
  end
122
124
 
123
125
  def insert_comment(corrector)
124
- last_special_comment = last_special_comment(processed_source)
125
- if last_special_comment.nil?
126
- corrector.insert_before(correction_range, preceding_comment)
127
- else
128
- corrector.insert_after(correction_range, proceeding_comment)
129
- end
130
- end
126
+ comment = last_special_comment(processed_source)
131
127
 
132
- def preceding_comment
133
- if processed_source.tokens[0].space_before?
134
- "#{FROZEN_STRING_LITERAL_ENABLED}\n"
128
+ if comment
129
+ corrector.insert_after(line_range(comment.line), following_comment)
135
130
  else
136
- "#{FROZEN_STRING_LITERAL_ENABLED}\n\n"
131
+ corrector.insert_before(line_range(1), preceding_comment)
137
132
  end
138
133
  end
139
134
 
140
- def proceeding_comment
141
- last_special_comment = last_special_comment(processed_source)
142
- following_line = processed_source.following_line(last_special_comment)
143
-
144
- if following_line&.empty?
145
- "\n#{FROZEN_STRING_LITERAL_ENABLED}"
146
- else
147
- "\n#{FROZEN_STRING_LITERAL_ENABLED}\n"
148
- end
135
+ def line_range(line)
136
+ processed_source.buffer.line_range(line)
149
137
  end
150
138
 
151
- def correction_range
152
- last_special_comment = last_special_comment(processed_source)
139
+ def preceding_comment
140
+ "#{FROZEN_STRING_LITERAL_ENABLED}\n"
141
+ end
153
142
 
154
- if last_special_comment.nil?
155
- range_with_surrounding_space(range: processed_source.tokens[0],
156
- side: :left)
157
- else
158
- last_special_comment.pos
159
- end
143
+ def following_comment
144
+ "\n#{FROZEN_STRING_LITERAL_ENABLED}"
160
145
  end
161
146
  end
162
147
  end
@@ -3,10 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for if and unless statements that would fit on one line
7
- # if written as a modifier if/unless. The maximum line length is
8
- # configured in the `Metrics/LineLength` cop. The tab size is configured
9
- # in the `IndentationWidth` of the `Layout/Tab` cop.
6
+ # Checks for `if` and `unless` statements that would fit on one line if
7
+ # written as modifier `if`/`unless`. The cop also checks for modifier
8
+ # `if`/`unless` lines that exceed the maximum line length.
9
+ #
10
+ # The maximum line length is configured in the `Metrics/LineLength`
11
+ # cop. The tab size is configured in the `IndentationWidth` of the
12
+ # `Layout/Tab` cop.
10
13
  #
11
14
  # @example
12
15
  # # bad
@@ -18,35 +21,59 @@ module RuboCop
18
21
  # Foo.do_something
19
22
  # end
20
23
  #
24
+ # do_something_in_a_method_with_a_long_name(arg) if long_condition
25
+ #
21
26
  # # good
22
27
  # do_stuff(bar) if condition
23
28
  # Foo.do_something unless qux.empty?
29
+ #
30
+ # if long_condition
31
+ # do_something_in_a_method_with_a_long_name(arg)
32
+ # end
24
33
  class IfUnlessModifier < Cop
25
34
  include StatementModifier
26
35
 
27
- MSG = 'Favor modifier `%<keyword>s` usage when having a single-line ' \
28
- 'body. Another good alternative is the usage of control flow ' \
29
- '`&&`/`||`.'
36
+ MSG_USE_MODIFIER = 'Favor modifier `%<keyword>s` usage when having a ' \
37
+ 'single-line body. Another good alternative is ' \
38
+ 'the usage of control flow `&&`/`||`.'
39
+ MSG_USE_NORMAL =
40
+ 'Modifier form of `%<keyword>s` makes the line too long.'
30
41
 
31
42
  ASSIGNMENT_TYPES = %i[lvasgn casgn cvasgn
32
43
  gvasgn ivasgn masgn].freeze
33
44
 
34
45
  def on_if(node)
35
- return unless eligible_node?(node)
36
- return if named_capture_in_condition?(node)
37
-
38
- add_offense(node, location: :keyword,
39
- message: format(MSG, keyword: node.keyword))
46
+ msg = if eligible_node?(node)
47
+ MSG_USE_MODIFIER unless named_capture_in_condition?(node)
48
+ elsif node.modifier_form? && too_long_single_line?(node)
49
+ MSG_USE_NORMAL
50
+ end
51
+ return unless msg
52
+
53
+ add_offense(node,
54
+ location: :keyword,
55
+ message: format(msg, keyword: node.keyword))
40
56
  end
41
57
 
42
58
  def autocorrect(node)
43
- lambda do |corrector|
44
- corrector.replace(node.source_range, to_modifier_form(node))
45
- end
59
+ replacement = if node.modifier_form?
60
+ to_normal_form(node)
61
+ else
62
+ to_modifier_form(node)
63
+ end
64
+ ->(corrector) { corrector.replace(node.source_range, replacement) }
46
65
  end
47
66
 
48
67
  private
49
68
 
69
+ def too_long_single_line?(node)
70
+ return false unless max_line_length
71
+
72
+ range = node.source_range
73
+ range.first_line == range.last_line &&
74
+ range.last_column > max_line_length
75
+ end
76
+
50
77
  def named_capture_in_condition?(node)
51
78
  node.condition.match_with_lvasgn_type?
52
79
  end
@@ -79,6 +106,15 @@ module RuboCop
79
106
  parenthesize?(node) ? "(#{expression})" : expression
80
107
  end
81
108
 
109
+ def to_normal_form(node)
110
+ indentation = ' ' * node.source_range.column
111
+ <<~RUBY.chomp
112
+ #{node.keyword} #{node.condition.source}
113
+ #{indentation} #{node.body.source}
114
+ #{indentation}end
115
+ RUBY
116
+ end
117
+
82
118
  def first_line_comment(node)
83
119
  comment =
84
120
  processed_source.find_comment { |c| c.loc.line == node.loc.line }
@@ -119,7 +119,7 @@ module RuboCop
119
119
  end
120
120
 
121
121
  def configured_indent
122
- ' ' * config.for_cop('IndentationWidth')['Width']
122
+ ' ' * config.for_cop('Layout/IndentationWidth')['Width']
123
123
  end
124
124
  end
125
125
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  #
10
10
  # In the default style (require_parentheses), macro methods are ignored.
11
11
  # Additional methods can be added to the `IgnoredMethods`
12
- # or `IgnoredMethodPatterns` list. These options are
12
+ # or `IgnoredPatterns` list. These options are
13
13
  # valid only in the default style. Macros can be included by
14
14
  # either setting `IgnoreMacros` to false or adding specific macros to
15
15
  # the `IncludedMacros` list.
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # Precedence of options is all follows:
18
18
  #
19
19
  # 1. `IgnoredMethods`
20
- # 2. `IgnoredMethodPatterns`
20
+ # 2. `IgnoredPatterns`
21
21
  # 3. `IncludedMacros`
22
22
  #
23
23
  # eg. If a method is listed in both
@@ -61,7 +61,7 @@ module RuboCop
61
61
  # # okay with `puts` listed in `IgnoredMethods`
62
62
  # puts 'test'
63
63
  #
64
- # # okay with `^assert` listed in `IgnoredMethodPatterns`
64
+ # # okay with `^assert` listed in `IgnoredPatterns`
65
65
  # assert_equal 'test', x
66
66
  #
67
67
  # # IgnoreMacros: true (default)
@@ -148,7 +148,7 @@ module RuboCop
148
148
  class MethodCallWithArgsParentheses < Cop
149
149
  include ConfigurableEnforcedStyle
150
150
  include IgnoredMethods
151
- include IgnoredMethodPatterns
151
+ include IgnoredPattern
152
152
 
153
153
  TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
154
154
 
@@ -186,7 +186,7 @@ module RuboCop
186
186
 
187
187
  def add_offense_for_require_parentheses(node)
188
188
  return if ignored_method?(node.method_name)
189
- return if ignored_method_pattern?(node.method_name)
189
+ return if matches_ignored_pattern?(node.method_name)
190
190
  return if eligible_for_parentheses_omission?(node)
191
191
  return unless node.arguments? && !node.parenthesized?
192
192
 
@@ -49,6 +49,10 @@ module RuboCop
49
49
  const)
50
50
  PATTERN
51
51
 
52
+ def_node_matcher :wrapped_macro_scope?, <<~PATTERN
53
+ {({sclass class module block} ... ({begin if} ...))}
54
+ PATTERN
55
+
52
56
  def on_send(node)
53
57
  include_statement(node) do |statement|
54
58
  return if node.argument? ||
@@ -62,7 +66,13 @@ module RuboCop
62
66
  private
63
67
 
64
68
  def accepted_include?(node)
65
- node.parent && node.macro?
69
+ node.parent && (node.macro? || ascend_macro_scope?(node.parent))
70
+ end
71
+
72
+ def ascend_macro_scope?(ancestor)
73
+ return true if wrapped_macro_scope?(ancestor)
74
+
75
+ ancestor.parent && ascend_macro_scope?(ancestor.parent)
66
76
  end
67
77
 
68
78
  def belongs_to_class_or_module?(node)
@@ -73,7 +73,7 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def keyword_begin_str(node, node_buf)
76
- indent = config.for_cop('IndentationWidth')['Width'] || 2
76
+ indent = config.for_cop('Layout/IndentationWidth')['Width'] || 2
77
77
  if node_buf.source[node.loc.begin.end_pos] == "\n"
78
78
  'begin'
79
79
  else
@@ -73,12 +73,28 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def right_hand_operand(node, left_hand_keyword)
76
- expr = node.condition.source
77
- expr = "(#{expr})" if requires_parens?(node.condition)
76
+ condition = node.condition
77
+
78
+ expr = if condition.send_type? && !condition.arguments.empty? &&
79
+ !condition.operator_method?
80
+ add_parentheses_to_method_arguments(condition)
81
+ else
82
+ condition.source
83
+ end
84
+ expr = "(#{expr})" if requires_parens?(condition)
78
85
  expr = "!#{expr}" unless left_hand_keyword == node.keyword
79
86
  expr
80
87
  end
81
88
 
89
+ def add_parentheses_to_method_arguments(send_node)
90
+ expr = +''
91
+ expr << "#{send_node.receiver.source}." if send_node.receiver
92
+ expr << send_node.method_name.to_s
93
+ expr << "(#{send_node.arguments.map(&:source).join(', ')})"
94
+
95
+ expr
96
+ end
97
+
82
98
  def requires_parens?(node)
83
99
  node.or_type? ||
84
100
  !(RuboCop::AST::Node::COMPARISON_OPERATORS & node.children).empty?
@@ -81,7 +81,12 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def take_variable_and_default_from_unless(node)
84
- variable, default = *node.if_branch
84
+ if node.if_branch
85
+ variable, default = *node.if_branch
86
+ else
87
+ variable, default = *node.else_branch
88
+ end
89
+
85
90
  [variable, default]
86
91
  end
87
92
  end
@@ -6,6 +6,11 @@ module RuboCop
6
6
  # This cop checks for the presence of superfluous parentheses around the
7
7
  # condition of if/unless/while/until.
8
8
  #
9
+ # `AllowSafeAssignment` option for safe assignment.
10
+ # By safe assignment we mean putting parentheses around
11
+ # an assignment to indicate "I know I'm using an assignment
12
+ # as a condition. It's not a mistake."
13
+ #
9
14
  # @example
10
15
  # # bad
11
16
  # x += 1 while (x < 10)
@@ -23,6 +28,14 @@ module RuboCop
23
28
  # elsif x < 3
24
29
  # end
25
30
  #
31
+ # @example AllowSafeAssignment: true (default)
32
+ # # good
33
+ # foo unless (bar = baz)
34
+ #
35
+ # @example AllowSafeAssignment: false
36
+ # # bad
37
+ # foo unless (bar = baz)
38
+ #
26
39
  # @example AllowInMultilineConditions: false (default)
27
40
  # # bad
28
41
  # if (x > 10 &&
@@ -39,6 +52,7 @@ module RuboCop
39
52
  # if (x > 10 &&
40
53
  # y > 10)
41
54
  # end
55
+ #
42
56
  class ParenthesesAroundCondition < Cop
43
57
  include SafeAssignment
44
58
  include Parentheses
@@ -40,7 +40,7 @@ module RuboCop
40
40
 
41
41
  def parens_allowed?(node)
42
42
  empty_parentheses?(node) ||
43
- hash_literal_as_first_arg?(node) ||
43
+ first_arg_begins_with_hash_literal?(node) ||
44
44
  rescue?(node) ||
45
45
  allowed_expression?(node)
46
46
  end
@@ -76,12 +76,21 @@ module RuboCop
76
76
  node.children.empty?
77
77
  end
78
78
 
79
- def hash_literal_as_first_arg?(node)
80
- # Don't flag `method ({key: value})`
81
- node.children.first.hash_type? && first_argument?(node) &&
79
+ def first_arg_begins_with_hash_literal?(node)
80
+ # Don't flag `method ({key: value})` or `method ({key: value}.method)`
81
+ method_chain_begins_with_hash_literal?(node.children.first) &&
82
+ first_argument?(node) &&
82
83
  !parentheses?(node.parent)
83
84
  end
84
85
 
86
+ def method_chain_begins_with_hash_literal?(node)
87
+ return false if node.nil?
88
+ return true if node.hash_type?
89
+ return false unless node.send_type?
90
+
91
+ method_chain_begins_with_hash_literal?(node.children.first)
92
+ end
93
+
85
94
  def check(begin_node)
86
95
  node = begin_node.children.first
87
96
  if keyword_with_redundant_parentheses?(node)
@@ -83,9 +83,16 @@ module RuboCop
83
83
  on_argument(node)
84
84
  end
85
85
 
86
+ def on_masgn(node)
87
+ lhs, rhs = *node
88
+ lhs.children.each do |child|
89
+ add_lhs_to_local_variables_scopes(rhs, child.to_a.first)
90
+ end
91
+ end
92
+
86
93
  def on_lvasgn(node)
87
94
  lhs, rhs = *node
88
- @local_variables_scopes[rhs] << lhs
95
+ add_lhs_to_local_variables_scopes(rhs, lhs)
89
96
  end
90
97
 
91
98
  def on_send(node)
@@ -148,6 +155,16 @@ module RuboCop
148
155
 
149
156
  @allowed_send_nodes << node
150
157
  end
158
+
159
+ def add_lhs_to_local_variables_scopes(rhs, lhs)
160
+ if rhs&.send_type? && !rhs.arguments.empty?
161
+ rhs.arguments.each do |argument|
162
+ @local_variables_scopes[argument] << lhs
163
+ end
164
+ else
165
+ @local_variables_scopes[rhs] << lhs
166
+ end
167
+ end
151
168
  end
152
169
  end
153
170
  end
@@ -5,16 +5,40 @@ module RuboCop
5
5
  module Style
6
6
  # This cop checks for uses of rescue in its modifier form.
7
7
  #
8
+ # The cop to check `rescue` in its modifier form is added for following
9
+ # reasons:
10
+ #
11
+ # * The syntax of modifier form `rescue` can be misleading because it
12
+ # might led us to believe that `rescue` handles the given exception
13
+ # but it actually rescue all exceptions to return the given rescue
14
+ # block. In this case, value returned by handle_error or
15
+ # SomeException.
16
+ #
17
+ # * Modifier form `rescue` would rescue all the exceptions. It would
18
+ # silently skip all exception or errors and handle the error.
19
+ # Example: If `NoMethodError` is raised, modifier form rescue would
20
+ # handle the exception.
21
+ #
8
22
  # @example
9
23
  # # bad
10
24
  # some_method rescue handle_error
11
25
  #
26
+ # # bad
27
+ # some_method rescue SomeException
28
+ #
12
29
  # # good
13
30
  # begin
14
31
  # some_method
15
32
  # rescue
16
33
  # handle_error
17
34
  # end
35
+ #
36
+ # # good
37
+ # begin
38
+ # some_method
39
+ # rescue SomeException
40
+ # handle_error
41
+ # end
18
42
  class RescueModifier < Cop
19
43
  include Alignment
20
44
  include RescueNode