rubocop 0.54.0 → 0.55.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -1
  3. data/config/default.yml +17 -2
  4. data/config/enabled.yml +13 -0
  5. data/lib/rubocop.rb +4 -0
  6. data/lib/rubocop/ast/node/mixin/binary_operator_node.rb +20 -0
  7. data/lib/rubocop/cli.rb +6 -2
  8. data/lib/rubocop/cop/commissioner.rb +21 -25
  9. data/lib/rubocop/cop/layout/end_of_line.rb +33 -0
  10. data/lib/rubocop/cop/layout/space_inside_parens.rb +64 -5
  11. data/lib/rubocop/cop/layout/trailing_whitespace.rb +20 -0
  12. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +80 -0
  13. data/lib/rubocop/cop/lint/shadowed_argument.rb +3 -0
  14. data/lib/rubocop/cop/lint/void.rb +20 -9
  15. data/lib/rubocop/cop/metrics/block_length.rb +17 -1
  16. data/lib/rubocop/cop/metrics/line_length.rb +2 -3
  17. data/lib/rubocop/cop/mixin/percent_literal.rb +9 -8
  18. data/lib/rubocop/cop/performance/end_with.rb +2 -1
  19. data/lib/rubocop/cop/performance/regexp_match.rb +43 -7
  20. data/lib/rubocop/cop/performance/start_with.rb +2 -1
  21. data/lib/rubocop/cop/performance/unneeded_sort.rb +130 -0
  22. data/lib/rubocop/cop/rails/http_status.rb +19 -16
  23. data/lib/rubocop/cop/rails/inverse_of.rb +29 -22
  24. data/lib/rubocop/cop/rails/read_write_attribute.rb +9 -2
  25. data/lib/rubocop/cop/style/array_join.rb +1 -1
  26. data/lib/rubocop/cop/style/class_vars.rb +5 -4
  27. data/lib/rubocop/cop/style/commented_keyword.rb +2 -3
  28. data/lib/rubocop/cop/style/empty_line_after_guard_clause.rb +39 -8
  29. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +22 -11
  30. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +5 -0
  31. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  32. data/lib/rubocop/cop/style/negated_while.rb +18 -0
  33. data/lib/rubocop/cop/style/nested_ternary_operator.rb +11 -0
  34. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  35. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -0
  36. data/lib/rubocop/cop/style/option_hash.rb +6 -0
  37. data/lib/rubocop/cop/style/single_line_block_params.rb +20 -0
  38. data/lib/rubocop/cop/style/special_global_vars.rb +52 -0
  39. data/lib/rubocop/cop/style/string_literals.rb +1 -1
  40. data/lib/rubocop/cop/style/unpack_first.rb +0 -2
  41. data/lib/rubocop/formatter/auto_gen_config_formatter.rb +16 -0
  42. data/lib/rubocop/formatter/formatter_set.rb +14 -13
  43. data/lib/rubocop/node_pattern.rb +2 -2
  44. data/lib/rubocop/options.rb +1 -0
  45. data/lib/rubocop/version.rb +1 -1
  46. metadata +13 -4
@@ -95,6 +95,9 @@ module RuboCop
95
95
  argument.assignments.reduce(true) do |location_known, assignment|
96
96
  assignment_node = assignment.meta_assignment_node || assignment.node
97
97
 
98
+ # Shorthand assignments always use their arguments
99
+ next false if assignment_node.shorthand_asgn?
100
+
98
101
  node_within_block_or_conditional =
99
102
  node_within_block_or_conditional?(assignment_node.parent,
100
103
  argument.scope.node)
@@ -72,13 +72,20 @@ module RuboCop
72
72
  UNARY_OPERATORS = %i[+@ -@ ~ !].freeze
73
73
  OPERATORS = (BINARY_OPERATORS + UNARY_OPERATORS).freeze
74
74
  VOID_CONTEXT_TYPES = %i[def for block].freeze
75
- NONMUTATING_METHODS = %i[capitalize chomp chop collect compact downcase
75
+ NONMUTATING_METHODS = %i[capitalize chomp chop collect compact
76
+ delete_prefix delete_suffix downcase
76
77
  encode flatten gsub lstrip map next reject
77
78
  reverse rotate rstrip scrub select shuffle
78
79
  slice sort sort_by squeeze strip sub succ
79
80
  swapcase tr tr_s transform_values
80
81
  unicode_normalize uniq upcase].freeze
81
82
 
83
+ def on_block(node)
84
+ return unless node.body && !node.body.begin_type?
85
+ return unless in_void_context?(node.body)
86
+ check_expression(node.body)
87
+ end
88
+
82
89
  def on_begin(node)
83
90
  check_begin(node)
84
91
  end
@@ -90,17 +97,21 @@ module RuboCop
90
97
  expressions = *node
91
98
  expressions = expressions.drop_last(1) unless in_void_context?(node)
92
99
  expressions.each do |expr|
93
- check_void_op(expr)
94
- check_literal(expr)
95
- check_var(expr)
96
- check_self(expr)
97
- check_defined(expr)
98
- if cop_config['CheckForMethodsWithNoSideEffects']
99
- check_nonmutating(expr)
100
- end
100
+ check_expression(expr)
101
101
  end
102
102
  end
103
103
 
104
+ def check_expression(expr)
105
+ check_void_op(expr)
106
+ check_literal(expr)
107
+ check_var(expr)
108
+ check_self(expr)
109
+ check_defined(expr)
110
+ return unless cop_config['CheckForMethodsWithNoSideEffects']
111
+
112
+ check_nonmutating(expr)
113
+ end
114
+
104
115
  def check_void_op(node)
105
116
  return unless node.send_type? && OPERATORS.include?(node.method_name)
106
117
 
@@ -13,12 +13,28 @@ module RuboCop
13
13
  LABEL = 'Block'.freeze
14
14
 
15
15
  def on_block(node)
16
- return if excluded_methods.include?(node.send_node.method_name.to_s)
16
+ return if excluded_method?(node)
17
17
  check_code_length(node)
18
18
  end
19
19
 
20
20
  private
21
21
 
22
+ def excluded_method?(node)
23
+ node_receiver = node.receiver && node.receiver.source.gsub(/\s+/, '')
24
+ node_method = String(node.method_name)
25
+
26
+ excluded_methods.any? do |config|
27
+ receiver, method = config.split('.')
28
+
29
+ unless method
30
+ method = receiver
31
+ receiver = node_receiver
32
+ end
33
+
34
+ method == node_method && receiver == node_receiver
35
+ end
36
+ end
37
+
22
38
  def excluded_methods
23
39
  cop_config['ExcludedMethods'] || []
24
40
  end
@@ -76,11 +76,10 @@ module RuboCop
76
76
 
77
77
  def extract_heredocs(ast)
78
78
  return [] unless ast
79
- ast.each_node.with_object([]) do |node, heredocs|
80
- next unless node.location.is_a?(Parser::Source::Map::Heredoc)
79
+ ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
81
80
  body = node.location.heredoc_body
82
81
  delimiter = node.location.heredoc_end.source.strip
83
- heredocs << [body.first_line...body.last_line, delimiter]
82
+ [body.first_line...body.last_line, delimiter]
84
83
  end
85
84
  end
86
85
 
@@ -61,11 +61,12 @@ module RuboCop
61
61
  def autocorrect_multiline_words(node, escape, delimiters)
62
62
  base_line_number = node.first_line
63
63
  previous_line_number = base_line_number
64
- contents = node.children.map do |word_node|
64
+ contents = node.children.map.with_index do |word_node, index|
65
65
  line_breaks = line_breaks(word_node,
66
66
  node.source,
67
67
  previous_line_number,
68
- base_line_number)
68
+ base_line_number,
69
+ index)
69
70
  previous_line_number = word_node.first_line
70
71
  content = escaped_content(word_node, escape, delimiters)
71
72
  line_breaks + content
@@ -85,14 +86,14 @@ module RuboCop
85
86
  end.join(' ')
86
87
  end
87
88
 
88
- def line_breaks(node, source, previous_line_number, base_line_number)
89
+ def line_breaks(node, source, previous_line_num, base_line_num, node_idx)
89
90
  source_in_lines = source.split("\n")
90
- if node.first_line == previous_line_number
91
- node.first_line == base_line_number ? '' : ' '
91
+ if node.first_line == previous_line_num
92
+ node_idx.zero? && node.first_line == base_line_num ? '' : ' '
92
93
  else
93
- begin_line_number = previous_line_number - base_line_number + 1
94
- end_line_number = node.first_line - base_line_number + 1
95
- lines = source_in_lines[begin_line_number...end_line_number]
94
+ begin_line_num = previous_line_num - base_line_num + 1
95
+ end_line_num = node.first_line - base_line_num + 1
96
+ lines = source_in_lines[begin_line_num...end_line_num]
96
97
  "\n" + lines.join("\n").split(node.source).first
97
98
  end
98
99
  end
@@ -8,6 +8,7 @@ module RuboCop
8
8
  #
9
9
  # @example
10
10
  # # bad
11
+ # 'abc'.match?(/bc\Z/)
11
12
  # 'abc' =~ /bc\Z/
12
13
  # 'abc'.match(/bc\Z/)
13
14
  #
@@ -19,7 +20,7 @@ module RuboCop
19
20
  SINGLE_QUOTE = "'".freeze
20
21
 
21
22
  def_node_matcher :redundant_regex?, <<-PATTERN
22
- {(send $!nil? {:match :=~} (regexp (str $#literal_at_end?) (regopt)))
23
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
23
24
  (send (regexp (str $#literal_at_end?) (regopt)) {:match :=~} $_)}
24
25
  PATTERN
25
26
 
@@ -19,6 +19,13 @@ module RuboCop
19
19
  #
20
20
  # # bad
21
21
  # def foo
22
+ # if x !~ /re/
23
+ # do_something
24
+ # end
25
+ # end
26
+ #
27
+ # # bad
28
+ # def foo
22
29
  # if x.match(/re/)
23
30
  # do_something
24
31
  # end
@@ -40,6 +47,13 @@ module RuboCop
40
47
  #
41
48
  # # good
42
49
  # def foo
50
+ # if !x.match?(/re/)
51
+ # do_something
52
+ # end
53
+ # end
54
+ #
55
+ # # good
56
+ # def foo
43
57
  # if x =~ /re/
44
58
  # do_something(Regexp.last_match)
45
59
  # end
@@ -63,6 +77,9 @@ module RuboCop
63
77
 
64
78
  minimum_target_ruby_version 2.4
65
79
 
80
+ # Constants are included in this list because it is unlikely that
81
+ # someone will store `nil` as a constant and then use it for comparison
82
+ TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
66
83
  MSG =
67
84
  'Use `match?` instead of `%<current>s` when `MatchData` ' \
68
85
  'is not used.'.freeze
@@ -75,7 +92,7 @@ module RuboCop
75
92
  PATTERN
76
93
 
77
94
  def_node_matcher :match_operator?, <<-PATTERN
78
- (send !nil? :=~ !nil?)
95
+ (send !nil? {:=~ :!~} !nil?)
79
96
  PATTERN
80
97
 
81
98
  def_node_matcher :match_threequals?, <<-PATTERN
@@ -128,8 +145,8 @@ module RuboCop
128
145
  if match_method?(node)
129
146
  corrector.replace(node.loc.selector, 'match?')
130
147
  elsif match_operator?(node) || match_threequals?(node)
131
- recv, _, arg = *node
132
- correct_operator(corrector, recv, arg)
148
+ recv, oper, arg = *node
149
+ correct_operator(corrector, recv, arg, oper)
133
150
  elsif match_with_lvasgn?(node)
134
151
  recv, arg = *node
135
152
  correct_operator(corrector, recv, arg)
@@ -214,13 +231,32 @@ module RuboCop
214
231
  ].include?(sym)
215
232
  end
216
233
 
217
- def correct_operator(corrector, recv, arg)
234
+ def correct_operator(corrector, recv, arg, oper = nil)
235
+ op_range = correction_range(recv, arg)
236
+
237
+ if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
238
+ corrector.replace(op_range, '.match?(')
239
+ elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
240
+ corrector.replace(op_range, '.match?(')
241
+ swap_receiver_and_arg(corrector, recv, arg)
242
+ else
243
+ corrector.replace(op_range, '&.match?(')
244
+ end
245
+
246
+ corrector.insert_after(arg.loc.expression, ')')
247
+ corrector.insert_before(recv.loc.expression, '!') if oper == :!~
248
+ end
249
+
250
+ def swap_receiver_and_arg(corrector, recv, arg)
251
+ corrector.replace(recv.loc.expression, arg.source)
252
+ corrector.replace(arg.loc.expression, recv.source)
253
+ end
254
+
255
+ def correction_range(recv, arg)
218
256
  buffer = processed_source.buffer
219
257
  op_begin_pos = recv.loc.expression.end_pos
220
258
  op_end_pos = arg.loc.expression.begin_pos
221
- op_range = Parser::Source::Range.new(buffer, op_begin_pos, op_end_pos)
222
- corrector.replace(op_range, '.match?(')
223
- corrector.insert_after(arg.loc.expression, ')')
259
+ Parser::Source::Range.new(buffer, op_begin_pos, op_end_pos)
224
260
  end
225
261
  end
226
262
  end
@@ -8,6 +8,7 @@ module RuboCop
8
8
  #
9
9
  # @example
10
10
  # # bad
11
+ # 'abc'.match?(/\Aab/)
11
12
  # 'abc' =~ /\Aab/
12
13
  # 'abc'.match(/\Aab/)
13
14
  #
@@ -19,7 +20,7 @@ module RuboCop
19
20
  SINGLE_QUOTE = "'".freeze
20
21
 
21
22
  def_node_matcher :redundant_regex?, <<-PATTERN
22
- {(send $!nil? {:match :=~} (regexp (str $#literal_at_start?) (regopt)))
23
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
23
24
  (send (regexp (str $#literal_at_start?) (regopt)) {:match :=~} $_)}
24
25
  PATTERN
25
26
 
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify instances of sorting and then taking
7
+ # only the first or last element.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # [].sort.first
12
+ # [].sort_by(&:length).last
13
+ #
14
+ # # good
15
+ # [].min
16
+ # [].max_by(&:length)
17
+ class UnneededSort < Cop
18
+ include RangeHelp
19
+
20
+ MSG = 'Use `%<suggestion>s` instead of '\
21
+ '`%<sorter>s...%<accessor_source>s`.'.freeze
22
+
23
+ def_node_matcher :unneeded_sort?, <<-MATCHER
24
+ {
25
+ (send $(send _ $:sort ...) ${:last :first})
26
+ (send $(send _ $:sort ...) ${:[] :at :slice} {(int 0) (int -1)})
27
+
28
+ (send $(send _ $:sort_by _) ${:last :first})
29
+ (send $(send _ $:sort_by _) ${:[] :at :slice} {(int 0) (int -1)})
30
+
31
+ (send (block $(send _ ${:sort_by :sort}) ...) ${:last :first})
32
+ (send
33
+ (block $(send _ ${:sort_by :sort}) ...)
34
+ ${:[] :at :slice} {(int 0) (int -1)}
35
+ )
36
+ }
37
+ MATCHER
38
+
39
+ def on_send(node)
40
+ unneeded_sort?(node) do |sort_node, sorter, accessor|
41
+ range = range_between(
42
+ sort_node.loc.selector.begin_pos,
43
+ node.loc.expression.end_pos
44
+ )
45
+
46
+ add_offense(node,
47
+ location: range,
48
+ message: message(node,
49
+ sorter,
50
+ accessor))
51
+ end
52
+ end
53
+
54
+ def autocorrect(node)
55
+ sort_node, sorter, accessor = unneeded_sort?(node)
56
+
57
+ lambda do |corrector|
58
+ # Remove accessor, e.g. `first` or `[-1]`.
59
+ corrector.remove(
60
+ range_between(
61
+ accessor_start(node),
62
+ node.loc.expression.end_pos
63
+ )
64
+ )
65
+
66
+ # Replace "sort" or "sort_by" with the appropriate min/max method.
67
+ corrector.replace(
68
+ sort_node.loc.selector,
69
+ suggestion(sorter, accessor, arg_value(node))
70
+ )
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def message(node, sorter, accessor)
77
+ accessor_source = range_between(
78
+ node.loc.selector.begin_pos,
79
+ node.loc.expression.end_pos
80
+ ).source
81
+
82
+ format(MSG,
83
+ suggestion: suggestion(sorter,
84
+ accessor,
85
+ arg_value(node)),
86
+ sorter: sorter,
87
+ accessor_source: accessor_source)
88
+ end
89
+
90
+ def suggestion(sorter, accessor, arg)
91
+ base(accessor, arg) + suffix(sorter)
92
+ end
93
+
94
+ def base(accessor, arg)
95
+ if accessor == :first || (arg && arg.zero?)
96
+ 'min'
97
+ elsif accessor == :last || arg == -1
98
+ 'max'
99
+ end
100
+ end
101
+
102
+ def suffix(sorter)
103
+ if sorter == :sort
104
+ ''
105
+ elsif sorter == :sort_by
106
+ '_by'
107
+ end
108
+ end
109
+
110
+ def arg_node(node)
111
+ node.arguments.first
112
+ end
113
+
114
+ def arg_value(node)
115
+ arg_node(node).nil? ? nil : arg_node(node).node_parts.first
116
+ end
117
+
118
+ # This gets the start of the accessor whether it has a dot
119
+ # (e.g. `.first`) or doesn't (e.g. `[0]`)
120
+ def accessor_start(node)
121
+ if node.loc.dot
122
+ node.loc.dot.begin_pos
123
+ else
124
+ node.loc.selector.begin_pos
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Rails
6
6
  # Enforces use of symbolic or numeric value to define HTTP status.
7
7
  #
8
- # @example `EnforcedStyle: symbolic` (default)
8
+ # @example EnforcedStyle: symbolic (default)
9
9
  # # bad
10
10
  # render :foo, status: 200
11
11
  # render json: { foo: 'bar' }, status: 200
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # render plain: 'foo/bar', status: :not_modified
19
19
  # redirect_to root_url, status: :moved_permanently
20
20
  #
21
- # @example `EnforcedStyle: numeric`
21
+ # @example EnforcedStyle: numeric
22
22
  # # bad
23
23
  # render :foo, status: :ok
24
24
  # render json: { foo: 'bar' }, status: :not_found
@@ -43,24 +43,20 @@ module RuboCop
43
43
 
44
44
  def_node_matcher :http_status, <<-PATTERN
45
45
  {
46
- (send nil? {:render :redirect_to}
47
- _
48
- (hash
49
- (pair
50
- (sym :status)
51
- ${int sym})))
52
- (send nil? {:render}
53
- (hash
54
- _
55
- (pair
56
- (sym :status)
57
- ${int sym})))
46
+ (send nil? {:render :redirect_to} _ $hash)
47
+ (send nil? {:render :redirect_to} $hash)
58
48
  }
59
49
  PATTERN
60
50
 
51
+ def_node_matcher :status_pair?, <<-PATTERN
52
+ (pair (sym :status) ${int sym})
53
+ PATTERN
54
+
61
55
  def on_send(node)
62
- http_status(node) do |ast_node|
63
- checker = checker_class.new(ast_node)
56
+ http_status(node) do |hash_node|
57
+ status = status_code(hash_node)
58
+ return unless status
59
+ checker = checker_class.new(status)
64
60
  return unless checker.offensive?
65
61
  add_offense(checker.node, message: checker.message)
66
62
  end
@@ -79,6 +75,13 @@ module RuboCop
79
75
 
80
76
  private
81
77
 
78
+ def status_code(node)
79
+ node.each_pair.each do |pair|
80
+ status_pair?(pair) { |code| return code }
81
+ end
82
+ false
83
+ end
84
+
82
85
  def checker_class
83
86
  case style
84
87
  when :symbolic