rubocop 0.78.0 → 0.79.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/README.md +1 -1
- data/config/default.yml +2 -1
- data/lib/rubocop.rb +6 -0
- data/lib/rubocop/ast/builder.rb +43 -42
- data/lib/rubocop/ast/node/def_node.rb +11 -0
- data/lib/rubocop/ast/node/forward_args_node.rb +18 -0
- data/lib/rubocop/ast/traversal.rb +11 -3
- data/lib/rubocop/cli/command/show_cops.rb +11 -4
- data/lib/rubocop/config_loader.rb +19 -19
- data/lib/rubocop/config_validator.rb +49 -91
- data/lib/rubocop/cop/autocorrect_logic.rb +6 -3
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
- data/lib/rubocop/cop/generator.rb +3 -4
- data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
- data/lib/rubocop/cop/layout/hash_alignment.rb +8 -4
- data/lib/rubocop/cop/layout/multiline_block_layout.rb +14 -5
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -2
- data/lib/rubocop/cop/lint/debugger.rb +2 -2
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -1
- data/lib/rubocop/cop/migration/department_name.rb +16 -1
- data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -7
- data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
- data/lib/rubocop/cop/registry.rb +7 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -207
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +168 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +54 -0
- data/lib/rubocop/cop/style/multiline_when_then.rb +5 -1
- data/lib/rubocop/cop/style/numeric_predicate.rb +4 -3
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +7 -7
- data/lib/rubocop/cop/style/yoda_condition.rb +16 -1
- data/lib/rubocop/rspec/shared_contexts.rb +5 -0
- data/lib/rubocop/target_ruby.rb +151 -0
- data/lib/rubocop/version.rb +1 -1
- metadata +8 -4
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
class MethodCallWithArgsParentheses
|
7
|
+
# Style omit_parentheses
|
8
|
+
module OmitParentheses
|
9
|
+
TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
|
10
|
+
|
11
|
+
def on_send(node)
|
12
|
+
return unless node.parenthesized?
|
13
|
+
return if node.implicit_call?
|
14
|
+
return if super_call_without_arguments?(node)
|
15
|
+
return if allowed_camel_case_method_call?(node)
|
16
|
+
return if legitimate_call_with_parentheses?(node)
|
17
|
+
|
18
|
+
add_offense(node, location: node.loc.begin.join(node.loc.end))
|
19
|
+
end
|
20
|
+
alias on_csend on_send
|
21
|
+
alias on_super on_send
|
22
|
+
alias on_yield on_send
|
23
|
+
|
24
|
+
def autocorrect(node)
|
25
|
+
lambda do |corrector|
|
26
|
+
if parentheses_at_the_end_of_multiline_call?(node)
|
27
|
+
corrector.replace(args_begin(node), ' \\')
|
28
|
+
else
|
29
|
+
corrector.replace(args_begin(node), ' ')
|
30
|
+
end
|
31
|
+
corrector.remove(node.loc.end)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def message(_node = nil)
|
36
|
+
'Omit parentheses for method calls with arguments.'
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def super_call_without_arguments?(node)
|
42
|
+
node.super_type? && node.arguments.none?
|
43
|
+
end
|
44
|
+
|
45
|
+
def allowed_camel_case_method_call?(node)
|
46
|
+
node.camel_case_method? &&
|
47
|
+
(node.arguments.none? ||
|
48
|
+
cop_config['AllowParenthesesInCamelCaseMethod'])
|
49
|
+
end
|
50
|
+
|
51
|
+
def parentheses_at_the_end_of_multiline_call?(node)
|
52
|
+
node.multiline? &&
|
53
|
+
node.loc.begin.source_line
|
54
|
+
.gsub(TRAILING_WHITESPACE_REGEX, '')
|
55
|
+
.end_with?('(')
|
56
|
+
end
|
57
|
+
|
58
|
+
def legitimate_call_with_parentheses?(node)
|
59
|
+
call_in_literals?(node) ||
|
60
|
+
call_with_ambiguous_arguments?(node) ||
|
61
|
+
call_in_logical_operators?(node) ||
|
62
|
+
call_in_optional_arguments?(node) ||
|
63
|
+
allowed_multiline_call_with_parentheses?(node) ||
|
64
|
+
allowed_chained_call_with_parentheses?(node)
|
65
|
+
end
|
66
|
+
|
67
|
+
def call_in_literals?(node)
|
68
|
+
node.parent &&
|
69
|
+
(node.parent.pair_type? ||
|
70
|
+
node.parent.array_type? ||
|
71
|
+
node.parent.range_type? ||
|
72
|
+
splat?(node.parent) ||
|
73
|
+
ternary_if?(node.parent))
|
74
|
+
end
|
75
|
+
|
76
|
+
def call_in_logical_operators?(node)
|
77
|
+
node.parent &&
|
78
|
+
(logical_operator?(node.parent) ||
|
79
|
+
node.parent.send_type? &&
|
80
|
+
node.parent.arguments.any?(&method(:logical_operator?)))
|
81
|
+
end
|
82
|
+
|
83
|
+
def call_in_optional_arguments?(node)
|
84
|
+
node.parent &&
|
85
|
+
(node.parent.optarg_type? || node.parent.kwoptarg_type?)
|
86
|
+
end
|
87
|
+
|
88
|
+
def call_with_ambiguous_arguments?(node)
|
89
|
+
call_with_braced_block?(node) ||
|
90
|
+
call_as_argument_or_chain?(node) ||
|
91
|
+
hash_literal_in_arguments?(node) ||
|
92
|
+
node.descendants.any? do |n|
|
93
|
+
ambigious_literal?(n) || logical_operator?(n) ||
|
94
|
+
call_with_braced_block?(n)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def call_with_braced_block?(node)
|
99
|
+
(node.send_type? || node.super_type?) &&
|
100
|
+
node.block_node && node.block_node.braces?
|
101
|
+
end
|
102
|
+
|
103
|
+
def call_as_argument_or_chain?(node)
|
104
|
+
node.parent &&
|
105
|
+
(node.parent.send_type? && !assigned_before?(node.parent, node) ||
|
106
|
+
node.parent.csend_type? || node.parent.super_type?)
|
107
|
+
end
|
108
|
+
|
109
|
+
def hash_literal_in_arguments?(node)
|
110
|
+
node.arguments.any? do |n|
|
111
|
+
hash_literal?(n) ||
|
112
|
+
n.send_type? && node.descendants.any?(&method(:hash_literal?))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def allowed_multiline_call_with_parentheses?(node)
|
117
|
+
cop_config['AllowParenthesesInMultilineCall'] && node.multiline?
|
118
|
+
end
|
119
|
+
|
120
|
+
def allowed_chained_call_with_parentheses?(node)
|
121
|
+
return false unless cop_config['AllowParenthesesInChaining']
|
122
|
+
|
123
|
+
previous = node.descendants.first
|
124
|
+
return false unless previous&.send_type?
|
125
|
+
|
126
|
+
previous.parenthesized? ||
|
127
|
+
allowed_chained_call_with_parentheses?(previous)
|
128
|
+
end
|
129
|
+
|
130
|
+
def ambigious_literal?(node)
|
131
|
+
splat?(node) || ternary_if?(node) || regexp_slash_literal?(node) ||
|
132
|
+
unary_literal?(node)
|
133
|
+
end
|
134
|
+
|
135
|
+
def splat?(node)
|
136
|
+
node.splat_type? || node.kwsplat_type? || node.block_pass_type?
|
137
|
+
end
|
138
|
+
|
139
|
+
def ternary_if?(node)
|
140
|
+
node.if_type? && node.ternary?
|
141
|
+
end
|
142
|
+
|
143
|
+
def logical_operator?(node)
|
144
|
+
(node.and_type? || node.or_type?) && node.logical_operator?
|
145
|
+
end
|
146
|
+
|
147
|
+
def hash_literal?(node)
|
148
|
+
node.hash_type? && node.braces?
|
149
|
+
end
|
150
|
+
|
151
|
+
def regexp_slash_literal?(node)
|
152
|
+
node.regexp_type? && node.loc.begin.source == '/'
|
153
|
+
end
|
154
|
+
|
155
|
+
def unary_literal?(node)
|
156
|
+
node.numeric_type? && node.sign? ||
|
157
|
+
node.parent&.send_type? && node.parent&.unary_operation?
|
158
|
+
end
|
159
|
+
|
160
|
+
def assigned_before?(node, target)
|
161
|
+
node.assignment? &&
|
162
|
+
node.loc.operator.begin < target.loc.begin
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
class MethodCallWithArgsParentheses
|
7
|
+
# Style require_parentheses
|
8
|
+
module RequireParentheses
|
9
|
+
def on_send(node)
|
10
|
+
return if ignored_method?(node.method_name)
|
11
|
+
return if matches_ignored_pattern?(node.method_name)
|
12
|
+
return if eligible_for_parentheses_omission?(node)
|
13
|
+
return unless node.arguments? && !node.parenthesized?
|
14
|
+
|
15
|
+
add_offense(node)
|
16
|
+
end
|
17
|
+
alias on_csend on_send
|
18
|
+
alias on_super on_send
|
19
|
+
alias on_yield on_send
|
20
|
+
|
21
|
+
def autocorrect(node)
|
22
|
+
lambda do |corrector|
|
23
|
+
corrector.replace(args_begin(node), '(')
|
24
|
+
|
25
|
+
unless args_parenthesized?(node)
|
26
|
+
corrector.insert_after(args_end(node), ')')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def message(_node = nil)
|
32
|
+
'Use parentheses for method calls with arguments.'
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def eligible_for_parentheses_omission?(node)
|
38
|
+
node.operator_method? || node.setter_method? || ignored_macro?(node)
|
39
|
+
end
|
40
|
+
|
41
|
+
def included_macros_list
|
42
|
+
cop_config.fetch('IncludedMacros', []).map(&:to_sym)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ignored_macro?(node)
|
46
|
+
cop_config['IgnoreMacros'] &&
|
47
|
+
node.macro? &&
|
48
|
+
!included_macros_list.include?(node.method_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
return if !node.children.last.nil? && !node.multiline? && node.then?
|
36
36
|
|
37
37
|
# With more than one statements after then, there's not offense
|
38
|
-
return if node.
|
38
|
+
return if accept_node_type?(node.body)
|
39
39
|
|
40
40
|
add_offense(node, location: :begin)
|
41
41
|
end
|
@@ -49,6 +49,10 @@ module RuboCop
|
|
49
49
|
)
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
def accept_node_type?(node)
|
54
|
+
node&.begin_type? || node&.array_type? || node&.hash_type?
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
@@ -54,9 +54,10 @@ module RuboCop
|
|
54
54
|
}.freeze
|
55
55
|
|
56
56
|
def on_send(node)
|
57
|
-
return if node.
|
58
|
-
|
59
|
-
|
57
|
+
return if ignored_method?(node.method_name) ||
|
58
|
+
node.each_ancestor(:send, :block).any? do |ancestor|
|
59
|
+
ignored_method?(ancestor.method_name)
|
60
|
+
end
|
60
61
|
|
61
62
|
numeric, replacement = check(node)
|
62
63
|
|
@@ -88,27 +88,27 @@ module RuboCop
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def contains_preferred_delimiter?(node, type)
|
91
|
-
|
92
|
-
node
|
93
|
-
.children.map { |n| string_source(n) }.compact
|
94
|
-
.any? { |s| preferred_delimiters.any? { |d| s.include?(d) } }
|
91
|
+
contains_delimiter?(node, preferred_delimiters_for(type))
|
95
92
|
end
|
96
93
|
|
97
94
|
def include_same_character_as_used_for_delimiter?(node, type)
|
98
95
|
return false unless %w[%w %i].include?(type)
|
99
96
|
|
100
97
|
used_delimiters = matchpairs(begin_source(node)[-1])
|
101
|
-
|
98
|
+
contains_delimiter?(node, used_delimiters)
|
99
|
+
end
|
102
100
|
|
101
|
+
def contains_delimiter?(node, delimiters)
|
102
|
+
delimiters_regexp = Regexp.union(delimiters)
|
103
103
|
node
|
104
104
|
.children.map { |n| string_source(n) }.compact
|
105
|
-
.any? { |s|
|
105
|
+
.any? { |s| delimiters_regexp =~ s }
|
106
106
|
end
|
107
107
|
|
108
108
|
def string_source(node)
|
109
109
|
if node.is_a?(String)
|
110
110
|
node
|
111
|
-
elsif node.respond_to?(:type) && node.str_type?
|
111
|
+
elsif node.respond_to?(:type) && (node.str_type? || node.sym_type?)
|
112
112
|
node.source
|
113
113
|
end
|
114
114
|
end
|
@@ -67,9 +67,16 @@ module RuboCop
|
|
67
67
|
|
68
68
|
NONCOMMUTATIVE_OPERATORS = %i[===].freeze
|
69
69
|
|
70
|
+
PROGRAM_NAMES = %i[$0 $PROGRAM_NAME].freeze
|
71
|
+
|
72
|
+
def_node_matcher :file_constant_equal_program_name?, <<~PATTERN
|
73
|
+
(send #source_file_path_constant? {:== :!=} (gvar #program_name?))
|
74
|
+
PATTERN
|
75
|
+
|
70
76
|
def on_send(node)
|
71
77
|
return unless yoda_compatible_condition?(node)
|
72
|
-
return if equality_only? && non_equality_operator?(node)
|
78
|
+
return if equality_only? && non_equality_operator?(node) ||
|
79
|
+
file_constant_equal_program_name?(node)
|
73
80
|
|
74
81
|
valid_yoda?(node) || add_offense(node)
|
75
82
|
end
|
@@ -135,6 +142,14 @@ module RuboCop
|
|
135
142
|
def noncommutative_operator?(node)
|
136
143
|
NONCOMMUTATIVE_OPERATORS.include?(node.method_name)
|
137
144
|
end
|
145
|
+
|
146
|
+
def source_file_path_constant?(node)
|
147
|
+
node.source == '__FILE__'
|
148
|
+
end
|
149
|
+
|
150
|
+
def program_name?(name)
|
151
|
+
PROGRAM_NAMES.include?(name)
|
152
|
+
end
|
138
153
|
end
|
139
154
|
end
|
140
155
|
end
|
@@ -52,6 +52,7 @@ RSpec.shared_context 'config', :config do
|
|
52
52
|
cop_name = described_class.cop_name
|
53
53
|
hash[cop_name] = RuboCop::ConfigLoader
|
54
54
|
.default_configuration[cop_name]
|
55
|
+
.merge('Enabled' => true) # in case it is 'pending'
|
55
56
|
.merge(cop_config)
|
56
57
|
end
|
57
58
|
|
@@ -88,3 +89,7 @@ end
|
|
88
89
|
RSpec.shared_context 'ruby 2.6', :ruby26 do
|
89
90
|
let(:ruby_version) { 2.6 }
|
90
91
|
end
|
92
|
+
|
93
|
+
RSpec.shared_context 'ruby 2.7', :ruby27 do
|
94
|
+
let(:ruby_version) { 2.7 }
|
95
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
# The kind of Ruby that code inspected by RuboCop is written in.
|
5
|
+
class TargetRuby
|
6
|
+
KNOWN_RUBIES = [2.3, 2.4, 2.5, 2.6, 2.7].freeze
|
7
|
+
DEFAULT_VERSION = KNOWN_RUBIES.first
|
8
|
+
|
9
|
+
OBSOLETE_RUBIES = {
|
10
|
+
1.9 => '0.50', 2.0 => '0.50', 2.1 => '0.58', 2.2 => '0.69'
|
11
|
+
}.freeze
|
12
|
+
private_constant :KNOWN_RUBIES, :OBSOLETE_RUBIES
|
13
|
+
|
14
|
+
# A place where information about a target ruby version is found.
|
15
|
+
class Source
|
16
|
+
attr_reader :version, :name
|
17
|
+
|
18
|
+
def initialize(config)
|
19
|
+
@config = config
|
20
|
+
@version = find_version
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# The target ruby version may be configured in RuboCop's config.
|
29
|
+
class RuboCopConfig < Source
|
30
|
+
def name
|
31
|
+
"`TargetRubyVersion` parameter (in #{@config.smart_loaded_path})"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def find_version
|
37
|
+
@config.for_all_cops['TargetRubyVersion']&.to_f
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# The target ruby version may be found in a .ruby-version file.
|
42
|
+
class RubyVersionFile < Source
|
43
|
+
FILENAME = '.ruby-version'
|
44
|
+
|
45
|
+
def name
|
46
|
+
"`#{FILENAME}`"
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def find_version
|
52
|
+
file = ruby_version_file
|
53
|
+
return unless file && File.file?(file)
|
54
|
+
|
55
|
+
File.read(file).match(/\A(ruby-)?(?<version>\d+\.\d+)/) do |md|
|
56
|
+
md[:version].to_f
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def ruby_version_file
|
61
|
+
@ruby_version_file ||=
|
62
|
+
@config.find_file_upwards(FILENAME,
|
63
|
+
@config.base_dir_for_path_parameters)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The lock file of Bundler may identify the target ruby version.
|
68
|
+
class BundlerLockFile < Source
|
69
|
+
def name
|
70
|
+
"`#{bundler_lock_file_path}`"
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def find_version
|
76
|
+
lock_file_path = bundler_lock_file_path
|
77
|
+
return nil unless lock_file_path
|
78
|
+
|
79
|
+
in_ruby_section = false
|
80
|
+
File.foreach(lock_file_path) do |line|
|
81
|
+
# If ruby is in Gemfile.lock or gems.lock, there should be two lines
|
82
|
+
# towards the bottom of the file that look like:
|
83
|
+
# RUBY VERSION
|
84
|
+
# ruby W.X.YpZ
|
85
|
+
# We ultimately want to match the "ruby W.X.Y.pZ" line, but there's
|
86
|
+
# extra logic to make sure we only start looking once we've seen the
|
87
|
+
# "RUBY VERSION" line.
|
88
|
+
in_ruby_section ||= line.match(/^\s*RUBY\s*VERSION\s*$/)
|
89
|
+
next unless in_ruby_section
|
90
|
+
|
91
|
+
# We currently only allow this feature to work with MRI ruby. If
|
92
|
+
# jruby (or something else) is used by the project, it's lock file
|
93
|
+
# will have a line that looks like:
|
94
|
+
# RUBY VERSION
|
95
|
+
# ruby W.X.YpZ (jruby x.x.x.x)
|
96
|
+
# The regex won't match in this situation.
|
97
|
+
result = line.match(/^\s*ruby\s+(\d+\.\d+)[p.\d]*\s*$/)
|
98
|
+
return result.captures.first.to_f if result
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def bundler_lock_file_path
|
103
|
+
@config.bundler_lock_file_path
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# If all else fails, a default version will be picked.
|
108
|
+
class Default < Source
|
109
|
+
def name
|
110
|
+
'default'
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def find_version
|
116
|
+
DEFAULT_VERSION
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.supported_versions
|
121
|
+
KNOWN_RUBIES
|
122
|
+
end
|
123
|
+
|
124
|
+
SOURCES = [RuboCopConfig, RubyVersionFile, BundlerLockFile, Default].freeze
|
125
|
+
private_constant :SOURCES
|
126
|
+
|
127
|
+
def initialize(config)
|
128
|
+
@config = config
|
129
|
+
end
|
130
|
+
|
131
|
+
def source
|
132
|
+
@source ||= SOURCES.each.lazy.map { |c| c.new(@config) }.detect(&:version)
|
133
|
+
end
|
134
|
+
|
135
|
+
def version
|
136
|
+
source.version
|
137
|
+
end
|
138
|
+
|
139
|
+
def supported?
|
140
|
+
KNOWN_RUBIES.include?(version)
|
141
|
+
end
|
142
|
+
|
143
|
+
def rubocop_version_with_support
|
144
|
+
if supported?
|
145
|
+
RuboCop::Version.version
|
146
|
+
else
|
147
|
+
OBSOLETE_RUBIES[version]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|