rubocop 1.22.2 → 1.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/config/default.yml +87 -5
  4. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  5. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  6. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader_resolver.rb +1 -1
  10. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  11. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  12. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  13. data/lib/rubocop/cop/documentation.rb +19 -2
  14. data/lib/rubocop/cop/gemspec/date_assignment.rb +2 -10
  15. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
  16. data/lib/rubocop/cop/gemspec/require_mfa.rb +144 -0
  17. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
  18. data/lib/rubocop/cop/generator.rb +1 -1
  19. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
  20. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  21. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  22. data/lib/rubocop/cop/layout/block_alignment.rb +3 -3
  23. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  24. data/lib/rubocop/cop/layout/dot_position.rb +9 -1
  25. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +22 -1
  26. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  27. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  29. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +11 -5
  30. data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
  31. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  32. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  33. data/lib/rubocop/cop/lint/deprecated_constants.rb +3 -2
  34. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  35. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  36. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
  37. data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +7 -4
  39. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
  40. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  41. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  42. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  43. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  44. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  45. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  46. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
  47. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  48. data/lib/rubocop/cop/mixin/gemspec_help.rb +30 -0
  49. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  50. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
  51. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
  52. data/lib/rubocop/cop/naming/block_forwarding.rb +107 -0
  53. data/lib/rubocop/cop/naming/file_name.rb +37 -4
  54. data/lib/rubocop/cop/security/open.rb +11 -1
  55. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  56. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  57. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  58. data/lib/rubocop/cop/style/documentation.rb +1 -1
  59. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  60. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  61. data/lib/rubocop/cop/style/file_read.rb +112 -0
  62. data/lib/rubocop/cop/style/file_write.rb +124 -0
  63. data/lib/rubocop/cop/style/format_string_token.rb +2 -1
  64. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  65. data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
  66. data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
  67. data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
  68. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  69. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  70. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +13 -0
  71. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
  72. data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
  73. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  74. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  75. data/lib/rubocop/cop/style/open_struct_use.rb +69 -0
  76. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
  77. data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
  78. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  79. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  80. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  81. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  82. data/lib/rubocop/cop/style/select_by_regexp.rb +4 -3
  83. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  84. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  85. data/lib/rubocop/cop/team.rb +1 -1
  86. data/lib/rubocop/cop/util.rb +9 -1
  87. data/lib/rubocop/formatter/html_formatter.rb +5 -2
  88. data/lib/rubocop/formatter/json_formatter.rb +4 -1
  89. data/lib/rubocop/options.rb +6 -1
  90. data/lib/rubocop/remote_config.rb +2 -4
  91. data/lib/rubocop/result_cache.rb +1 -1
  92. data/lib/rubocop/version.rb +1 -1
  93. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  94. data/lib/rubocop.rb +11 -0
  95. metadata +18 -5
@@ -150,7 +150,7 @@ module RuboCop
150
150
  1. Modify the description of #{badge} in config/default.yml
151
151
  2. Implement your new cop in the generated file!
152
152
  3. Commit your new cop with a message such as
153
- e.g. "Add new `#{badge}` cop."
153
+ e.g. "Add new `#{badge}` cop"
154
154
  4. Run `bundle exec rake changelog:new` to generate a changelog entry
155
155
  for your new cop.
156
156
  TODO
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Checks for redundant `send_node` method dispatch node.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # node.send_node.method_name
12
+ #
13
+ # # good
14
+ # node.method_name
15
+ #
16
+ # # bad
17
+ # node.send_node.receiver
18
+ #
19
+ # # good
20
+ # node.receiver
21
+ #
22
+ class RedundantMethodDispatchNode < Base
23
+ include RangeHelp
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Remove the redundant `send_node`.'
27
+ RESTRICT_ON_SEND = %i[method_name receiver].freeze
28
+
29
+ # @!method dispatch_method(node)
30
+ def_node_matcher :dispatch_method, <<~PATTERN
31
+ (send $(send _ :send_node) _)
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ return unless (dispatch_node = dispatch_method(node))
36
+ return unless (dot = dispatch_node.loc.dot)
37
+
38
+ range = range_between(dot.begin_pos, dispatch_node.loc.selector.end_pos)
39
+
40
+ add_offense(range) do |corrector|
41
+ corrector.remove(range)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -14,7 +14,9 @@ module RuboCop
14
14
 
15
15
  # @!method cop_class_def(node)
16
16
  def_node_search :cop_class_def, <<~PATTERN
17
- (class _ (const _ {:Base :Cop}) ...)
17
+ (class _
18
+ (const {nil? (const nil? :Cop) (const (const {cbase nil?} :RuboCop) :Cop)}
19
+ {:Base :Cop}) ...)
18
20
  PATTERN
19
21
 
20
22
  # @!method cop_config_accessor?(node)
@@ -13,6 +13,7 @@ require_relative 'internal_affairs/redundant_described_class_as_subject'
13
13
  require_relative 'internal_affairs/redundant_let_rubocop_config_new'
14
14
  require_relative 'internal_affairs/redundant_location_argument'
15
15
  require_relative 'internal_affairs/redundant_message_argument'
16
+ require_relative 'internal_affairs/redundant_method_dispatch_node'
16
17
  require_relative 'internal_affairs/style_detected_api_use'
17
18
  require_relative 'internal_affairs/undefined_config'
18
19
  require_relative 'internal_affairs/useless_message_assertion'
@@ -101,11 +101,11 @@ module RuboCop
101
101
  def block_end_align_target(node)
102
102
  lineage = [node, *node.ancestors]
103
103
 
104
- target = lineage.each_cons(2) do |current, parent|
105
- break current if end_align_target?(current, parent)
104
+ lineage.each_cons(2) do |current, parent|
105
+ return current if end_align_target?(current, parent)
106
106
  end
107
107
 
108
- target || lineage.last
108
+ lineage.last
109
109
  end
110
110
 
111
111
  def end_align_target?(node, parent)
@@ -32,6 +32,19 @@ module RuboCop
32
32
  # true
33
33
  # end
34
34
  #
35
+ # @example AllowForAlignment: false (default)
36
+ # # bad
37
+ # a = 1 # A really long comment
38
+ # # spanning two lines.
39
+ #
40
+ # # good
41
+ # # A really long comment spanning one line.
42
+ # a = 1
43
+ #
44
+ # @example AllowForAlignment: true
45
+ # # good
46
+ # a = 1 # A really long comment
47
+ # # spanning two lines.
35
48
  class CommentIndentation < Base
36
49
  include Alignment
37
50
  extend AutoCorrector
@@ -40,7 +53,7 @@ module RuboCop
40
53
  'instead of %<correct_comment_indentation>d).'
41
54
 
42
55
  def on_new_investigation
43
- processed_source.comments.each { |comment| check(comment) }
56
+ processed_source.comments.each_with_index { |comment, ix| check(comment, ix) }
44
57
  end
45
58
 
46
59
  private
@@ -76,7 +89,7 @@ module RuboCop
76
89
  AlignmentCorrector.correct(corrector, processed_source, comment, @column_delta)
77
90
  end
78
91
 
79
- def check(comment)
92
+ def check(comment, comment_index)
80
93
  return unless own_line_comment?(comment)
81
94
 
82
95
  next_line = line_after_comment(comment)
@@ -94,11 +107,27 @@ module RuboCop
94
107
  return if column == correct_comment_indentation
95
108
  end
96
109
 
110
+ return if correctly_aligned_with_preceding_comment?(comment_index, column)
111
+
97
112
  add_offense(comment, message: message(column, correct_comment_indentation)) do |corrector|
98
113
  autocorrect(corrector, comment)
99
114
  end
100
115
  end
101
116
 
117
+ # Returns true if:
118
+ # a) the cop is configured to allow extra indentation for alignment, and
119
+ # b) the currently inspected comment is aligned with the nearest preceding end-of-line
120
+ # comment.
121
+ def correctly_aligned_with_preceding_comment?(comment_index, column)
122
+ return false unless cop_config['AllowForAlignment']
123
+
124
+ processed_source.comments[0...comment_index].reverse_each do |other_comment|
125
+ return other_comment.loc.column == column unless own_line_comment?(other_comment)
126
+ end
127
+
128
+ false
129
+ end
130
+
102
131
  def message(column, correct_comment_indentation)
103
132
  format(MSG, column: column, correct_comment_indentation: correct_comment_indentation)
104
133
  end
@@ -27,6 +27,10 @@ module RuboCop
27
27
  include RangeHelp
28
28
  extend AutoCorrector
29
29
 
30
+ def self.autocorrect_incompatible_with
31
+ [Style::RedundantSelf]
32
+ end
33
+
30
34
  def on_send(node)
31
35
  return unless node.dot? || ampersand_dot?(node)
32
36
 
@@ -70,7 +74,7 @@ module RuboCop
70
74
  def proper_dot_position?(node)
71
75
  selector_range = selector_range(node)
72
76
 
73
- return true if same_line?(selector_range, selector_range(node.receiver))
77
+ return true if same_line?(selector_range, end_range(node.receiver))
74
78
 
75
79
  selector_line = selector_range.line
76
80
  receiver_line = receiver_end_line(node.receiver)
@@ -119,6 +123,10 @@ module RuboCop
119
123
  (node.str_type? || node.dstr_type?) && node.heredoc?
120
124
  end
121
125
 
126
+ def end_range(node)
127
+ node.source_range.end
128
+ end
129
+
122
130
  def selector_range(node)
123
131
  return node unless node.call_type?
124
132
 
@@ -77,13 +77,34 @@ module RuboCop
77
77
  # def b
78
78
  # end
79
79
  #
80
- # @example AllowAdjacentOneLineDefs: true
80
+ # @example AllowAdjacentOneLineDefs: true (default)
81
81
  #
82
82
  # # good
83
83
  # class ErrorA < BaseError; end
84
84
  # class ErrorB < BaseError; end
85
85
  # class ErrorC < BaseError; end
86
86
  #
87
+ # # good
88
+ # class ErrorA < BaseError; end
89
+ #
90
+ # class ErrorB < BaseError; end
91
+ #
92
+ # class ErrorC < BaseError; end
93
+ #
94
+ # @example AllowAdjacentOneLineDefs: false
95
+ #
96
+ # # bad
97
+ # class ErrorA < BaseError; end
98
+ # class ErrorB < BaseError; end
99
+ # class ErrorC < BaseError; end
100
+ #
101
+ # # good
102
+ # class ErrorA < BaseError; end
103
+ #
104
+ # class ErrorB < BaseError; end
105
+ #
106
+ # class ErrorC < BaseError; end
107
+ #
87
108
  class EmptyLineBetweenDefs < Base
88
109
  include RangeHelp
89
110
  extend AutoCorrector
@@ -313,7 +313,7 @@ module RuboCop
313
313
  # just give each lambda the same reference and they would all get the
314
314
  # last value of each. A local variable fixes the problem.
315
315
 
316
- if node.value
316
+ if node.value && node.respond_to?(:value_omission?) && !node.value_omission?
317
317
  correct_key_value(corrector, delta, node.key.source_range,
318
318
  node.value.source_range,
319
319
  node.loc.operator)
@@ -19,7 +19,7 @@ module RuboCop
19
19
  MSG = 'Space missing after colon.'
20
20
 
21
21
  def on_pair(node)
22
- return unless node.colon?
22
+ return if !node.colon? || node.value_omission?
23
23
 
24
24
  colon = node.loc.operator
25
25
 
@@ -28,6 +28,10 @@ module RuboCop
28
28
 
29
29
  MSG = 'Put one space between the method name and the first argument.'
30
30
 
31
+ def self.autocorrect_incompatible_with
32
+ [Style::MethodCallWithArgsParentheses]
33
+ end
34
+
31
35
  def on_send(node)
32
36
  return unless regular_method_call_with_arguments?(node)
33
37
 
@@ -30,15 +30,17 @@ module RuboCop
30
30
  def on_send(node)
31
31
  return unless arrow_lambda_with_args?(node)
32
32
 
33
- lambda_node = range_of_offense(node)
34
-
35
33
  if style == :require_space && !space_after_arrow?(node)
34
+ lambda_node = range_of_offense(node)
35
+
36
36
  add_offense(lambda_node, message: MSG_REQUIRE_SPACE) do |corrector|
37
- corrector.insert_before(node.parent.children[1], ' ')
37
+ corrector.insert_before(lambda_arguments(node), ' ')
38
38
  end
39
39
  elsif style == :require_no_space && space_after_arrow?(node)
40
- add_offense(lambda_node, message: MSG_REQUIRE_NO_SPACE) do |corrector|
41
- corrector.remove(space_after_arrow(node))
40
+ space = space_after_arrow(node)
41
+
42
+ add_offense(space, message: MSG_REQUIRE_NO_SPACE) do |corrector|
43
+ corrector.remove(space)
42
44
  end
43
45
  end
44
46
  end
@@ -66,6 +68,10 @@ module RuboCop
66
68
  node.parent.arguments.loc.expression.end_pos
67
69
  )
68
70
  end
71
+
72
+ def lambda_arguments(node)
73
+ node.parent.children[1]
74
+ end
69
75
  end
70
76
  end
71
77
  end
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # Ranges have quite low precedence, which leads to unexpected behaviour when
9
9
  # using a range with other operators. This cop avoids that by making ranges
10
10
  # explicit by requiring parenthesis around complex range boundaries (anything
11
- # that is not a basic literal: numerics, strings, symbols, etc.).
11
+ # that is not a literal: numerics, strings, symbols, etc.).
12
12
  #
13
13
  # This cop can be configured with `RequireParenthesesForMethodChains` in order to
14
14
  # specify whether method chains (including `self.foo`) should be wrapped in parens
@@ -81,7 +81,7 @@ module RuboCop
81
81
 
82
82
  def acceptable?(node)
83
83
  node.begin_type? ||
84
- node.basic_literal? ||
84
+ node.literal? ||
85
85
  node.variable? || node.const_type? || node.self_type? ||
86
86
  (node.call_type? && acceptable_call?(node))
87
87
  end
@@ -92,7 +92,7 @@ module RuboCop
92
92
  private
93
93
 
94
94
  def method_name(node)
95
- node.ancestors.find(&:block_type?).send_node.method_name
95
+ node.ancestors.find(&:block_type?).method_name
96
96
  end
97
97
  end
98
98
  end
@@ -12,6 +12,7 @@ module RuboCop
12
12
  # File.exists?(some_path)
13
13
  # Dir.exists?(some_path)
14
14
  # iterator?
15
+ # ENV.freeze # Calling `Env.freeze` raises `TypeError` since Ruby 2.7.
15
16
  # Socket.gethostbyname(host)
16
17
  # Socket.gethostbyaddr(host)
17
18
  #
@@ -22,6 +23,7 @@ module RuboCop
22
23
  # File.exist?(some_path)
23
24
  # Dir.exist?(some_path)
24
25
  # block_given?
26
+ # ENV # `ENV.freeze` cannot prohibit changes to environment variables.
25
27
  # Addrinfo.getaddrinfo(nodename, service)
26
28
  # Addrinfo.tcp(host, port).getnameinfo
27
29
  class DeprecatedClassMethods < Base
@@ -106,6 +108,9 @@ module RuboCop
106
108
 
107
109
  DeprecatedClassMethod.new(:iterator?) => Replacement.new(:block_given?),
108
110
 
111
+ DeprecatedClassMethod.new(:freeze, class_constant: :ENV) =>
112
+ Replacement.new(nil, class_constant: :ENV),
113
+
109
114
  DeprecatedClassMethod.new(:gethostbyaddr, class_constant: :Socket, correctable: false) =>
110
115
  Replacement.new(:getnameinfo, class_constant: :Addrinfo, instance_method: true),
111
116
 
@@ -120,11 +125,18 @@ module RuboCop
120
125
 
121
126
  def on_send(node)
122
127
  check(node) do |deprecated|
123
- message = format(MSG, current: deprecated, prefer: replacement(deprecated))
128
+ prefer = replacement(deprecated)
129
+ message = format(MSG, current: deprecated, prefer: prefer)
130
+ current_method = node.loc.selector
131
+
132
+ add_offense(current_method, message: message) do |corrector|
133
+ next unless deprecated.correctable?
124
134
 
125
- add_offense(node.loc.selector, message: message) do |corrector|
126
- if deprecated.correctable?
127
- corrector.replace(node.loc.selector, replacement(deprecated).method)
135
+ if (preferred_method = prefer.method)
136
+ corrector.replace(current_method, preferred_method)
137
+ else
138
+ corrector.remove(node.loc.dot)
139
+ corrector.remove(current_method)
128
140
  end
129
141
  end
130
142
  end
@@ -42,11 +42,12 @@ module RuboCop
42
42
  # Maybe further investigation of RuboCop AST will lead to an essential solution.
43
43
  return unless node.loc
44
44
 
45
- constant = node.absolute? ? constant_name(node, node.short_name.to_s) : node.source
45
+ constant = node.absolute? ? constant_name(node, node.short_name) : node.source
46
46
  return unless (deprecated_constant = deprecated_constants[constant])
47
47
 
48
48
  alternative = deprecated_constant['Alternative']
49
49
  version = deprecated_constant['DeprecatedVersion']
50
+ return if target_ruby_version < version.to_f
50
51
 
51
52
  add_offense(node, message: message(alternative, node.source, version)) do |corrector|
52
53
  corrector.replace(node, alternative)
@@ -56,7 +57,7 @@ module RuboCop
56
57
  private
57
58
 
58
59
  def constant_name(node, nested_constant_name)
59
- return nested_constant_name unless node.namespace.const_type?
60
+ return nested_constant_name.to_s unless node.namespace.const_type?
60
61
 
61
62
  constant_name(node.namespace, "#{node.namespace.short_name}::#{nested_constant_name}")
62
63
  end
@@ -55,8 +55,14 @@ module RuboCop
55
55
  ...)
56
56
  PATTERN
57
57
 
58
+ # @!method digest_const?(node)
59
+ def_node_matcher :digest_const?, <<~PATTERN
60
+ (const _ :Digest)
61
+ PATTERN
62
+
58
63
  def on_send(node)
59
64
  return if node.arguments.any? { |arg| arg.variable? || arg.send_type? || arg.const_type? }
65
+ return if digest_const?(node.receiver)
60
66
  return unless algorithm_const(node)
61
67
 
62
68
  message = message(node)
@@ -27,7 +27,7 @@ module RuboCop
27
27
 
28
28
  # @!method each_with_object?(node)
29
29
  def_node_matcher :each_with_object?, <<~PATTERN
30
- ({send csend} _ :each_with_object $_)
30
+ (call _ :each_with_object $_)
31
31
  PATTERN
32
32
 
33
33
  def on_send(node)
@@ -20,6 +20,10 @@ module RuboCop
20
20
  # # good
21
21
  # io.wait_writable(timeout)
22
22
  #
23
+ # @safety
24
+ # This cop's autocorrection is unsafe because `NoMethodError` occurs
25
+ # if `require 'io/wait'` is not called.
26
+ #
23
27
  class IncompatibleIoSelectWithFiberScheduler < Base
24
28
  extend AutoCorrector
25
29
 
@@ -30,6 +30,7 @@ module RuboCop
30
30
  # '10'.to_i
31
31
  # '10.2'.to_f
32
32
  # '10'.to_c
33
+ # '1/3'.to_r
33
34
  # ['1', '2', '3'].map(&:to_i)
34
35
  # foo.try(:to_f)
35
36
  # bar.send(:to_c)
@@ -39,6 +40,7 @@ module RuboCop
39
40
  # Integer('10', 10)
40
41
  # Float('10.2')
41
42
  # Complex('10')
43
+ # Rational('1/3')
42
44
  # ['1', '2', '3'].map { |i| Integer(i, 10) }
43
45
  # foo.try { |i| Float(i) }
44
46
  # bar.send { |i| Complex(i) }
@@ -59,13 +61,14 @@ module RuboCop
59
61
  CONVERSION_METHOD_CLASS_MAPPING = {
60
62
  to_i: "#{Integer.name}(%<number_object>s, 10)",
61
63
  to_f: "#{Float.name}(%<number_object>s)",
62
- to_c: "#{Complex.name}(%<number_object>s)"
64
+ to_c: "#{Complex.name}(%<number_object>s)",
65
+ to_r: "#{Rational.name}(%<number_object>s)"
63
66
  }.freeze
64
67
  MSG = 'Replace unsafe number conversion with number '\
65
68
  'class parsing, instead of using '\
66
69
  '`%<current>s`, use stricter '\
67
70
  '`%<corrected_method>s`.'
68
- CONVERSION_METHODS = %i[Integer Float Complex to_i to_f to_c].freeze
71
+ CONVERSION_METHODS = %i[Integer Float Complex Rational to_i to_f to_c to_r].freeze
69
72
  METHODS = CONVERSION_METHOD_CLASS_MAPPING.keys.map(&:inspect).join(' ')
70
73
 
71
74
  # @!method to_method(node)
@@ -41,7 +41,11 @@ module RuboCop
41
41
  end
42
42
 
43
43
  node.operator_method? || node.setter_method? || chained_calls?(node) ||
44
- operator_keyword?(node) || node.first_argument.hash_type?
44
+ valid_first_argument?(node.first_argument)
45
+ end
46
+
47
+ def valid_first_argument?(first_arg)
48
+ first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg)
45
49
  end
46
50
 
47
51
  def first_argument_starts_with_left_parenthesis?(node)
@@ -53,9 +57,8 @@ module RuboCop
53
57
  first_argument.send_type? && (node.children.last&.children&.count || 0) > 1
54
58
  end
55
59
 
56
- def operator_keyword?(node)
57
- first_argument = node.first_argument
58
- first_argument.operator_keyword?
60
+ def ternary_expression?(node)
61
+ node.if_type? && node.ternary?
59
62
  end
60
63
 
61
64
  def spaces_before_left_parenthesis(node)
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop looks for `ruby2_keywords` calls for methods that do not need it.
7
+ #
8
+ # `ruby2_keywords` should only be called on methods that accept an argument splat
9
+ # (`*args`) but do not explicit keyword arguments (`k:` or `k: true`) or
10
+ # a keyword splat (`**kwargs`).
11
+ #
12
+ # @example
13
+ # # good (splat argument without keyword arguments)
14
+ # ruby2_keywords def foo(*args); end
15
+ #
16
+ # # bad (no arguments)
17
+ # ruby2_keywords def foo; end
18
+ #
19
+ # # good
20
+ # def foo; end
21
+ #
22
+ # # bad (positional argument)
23
+ # ruby2_keywords def foo(arg); end
24
+ #
25
+ # # good
26
+ # def foo(arg); end
27
+ #
28
+ # # bad (double splatted argument)
29
+ # ruby2_keywords def foo(**args); end
30
+ #
31
+ # # good
32
+ # def foo(**args); end
33
+ #
34
+ # # bad (keyword arguments)
35
+ # ruby2_keywords def foo(i:, j:); end
36
+ #
37
+ # # good
38
+ # def foo(i:, j:); end
39
+ #
40
+ # # bad (splat argument with keyword arguments)
41
+ # ruby2_keywords def foo(*args, i:, j:); end
42
+ #
43
+ # # good
44
+ # def foo(*args, i:, j:); end
45
+ #
46
+ # # bad (splat argument with double splat)
47
+ # ruby2_keywords def foo(*args, **kwargs); end
48
+ #
49
+ # # good
50
+ # def foo(*args, **kwargs); end
51
+ #
52
+ # # bad (ruby2_keywords given a symbol)
53
+ # def foo; end
54
+ # ruby2_keywords :foo
55
+ #
56
+ # # good
57
+ # def foo; end
58
+ #
59
+ # # bad (ruby2_keywords with dynamic method)
60
+ # define_method(:foo) { |arg| }
61
+ # ruby2_keywords :foo
62
+ #
63
+ # # good
64
+ # define_method(:foo) { |arg| }
65
+ #
66
+ class UselessRuby2Keywords < Base
67
+ MSG = '`ruby2_keywords` is unnecessary for method `%<method_name>s`.'
68
+ RESTRICT_ON_SEND = %i[ruby2_keywords].freeze
69
+
70
+ # Looks for statically or dynamically defined methods with a given name
71
+ # @!method method_definition(node, method_name)
72
+ def_node_matcher :method_definition, <<~PATTERN
73
+ {
74
+ (def %1 ...)
75
+ ({block numblock} (send _ :define_method (sym %1)) ...)
76
+ }
77
+ PATTERN
78
+
79
+ def on_send(node)
80
+ if node.first_argument.def_type?
81
+ inspect_def(node, node.first_argument)
82
+ elsif node.first_argument.sym_type?
83
+ inspect_sym(node, node.first_argument)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def inspect_def(node, def_node)
90
+ return if allowed_arguments(def_node.arguments)
91
+
92
+ add_offense(node.loc.selector, message: format(MSG, method_name: def_node.method_name))
93
+ end
94
+
95
+ def inspect_sym(node, sym_node)
96
+ return unless node.parent
97
+
98
+ method_name = sym_node.value
99
+ definition = node.parent.each_child_node.detect { |n| method_definition(n, method_name) }
100
+
101
+ return unless definition
102
+ return if allowed_arguments(definition.arguments)
103
+
104
+ add_offense(node, message: format(MSG, method_name: method_name))
105
+ end
106
+
107
+ # `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
108
+ def allowed_arguments(arguments)
109
+ return false if arguments.empty?
110
+
111
+ arguments.each_child_node(:restarg).any? &&
112
+ arguments.each_child_node(:kwarg, :kwoptarg, :kwrestarg).none?
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -50,6 +50,7 @@ module RuboCop
50
50
 
51
51
  check_code_length(node)
52
52
  end
53
+ alias on_numblock on_block
53
54
 
54
55
  private
55
56
 
@@ -46,15 +46,6 @@ module RuboCop
46
46
  1
47
47
  end
48
48
 
49
- def block_method(node)
50
- case node.type
51
- when :block
52
- node.method_name
53
- when :block_pass
54
- node.parent.method_name
55
- end
56
- end
57
-
58
49
  def count_block?(block)
59
50
  KNOWN_ITERATING_METHODS.include? block.method_name
60
51
  end
@@ -52,6 +52,7 @@ module RuboCop
52
52
 
53
53
  check_code_length(node)
54
54
  end
55
+ alias on_numblock on_block
55
56
 
56
57
  private
57
58
 
@@ -44,7 +44,7 @@ module RuboCop
44
44
 
45
45
  # @!method module_definition?(node)
46
46
  def_node_matcher :module_definition?, <<~PATTERN
47
- (casgn nil? _ (block (send (const {nil? cbase} :Module) :new) ...))
47
+ (casgn nil? _ ({block numblock} (send (const {nil? cbase} :Module) :new) ...))
48
48
  PATTERN
49
49
 
50
50
  def message(length, max_length)
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # as they add less complexity than positional or optional parameters.
11
11
  #
12
12
  # NOTE: Explicit block argument `&block` is not counted to prevent
13
- # erroneous change that is avoided by making block argument implicit.
13
+ # erroneous change that is avoided by making block argument implicit.
14
14
  #
15
15
  # @example Max: 3
16
16
  # # good