rubocop 0.54.0 → 0.55.0

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