rubocop 1.7.0 → 1.10.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +4 -3
  4. data/config/default.yml +137 -31
  5. data/config/obsoletion.yml +4 -0
  6. data/lib/rubocop.rb +14 -1
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  8. data/lib/rubocop/comment_config.rb +6 -6
  9. data/lib/rubocop/config.rb +5 -2
  10. data/lib/rubocop/config_loader.rb +7 -14
  11. data/lib/rubocop/config_store.rb +12 -1
  12. data/lib/rubocop/cop/base.rb +2 -1
  13. data/lib/rubocop/cop/exclude_limit.rb +26 -0
  14. data/lib/rubocop/cop/gemspec/date_assignment.rb +56 -0
  15. data/lib/rubocop/cop/generator.rb +1 -3
  16. data/lib/rubocop/cop/internal_affairs.rb +5 -1
  17. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  18. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  19. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  20. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  21. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  22. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +38 -18
  23. data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
  24. data/lib/rubocop/cop/layout/line_length.rb +2 -1
  25. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +26 -0
  26. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
  27. data/lib/rubocop/cop/layout/space_before_brackets.rb +19 -16
  28. data/lib/rubocop/cop/lint/debugger.rb +58 -14
  29. data/lib/rubocop/cop/lint/deprecated_constants.rb +80 -0
  30. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +13 -4
  31. data/lib/rubocop/cop/lint/duplicate_require.rb +2 -2
  32. data/lib/rubocop/cop/lint/else_layout.rb +1 -1
  33. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
  34. data/lib/rubocop/cop/lint/multiple_comparison.rb +4 -4
  35. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
  36. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  37. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  38. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  39. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
  40. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +50 -0
  41. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +5 -3
  42. data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
  43. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  44. data/lib/rubocop/cop/message_annotator.rb +4 -1
  45. data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
  46. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  47. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
  48. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  49. data/lib/rubocop/cop/mixin/code_length.rb +3 -1
  50. data/lib/rubocop/cop/mixin/comments_help.rb +1 -11
  51. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
  52. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  53. data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
  54. data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
  55. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +5 -1
  56. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  57. data/lib/rubocop/cop/naming/variable_name.rb +2 -0
  58. data/lib/rubocop/cop/naming/variable_number.rb +2 -9
  59. data/lib/rubocop/cop/registry.rb +1 -1
  60. data/lib/rubocop/cop/severity.rb +3 -3
  61. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  62. data/lib/rubocop/cop/style/constant_visibility.rb +27 -0
  63. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  64. data/lib/rubocop/cop/style/double_negation.rb +2 -2
  65. data/lib/rubocop/cop/style/empty_literal.rb +6 -2
  66. data/lib/rubocop/cop/style/endless_method.rb +102 -0
  67. data/lib/rubocop/cop/style/eval_with_location.rb +138 -49
  68. data/lib/rubocop/cop/style/explicit_block_argument.rb +11 -1
  69. data/lib/rubocop/cop/style/exponential_notation.rb +6 -7
  70. data/lib/rubocop/cop/style/float_division.rb +3 -0
  71. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  72. data/lib/rubocop/cop/style/hash_conversion.rb +81 -0
  73. data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
  74. data/lib/rubocop/cop/style/if_inside_else.rb +22 -10
  75. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
  76. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -0
  77. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
  78. data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
  79. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  80. data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
  81. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  82. data/lib/rubocop/cop/style/raise_args.rb +3 -2
  83. data/lib/rubocop/cop/style/redundant_return.rb +1 -1
  84. data/lib/rubocop/cop/style/single_line_methods.rb +32 -2
  85. data/lib/rubocop/cop/style/sole_nested_conditional.rb +29 -5
  86. data/lib/rubocop/cop/style/special_global_vars.rb +3 -3
  87. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  88. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  89. data/lib/rubocop/cop/style/while_until_modifier.rb +2 -4
  90. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  91. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  92. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
  93. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  94. data/lib/rubocop/magic_comment.rb +30 -1
  95. data/lib/rubocop/options.rb +1 -1
  96. data/lib/rubocop/rspec/expect_offense.rb +5 -2
  97. data/lib/rubocop/runner.rb +1 -0
  98. data/lib/rubocop/target_ruby.rb +47 -11
  99. data/lib/rubocop/version.rb +2 -2
  100. metadata +24 -7
@@ -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?('_')
@@ -20,6 +20,7 @@ module RuboCop
20
20
  # # good
21
21
  # fooBar = 1
22
22
  class VariableName < Base
23
+ include AllowedIdentifiers
23
24
  include ConfigurableNaming
24
25
 
25
26
  MSG = 'Use %<style>s for variable names.'
@@ -27,6 +28,7 @@ module RuboCop
27
28
  def on_lvasgn(node)
28
29
  name, = *node
29
30
  return unless name
31
+ return if allowed_identifier?(name)
30
32
 
31
33
  check_name(node, name, node.loc.name)
32
34
  end
@@ -92,11 +92,12 @@ 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
  #
99
99
  class VariableNumber < Base
100
+ include AllowedIdentifiers
100
101
  include ConfigurableNumbering
101
102
 
102
103
  MSG = 'Use %<style>s for %<identifier_type>s numbers.'
@@ -139,14 +140,6 @@ module RuboCop
139
140
 
140
141
  format(MSG, style: style, identifier_type: identifier_type)
141
142
  end
142
-
143
- def allowed_identifier?(name)
144
- allowed_identifiers.include?(name.to_s.delete('@'))
145
- end
146
-
147
- def allowed_identifiers
148
- cop_config.fetch('AllowedIdentifiers', [])
149
- end
150
143
  end
151
144
  end
152
145
  end
@@ -185,7 +185,7 @@ module RuboCop
185
185
 
186
186
  def sort!
187
187
  clear_enrollment_queue
188
- @registry = Hash[@registry.sort_by { |badge, _| badge.cop_name }]
188
+ @registry = @registry.sort_by { |badge, _| badge.cop_name }.to_h
189
189
 
190
190
  self
191
191
  end
@@ -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
@@ -26,6 +26,24 @@ module RuboCop
26
26
  # public_constant :BAZ
27
27
  # end
28
28
  #
29
+ # @example IgnoreModules: false (default)
30
+ # # bad
31
+ # class Foo
32
+ # MyClass = Struct.new()
33
+ # end
34
+ #
35
+ # # good
36
+ # class Foo
37
+ # MyClass = Struct.new()
38
+ # public_constant :MyClass
39
+ # end
40
+ #
41
+ # @example IgnoreModules: true
42
+ # # good
43
+ # class Foo
44
+ # MyClass = Struct.new()
45
+ # end
46
+ #
29
47
  class ConstantVisibility < Base
30
48
  MSG = 'Explicitly make `%<constant_name>s` public or private using ' \
31
49
  'either `#public_constant` or `#private_constant`.'
@@ -33,6 +51,7 @@ module RuboCop
33
51
  def on_casgn(node)
34
52
  return unless class_or_module_scope?(node)
35
53
  return if visibility_declaration?(node)
54
+ return if ignore_modules? && module?(node)
36
55
 
37
56
  message = message(node)
38
57
  add_offense(node, message: message)
@@ -40,6 +59,14 @@ module RuboCop
40
59
 
41
60
  private
42
61
 
62
+ def ignore_modules?
63
+ cop_config.fetch('IgnoreModules', false)
64
+ end
65
+
66
+ def module?(node)
67
+ node.children.last.class_constructor?
68
+ end
69
+
43
70
  def message(node)
44
71
  _namespace, constant_name, _value = *node
45
72
 
@@ -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
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # This cop checks for uses of double negation (`!!`) to convert something to a boolean value.
7
7
  #
8
- # When using `EnforcedStyle: allowed_in_returns`, allow double nagation in contexts
9
- # that use boolean as a return value. When using `EnforcedStyle: forbidden`, double nagation
8
+ # When using `EnforcedStyle: allowed_in_returns`, allow double negation in contexts
9
+ # that use boolean as a return value. When using `EnforcedStyle: forbidden`, double negation
10
10
  # should be forbidden always.
11
11
  #
12
12
  # @example
@@ -32,8 +32,12 @@ module RuboCop
32
32
  def_node_matcher :str_node, '(send (const {nil? cbase} :String) :new)'
33
33
  def_node_matcher :array_with_block,
34
34
  '(block (send (const {nil? cbase} :Array) :new) args _)'
35
- def_node_matcher :hash_with_block,
36
- '(block (send (const {nil? cbase} :Hash) :new) args _)'
35
+ def_node_matcher :hash_with_block, <<~PATTERN
36
+ {
37
+ (block (send (const {nil? cbase} :Hash) :new) args _)
38
+ (numblock (send (const {nil? cbase} :Hash) :new) ...)
39
+ }
40
+ PATTERN
37
41
 
38
42
  def on_send(node)
39
43
  return unless (message = offense_message(node))
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for endless methods.
7
+ #
8
+ # It can enforce either the use of endless methods definitions
9
+ # for single-lined method bodies, or disallow endless methods.
10
+ #
11
+ # Other method definition types are not considered by this cop.
12
+ #
13
+ # The supported styles are:
14
+ # * allow_single_line (default) - only single line endless method definitions are allowed.
15
+ # * allow_always - all endless method definitions are allowed.
16
+ # * disallow - all endless method definitions are disallowed.
17
+ #
18
+ # NOTE: Incorrect endless method definitions will always be
19
+ # corrected to a multi-line definition.
20
+ #
21
+ # @example EnforcedStyle: allow_single_line (default)
22
+ # # good
23
+ # def my_method() = x
24
+ #
25
+ # # bad, multi-line endless method
26
+ # def my_method() = x.foo
27
+ # .bar
28
+ # .baz
29
+ #
30
+ # @example EnforcedStyle: allow_always
31
+ # # good
32
+ # def my_method() = x
33
+ #
34
+ # # good
35
+ # def my_method() = x.foo
36
+ # .bar
37
+ # .baz
38
+ #
39
+ # @example EnforcedStyle: disallow
40
+ # # bad
41
+ # def my_method; x end
42
+ #
43
+ # # bad
44
+ # def my_method() = x.foo
45
+ # .bar
46
+ # .baz
47
+ #
48
+ class EndlessMethod < Base
49
+ include ConfigurableEnforcedStyle
50
+ extend TargetRubyVersion
51
+ extend AutoCorrector
52
+
53
+ minimum_target_ruby_version 3.0
54
+
55
+ CORRECTION_STYLES = %w[multiline single_line].freeze
56
+ MSG = 'Avoid endless method definitions.'
57
+ MSG_MULTI_LINE = 'Avoid endless method definitions with multiple lines.'
58
+
59
+ def on_def(node)
60
+ if style == :disallow
61
+ handle_disallow_style(node)
62
+ else
63
+ handle_allow_style(node)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def handle_allow_style(node)
70
+ return unless node.endless?
71
+ return if node.single_line? || style == :allow_always
72
+
73
+ add_offense(node, message: MSG_MULTI_LINE) do |corrector|
74
+ correct_to_multiline(corrector, node)
75
+ end
76
+ end
77
+
78
+ def handle_disallow_style(node)
79
+ return unless node.endless?
80
+
81
+ add_offense(node) do |corrector|
82
+ correct_to_multiline(corrector, node)
83
+ end
84
+ end
85
+
86
+ def correct_to_multiline(corrector, node)
87
+ replacement = <<~RUBY.strip
88
+ def #{node.method_name}#{arguments(node)}
89
+ #{node.body.source}
90
+ end
91
+ RUBY
92
+
93
+ corrector.replace(node, replacement)
94
+ end
95
+
96
+ def arguments(node, missing = '')
97
+ node.arguments.any? ? node.arguments.source : missing
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -3,9 +3,19 @@
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.
14
+ #
15
+ # This cop will autocorrect incorrect or missing filename and line number
16
+ # values. However, if `eval` is called without a binding argument, the cop
17
+ # will not attempt to automatically add a binding, or add filename and
18
+ # line values.
9
19
  #
10
20
  # @example
11
21
  # # bad
@@ -31,28 +41,32 @@ module RuboCop
31
41
  # def do_something
32
42
  # end
33
43
  # RUBY
44
+ #
45
+ # This cop works only when a string literal is given as a code string.
46
+ # No offence is reported if a string variable is given as below:
47
+ #
48
+ # @example
49
+ # # not checked
50
+ # code = <<-RUBY
51
+ # def do_something
52
+ # end
53
+ # RUBY
54
+ # eval code
55
+ #
34
56
  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.'
57
+ extend AutoCorrector
58
+
59
+ MSG = 'Pass `__FILE__` and `__LINE__` to `%<method_name>s`.'
60
+ MSG_EVAL = 'Pass a binding, `__FILE__` and `__LINE__` to `eval`.'
61
+ MSG_INCORRECT_FILE = 'Incorrect file for `%<method_name>s`; ' \
62
+ 'use `%<expected>s` instead of `%<actual>s`.'
63
+ MSG_INCORRECT_LINE = 'Incorrect line number for `%<method_name>s`; ' \
64
+ 'use `%<expected>s` instead of `%<actual>s`.'
39
65
 
40
66
  RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
41
67
 
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
- }
68
+ def_node_matcher :valid_eval_receiver?, <<~PATTERN
69
+ { nil? (const {nil? cbase} :Kernel) }
56
70
  PATTERN
57
71
 
58
72
  def_node_matcher :line_with_offset?, <<~PATTERN
@@ -63,17 +77,38 @@ module RuboCop
63
77
  PATTERN
64
78
 
65
79
  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
72
- end
80
+ # Classes should not redefine eval, but in case one does, it shouldn't
81
+ # register an offense. Only `eval` without a receiver and `Kernel.eval`
82
+ # are considered.
83
+ return if node.method?(:eval) && !valid_eval_receiver?(node.receiver)
84
+
85
+ code = node.arguments.first
86
+ return unless code && (code.str_type? || code.dstr_type?)
87
+
88
+ check_location(node, code)
73
89
  end
74
90
 
75
91
  private
76
92
 
93
+ def check_location(node, code)
94
+ file, line = file_and_line(node)
95
+
96
+ if line
97
+ check_file(node, file)
98
+ check_line(node, code)
99
+ elsif file
100
+ check_file(node, file)
101
+ add_offense_for_missing_line(node, code)
102
+ else
103
+ add_offense_for_missing_location(node, code)
104
+ end
105
+ end
106
+
107
+ def register_offense(node, &block)
108
+ msg = node.method?(:eval) ? MSG_EVAL : format(MSG, method_name: node.method_name)
109
+ add_offense(node, message: msg, &block)
110
+ end
111
+
77
112
  def special_file_keyword?(node)
78
113
  node.str_type? &&
79
114
  node.source == '__FILE__'
@@ -84,6 +119,15 @@ module RuboCop
84
119
  node.source == '__LINE__'
85
120
  end
86
121
 
122
+ def file_and_line(node)
123
+ base = node.method?(:eval) ? 2 : 1
124
+ [node.arguments[base], node.arguments[base + 1]]
125
+ end
126
+
127
+ def with_binding?(node)
128
+ node.method?(:eval) ? node.arguments.size >= 2 : true
129
+ end
130
+
87
131
  # FIXME: It's a Style/ConditionalAssignment's false positive.
88
132
  # rubocop:disable Style/ConditionalAssignment
89
133
  def with_lineno?(node)
@@ -95,20 +139,34 @@ module RuboCop
95
139
  end
96
140
  # rubocop:enable Style/ConditionalAssignment
97
141
 
98
- def message_incorrect_line(actual, sign, line_diff)
99
- expected =
100
- if line_diff.zero?
101
- '__LINE__'
102
- else
103
- "__LINE__ #{sign} #{line_diff}"
104
- end
105
- format(MSG_INCORRECT_LINE, actual: actual.source, expected: expected)
142
+ def add_offense_for_incorrect_line(method_name, line_node, sign, line_diff)
143
+ expected = expected_line(sign, line_diff)
144
+ message = format(MSG_INCORRECT_LINE,
145
+ method_name: method_name,
146
+ actual: line_node.source,
147
+ expected: expected)
148
+
149
+ add_offense(line_node.loc.expression, message: message) do |corrector|
150
+ corrector.replace(line_node, expected)
151
+ end
152
+ end
153
+
154
+ def check_file(node, file_node)
155
+ return true if special_file_keyword?(file_node)
156
+
157
+ message = format(MSG_INCORRECT_FILE,
158
+ method_name: node.method_name,
159
+ expected: '__FILE__',
160
+ actual: file_node.source)
161
+
162
+ add_offense(file_node, message: message) do |corrector|
163
+ corrector.replace(file_node, '__FILE__')
164
+ end
106
165
  end
107
166
 
108
- def on_with_lineno(node, code)
167
+ def check_line(node, code)
109
168
  line_node = node.arguments.last
110
- lineno_range = line_node.loc.expression
111
- line_diff = string_first_line(code) - lineno_range.first_line
169
+ line_diff = line_difference(line_node, code)
112
170
  if line_diff.zero?
113
171
  add_offense_for_same_line(node, line_node)
114
172
  else
@@ -116,6 +174,10 @@ module RuboCop
116
174
  end
117
175
  end
118
176
 
177
+ def line_difference(line_node, code)
178
+ string_first_line(code) - line_node.loc.expression.first_line
179
+ end
180
+
119
181
  def string_first_line(str_node)
120
182
  if str_node.heredoc?
121
183
  str_node.loc.heredoc_body.first_line
@@ -124,23 +186,50 @@ module RuboCop
124
186
  end
125
187
  end
126
188
 
127
- def add_offense_for_same_line(_node, line_node)
189
+ def add_offense_for_same_line(node, line_node)
128
190
  return if special_line_keyword?(line_node)
129
191
 
130
- add_offense(
131
- line_node.loc.expression,
132
- message: message_incorrect_line(line_node, nil, 0)
133
- )
192
+ add_offense_for_incorrect_line(node.method_name, line_node, nil, 0)
134
193
  end
135
194
 
136
- def add_offense_for_different_line(_node, line_node, line_diff)
195
+ def add_offense_for_different_line(node, line_node, line_diff)
137
196
  sign = line_diff.positive? ? :+ : :-
138
197
  return if line_with_offset?(line_node, sign, line_diff.abs)
139
198
 
140
- add_offense(
141
- line_node.loc.expression,
142
- message: message_incorrect_line(line_node, sign, line_diff.abs)
143
- )
199
+ add_offense_for_incorrect_line(node.method_name, line_node, sign, line_diff.abs)
200
+ end
201
+
202
+ def expected_line(sign, line_diff)
203
+ if line_diff.zero?
204
+ '__LINE__'
205
+ else
206
+ "__LINE__ #{sign} #{line_diff.abs}"
207
+ end
208
+ end
209
+
210
+ def add_offense_for_missing_line(node, code)
211
+ register_offense(node) do |corrector|
212
+ line_str = missing_line(node, code)
213
+ corrector.insert_after(node.loc.expression.end, ", #{line_str}")
214
+ end
215
+ end
216
+
217
+ def add_offense_for_missing_location(node, code)
218
+ if node.method?(:eval) && !with_binding?(node)
219
+ register_offense(node)
220
+ return
221
+ end
222
+
223
+ register_offense(node) do |corrector|
224
+ line_str = missing_line(node, code)
225
+ corrector.insert_after(node.loc.expression.end, ", __FILE__, #{line_str}")
226
+ end
227
+ end
228
+
229
+ def missing_line(node, code)
230
+ line_diff = line_difference(node.arguments.last, code)
231
+ sign = line_diff.positive? ? :+ : :-
232
+ expected_line(sign, line_diff)
144
233
  end
145
234
  end
146
235
  end