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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +4 -3
- data/config/default.yml +137 -31
- data/config/obsoletion.yml +4 -0
- data/lib/rubocop.rb +14 -1
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
- data/lib/rubocop/comment_config.rb +6 -6
- data/lib/rubocop/config.rb +5 -2
- data/lib/rubocop/config_loader.rb +7 -14
- data/lib/rubocop/config_store.rb +12 -1
- data/lib/rubocop/cop/base.rb +2 -1
- data/lib/rubocop/cop/exclude_limit.rb +26 -0
- data/lib/rubocop/cop/gemspec/date_assignment.rb +56 -0
- data/lib/rubocop/cop/generator.rb +1 -3
- data/lib/rubocop/cop/internal_affairs.rb +5 -1
- data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
- data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
- data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
- data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
- data/lib/rubocop/cop/layout/class_structure.rb +7 -2
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +38 -18
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
- data/lib/rubocop/cop/layout/line_length.rb +2 -1
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +26 -0
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
- data/lib/rubocop/cop/layout/space_before_brackets.rb +19 -16
- data/lib/rubocop/cop/lint/debugger.rb +58 -14
- data/lib/rubocop/cop/lint/deprecated_constants.rb +80 -0
- data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +13 -4
- data/lib/rubocop/cop/lint/duplicate_require.rb +2 -2
- data/lib/rubocop/cop/lint/else_layout.rb +1 -1
- data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
- data/lib/rubocop/cop/lint/multiple_comparison.rb +4 -4
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
- data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
- data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
- data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +50 -0
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +5 -3
- data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
- data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
- data/lib/rubocop/cop/message_annotator.rb +4 -1
- data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
- data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
- data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
- data/lib/rubocop/cop/mixin/code_length.rb +3 -1
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -11
- data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
- data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
- data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
- data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
- data/lib/rubocop/cop/mixin/uncommunicative_name.rb +5 -1
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
- data/lib/rubocop/cop/naming/variable_name.rb +2 -0
- data/lib/rubocop/cop/naming/variable_number.rb +2 -9
- data/lib/rubocop/cop/registry.rb +1 -1
- data/lib/rubocop/cop/severity.rb +3 -3
- data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
- data/lib/rubocop/cop/style/constant_visibility.rb +27 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
- data/lib/rubocop/cop/style/double_negation.rb +2 -2
- data/lib/rubocop/cop/style/empty_literal.rb +6 -2
- data/lib/rubocop/cop/style/endless_method.rb +102 -0
- data/lib/rubocop/cop/style/eval_with_location.rb +138 -49
- data/lib/rubocop/cop/style/explicit_block_argument.rb +11 -1
- data/lib/rubocop/cop/style/exponential_notation.rb +6 -7
- data/lib/rubocop/cop/style/float_division.rb +3 -0
- data/lib/rubocop/cop/style/format_string_token.rb +18 -2
- data/lib/rubocop/cop/style/hash_conversion.rb +81 -0
- data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
- data/lib/rubocop/cop/style/if_inside_else.rb +22 -10
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -0
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
- data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
- data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/raise_args.rb +3 -2
- data/lib/rubocop/cop/style/redundant_return.rb +1 -1
- data/lib/rubocop/cop/style/single_line_methods.rb +32 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +29 -5
- data/lib/rubocop/cop/style/special_global_vars.rb +3 -3
- data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/while_until_modifier.rb +2 -4
- data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
- data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
- data/lib/rubocop/magic_comment.rb +30 -1
- data/lib/rubocop/options.rb +1 -1
- data/lib/rubocop/rspec/expect_offense.rb +5 -2
- data/lib/rubocop/runner.rb +1 -0
- data/lib/rubocop/target_ruby.rb +47 -11
- data/lib/rubocop/version.rb +2 -2
- 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
|
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
|
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
|
data/lib/rubocop/cop/registry.rb
CHANGED
data/lib/rubocop/cop/severity.rb
CHANGED
@@ -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 (
|
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
|
18
|
+
# def foo
|
16
19
|
# end
|
17
20
|
# # rubocop:enable Metrics/AbcSize
|
18
21
|
#
|
19
22
|
# # good
|
20
|
-
# def
|
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 = '
|
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
|
-
|
42
|
+
directive_cops = directive_cops(comment)
|
43
|
+
disallowed_cops = directive_cops - allowed_cops
|
32
44
|
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
42
|
-
|
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
|
9
|
-
# that use boolean as a return value. When using `EnforcedStyle: forbidden`, double
|
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
|
-
|
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
|
7
|
-
#
|
8
|
-
#
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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 :
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
99
|
-
expected =
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
167
|
+
def check_line(node, code)
|
109
168
|
line_node = node.arguments.last
|
110
|
-
|
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(
|
189
|
+
def add_offense_for_same_line(node, line_node)
|
128
190
|
return if special_line_keyword?(line_node)
|
129
191
|
|
130
|
-
|
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(
|
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
|
-
|
141
|
-
|
142
|
-
|
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
|