rubocop 1.8.1 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +37 -4
  4. data/lib/rubocop.rb +6 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  6. data/lib/rubocop/config.rb +5 -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 +2 -1
  10. data/lib/rubocop/cop/exclude_limit.rb +26 -0
  11. data/lib/rubocop/cop/generator.rb +1 -3
  12. data/lib/rubocop/cop/internal_affairs.rb +5 -1
  13. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  14. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  15. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  16. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  17. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  18. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +37 -17
  19. data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
  20. data/lib/rubocop/cop/layout/line_length.rb +2 -1
  21. data/lib/rubocop/cop/layout/space_before_brackets.rb +9 -4
  22. data/lib/rubocop/cop/lint/deprecated_constants.rb +5 -0
  23. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  24. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  25. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  26. data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
  27. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  28. data/lib/rubocop/cop/message_annotator.rb +4 -1
  29. data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
  30. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  31. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  32. data/lib/rubocop/cop/mixin/code_length.rb +3 -1
  33. data/lib/rubocop/cop/mixin/comments_help.rb +0 -1
  34. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
  35. data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
  36. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  37. data/lib/rubocop/cop/naming/variable_number.rb +1 -1
  38. data/lib/rubocop/cop/severity.rb +3 -3
  39. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  40. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  41. data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
  42. data/lib/rubocop/cop/style/float_division.rb +3 -0
  43. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  44. data/lib/rubocop/cop/style/if_inside_else.rb +14 -7
  45. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
  46. data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
  47. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  48. data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
  49. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  50. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  51. data/lib/rubocop/cop/style/sole_nested_conditional.rb +26 -2
  52. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  53. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  54. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
  55. data/lib/rubocop/magic_comment.rb +30 -1
  56. data/lib/rubocop/options.rb +1 -1
  57. data/lib/rubocop/rspec/expect_offense.rb +5 -2
  58. data/lib/rubocop/runner.rb +1 -0
  59. data/lib/rubocop/version.rb +2 -2
  60. metadata +13 -3
@@ -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 && 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 && (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
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for redundant `if` with boolean literal branches.
7
+ # It checks only conditions to return boolean value (`true` or `false`) for safe detection.
8
+ # The conditions to be checked are comparison methods, predicate methods, and double negative.
9
+ # However, auto-correction is unsafe because there is no guarantee that all predicate methods
10
+ # will return boolean value. Those methods can be allowed with `AllowedMethods` config.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # if foo == bar
15
+ # true
16
+ # else
17
+ # false
18
+ # end
19
+ #
20
+ # # bad
21
+ # foo == bar ? true : false
22
+ #
23
+ # # good
24
+ # foo == bar
25
+ #
26
+ # @example AllowedMethods: ['nonzero?']
27
+ # # good
28
+ # num.nonzero? ? true : false
29
+ #
30
+ class IfWithBooleanLiteralBranches < Base
31
+ include AllowedMethods
32
+ extend AutoCorrector
33
+
34
+ MSG = 'Remove redundant %<keyword>s with boolean literal branches.'
35
+ MSG_FOR_ELSIF = 'Use `else` instead of redundant `elsif` with boolean literal branches.'
36
+
37
+ def_node_matcher :if_with_boolean_literal_branches?, <<~PATTERN
38
+ (if #return_boolean_value? {(true) (false) | (false) (true)})
39
+ PATTERN
40
+ def_node_matcher :double_negative?, '(send (send _ :!) :!)'
41
+
42
+ def on_if(node)
43
+ return unless if_with_boolean_literal_branches?(node)
44
+
45
+ condition = node.condition
46
+ range, keyword = offense_range_with_keyword(node, condition)
47
+
48
+ add_offense(range, message: message(node, keyword)) do |corrector|
49
+ replacement = replacement_condition(node, condition)
50
+
51
+ if node.elsif?
52
+ corrector.insert_before(node, "else\n")
53
+ corrector.replace(node, "#{indent(node.if_branch)}#{replacement}")
54
+ else
55
+ corrector.replace(node, replacement)
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def offense_range_with_keyword(node, condition)
63
+ if node.ternary?
64
+ range = condition.source_range.end.join(node.source_range.end)
65
+
66
+ [range, 'ternary operator']
67
+ else
68
+ keyword = node.loc.keyword
69
+
70
+ [keyword, "`#{keyword.source}`"]
71
+ end
72
+ end
73
+
74
+ def message(node, keyword)
75
+ message_template = node.elsif? ? MSG_FOR_ELSIF : MSG
76
+
77
+ format(message_template, keyword: keyword)
78
+ end
79
+
80
+ def return_boolean_value?(condition)
81
+ if condition.begin_type?
82
+ return_boolean_value?(condition.children.first)
83
+ elsif condition.or_type?
84
+ return_boolean_value?(condition.lhs) && return_boolean_value?(condition.rhs)
85
+ elsif condition.and_type?
86
+ return_boolean_value?(condition.rhs)
87
+ else
88
+ assume_boolean_value?(condition)
89
+ end
90
+ end
91
+
92
+ def assume_boolean_value?(condition)
93
+ return false unless condition.send_type?
94
+ return false if allowed_method?(condition.method_name)
95
+
96
+ condition.comparison_method? || condition.predicate_method? || double_negative?(condition)
97
+ end
98
+
99
+ def replacement_condition(node, condition)
100
+ bang = '!' if opposite_condition?(node)
101
+
102
+ if bang && require_parentheses?(condition)
103
+ "#{bang}(#{condition.source})"
104
+ else
105
+ "#{bang}#{condition.source}"
106
+ end
107
+ end
108
+
109
+ def opposite_condition?(node)
110
+ !node.unless? && node.if_branch.false_type? || node.unless? && node.if_branch.true_type?
111
+ end
112
+
113
+ def require_parentheses?(condition)
114
+ condition.and_type? || condition.or_type? ||
115
+ condition.send_type? && condition.comparison_method?
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end