rubocop 1.22.3 → 1.26.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -3
  4. data/config/default.yml +110 -19
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  6. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  7. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  9. data/lib/rubocop/cli.rb +2 -1
  10. data/lib/rubocop/config_loader_resolver.rb +1 -1
  11. data/lib/rubocop/cop/badge.rb +7 -1
  12. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  13. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  14. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  15. data/lib/rubocop/cop/documentation.rb +19 -2
  16. data/lib/rubocop/cop/gemspec/date_assignment.rb +2 -10
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
  18. data/lib/rubocop/cop/gemspec/require_mfa.rb +145 -0
  19. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -3
  20. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
  21. data/lib/rubocop/cop/generator.rb +7 -11
  22. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +46 -0
  23. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
  24. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  25. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  26. data/lib/rubocop/cop/layout/argument_alignment.rb +36 -9
  27. data/lib/rubocop/cop/layout/case_indentation.rb +1 -1
  28. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  29. data/lib/rubocop/cop/layout/dot_position.rb +4 -0
  30. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +22 -1
  31. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +5 -1
  32. data/lib/rubocop/cop/layout/hash_alignment.rb +7 -2
  33. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +7 -8
  34. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  35. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  36. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  37. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +11 -5
  38. data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
  39. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -1
  40. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  41. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  42. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  43. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  44. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +10 -5
  45. data/lib/rubocop/cop/lint/inherit_exception.rb +19 -28
  46. data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
  47. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +10 -6
  48. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +5 -0
  49. data/lib/rubocop/cop/lint/symbol_conversion.rb +3 -2
  50. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
  51. data/lib/rubocop/cop/lint/useless_times.rb +13 -9
  52. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  53. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  54. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  55. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  56. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  57. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  58. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
  59. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  60. data/lib/rubocop/cop/mixin/gemspec_help.rb +30 -0
  61. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  62. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +82 -0
  63. data/lib/rubocop/cop/mixin/line_length_help.rb +17 -6
  64. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -1
  65. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
  66. data/lib/rubocop/cop/naming/block_forwarding.rb +121 -0
  67. data/lib/rubocop/cop/naming/file_name.rb +37 -4
  68. data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
  69. data/lib/rubocop/cop/security/open.rb +11 -1
  70. data/lib/rubocop/cop/security/yaml_load.rb +9 -3
  71. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  72. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  73. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  74. data/lib/rubocop/cop/style/def_with_parentheses.rb +16 -11
  75. data/lib/rubocop/cop/style/documentation.rb +1 -1
  76. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  77. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  78. data/lib/rubocop/cop/style/file_read.rb +112 -0
  79. data/lib/rubocop/cop/style/file_write.rb +124 -0
  80. data/lib/rubocop/cop/style/for.rb +4 -0
  81. data/lib/rubocop/cop/style/format_string_token.rb +2 -1
  82. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  83. data/lib/rubocop/cop/style/hash_syntax.rb +36 -0
  84. data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
  85. data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
  86. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  87. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
  88. data/lib/rubocop/cop/style/lambda_call.rb +12 -20
  89. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -0
  91. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
  92. data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
  93. data/lib/rubocop/cop/style/nested_file_dirname.rb +66 -0
  94. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  95. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  96. data/lib/rubocop/cop/style/open_struct_use.rb +69 -0
  97. data/lib/rubocop/cop/style/optional_boolean_parameter.rb +3 -2
  98. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
  99. data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
  100. data/lib/rubocop/cop/style/redundant_begin.rb +2 -6
  101. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  102. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  103. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  104. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  105. data/lib/rubocop/cop/style/sample.rb +5 -3
  106. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -1
  107. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  108. data/lib/rubocop/cop/style/sole_nested_conditional.rb +53 -13
  109. data/lib/rubocop/cop/style/string_concatenation.rb +7 -1
  110. data/lib/rubocop/cop/style/swap_values.rb +2 -0
  111. data/lib/rubocop/cop/style/ternary_parentheses.rb +16 -2
  112. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +1 -1
  113. data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +1 -1
  114. data/lib/rubocop/cop/style/unless_else.rb +4 -0
  115. data/lib/rubocop/cop/team.rb +1 -1
  116. data/lib/rubocop/cop/util.rb +9 -1
  117. data/lib/rubocop/cop/variable_force.rb +1 -5
  118. data/lib/rubocop/cops_documentation_generator.rb +2 -2
  119. data/lib/rubocop/formatter/disabled_config_formatter.rb +16 -2
  120. data/lib/rubocop/formatter/html_formatter.rb +5 -2
  121. data/lib/rubocop/formatter/json_formatter.rb +4 -1
  122. data/lib/rubocop/options.rb +14 -3
  123. data/lib/rubocop/remote_config.rb +2 -4
  124. data/lib/rubocop/result_cache.rb +1 -1
  125. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  126. data/lib/rubocop/runner.rb +1 -1
  127. data/lib/rubocop/target_ruby.rb +1 -1
  128. data/lib/rubocop/version.rb +1 -1
  129. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  130. data/lib/rubocop.rb +12 -0
  131. metadata +22 -7
@@ -79,6 +79,8 @@ module RuboCop
79
79
  when_nodes.each do |when_node|
80
80
  conditions = when_node.conditions
81
81
 
82
+ replace_then_with_line_break(corrector, conditions, when_node)
83
+
82
84
  next unless conditions.size > 1
83
85
 
84
86
  range = range_between(conditions.first.source_range.begin_pos,
@@ -97,6 +99,14 @@ module RuboCop
97
99
  line_beginning = case_range.adjust(begin_pos: -case_range.column)
98
100
  corrector.insert_before(line_beginning, comments)
99
101
  end
102
+
103
+ def replace_then_with_line_break(corrector, conditions, when_node)
104
+ return unless when_node.parent.parent && when_node.then?
105
+
106
+ range = range_between(conditions.last.source_range.end_pos, when_node.loc.begin.end_pos)
107
+
108
+ corrector.replace(range, "\n")
109
+ end
100
110
  end
101
111
  end
102
112
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # to go on its own line (expanded style).
10
10
  #
11
11
  # NOTE: A method definition is not considered empty if it contains
12
- # comments.
12
+ # comments.
13
13
  #
14
14
  # @example EnforcedStyle: compact (default)
15
15
  # # bad
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Favor `File.(bin)read` convenience methods.
7
+ #
8
+ # @example
9
+ # ## text mode
10
+ # # bad
11
+ # File.open(filename).read
12
+ # File.open(filename, &:read)
13
+ # File.open(filename) { |f| f.read }
14
+ # File.open(filename) do |f|
15
+ # f.read
16
+ # end
17
+ # File.open(filename, 'r').read
18
+ # File.open(filename, 'r', &:read)
19
+ # File.open(filename, 'r') do |f|
20
+ # f.read
21
+ # end
22
+ #
23
+ # # good
24
+ # File.read(filename)
25
+ #
26
+ # @example
27
+ # ## binary mode
28
+ # # bad
29
+ # File.open(filename, 'rb').read
30
+ # File.open(filename, 'rb', &:read)
31
+ # File.open(filename, 'rb') do |f|
32
+ # f.read
33
+ # end
34
+ #
35
+ # # good
36
+ # File.binread(filename)
37
+ #
38
+ class FileRead < Base
39
+ extend AutoCorrector
40
+ include RangeHelp
41
+
42
+ MSG = 'Use `File.%<read_method>s`.'
43
+
44
+ RESTRICT_ON_SEND = %i[open].freeze
45
+
46
+ READ_FILE_START_TO_FINISH_MODES = %w[r rt rb r+ r+t r+b].to_set.freeze
47
+
48
+ # @!method file_open?(node)
49
+ def_node_matcher :file_open?, <<~PATTERN
50
+ (send
51
+ (const {nil? cbase} :File)
52
+ :open
53
+ $_
54
+ (str $%READ_FILE_START_TO_FINISH_MODES)?
55
+ $(block-pass (sym :read))?
56
+ )
57
+ PATTERN
58
+
59
+ # @!method send_read?(node)
60
+ def_node_matcher :send_read?, <<~PATTERN
61
+ (send _ :read)
62
+ PATTERN
63
+
64
+ # @!method block_read?(node)
65
+ def_node_matcher :block_read?, <<~PATTERN
66
+ (block _ (args (arg $_)) (send (lvar $_) :read))
67
+ PATTERN
68
+
69
+ def on_send(node)
70
+ evidence(node) do |filename, mode, read_node|
71
+ message = format(MSG, read_method: read_method(mode))
72
+
73
+ add_offense(read_node, message: message) do |corrector|
74
+ range = range_between(node.loc.selector.begin_pos, read_node.loc.expression.end_pos)
75
+ replacement = "#{read_method(mode)}(#{filename.source})"
76
+
77
+ corrector.replace(range, replacement)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def evidence(node)
85
+ file_open?(node) do |filename, mode_array, block_pass|
86
+ read_node?(node, block_pass) do |read_node|
87
+ yield(filename, mode_array.first || 'r', read_node)
88
+ end
89
+ end
90
+ end
91
+
92
+ def read_node?(node, block_pass)
93
+ if block_pass.any?
94
+ yield(node)
95
+ elsif file_open_read?(node.parent)
96
+ yield(node.parent)
97
+ end
98
+ end
99
+
100
+ def file_open_read?(node)
101
+ return true if send_read?(node)
102
+
103
+ block_read?(node) { |block_arg, read_lvar| block_arg == read_lvar }
104
+ end
105
+
106
+ def read_method(mode)
107
+ mode.end_with?('b') ? :binread : :read
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Favor `File.(bin)write` convenience methods.
7
+ #
8
+ # @example
9
+ # ## text mode
10
+ # # bad
11
+ # File.open(filename, 'w').write(content)
12
+ # File.open(filename, 'w') do |f|
13
+ # f.write(content)
14
+ # end
15
+ #
16
+ # # good
17
+ # File.write(filename, content)
18
+ #
19
+ # @example
20
+ # ## binary mode
21
+ # # bad
22
+ # File.open(filename, 'wb').write(content)
23
+ # File.open(filename, 'wb') do |f|
24
+ # f.write(content)
25
+ # end
26
+ #
27
+ # # good
28
+ # File.binwrite(filename, content)
29
+ #
30
+ class FileWrite < Base
31
+ extend AutoCorrector
32
+ include RangeHelp
33
+
34
+ MSG = 'Use `File.%<write_method>s`.'
35
+
36
+ RESTRICT_ON_SEND = %i[open].to_set.freeze
37
+
38
+ TRUNCATING_WRITE_MODES = %w[w wt wb w+ w+t w+b].to_set.freeze
39
+
40
+ # @!method file_open?(node)
41
+ def_node_matcher :file_open?, <<~PATTERN
42
+ (send
43
+ (const {nil? cbase} :File)
44
+ :open
45
+ $_
46
+ (str $%TRUNCATING_WRITE_MODES)
47
+ (block-pass (sym :write))?
48
+ )
49
+ PATTERN
50
+
51
+ # @!method send_write?(node)
52
+ def_node_matcher :send_write?, <<~PATTERN
53
+ (send _ :write $_)
54
+ PATTERN
55
+
56
+ # @!method block_write?(node)
57
+ def_node_matcher :block_write?, <<~PATTERN
58
+ (block _ (args (arg $_)) (send (lvar $_) :write $_))
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ evidence(node) do |filename, mode, content, write_node|
63
+ message = format(MSG, write_method: write_method(mode))
64
+
65
+ add_offense(write_node, message: message) do |corrector|
66
+ range = range_between(node.loc.selector.begin_pos, write_node.loc.expression.end_pos)
67
+ replacement = replacement(mode, filename, content, write_node)
68
+
69
+ corrector.replace(range, replacement)
70
+ end
71
+ end
72
+ end
73
+
74
+ def evidence(node)
75
+ file_open?(node) do |filename, mode|
76
+ file_open_write?(node.parent) do |content|
77
+ yield(filename, mode, content, node.parent)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def file_open_write?(node)
85
+ content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
86
+ write_arg if block_arg == lvar
87
+ end
88
+
89
+ yield(content) if content
90
+ end
91
+
92
+ def write_method(mode)
93
+ mode.end_with?('b') ? :binwrite : :write
94
+ end
95
+
96
+ def replacement(mode, filename, content, write_node)
97
+ replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
98
+
99
+ if heredoc?(write_node)
100
+ first_argument = write_node.body.first_argument
101
+
102
+ <<~REPLACEMENT.chomp
103
+ #{replacement}
104
+ #{heredoc_range(first_argument).source}
105
+ REPLACEMENT
106
+ else
107
+ replacement
108
+ end
109
+ end
110
+
111
+ def heredoc?(write_node)
112
+ write_node.block_type? && (first_argument = write_node.body.first_argument) &&
113
+ first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
114
+ end
115
+
116
+ def heredoc_range(first_argument)
117
+ range_between(
118
+ first_argument.loc.heredoc_body.begin_pos, first_argument.loc.heredoc_end.end_pos
119
+ )
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -38,6 +38,10 @@ module RuboCop
38
38
  # end
39
39
  # end
40
40
  #
41
+ # @safety
42
+ # This cop's autocorrection is unsafe because the scope of
43
+ # variables is different between `each` and `for`.
44
+ #
41
45
  class For < Base
42
46
  include ConfigurableEnforcedStyle
43
47
  include RangeHelp
@@ -102,7 +102,8 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def use_ignored_method?(node)
105
- (parent = node.parent) && parent.send_type? && ignored_method?(parent.method_name)
105
+ send_parent = node.each_ancestor(:send).first
106
+ send_parent && ignored_method?(send_parent.method_name)
106
107
  end
107
108
 
108
109
  def unannotated_format?(node, detected_style)
@@ -101,7 +101,8 @@ module RuboCop
101
101
  end
102
102
 
103
103
  def requires_parens?(node)
104
- node.call_type? && node.arguments.any? && !node.parenthesized?
104
+ (node.call_type? && node.arguments.any? && !node.parenthesized?) ||
105
+ node.or_type? || node.and_type?
105
106
  end
106
107
 
107
108
  def multi_argument(node)
@@ -19,6 +19,16 @@ module RuboCop
19
19
  # * ruby19_no_mixed_keys - forces use of ruby 1.9 syntax and forbids mixed
20
20
  # syntax hashes
21
21
  #
22
+ # This cop has `EnforcedShorthandSyntax` option.
23
+ # It can enforce either the use of the explicit hash value syntax or
24
+ # the use of Ruby 3.1's hash value shorthand syntax.
25
+ #
26
+ # The supported styles are:
27
+ #
28
+ # * always - forces use of the 3.1 syntax (e.g. {foo:})
29
+ # * never - forces use of explicit hash literal value
30
+ # * either - accepts both shorthand and explicit use of hash literal value
31
+ #
22
32
  # @example EnforcedStyle: ruby19 (default)
23
33
  # # bad
24
34
  # {:a => 2}
@@ -54,8 +64,34 @@ module RuboCop
54
64
  # # good
55
65
  # {a: 1, b: 2}
56
66
  # {:c => 3, 'd' => 4}
67
+ #
68
+ # @example EnforcedShorthandSyntax: always (default)
69
+ #
70
+ # # bad
71
+ # {foo: foo, bar: bar}
72
+ #
73
+ # # good
74
+ # {foo:, bar:}
75
+ #
76
+ # @example EnforcedShorthandSyntax: never
77
+ #
78
+ # # bad
79
+ # {foo:, bar:}
80
+ #
81
+ # # good
82
+ # {foo: foo, bar: bar}
83
+ #
84
+ # @example EnforcedShorthandSyntax: either
85
+ #
86
+ # # good
87
+ # {foo: foo, bar: bar}
88
+ #
89
+ # # good
90
+ # {foo:, bar:}
91
+ #
57
92
  class HashSyntax < Base
58
93
  include ConfigurableEnforcedStyle
94
+ include HashShorthandSyntax
59
95
  include RangeHelp
60
96
  extend AutoCorrector
61
97
 
@@ -30,13 +30,13 @@ module RuboCop
30
30
  # @!method on_bad_each_with_object(node)
31
31
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
32
32
  (block
33
- ({send csend} !#array_receiver? :each_with_object (hash))
33
+ (call !#array_receiver? :each_with_object (hash))
34
34
  (args
35
35
  (mlhs
36
36
  (arg $_)
37
37
  (arg _val))
38
38
  (arg _memo))
39
- ({send csend} (lvar _memo) :[]= $!`_memo $(lvar _val)))
39
+ (call (lvar _memo) :[]= $!`_memo $(lvar _val)))
40
40
  PATTERN
41
41
 
42
42
  # @!method on_bad_hash_brackets_map(node)
@@ -45,7 +45,7 @@ module RuboCop
45
45
  (const _ :Hash)
46
46
  :[]
47
47
  (block
48
- ({send csend} !#array_receiver? {:map :collect})
48
+ (call !#array_receiver? {:map :collect})
49
49
  (args
50
50
  (arg $_)
51
51
  (arg _val))
@@ -54,9 +54,9 @@ module RuboCop
54
54
 
55
55
  # @!method on_bad_map_to_h(node)
56
56
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
57
- ({send csend}
57
+ (call
58
58
  (block
59
- ({send csend} !#array_receiver? {:map :collect})
59
+ (call !#array_receiver? {:map :collect})
60
60
  (args
61
61
  (arg $_)
62
62
  (arg _val))
@@ -67,7 +67,7 @@ module RuboCop
67
67
  # @!method on_bad_to_h(node)
68
68
  def_node_matcher :on_bad_to_h, <<~PATTERN
69
69
  (block
70
- ({send csend} !#array_receiver? :to_h)
70
+ (call !#array_receiver? :to_h)
71
71
  (args
72
72
  (arg $_)
73
73
  (arg _val))
@@ -30,13 +30,13 @@ module RuboCop
30
30
  # @!method on_bad_each_with_object(node)
31
31
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
32
32
  (block
33
- ({send csend} !#array_receiver? :each_with_object (hash))
33
+ (call !#array_receiver? :each_with_object (hash))
34
34
  (args
35
35
  (mlhs
36
36
  (arg _key)
37
37
  (arg $_))
38
38
  (arg _memo))
39
- ({send csend} (lvar _memo) :[]= $(lvar _key) $!`_memo))
39
+ (call (lvar _memo) :[]= $(lvar _key) $!`_memo))
40
40
  PATTERN
41
41
 
42
42
  # @!method on_bad_hash_brackets_map(node)
@@ -45,7 +45,7 @@ module RuboCop
45
45
  (const _ :Hash)
46
46
  :[]
47
47
  (block
48
- ({send csend} !#array_receiver? {:map :collect})
48
+ (call !#array_receiver? {:map :collect})
49
49
  (args
50
50
  (arg _key)
51
51
  (arg $_))
@@ -54,9 +54,9 @@ module RuboCop
54
54
 
55
55
  # @!method on_bad_map_to_h(node)
56
56
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
57
- ({send csend}
57
+ (call
58
58
  (block
59
- ({send csend} !#array_receiver? {:map :collect})
59
+ (call !#array_receiver? {:map :collect})
60
60
  (args
61
61
  (arg _key)
62
62
  (arg $_))
@@ -67,7 +67,7 @@ module RuboCop
67
67
  # @!method on_bad_to_h(node)
68
68
  def_node_matcher :on_bad_to_h, <<~PATTERN
69
69
  (block
70
- ({send csend} !#array_receiver? :to_h)
70
+ (call !#array_receiver? :to_h)
71
71
  (args
72
72
  (arg _key)
73
73
  (arg $_))
@@ -80,11 +80,19 @@ module RuboCop
80
80
  private
81
81
 
82
82
  def autocorrect(corrector, node)
83
+ if then?(node)
84
+ # If the nested `if` is a then node, correct it first,
85
+ # then the next pass will use `correct_to_elsif_from_if_inside_else_form`
86
+ IfThenCorrector.new(node, indentation: 0).call(corrector)
87
+ return
88
+ end
89
+
83
90
  if node.modifier_form?
84
91
  correct_to_elsif_from_modifier_form(corrector, node)
85
92
  else
86
93
  correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
87
94
  end
95
+
88
96
  corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
89
97
  return unless (if_branch = node.if_branch)
90
98
 
@@ -102,15 +110,22 @@ module RuboCop
102
110
 
103
111
  def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
104
112
  corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
113
+
105
114
  if_condition_range = if_condition_range(node, condition)
115
+
106
116
  if (if_branch = node.if_branch)
107
117
  corrector.replace(if_condition_range, if_branch.source)
108
118
  else
109
119
  corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
110
120
  end
121
+
111
122
  corrector.remove(condition)
112
123
  end
113
124
 
125
+ def then?(node)
126
+ node.loc.begin&.source == 'then'
127
+ end
128
+
114
129
  def find_end_range(node)
115
130
  end_range = node.loc.end
116
131
  return end_range if end_range
@@ -36,7 +36,7 @@ module RuboCop
36
36
  # # good (but potentially an unsafe correction)
37
37
  # foo.do_something?
38
38
  #
39
- # @example AllowedMethods: ['nonzero?']
39
+ # @example AllowedMethods: ['nonzero?'] (default)
40
40
  # # good
41
41
  # num.nonzero? ? true : false
42
42
  #
@@ -22,45 +22,37 @@ module RuboCop
22
22
  include ConfigurableEnforcedStyle
23
23
  extend AutoCorrector
24
24
 
25
+ MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
25
26
  RESTRICT_ON_SEND = %i[call].freeze
26
27
 
27
28
  def on_send(node)
28
29
  return unless node.receiver
29
30
 
30
31
  if offense?(node)
31
- add_offense(node) do |corrector|
32
+ prefer = prefer(node)
33
+ current = node.source
34
+
35
+ add_offense(node, message: format(MSG, prefer: prefer, current: current)) do |corrector|
32
36
  opposite_style_detected
33
- autocorrect(corrector, node)
37
+ corrector.replace(node, prefer)
34
38
  end
35
39
  else
36
40
  correct_style_detected
37
41
  end
38
42
  end
39
43
 
40
- def autocorrect(corrector, node)
41
- if explicit_style?
42
- receiver = node.receiver.source
43
- replacement = node.source.sub("#{receiver}.", "#{receiver}.call")
44
-
45
- corrector.replace(node, replacement)
46
- else
47
- add_parentheses(node, corrector) unless node.parenthesized?
48
- corrector.remove(node.loc.selector)
49
- end
50
- end
51
-
52
44
  private
53
45
 
54
46
  def offense?(node)
55
47
  (explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
56
48
  end
57
49
 
58
- def message(_node)
59
- if explicit_style?
60
- 'Prefer the use of `lambda.call(...)` over `lambda.(...)`.'
61
- else
62
- 'Prefer the use of `lambda.(...)` over `lambda.call(...)`.'
63
- end
50
+ def prefer(node)
51
+ receiver = node.receiver.source
52
+ arguments = node.arguments.map(&:source).join(', ')
53
+ method = explicit_style? ? "call(#{arguments})" : "(#{arguments})"
54
+
55
+ "#{receiver}.#{method}"
64
56
  end
65
57
 
66
58
  def implicit_style?
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop looks for uses of `map.to_h` or `collect.to_h` that could be
7
+ # written with just `to_h` in Ruby >= 2.6.
8
+ #
9
+ # NOTE: `Style/HashTransformKeys` and `Style/HashTransformValues` will
10
+ # also change this pattern if only hash keys or hash values are being
11
+ # transformed.
12
+ #
13
+ # @safety
14
+ # This cop is unsafe, as it can produce false positives if the receiver
15
+ # is not an `Enumerable`.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # something.map { |v| [v, v * 2] }.to_h
20
+ #
21
+ # # good
22
+ # something.to_h { |v| [v, v * 2] }
23
+ #
24
+ # # bad
25
+ # {foo: bar}.collect { |k, v| [k.to_s, v.do_something] }.to_h
26
+ #
27
+ # # good
28
+ # {foo: bar}.to_h { |k, v| [k.to_s, v.do_something] }
29
+ #
30
+ class MapToHash < Base
31
+ extend AutoCorrector
32
+ extend TargetRubyVersion
33
+ include RangeHelp
34
+
35
+ minimum_target_ruby_version 2.6
36
+
37
+ MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
38
+ RESTRICT_ON_SEND = %i[to_h].freeze
39
+
40
+ # @!method map_to_h?(node)
41
+ def_node_matcher :map_to_h?, <<~PATTERN
42
+ $(send (block $(send _ {:map :collect}) ...) :to_h)
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ return unless (to_h_node, map_node = map_to_h?(node))
47
+
48
+ message = format(MSG, method: map_node.loc.selector.source)
49
+ add_offense(map_node.loc.selector, message: message) do |corrector|
50
+ # If the `to_h` call already has a block, do not auto-correct.
51
+ next if to_h_node.block_node
52
+
53
+ autocorrect(corrector, to_h_node, map_node)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def autocorrect(corrector, to_h, map)
60
+ removal_range = range_between(to_h.loc.dot.begin_pos, to_h.loc.selector.end_pos)
61
+
62
+ corrector.remove(range_with_surrounding_space(range: removal_range, side: :left))
63
+ corrector.replace(map.loc.selector, 'to_h')
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -13,9 +13,11 @@ module RuboCop
13
13
 
14
14
  private
15
15
 
16
+ # rubocop:disable Metrics/PerceivedComplexity
16
17
  def omit_parentheses(node)
17
18
  return unless node.parenthesized?
18
19
  return if inside_endless_method_def?(node)
20
+ return if require_parentheses_for_hash_value_omission?(node)
19
21
  return if syntax_like_method_call?(node)
20
22
  return if super_call_without_arguments?(node)
21
23
  return if allowed_camel_case_method_call?(node)
@@ -26,6 +28,7 @@ module RuboCop
26
28
  auto_correct(corrector, node)
27
29
  end
28
30
  end
31
+ # rubocop:enable Metrics/PerceivedComplexity
29
32
 
30
33
  def auto_correct(corrector, node)
31
34
  if parentheses_at_the_end_of_multiline_call?(node)
@@ -45,6 +48,23 @@ module RuboCop
45
48
  node.each_ancestor(:def, :defs).any?(&:endless?) && node.arguments.any?
46
49
  end
47
50
 
51
+ def require_parentheses_for_hash_value_omission?(node)
52
+ return false unless (last_argument = node.last_argument)
53
+ return false if !last_argument.hash_type? || !last_argument.pairs.last&.value_omission?
54
+
55
+ modifier_form?(node) || exist_next_line_expression?(node)
56
+ end
57
+
58
+ def modifier_form?(node)
59
+ node.parent.respond_to?(:modifier_form?) && node.parent.modifier_form?
60
+ end
61
+
62
+ # Require hash value omission be enclosed in parentheses to prevent the following issue:
63
+ # https://bugs.ruby-lang.org/issues/18396.
64
+ def exist_next_line_expression?(node)
65
+ node.parent&.assignment? ? node.parent.right_sibling : node.right_sibling
66
+ end
67
+
48
68
  def syntax_like_method_call?(node)
49
69
  node.implicit_call? || node.operator_method?
50
70
  end
@@ -43,9 +43,11 @@ module RuboCop
43
43
  # NOTE: Parentheses are still allowed in cases where omitting them
44
44
  # results in ambiguous or syntactically incorrect code. For example,
45
45
  # parentheses are required around a method with arguments when inside an
46
- # endless method definition introduced in Ruby 3.0. Parentheses are also
46
+ # endless method definition introduced in Ruby 3.0. Parentheses are also
47
47
  # allowed when forwarding arguments with the triple-dot syntax introduced
48
48
  # in Ruby 2.7 as omitting them starts an endless range.
49
+ # And Ruby 3.1's hash omission syntax has a case that requires parentheses
50
+ # because of the following issue: https://bugs.ruby-lang.org/issues/18396.
49
51
  #
50
52
  # @example EnforcedStyle: require_parentheses (default)
51
53
  #