rubocop 1.1.0 → 1.2.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 (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