rubocop 1.22.1 → 1.24.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +88 -8
  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/bundler/gem_comment.rb +3 -3
  12. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  13. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  14. data/lib/rubocop/cop/documentation.rb +19 -2
  15. data/lib/rubocop/cop/gemspec/date_assignment.rb +2 -10
  16. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
  17. data/lib/rubocop/cop/gemspec/require_mfa.rb +146 -0
  18. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +30 -23
  19. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
  20. data/lib/rubocop/cop/generator.rb +1 -1
  21. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +60 -0
  22. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +46 -0
  23. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  24. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  25. data/lib/rubocop/cop/layout/assignment_indentation.rb +1 -1
  26. data/lib/rubocop/cop/layout/block_alignment.rb +3 -3
  27. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  28. data/lib/rubocop/cop/layout/dot_position.rb +13 -7
  29. data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
  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 +7 -4
  32. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  33. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  34. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -1
  35. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +1 -1
  36. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -2
  37. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  38. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  39. data/lib/rubocop/cop/layout/line_length.rb +1 -1
  40. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -1
  41. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -2
  42. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +1 -1
  43. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  44. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  45. data/lib/rubocop/cop/layout/space_before_comment.rb +1 -1
  46. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  47. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +11 -5
  48. data/lib/rubocop/cop/layout/space_inside_parens.rb +0 -4
  49. data/lib/rubocop/cop/lint/ambiguous_range.rb +3 -3
  50. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  51. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  52. data/lib/rubocop/cop/lint/deprecated_constants.rb +3 -2
  53. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  54. data/lib/rubocop/cop/lint/else_layout.rb +1 -1
  55. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
  56. data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
  57. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
  58. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  59. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  60. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  61. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  62. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  63. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  64. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -2
  65. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  66. data/lib/rubocop/cop/mixin/gemspec_help.rb +30 -0
  67. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  68. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
  69. data/lib/rubocop/cop/mixin/hash_transform_method.rb +3 -3
  70. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +1 -1
  71. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  72. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +1 -1
  73. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  74. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  75. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
  76. data/lib/rubocop/cop/mixin/trailing_body.rb +1 -1
  77. data/lib/rubocop/cop/naming/block_forwarding.rb +102 -0
  78. data/lib/rubocop/cop/naming/file_name.rb +37 -4
  79. data/lib/rubocop/cop/security/json_load.rb +1 -1
  80. data/lib/rubocop/cop/security/open.rb +11 -1
  81. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  82. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  83. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  84. data/lib/rubocop/cop/style/commented_keyword.rb +5 -3
  85. data/lib/rubocop/cop/style/documentation.rb +1 -1
  86. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  87. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  88. data/lib/rubocop/cop/style/file_read.rb +112 -0
  89. data/lib/rubocop/cop/style/file_write.rb +98 -0
  90. data/lib/rubocop/cop/style/format_string_token.rb +2 -1
  91. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  92. data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
  93. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  94. data/lib/rubocop/cop/style/line_end_concatenation.rb +1 -1
  95. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  96. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +1 -1
  97. data/lib/rubocop/cop/style/multiline_when_then.rb +1 -1
  98. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  99. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  100. data/lib/rubocop/cop/style/open_struct_use.rb +69 -0
  101. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
  102. data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
  103. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  104. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  105. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  106. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  107. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -3
  108. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  109. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  110. data/lib/rubocop/cop/team.rb +1 -1
  111. data/lib/rubocop/cop/util.rb +11 -1
  112. data/lib/rubocop/formatter/html_formatter.rb +5 -2
  113. data/lib/rubocop/formatter/json_formatter.rb +4 -1
  114. data/lib/rubocop/options.rb +6 -1
  115. data/lib/rubocop/remote_config.rb +2 -4
  116. data/lib/rubocop/result_cache.rb +1 -1
  117. data/lib/rubocop/rspec/parallel_formatter.rb +90 -0
  118. data/lib/rubocop/rspec/support.rb +1 -0
  119. data/lib/rubocop/target_finder.rb +1 -1
  120. data/lib/rubocop/version.rb +1 -1
  121. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  122. data/lib/rubocop.rb +11 -0
  123. metadata +24 -9
@@ -19,6 +19,10 @@ 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
+ #
22
26
  # @example EnforcedStyle: ruby19 (default)
23
27
  # # bad
24
28
  # {:a => 2}
@@ -54,8 +58,26 @@ module RuboCop
54
58
  # # good
55
59
  # {a: 1, b: 2}
56
60
  # {:c => 3, 'd' => 4}
61
+ #
62
+ # @example EnforcedShorthandSyntax: always (default)
63
+ #
64
+ # # bad
65
+ # {foo: foo, bar: bar}
66
+ #
67
+ # # good
68
+ # {foo:, bar:}
69
+ #
70
+ # @example EnforcedShorthandSyntax: never
71
+ #
72
+ # # bad
73
+ # {foo:, bar:}
74
+ #
75
+ # # good
76
+ # {foo: foo, bar: bar}
77
+ #
57
78
  class HashSyntax < Base
58
79
  include ConfigurableEnforcedStyle
80
+ include HashShorthandSyntax
59
81
  include RangeHelp
60
82
  extend AutoCorrector
61
83
 
@@ -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
@@ -59,7 +59,7 @@ module RuboCop
59
59
 
60
60
  return unless eligible_token_set?(predecessor, operator, successor)
61
61
 
62
- return if operator.line == successor.line
62
+ return if same_line?(operator, successor)
63
63
 
64
64
  next_successor = token_after_last_string(successor, index)
65
65
 
@@ -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(removal_range)
63
+ corrector.replace(map.loc.selector, 'to_h')
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -54,7 +54,7 @@ module RuboCop
54
54
  return true if in_pattern_node.pattern.first_line != in_pattern_node.pattern.last_line
55
55
  return false unless in_pattern_node.body
56
56
 
57
- in_pattern_node.loc.line == in_pattern_node.body.loc.line
57
+ same_line?(in_pattern_node, in_pattern_node.body)
58
58
  end
59
59
  end
60
60
  end
@@ -54,7 +54,7 @@ module RuboCop
54
54
  end
55
55
  return false unless when_node.body
56
56
 
57
- when_node.loc.line == when_node.body.loc.line
57
+ same_line?(when_node, when_node.body)
58
58
  end
59
59
 
60
60
  def accept_node_type?(node)
@@ -27,6 +27,11 @@ module RuboCop
27
27
  # # bad
28
28
  # 10_000_00 # typical representation of $10,000 in cents
29
29
  #
30
+ # @example AllowedNumbers: [3000]
31
+ #
32
+ # # good
33
+ # 3000 # You can specify allowed numbers. (e.g. port number)
34
+ #
30
35
  class NumericLiterals < Base
31
36
  include IntegerNode
32
37
  extend AutoCorrector
@@ -51,9 +56,9 @@ module RuboCop
51
56
 
52
57
  def check(node)
53
58
  int = integer_part(node)
54
-
55
59
  # TODO: handle non-decimal literals as well
56
60
  return if int.start_with?('0')
61
+ return if allowed_numbers.include?(int)
57
62
  return unless int.size >= min_digits
58
63
 
59
64
  case int
@@ -99,6 +104,10 @@ module RuboCop
99
104
  def min_digits
100
105
  cop_config['MinDigits']
101
106
  end
107
+
108
+ def allowed_numbers
109
+ cop_config.fetch('AllowedNumbers', []).map(&:to_s)
110
+ end
102
111
  end
103
112
  end
104
113
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  message = message(node)
47
47
  add_offense(node, message: message) do |corrector|
48
- corrector.replace(node, replacement(node))
48
+ autocorrect(corrector, node)
49
49
  end
50
50
  end
51
51
 
@@ -55,55 +55,30 @@ module RuboCop
55
55
  format(MSG, keyword: node.keyword)
56
56
  end
57
57
 
58
- def replacement(node)
58
+ def autocorrect(corrector, node)
59
59
  if always_multiline? || cannot_replace_to_ternary?(node)
60
- multiline_replacement(node)
60
+ IfThenCorrector.new(node, indentation: indentation_width).call(corrector)
61
61
  else
62
- replaced_node = ternary_replacement(node)
63
- return replaced_node unless node.parent
64
- return "(#{replaced_node})" if %i[and or].include?(node.parent.type)
65
- return "(#{replaced_node})" if node.parent.send_type? && node.parent.operator_method?
66
-
67
- replaced_node
62
+ corrector.replace(node, ternary_correction(node))
68
63
  end
69
64
  end
70
65
 
71
- def always_multiline?
72
- @config.for_cop('Style/OneLineConditional')['AlwaysCorrectToMultiline']
73
- end
66
+ def ternary_correction(node)
67
+ replaced_node = ternary_replacement(node)
74
68
 
75
- def cannot_replace_to_ternary?(node)
76
- node.elsif_conditional?
77
- end
69
+ return replaced_node unless node.parent
70
+ return "(#{replaced_node})" if %i[and or].include?(node.parent.type)
71
+ return "(#{replaced_node})" if node.parent.send_type? && node.parent.operator_method?
78
72
 
79
- def multiline_replacement(node, indentation = nil)
80
- indentation = ' ' * node.source_range.column if indentation.nil?
81
- if_branch_source = node.if_branch&.source || 'nil'
82
- elsif_indentation = indentation if node.respond_to?(:elsif?) && node.elsif?
83
- if_branch = <<~RUBY
84
- #{elsif_indentation}#{node.keyword} #{node.condition.source}
85
- #{indentation}#{branch_body_indentation}#{if_branch_source}
86
- RUBY
87
- else_branch = else_branch_to_multiline(node.else_branch, indentation)
88
- if_branch + else_branch
73
+ replaced_node
89
74
  end
90
75
 
91
- def else_branch_to_multiline(else_branch, indentation)
92
- if else_branch.nil?
93
- 'end'
94
- elsif else_branch.if_type? && else_branch.elsif?
95
- multiline_replacement(else_branch, indentation)
96
- else
97
- <<~RUBY.chomp
98
- #{indentation}else
99
- #{indentation}#{branch_body_indentation}#{else_branch.source}
100
- #{indentation}end
101
- RUBY
102
- end
76
+ def always_multiline?
77
+ @config.for_cop('Style/OneLineConditional')['AlwaysCorrectToMultiline']
103
78
  end
104
79
 
105
- def branch_body_indentation
106
- ' ' * (@config.for_cop('Layout/IndentationWidth')['Width'] || 2)
80
+ def cannot_replace_to_ternary?(node)
81
+ node.elsif_conditional?
107
82
  end
108
83
 
109
84
  def ternary_replacement(node)
@@ -141,6 +116,10 @@ module RuboCop
141
116
 
142
117
  node.respond_to?(:arguments?) && node.arguments? && !node.parenthesized_call?
143
118
  end
119
+
120
+ def indentation_width
121
+ @config.for_cop('Layout/IndentationWidth')['Width']
122
+ end
144
123
  end
145
124
  end
146
125
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop flags uses of OpenStruct, as it is now officially discouraged
7
+ # to be used for performance, version compatibility, and potential security issues.
8
+ #
9
+ # @safety
10
+ #
11
+ # Note that this cop may flag false positives; for instance, the following legal
12
+ # use of a hand-rolled `OpenStruct` type would be considered an offense:
13
+ #
14
+ # ```
15
+ # module MyNamespace
16
+ # class OpenStruct # not the OpenStruct we're looking for
17
+ # end
18
+ #
19
+ # def new_struct
20
+ # OpenStruct.new # resolves to MyNamespace::OpenStruct
21
+ # end
22
+ # end
23
+ # ```
24
+ #
25
+ # @example
26
+ #
27
+ # # bad
28
+ # point = OpenStruct.new(x: 0, y: 1)
29
+ #
30
+ # # good
31
+ # Point = Struct.new(:x, :y)
32
+ # point = Point.new(0, 1)
33
+ #
34
+ # # also good
35
+ # point = { x: 0, y: 1 }
36
+ #
37
+ # # bad
38
+ # test_double = OpenStruct.new(a: 'b')
39
+ #
40
+ # # good (assumes test using rspec-mocks)
41
+ # test_double = double
42
+ # allow(test_double).to receive(:a).and_return('b')
43
+ #
44
+ class OpenStructUse < Base
45
+ MSG = 'Avoid using `OpenStruct`; use `Struct`, `Hash`, a class or test doubles instead.'
46
+
47
+ # @!method uses_open_struct?(node)
48
+ def_node_matcher :uses_open_struct?, <<-PATTERN
49
+ (const {nil? (cbase)} :OpenStruct)
50
+ PATTERN
51
+
52
+ def on_const(node)
53
+ return unless uses_open_struct?(node)
54
+ return if custom_class_or_module_definition?(node)
55
+
56
+ add_offense(node)
57
+ end
58
+
59
+ private
60
+
61
+ def custom_class_or_module_definition?(node)
62
+ parent = node.parent
63
+
64
+ (parent.class_type? || parent.module_type?) && node.left_siblings.empty?
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -56,6 +56,7 @@ module RuboCop
56
56
  class ParenthesesAroundCondition < Base
57
57
  include SafeAssignment
58
58
  include Parentheses
59
+ include RangeHelp
59
60
  extend AutoCorrector
60
61
 
61
62
  def on_if(node)
@@ -73,13 +74,14 @@ module RuboCop
73
74
 
74
75
  # @!method control_op_condition(node)
75
76
  def_node_matcher :control_op_condition, <<~PATTERN
76
- (begin $_ ...)
77
+ (begin $_ $...)
77
78
  PATTERN
78
79
 
79
80
  def process_control_op(node)
80
81
  cond = node.condition
81
82
 
82
- control_op_condition(cond) do |first_child|
83
+ control_op_condition(cond) do |first_child, rest_children|
84
+ return if semicolon_separated_expressions?(first_child, rest_children)
83
85
  return if modifier_op?(first_child)
84
86
  return if parens_allowed?(cond)
85
87
 
@@ -90,6 +92,14 @@ module RuboCop
90
92
  end
91
93
  end
92
94
 
95
+ def semicolon_separated_expressions?(first_exp, rest_exps)
96
+ return false unless (second_exp = rest_exps.first)
97
+
98
+ range = range_between(first_exp.source_range.end_pos, second_exp.source_range.begin_pos)
99
+
100
+ range.source.include?(';')
101
+ end
102
+
93
103
  def modifier_op?(node)
94
104
  return false if node.if_type? && node.ternary?
95
105
  return true if node.rescue_type?
@@ -46,7 +46,7 @@ module RuboCop
46
46
 
47
47
  message = style == :single_quotes ? MSG_SINGLE : MSG_DOUBLE
48
48
 
49
- if wrong_quotes?(node)
49
+ if wrong_quotes?(node) || invalid_double_quotes?(node.source)
50
50
  add_offense(node, message: message) do |corrector|
51
51
  opposite_style_detected
52
52
  autocorrect(corrector, node)
@@ -58,6 +58,16 @@ module RuboCop
58
58
 
59
59
  private
60
60
 
61
+ def invalid_double_quotes?(source)
62
+ return false unless style == :double_quotes
63
+
64
+ # The string needs single quotes if:
65
+ # 1. It contains a double quote
66
+ # 2. It contains text that would become an escape sequence with double quotes
67
+ # 3. It contains text that would become an interpolation with double quotes
68
+ !/" | (?<!\\)\\[aAbcdefkMnprsStuUxzZ0-7] | \#[@{$]/x.match?(source)
69
+ end
70
+
61
71
  def autocorrect(corrector, node)
62
72
  str = if hash_colon_key?(node)
63
73
  # strip quotes
@@ -82,10 +82,20 @@ module RuboCop
82
82
  end
83
83
 
84
84
  def autocorrect_single_variable_interpolation(corrector, embedded_node, node)
85
- variable_loc = embedded_node.children.first.loc
86
- replacement = "#{variable_loc.expression.source}.to_s"
85
+ embedded_var = embedded_node.children.first
87
86
 
88
- corrector.replace(node, replacement)
87
+ source = if require_parentheses?(embedded_var)
88
+ receiver = range_between(
89
+ embedded_var.loc.expression.begin_pos, embedded_var.loc.selector.end_pos
90
+ )
91
+ arguments = embedded_var.arguments.map(&:source).join(', ')
92
+
93
+ "#{receiver.source}(#{arguments})"
94
+ else
95
+ embedded_var.source
96
+ end
97
+
98
+ corrector.replace(node, "#{source}.to_s")
89
99
  end
90
100
 
91
101
  def autocorrect_other(corrector, embedded_node, node)
@@ -97,6 +107,10 @@ module RuboCop
97
107
  corrector.replace(embedded_loc.begin, '(')
98
108
  corrector.replace(embedded_loc.end, ').to_s')
99
109
  end
110
+
111
+ def require_parentheses?(node)
112
+ node.send_type? && !node.arguments.count.zero? && !node.parenthesized_call?
113
+ end
100
114
  end
101
115
  end
102
116
  end
@@ -80,7 +80,11 @@ module RuboCop
80
80
  end
81
81
 
82
82
  def without_character_class(loc)
83
- loc.source[1..-2]
83
+ without_character_class = loc.source[1..-2]
84
+
85
+ # Adds `\` to prevent auto-correction that changes to an interpolated string when `[#]`.
86
+ # e.g. From `/[#]{0}/` to `/#{0}/`
87
+ loc.source == '[#]' ? "\\#{without_character_class}" : without_character_class
84
88
  end
85
89
 
86
90
  def whitespace_in_free_space_mode?(node, elem)
@@ -53,7 +53,7 @@ module RuboCop
53
53
  yield __FILE__ __LINE__ __ENCODING__].freeze
54
54
 
55
55
  def self.autocorrect_incompatible_with
56
- [ColonMethodCall]
56
+ [ColonMethodCall, Layout::DotPosition]
57
57
  end
58
58
 
59
59
  def initialize(config = nil, options = nil)
@@ -218,11 +218,7 @@ module RuboCop
218
218
  def find_matching_receiver_invocation(method_chain, checked_variable)
219
219
  return nil unless method_chain
220
220
 
221
- receiver = if method_chain.block_type?
222
- method_chain.send_node.receiver
223
- else
224
- method_chain.receiver
225
- end
221
+ receiver = method_chain.receiver
226
222
 
227
223
  return receiver if receiver == checked_variable
228
224
 
@@ -83,8 +83,9 @@ module RuboCop
83
83
  return if block_node.body.begin_type?
84
84
  return if receiver_allowed?(block_node.receiver)
85
85
  return unless (regexp_method_send_node = extract_send_node(block_node))
86
+ return if match_predicate_without_receiver?(regexp_method_send_node)
86
87
 
87
- regexp = find_regexp(regexp_method_send_node)
88
+ regexp = find_regexp(regexp_method_send_node, block_node)
88
89
  register_offense(node, block_node, regexp)
89
90
  end
90
91
 
@@ -118,15 +119,20 @@ module RuboCop
118
119
  regexp_method_send_node
119
120
  end
120
121
 
121
- def find_regexp(node)
122
+ def find_regexp(node, block)
122
123
  return node.child_nodes.first if node.match_with_lvasgn_type?
123
124
 
124
- if node.receiver.lvar_type?
125
+ if node.receiver.lvar_type? &&
126
+ (block.numblock_type? || node.receiver.source == block.arguments.first.source)
125
127
  node.first_argument
126
128
  elsif node.first_argument.lvar_type?
127
129
  node.receiver
128
130
  end
129
131
  end
132
+
133
+ def match_predicate_without_receiver?(node)
134
+ node.send_type? && node.method?(:match?) && node.receiver.nil?
135
+ end
130
136
  end
131
137
  end
132
138
  end
@@ -39,7 +39,7 @@ module RuboCop
39
39
  return unless eligible_method?(node)
40
40
  return unless eligible_arguments?(node)
41
41
 
42
- method_name = node.send_node.method_name
42
+ method_name = node.method_name
43
43
  return if args_match?(method_name, node.arguments)
44
44
 
45
45
  preferred_block_arguments = build_preferred_arguments_map(node, target_args(method_name))
@@ -81,7 +81,7 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def eligible_method?(node)
84
- node.send_node.receiver && method_names.include?(node.send_node.method_name)
84
+ node.receiver && method_names.include?(node.method_name)
85
85
  end
86
86
 
87
87
  def methods
@@ -82,7 +82,9 @@ module RuboCop
82
82
  end
83
83
 
84
84
  def autocorrect(corrector, node, if_branch)
85
- corrector.wrap(node.condition, '(', ')') if node.condition.or_type?
85
+ if node.condition.or_type? || node.condition.assignment?
86
+ corrector.wrap(node.condition, '(', ')')
87
+ end
86
88
 
87
89
  correct_from_unless_to_if(corrector, node) if node.unless?
88
90
 
@@ -131,7 +131,7 @@ module RuboCop
131
131
  @options[:stdin] = new_source
132
132
  else
133
133
  filename = processed_source.buffer.name
134
- File.open(filename, 'w') { |f| f.write(new_source) }
134
+ File.write(filename, new_source)
135
135
  end
136
136
  @updated_source_file = true
137
137
  end
@@ -126,8 +126,18 @@ module RuboCop
126
126
  StringInterpreter.interpret(string)
127
127
  end
128
128
 
129
+ def line(node_or_range)
130
+ if node_or_range.respond_to?(:line)
131
+ node_or_range.line
132
+ elsif node_or_range.respond_to?(:loc)
133
+ node_or_range.loc.line
134
+ end
135
+ end
136
+
129
137
  def same_line?(node1, node2)
130
- node1.respond_to?(:loc) && node2.respond_to?(:loc) && node1.loc.line == node2.loc.line
138
+ line1 = line(node1)
139
+ line2 = line(node2)
140
+ line1 && line2 && line1 == line2
131
141
  end
132
142
 
133
143
  def indent(node, offset: 0)
@@ -23,12 +23,15 @@ module RuboCop
23
23
  end
24
24
  end
25
25
 
26
+ Summary = Struct.new(:offense_count, :inspected_files, :target_files, keyword_init: true)
27
+ FileOffenses = Struct.new(:path, :offenses, keyword_init: true)
28
+
26
29
  attr_reader :files, :summary
27
30
 
28
31
  def initialize(output, options = {})
29
32
  super
30
33
  @files = []
31
- @summary = OpenStruct.new(offense_count: 0)
34
+ @summary = Summary.new(offense_count: 0)
32
35
  end
33
36
 
34
37
  def started(target_files)
@@ -36,7 +39,7 @@ module RuboCop
36
39
  end
37
40
 
38
41
  def file_finished(file, offenses)
39
- files << OpenStruct.new(path: file, offenses: offenses)
42
+ files << FileOffenses.new(path: file, offenses: offenses)
40
43
  summary.offense_count += offenses.count
41
44
  end
42
45
 
@@ -59,12 +59,15 @@ module RuboCop
59
59
  end
60
60
 
61
61
  # TODO: Consider better solution for Offense#real_column.
62
+ # The minimum value of `start_column: real_column` is 1.
63
+ # So, the minimum value of `last_column` should be 1.
64
+ # And non-zero value of `last_column` should be used as is.
62
65
  def hash_for_location(offense)
63
66
  {
64
67
  start_line: offense.line,
65
68
  start_column: offense.real_column,
66
69
  last_line: offense.last_line,
67
- last_column: offense.last_column,
70
+ last_column: offense.last_column.zero? ? 1 : offense.last_column,
68
71
  length: offense.location.length,
69
72
  # `line` and `column` exist for compatibility.
70
73
  # Use `start_line` and `start_column` instead.