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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +2 -1
  4. data/lib/rubocop.rb +6 -0
  5. data/lib/rubocop/ast/builder.rb +43 -42
  6. data/lib/rubocop/ast/node/def_node.rb +11 -0
  7. data/lib/rubocop/ast/node/forward_args_node.rb +18 -0
  8. data/lib/rubocop/ast/traversal.rb +11 -3
  9. data/lib/rubocop/cli/command/show_cops.rb +11 -4
  10. data/lib/rubocop/config_loader.rb +19 -19
  11. data/lib/rubocop/config_validator.rb +49 -91
  12. data/lib/rubocop/cop/autocorrect_logic.rb +6 -3
  13. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  14. data/lib/rubocop/cop/generator.rb +3 -4
  15. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  16. data/lib/rubocop/cop/layout/hash_alignment.rb +8 -4
  17. data/lib/rubocop/cop/layout/multiline_block_layout.rb +14 -5
  18. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -2
  19. data/lib/rubocop/cop/lint/debugger.rb +2 -2
  20. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -1
  21. data/lib/rubocop/cop/migration/department_name.rb +16 -1
  22. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -7
  23. data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
  24. data/lib/rubocop/cop/registry.rb +7 -2
  25. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -207
  26. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +168 -0
  27. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +54 -0
  28. data/lib/rubocop/cop/style/multiline_when_then.rb +5 -1
  29. data/lib/rubocop/cop/style/numeric_predicate.rb +4 -3
  30. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +7 -7
  31. data/lib/rubocop/cop/style/yoda_condition.rb +16 -1
  32. data/lib/rubocop/rspec/shared_contexts.rb +5 -0
  33. data/lib/rubocop/target_ruby.rb +151 -0
  34. data/lib/rubocop/version.rb +1 -1
  35. 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.children.last&.begin_type?
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.each_ancestor(:send, :block).any? do |ancestor|
58
- ignored_method?(ancestor.method_name)
59
- end
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
- preferred_delimiters = preferred_delimiters_for(type)
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
- escaped_delimiters = used_delimiters.map { |d| "\\#{d}" }.join('|')
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| Regexp.new(escaped_delimiters) =~ 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