rubocop 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -3
  3. data/config/default.yml +31 -5
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +4 -0
  6. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  7. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  8. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  9. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  10. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  11. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  12. data/lib/rubocop/cop/lint/empty_block.rb +15 -2
  13. data/lib/rubocop/cop/lint/loop.rb +0 -4
  14. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  15. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  16. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  17. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  18. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +11 -1
  19. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  20. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  21. data/lib/rubocop/cop/naming/variable_number.rb +82 -8
  22. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  23. data/lib/rubocop/cop/style/case_like_if.rb +0 -4
  24. data/lib/rubocop/cop/style/collection_compact.rb +85 -0
  25. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  26. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  27. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  28. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  29. data/lib/rubocop/cop/style/negated_if_else_condition.rb +99 -0
  30. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  31. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  32. data/lib/rubocop/cop/util.rb +4 -0
  33. data/lib/rubocop/ext/regexp_node.rb +10 -5
  34. data/lib/rubocop/ext/regexp_parser.rb +9 -2
  35. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  36. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  37. data/lib/rubocop/options.rb +2 -0
  38. data/lib/rubocop/version.rb +1 -1
  39. metadata +7 -3
@@ -131,10 +131,6 @@ module RuboCop
131
131
  "#{indent(macro)}#{macro.method_name} #{rest_args.map(&:source).join(', ')}"
132
132
  end
133
133
  end
134
-
135
- def indent(node)
136
- ' ' * node.loc.column
137
- end
138
134
  end
139
135
  end
140
136
  end
@@ -227,10 +227,6 @@ module RuboCop
227
227
  range_between(node.parent.loc.keyword.begin_pos, node.loc.expression.end_pos)
228
228
  end
229
229
 
230
- def indent(node)
231
- ' ' * node.loc.column
232
- end
233
-
234
230
  # Named captures work with `=~` (if regexp is on lhs) and with `match` (both sides)
235
231
  def regexp_with_working_captures?(node)
236
232
  case node.type
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for places where custom logic on rejection nils from arrays
7
+ # and hashes can be replaced with `{Array,Hash}#{compact,compact!}`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.reject { |e| e.nil? }
12
+ # array.select { |e| !e.nil? }
13
+ #
14
+ # # good
15
+ # array.compact
16
+ #
17
+ # # bad
18
+ # hash.reject! { |k, v| v.nil? }
19
+ # hash.select! { |k, v| !v.nil? }
20
+ #
21
+ # # good
22
+ # hash.compact!
23
+ #
24
+ class CollectionCompact < Base
25
+ include RangeHelp
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'
29
+
30
+ RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
31
+
32
+ def_node_matcher :reject_method?, <<~PATTERN
33
+ (block
34
+ (send
35
+ _ ${:reject :reject!})
36
+ $(args ...)
37
+ (send
38
+ $(lvar _) :nil?))
39
+ PATTERN
40
+
41
+ def_node_matcher :select_method?, <<~PATTERN
42
+ (block
43
+ (send
44
+ _ ${:select :select!})
45
+ $(args ...)
46
+ (send
47
+ (send
48
+ $(lvar _) :nil?) :!))
49
+ PATTERN
50
+
51
+ def on_send(node)
52
+ block_node = node.parent
53
+ return unless block_node&.block_type?
54
+
55
+ return unless (method_name, args, receiver =
56
+ reject_method?(block_node) || select_method?(block_node))
57
+
58
+ return unless args.last.source == receiver.source
59
+
60
+ range = offense_range(node, block_node)
61
+ good = good_method_name(method_name)
62
+ message = format(MSG, good: good, bad: range.source)
63
+
64
+ add_offense(range, message: message) do |corrector|
65
+ corrector.replace(range, good)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def good_method_name(method_name)
72
+ if method_name.to_s.end_with?('!')
73
+ 'compact!'
74
+ else
75
+ 'compact'
76
+ end
77
+ end
78
+
79
+ def offense_range(send_node, block_node)
80
+ range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -34,6 +34,7 @@ module RuboCop
34
34
  # this is rarely a problem in practice.
35
35
  class DoubleNegation < Base
36
36
  include ConfigurableEnforcedStyle
37
+ extend AutoCorrector
37
38
 
38
39
  MSG = 'Avoid the use of double negation (`!!`).'
39
40
  RESTRICT_ON_SEND = %i[!].freeze
@@ -44,7 +45,11 @@ module RuboCop
44
45
  return unless double_negative?(node) && node.prefix_bang?
45
46
  return if style == :allowed_in_returns && allowed_in_returns?(node)
46
47
 
47
- add_offense(node.loc.selector)
48
+ location = node.loc.selector
49
+ add_offense(location) do |corrector|
50
+ corrector.remove(location)
51
+ corrector.insert_after(node, '.nil?')
52
+ end
48
53
  end
49
54
 
50
55
  private
@@ -97,10 +97,10 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def no_mixed_keys_check(pairs)
100
- if !sym_indices?(pairs)
101
- check(pairs, ':', MSG_NO_MIXED_KEYS)
102
- else
100
+ if sym_indices?(pairs)
103
101
  check(pairs, pairs.first.inverse_delimiter, MSG_NO_MIXED_KEYS)
102
+ else
103
+ check(pairs, ':', MSG_NO_MIXED_KEYS)
104
104
  end
105
105
  end
106
106
 
@@ -34,6 +34,10 @@ module RuboCop
34
34
  add_offense(node) do |corrector|
35
35
  if node.parent.find(&:kwoptarg_type?) == node
36
36
  corrector.insert_before(node, "#{kwarg_nodes.map(&:source).join(', ')}, ")
37
+
38
+ arguments = node.each_ancestor(:def, :defs).first.arguments
39
+ append_newline_to_last_kwoptarg(arguments, corrector) unless parentheses?(arguments)
40
+
37
41
  remove_kwargs(kwarg_nodes, corrector)
38
42
  end
39
43
  end
@@ -41,6 +45,14 @@ module RuboCop
41
45
 
42
46
  private
43
47
 
48
+ def append_newline_to_last_kwoptarg(arguments, corrector)
49
+ last_argument = arguments.last
50
+ return if last_argument.kwrestarg_type? || last_argument.blockarg_type?
51
+
52
+ last_kwoptarg = arguments.reverse.find(&:kwoptarg_type?)
53
+ corrector.insert_after(last_kwoptarg, "\n")
54
+ end
55
+
44
56
  def remove_kwargs(kwarg_nodes, corrector)
45
57
  kwarg_nodes.each do |kwarg|
46
58
  with_space = range_with_surrounding_space(range: kwarg.source_range)
@@ -135,10 +135,6 @@ module RuboCop
135
135
 
136
136
  "#{node.method_name} #{mixin_names.join(', ')}"
137
137
  end
138
-
139
- def indent(node)
140
- ' ' * node.loc.column
141
- end
142
138
  end
143
139
  end
144
140
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for uses of `if-else` and ternary operators with a negated condition
7
+ # which can be simplified by inverting condition and swapping branches.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # if !x
12
+ # do_something
13
+ # else
14
+ # do_something_else
15
+ # end
16
+ #
17
+ # # good
18
+ # if x
19
+ # do_something_else
20
+ # else
21
+ # do_something
22
+ # end
23
+ #
24
+ # # bad
25
+ # !x ? do_something : do_something_else
26
+ #
27
+ # # good
28
+ # x ? do_something_else : do_something
29
+ #
30
+ class NegatedIfElseCondition < Base
31
+ extend AutoCorrector
32
+
33
+ MSG = 'Invert the negated condition and swap the %<type>s branches.'
34
+
35
+ NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
36
+
37
+ def self.autocorrect_incompatible_with
38
+ [Style::InverseMethods, Style::Not]
39
+ end
40
+
41
+ def on_new_investigation
42
+ @corrected_nodes = nil
43
+ end
44
+
45
+ def on_if(node)
46
+ return unless if_else?(node)
47
+
48
+ condition = node.condition
49
+ return unless negated_condition?(condition)
50
+
51
+ type = node.ternary? ? 'ternary' : 'if-else'
52
+ add_offense(node, message: format(MSG, type: type)) do |corrector|
53
+ unless corrected_ancestor?(node)
54
+ correct_negated_condition(corrector, condition)
55
+ swap_branches(corrector, node)
56
+
57
+ @corrected_nodes ||= Set.new.compare_by_identity
58
+ @corrected_nodes.add(node)
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def if_else?(node)
66
+ else_branch = node.else_branch
67
+ !node.elsif? && else_branch && (!else_branch.if_type? || !else_branch.elsif?)
68
+ end
69
+
70
+ def negated_condition?(node)
71
+ node.send_type? &&
72
+ (node.negation_method? || NEGATED_EQUALITY_METHODS.include?(node.method_name))
73
+ end
74
+
75
+ def corrected_ancestor?(node)
76
+ node.each_ancestor(:if).any? { |ancestor| @corrected_nodes&.include?(ancestor) }
77
+ end
78
+
79
+ def correct_negated_condition(corrector, node)
80
+ receiver, method_name, rhs = *node
81
+ replacement =
82
+ if node.negation_method?
83
+ receiver.source
84
+ else
85
+ inverted_method = method_name.to_s.sub('!', '=')
86
+ "#{receiver.source} #{inverted_method} #{rhs.source}"
87
+ end
88
+
89
+ corrector.replace(node, replacement)
90
+ end
91
+
92
+ def swap_branches(corrector, node)
93
+ corrector.replace(node.if_branch, node.else_branch.source)
94
+ corrector.replace(node.else_branch, node.if_branch.source)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -13,25 +13,32 @@ module RuboCop
13
13
  # will also suggest constructing error objects when the exception is
14
14
  # passed multiple arguments.
15
15
  #
16
+ # The exploded style has an `AllowedCompactTypes` configuration
17
+ # option that takes an Array of exception name Strings.
18
+ #
16
19
  # @example EnforcedStyle: exploded (default)
17
20
  # # bad
18
- # raise StandardError.new("message")
21
+ # raise StandardError.new('message')
19
22
  #
20
23
  # # good
21
- # raise StandardError, "message"
22
- # fail "message"
24
+ # raise StandardError, 'message'
25
+ # fail 'message'
23
26
  # raise MyCustomError.new(arg1, arg2, arg3)
24
27
  # raise MyKwArgError.new(key1: val1, key2: val2)
25
28
  #
29
+ # # With `AllowedCompactTypes` set to ['MyWrappedError']
30
+ # raise MyWrappedError.new(obj)
31
+ # raise MyWrappedError.new(obj), 'message'
32
+ #
26
33
  # @example EnforcedStyle: compact
27
34
  # # bad
28
- # raise StandardError, "message"
35
+ # raise StandardError, 'message'
29
36
  # raise RuntimeError, arg1, arg2, arg3
30
37
  #
31
38
  # # good
32
- # raise StandardError.new("message")
39
+ # raise StandardError.new('message')
33
40
  # raise MyCustomError.new(arg1, arg2, arg3)
34
- # fail "message"
41
+ # fail 'message'
35
42
  class RaiseArgs < Base
36
43
  include ConfigurableEnforcedStyle
37
44
  extend AutoCorrector
@@ -102,6 +109,8 @@ module RuboCop
102
109
  return unless first_arg.send_type? && first_arg.method?(:new)
103
110
  return if acceptable_exploded_args?(first_arg.arguments)
104
111
 
112
+ return if allowed_non_exploded_type?(first_arg)
113
+
105
114
  add_offense(node, message: format(EXPLODED_MSG, method: node.method_name)) do |corrector|
106
115
  replacement = correction_compact_to_exploded(node)
107
116
 
@@ -123,6 +132,12 @@ module RuboCop
123
132
  arg.hash_type? || arg.splat_type?
124
133
  end
125
134
 
135
+ def allowed_non_exploded_type?(arg)
136
+ type = arg.receiver.const_name
137
+
138
+ Array(cop_config['AllowedCompactTypes']).include?(type)
139
+ end
140
+
126
141
  def requires_parens?(parent)
127
142
  parent.and_type? || parent.or_type? ||
128
143
  parent.if_type? && parent.ternary?
@@ -82,7 +82,7 @@ module RuboCop
82
82
 
83
83
  def each_escape(node)
84
84
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
85
- yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
85
+ yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
86
86
 
87
87
  if expr.type == :set
88
88
  char_class_depth + (event == :enter ? 1 : -1)
@@ -123,6 +123,10 @@ module RuboCop
123
123
  node1.loc.line == node2.loc.line
124
124
  end
125
125
 
126
+ def indent(node)
127
+ ' ' * node.loc.column
128
+ end
129
+
126
130
  def to_supported_styles(enforced_style)
127
131
  enforced_style
128
132
  .sub(/^Enforced/, 'Supported')
@@ -19,13 +19,18 @@ module RuboCop
19
19
  super
20
20
 
21
21
  str = with_interpolations_blanked
22
- @parsed_tree = begin
23
- Regexp::Parser.parse(str, options: options)
22
+ begin
23
+ @parsed_tree = Regexp::Parser.parse(str, options: options)
24
24
  rescue StandardError
25
- nil
25
+ @parsed_tree = nil
26
+ else
27
+ origin = loc.begin.end
28
+ source = @parsed_tree.to_s
29
+ @parsed_tree.each_expression(true) do |e|
30
+ e.origin = origin
31
+ e.source = source
32
+ end
26
33
  end
27
- origin = loc.begin.end
28
- @parsed_tree&.each_expression(true) { |e| e.origin = origin }
29
34
  end
30
35
 
31
36
  def each_capture(named: ANY)
@@ -20,11 +20,18 @@ module RuboCop
20
20
  module Expression
21
21
  # Add `expression` and `loc` to all `regexp_parser` nodes
22
22
  module Base
23
- attr_accessor :origin
23
+ attr_accessor :origin, :source
24
+
25
+ def start_index
26
+ # ts is a byte index; convert it to a character index
27
+ @start_index ||= source.byteslice(0, ts).length
28
+ end
24
29
 
25
30
  # Shortcut to `loc.expression`
26
31
  def expression
27
- @expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length)
32
+ @expression ||= begin
33
+ origin.adjust(begin_pos: start_index, end_pos: start_index + full_length)
34
+ end
28
35
  end
29
36
 
30
37
  # @returns a location map like `parser` does, with:
@@ -14,6 +14,7 @@ module RuboCop
14
14
  '[e]macs' => EmacsStyleFormatter,
15
15
  '[fi]les' => FileListFormatter,
16
16
  '[fu]ubar' => FuubarStyleFormatter,
17
+ '[g]ithub' => GitHubActionsFormatter,
17
18
  '[h]tml' => HTMLFormatter,
18
19
  '[j]son' => JSONFormatter,
19
20
  '[ju]nit' => JUnitFormatter,
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Formatter
5
+ # This formatter formats report data as GitHub Workflow commands resulting
6
+ # in GitHub check annotations when run within GitHub Actions.
7
+ class GitHubActionsFormatter < BaseFormatter
8
+ ESCAPE_MAP = {
9
+ '%' => '%25',
10
+ "\n" => '%0A',
11
+ "\r" => '%0D'
12
+ }.freeze
13
+
14
+ def file_finished(file, offenses)
15
+ offenses.each { |offense| report_offense(file, offense) }
16
+ end
17
+
18
+ private
19
+
20
+ def github_escape(string)
21
+ string.gsub(Regexp.union(ESCAPE_MAP.keys), ESCAPE_MAP)
22
+ end
23
+
24
+ def minimum_severity_to_fail
25
+ @minimum_severity_to_fail ||= begin
26
+ name = options.fetch(:fail_level, :refactor)
27
+ RuboCop::Cop::Severity.new(name)
28
+ end
29
+ end
30
+
31
+ def github_severity(offense)
32
+ offense.severity < minimum_severity_to_fail ? 'warning' : 'error'
33
+ end
34
+
35
+ def report_offense(file, offense)
36
+ output.printf(
37
+ "\n::%<severity>s file=%<file>s,line=%<line>d,col=%<column>d::%<message>s\n",
38
+ severity: github_severity(offense),
39
+ file: file,
40
+ line: offense.line,
41
+ column: offense.real_column,
42
+ message: github_escape(offense.message)
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end