rubocop 1.8.1 → 1.9.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +34 -4
  4. data/lib/rubocop.rb +5 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  6. data/lib/rubocop/config.rb +2 -2
  7. data/lib/rubocop/config_loader.rb +7 -14
  8. data/lib/rubocop/config_store.rb +12 -1
  9. data/lib/rubocop/cop/base.rb +1 -1
  10. data/lib/rubocop/cop/generator.rb +1 -3
  11. data/lib/rubocop/cop/internal_affairs.rb +5 -1
  12. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  13. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  14. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  15. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  16. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  17. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  18. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  19. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  20. data/lib/rubocop/cop/lint/symbol_conversion.rb +102 -0
  21. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  22. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  23. data/lib/rubocop/cop/mixin/comments_help.rb +0 -1
  24. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  25. data/lib/rubocop/cop/naming/variable_number.rb +1 -1
  26. data/lib/rubocop/cop/severity.rb +3 -3
  27. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  28. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  29. data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
  30. data/lib/rubocop/cop/style/float_division.rb +3 -0
  31. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  32. data/lib/rubocop/cop/style/if_inside_else.rb +14 -7
  33. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +96 -0
  34. data/lib/rubocop/cop/style/nil_comparison.rb +1 -0
  35. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  36. data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
  37. data/lib/rubocop/cop/style/sole_nested_conditional.rb +26 -2
  38. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  39. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  40. data/lib/rubocop/magic_comment.rb +30 -1
  41. data/lib/rubocop/options.rb +1 -1
  42. data/lib/rubocop/rspec/expect_offense.rb +5 -2
  43. data/lib/rubocop/runner.rb +1 -0
  44. data/lib/rubocop/version.rb +2 -2
  45. metadata +12 -3
@@ -69,6 +69,11 @@ module RuboCop
69
69
  # @api private
70
70
  def extract_first_element_over_column_limit(node, elements, max)
71
71
  line = node.first_line
72
+
73
+ # If the first argument is a hash pair but the method is not parenthesized,
74
+ # the argument cannot be moved to another line because it cause a syntax error.
75
+ elements.shift if node.send_type? && !node.parenthesized? && elements.first.pair_type?
76
+
72
77
  i = 0
73
78
  i += 1 while within_column_limit?(elements[i], max, line)
74
79
  return elements.first if i.zero?
@@ -9,7 +9,6 @@ module RuboCop
9
9
  def source_range_with_comment(node)
10
10
  begin_pos = begin_pos_with_comment(node)
11
11
  end_pos = end_position_for(node)
12
- end_pos += 1 if node.def_type?
13
12
 
14
13
  Parser::Source::Range.new(buffer, begin_pos, end_pos)
15
14
  end
@@ -71,11 +71,7 @@ module RuboCop
71
71
  add_offense(range, message: message) do |corrector|
72
72
  corrector.replace(range, preferred_name)
73
73
 
74
- node.body&.each_descendant(:lvar) do |var|
75
- next unless var.children.first == offending_name
76
-
77
- corrector.replace(var, preferred_name)
78
- end
74
+ correct_node(corrector, node.body, offending_name, preferred_name)
79
75
  end
80
76
  end
81
77
 
@@ -86,6 +82,43 @@ module RuboCop
86
82
  variable.loc.expression
87
83
  end
88
84
 
85
+ def variable_name_matches?(node, name)
86
+ if node.masgn_type?
87
+ node.each_descendant(:lvasgn).any? do |lvasgn_node|
88
+ variable_name_matches?(lvasgn_node, name)
89
+ end
90
+ else
91
+ node.children.first == name
92
+ end
93
+ end
94
+
95
+ def correct_node(corrector, node, offending_name, preferred_name)
96
+ return unless node
97
+
98
+ node.each_node(:lvar, :lvasgn, :masgn) do |child_node|
99
+ next unless variable_name_matches?(child_node, offending_name)
100
+
101
+ corrector.replace(child_node, preferred_name) if child_node.lvar_type?
102
+
103
+ if child_node.masgn_type? || child_node.lvasgn_type?
104
+ correct_reassignment(corrector, child_node, offending_name, preferred_name)
105
+ break
106
+ end
107
+ end
108
+ end
109
+
110
+ # If the exception variable is reassigned, that assignment needs to be corrected.
111
+ # Further `lvar` nodes will not be corrected though since they now refer to a
112
+ # different variable.
113
+ def correct_reassignment(corrector, node, offending_name, preferred_name)
114
+ if node.lvasgn_type?
115
+ correct_node(corrector, node.child_nodes.first, offending_name, preferred_name)
116
+ elsif node.masgn_type?
117
+ # With multiple assign, the assignments are in an array as the last child
118
+ correct_node(corrector, node.children.last, offending_name, preferred_name)
119
+ end
120
+ end
121
+
89
122
  def preferred_name(variable_name)
90
123
  preferred_name = cop_config.fetch('PreferredName', 'e')
91
124
  if variable_name.to_s.start_with?('_')
@@ -92,7 +92,7 @@ module RuboCop
92
92
  # # good
93
93
  # :some_sym_1
94
94
  #
95
- # @example AllowedIdentifier: [capture3]
95
+ # @example AllowedIdentifiers: [capture3]
96
96
  # # good
97
97
  # expect(Open3).to receive(:capture3)
98
98
  #
@@ -6,10 +6,10 @@ module RuboCop
6
6
  class Severity
7
7
  include Comparable
8
8
 
9
- NAMES = %i[refactor convention warning error fatal].freeze
9
+ NAMES = %i[info refactor convention warning error fatal].freeze
10
10
 
11
11
  # @api private
12
- CODE_TABLE = { R: :refactor, C: :convention,
12
+ CODE_TABLE = { I: :info, R: :refactor, C: :convention,
13
13
  W: :warning, E: :error, F: :fatal }.freeze
14
14
 
15
15
  # @api public
@@ -18,7 +18,7 @@ module RuboCop
18
18
  #
19
19
  # @return [Symbol]
20
20
  # severity.
21
- # any of `:refactor`, `:convention`, `:warning`, `:error` or `:fatal`.
21
+ # any of `:info`, `:refactor`, `:convention`, `:warning`, `:error` or `:fatal`.
22
22
  attr_reader :name
23
23
 
24
24
  def self.name_from_code(code)
@@ -7,7 +7,7 @@ module RuboCop
7
7
  module Style
8
8
  # This cop checks for non-ascii (non-English) characters
9
9
  # in comments. You could set an array of allowed non-ascii chars in
10
- # AllowedChars attribute (empty by default).
10
+ # `AllowedChars` attribute (copyright notice "©" by default).
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -9,37 +9,77 @@ module RuboCop
9
9
  # This is useful if want to make sure that every RuboCop error gets fixed
10
10
  # and not quickly disabled with a comment.
11
11
  #
12
+ # Specific cops can be allowed with the `AllowedCops` configuration. Note that
13
+ # if this configuration is set, `rubocop:disable all` is still disallowed.
14
+ #
12
15
  # @example
13
16
  # # bad
14
17
  # # rubocop:disable Metrics/AbcSize
15
- # def f
18
+ # def foo
16
19
  # end
17
20
  # # rubocop:enable Metrics/AbcSize
18
21
  #
19
22
  # # good
20
- # def fixed_method_name_and_no_rubocop_comments
23
+ # def foo
24
+ # end
25
+ #
26
+ # @example AllowedCops: [Metrics/AbcSize]
27
+ # # good
28
+ # # rubocop:disable Metrics/AbcSize
29
+ # def foo
21
30
  # end
31
+ # # rubocop:enable Metrics/AbcSize
22
32
  #
23
33
  class DisableCopsWithinSourceCodeDirective < Base
24
34
  extend AutoCorrector
25
35
 
26
36
  # rubocop:enable Lint/RedundantCopDisableDirective
27
- MSG = 'Comment to disable/enable RuboCop.'
37
+ MSG = 'Rubocop disable/enable directives are not permitted.'
38
+ MSG_FOR_COPS = 'Rubocop disable/enable directives for %<cops>s are not permitted.'
28
39
 
29
40
  def on_new_investigation
30
41
  processed_source.comments.each do |comment|
31
- next unless rubocop_directive_comment?(comment)
42
+ directive_cops = directive_cops(comment)
43
+ disallowed_cops = directive_cops - allowed_cops
32
44
 
33
- add_offense(comment) do |corrector|
34
- corrector.replace(comment, '')
35
- end
45
+ next unless disallowed_cops.any?
46
+
47
+ register_offense(comment, directive_cops, disallowed_cops)
36
48
  end
37
49
  end
38
50
 
39
51
  private
40
52
 
41
- def rubocop_directive_comment?(comment)
42
- CommentConfig::COMMENT_DIRECTIVE_REGEXP.match?(comment.text)
53
+ def register_offense(comment, directive_cops, disallowed_cops)
54
+ message = if any_cops_allowed?
55
+ format(MSG_FOR_COPS, cops: "`#{disallowed_cops.join('`, `')}`")
56
+ else
57
+ MSG
58
+ end
59
+
60
+ add_offense(comment, message: message) do |corrector|
61
+ replacement = ''
62
+
63
+ if directive_cops.length != disallowed_cops.length
64
+ replacement = comment.text.sub(/#{Regexp.union(disallowed_cops)},?\s*/, '')
65
+ .sub(/,\s*$/, '')
66
+ end
67
+
68
+ corrector.replace(comment, replacement)
69
+ end
70
+ end
71
+
72
+ def directive_cops(comment)
73
+ match = CommentConfig::COMMENT_DIRECTIVE_REGEXP.match(comment.text)
74
+ match[2] ? match[2].split(',').map(&:strip) : []
75
+ end
76
+
77
+ def allowed_cops
78
+ Array(cop_config['AllowedCops'])
79
+ end
80
+
81
+ def any_cops_allowed?
82
+ allowed_cops.any?
43
83
  end
44
84
  end
45
85
  end
@@ -3,9 +3,14 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop checks `eval` method usage. `eval` can receive source location
7
- # metadata, that are filename and line number. The metadata is used by
8
- # backtraces. This cop recommends to pass the metadata to `eval` method.
6
+ # This cop ensures that eval methods (`eval`, `instance_eval`, `class_eval`
7
+ # and `module_eval`) are given filename and line number values (`__FILE__`
8
+ # and `__LINE__`). This data is used to ensure that any errors raised
9
+ # within the evaluated code will be given the correct identification
10
+ # in a backtrace.
11
+ #
12
+ # The cop also checks that the line number given relative to `__LINE__` is
13
+ # correct.
9
14
  #
10
15
  # @example
11
16
  # # bad
@@ -32,27 +37,17 @@ module RuboCop
32
37
  # end
33
38
  # RUBY
34
39
  class EvalWithLocation < Base
35
- MSG = 'Pass `__FILE__` and `__LINE__` to `eval` method, ' \
36
- 'as they are used by backtraces.'
37
- MSG_INCORRECT_LINE = 'Use `%<expected>s` instead of `%<actual>s`, ' \
38
- 'as they are used by backtraces.'
40
+ MSG = 'Pass `__FILE__` and `__LINE__` to `%<method_name>s`.'
41
+ MSG_EVAL = 'Pass a binding, `__FILE__` and `__LINE__` to `eval`.'
42
+ MSG_INCORRECT_FILE = 'Incorrect file for `%<method_name>s`; ' \
43
+ 'use `%<expected>s` instead of `%<actual>s`.'
44
+ MSG_INCORRECT_LINE = 'Incorrect line number for `%<method_name>s`; ' \
45
+ 'use `%<expected>s` instead of `%<actual>s`.'
39
46
 
40
47
  RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
41
48
 
42
- def_node_matcher :eval_without_location?, <<~PATTERN
43
- {
44
- (send nil? :eval ${str dstr})
45
- (send nil? :eval ${str dstr} _)
46
- (send nil? :eval ${str dstr} _ #special_file_keyword?)
47
- (send nil? :eval ${str dstr} _ #special_file_keyword? _)
48
-
49
- (send _ {:class_eval :module_eval :instance_eval}
50
- ${str dstr})
51
- (send _ {:class_eval :module_eval :instance_eval}
52
- ${str dstr} #special_file_keyword?)
53
- (send _ {:class_eval :module_eval :instance_eval}
54
- ${str dstr} #special_file_keyword? _)
55
- }
49
+ def_node_matcher :valid_eval_receiver?, <<~PATTERN
50
+ { nil? (const {nil? cbase} :Kernel) }
56
51
  PATTERN
57
52
 
58
53
  def_node_matcher :line_with_offset?, <<~PATTERN
@@ -63,17 +58,31 @@ module RuboCop
63
58
  PATTERN
64
59
 
65
60
  def on_send(node)
66
- eval_without_location?(node) do |code|
67
- if with_lineno?(node)
68
- on_with_lineno(node, code)
69
- else
70
- add_offense(node)
71
- end
61
+ # Classes should not redefine eval, but in case one does, it shouldn't
62
+ # register an offense. Only `eval` without a receiver and `Kernel.eval`
63
+ # are considered.
64
+ return if node.method?(:eval) && !valid_eval_receiver?(node.receiver)
65
+
66
+ code = node.arguments.first
67
+ return unless code.str_type? || code.dstr_type?
68
+
69
+ file, line = file_and_line(node)
70
+
71
+ if line
72
+ check_file(node, file)
73
+ check_line(node, code)
74
+ else
75
+ register_offense(node)
72
76
  end
73
77
  end
74
78
 
75
79
  private
76
80
 
81
+ def register_offense(node)
82
+ msg = node.method?(:eval) ? MSG_EVAL : format(MSG, method_name: node.method_name)
83
+ add_offense(node, message: msg)
84
+ end
85
+
77
86
  def special_file_keyword?(node)
78
87
  node.str_type? &&
79
88
  node.source == '__FILE__'
@@ -84,6 +93,11 @@ module RuboCop
84
93
  node.source == '__LINE__'
85
94
  end
86
95
 
96
+ def file_and_line(node)
97
+ base = node.method?(:eval) ? 2 : 1
98
+ [node.arguments[base], node.arguments[base + 1]]
99
+ end
100
+
87
101
  # FIXME: It's a Style/ConditionalAssignment's false positive.
88
102
  # rubocop:disable Style/ConditionalAssignment
89
103
  def with_lineno?(node)
@@ -95,17 +109,32 @@ module RuboCop
95
109
  end
96
110
  # rubocop:enable Style/ConditionalAssignment
97
111
 
98
- def message_incorrect_line(actual, sign, line_diff)
112
+ def message_incorrect_line(method_name, actual, sign, line_diff)
99
113
  expected =
100
114
  if line_diff.zero?
101
115
  '__LINE__'
102
116
  else
103
117
  "__LINE__ #{sign} #{line_diff}"
104
118
  end
105
- format(MSG_INCORRECT_LINE, actual: actual.source, expected: expected)
119
+
120
+ format(MSG_INCORRECT_LINE,
121
+ method_name: method_name,
122
+ actual: actual.source,
123
+ expected: expected)
124
+ end
125
+
126
+ def check_file(node, file_node)
127
+ return true if special_file_keyword?(file_node)
128
+
129
+ message = format(MSG_INCORRECT_FILE,
130
+ method_name: node.method_name,
131
+ expected: '__FILE__',
132
+ actual: file_node.source)
133
+
134
+ add_offense(file_node, message: message)
106
135
  end
107
136
 
108
- def on_with_lineno(node, code)
137
+ def check_line(node, code)
109
138
  line_node = node.arguments.last
110
139
  lineno_range = line_node.loc.expression
111
140
  line_diff = string_first_line(code) - lineno_range.first_line
@@ -124,22 +153,22 @@ module RuboCop
124
153
  end
125
154
  end
126
155
 
127
- def add_offense_for_same_line(_node, line_node)
156
+ def add_offense_for_same_line(node, line_node)
128
157
  return if special_line_keyword?(line_node)
129
158
 
130
159
  add_offense(
131
160
  line_node.loc.expression,
132
- message: message_incorrect_line(line_node, nil, 0)
161
+ message: message_incorrect_line(node.method_name, line_node, nil, 0)
133
162
  )
134
163
  end
135
164
 
136
- def add_offense_for_different_line(_node, line_node, line_diff)
165
+ def add_offense_for_different_line(node, line_node, line_diff)
137
166
  sign = line_diff.positive? ? :+ : :-
138
167
  return if line_with_offset?(line_node, sign, line_diff.abs)
139
168
 
140
169
  add_offense(
141
170
  line_node.loc.expression,
142
- message: message_incorrect_line(line_node, sign, line_diff.abs)
171
+ message: message_incorrect_line(node.method_name, line_node, sign, line_diff.abs)
143
172
  )
144
173
  end
145
174
  end
@@ -7,6 +7,9 @@ module RuboCop
7
7
  # It is recommended to either always use `fdiv` or coerce one side only.
8
8
  # This cop also provides other options for code consistency.
9
9
  #
10
+ # This cop is marked as unsafe, because if operand variable is a string object
11
+ # then `.to_f` will be removed and an error will occur.
12
+ #
10
13
  # @example EnforcedStyle: single_coerce (default)
11
14
  # # bad
12
15
  # a.to_f / b.to_f
@@ -11,6 +11,8 @@ module RuboCop
11
11
  # The reason is that _unannotated_ format is very similar
12
12
  # to encoded URLs or Date/Time formatting strings.
13
13
  #
14
+ # This cop can be customized ignored methods with `IgnoredMethods`.
15
+ #
14
16
  # @example EnforcedStyle: annotated (default)
15
17
  #
16
18
  # # bad
@@ -58,12 +60,18 @@ module RuboCop
58
60
  #
59
61
  # # good
60
62
  # format('%06d', 10)
63
+ #
64
+ # @example IgnoredMethods: [redirect]
65
+ #
66
+ # # good
67
+ # redirect('foo/%{bar_id}')
68
+ #
61
69
  class FormatStringToken < Base
62
70
  include ConfigurableEnforcedStyle
71
+ include IgnoredMethods
63
72
 
64
73
  def on_str(node)
65
- return unless node.value.include?('%')
66
- return if node.each_ancestor(:xstr, :regexp).any?
74
+ return if format_string_token?(node) || use_ignored_method?(node)
67
75
 
68
76
  detections = collect_detections(node)
69
77
  return if detections.empty?
@@ -88,6 +96,14 @@ module RuboCop
88
96
  }
89
97
  PATTERN
90
98
 
99
+ def format_string_token?(node)
100
+ !node.value.include?('%') || node.each_ancestor(:xstr, :regexp).any?
101
+ end
102
+
103
+ def use_ignored_method?(node)
104
+ (parent = node.parent) && parent.send_type? && ignored_method?(parent.method_name)
105
+ end
106
+
91
107
  def unannotated_format?(node, detected_style)
92
108
  detected_style == :unannotated && !format_string_in_typical_context?(node)
93
109
  end
@@ -86,9 +86,10 @@ module RuboCop
86
86
  correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
87
87
  end
88
88
  corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
89
- corrector.remove(
90
- range_by_whole_lines(node.if_branch.source_range, include_final_newline: true)
91
- )
89
+ return unless (if_branch = node.if_branch)
90
+
91
+ range = range_by_whole_lines(if_branch.source_range, include_final_newline: true)
92
+ corrector.remove(range)
92
93
  end
93
94
 
94
95
  def correct_to_elsif_from_modifier_form(corrector, node)
@@ -101,10 +102,12 @@ module RuboCop
101
102
 
102
103
  def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
103
104
  corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
104
- if_condition_range = range_between(
105
- node.loc.keyword.begin_pos, condition.source_range.end_pos
106
- )
107
- corrector.replace(if_condition_range, node.if_branch.source)
105
+ if_condition_range = if_condition_range(node, condition)
106
+ if (if_branch = node.if_branch)
107
+ corrector.replace(if_condition_range, if_branch.source)
108
+ else
109
+ corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
110
+ end
108
111
  corrector.remove(condition)
109
112
  end
110
113
 
@@ -115,6 +118,10 @@ module RuboCop
115
118
  find_end_range(node.parent)
116
119
  end
117
120
 
121
+ def if_condition_range(node, condition)
122
+ range_between(node.loc.keyword.begin_pos, condition.source_range.end_pos)
123
+ end
124
+
118
125
  def allow_if_modifier_in_else_branch?(else_branch)
119
126
  allow_if_modifier? && else_branch&.modifier_form?
120
127
  end