rubocop 1.5.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +111 -14
  5. data/config/obsoletion.yml +196 -0
  6. data/lib/rubocop.rb +20 -1
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +19 -19
  8. data/lib/rubocop/comment_config.rb +6 -6
  9. data/lib/rubocop/config.rb +8 -5
  10. data/lib/rubocop/config_loader.rb +10 -6
  11. data/lib/rubocop/config_loader_resolver.rb +21 -4
  12. data/lib/rubocop/config_obsoletion.rb +64 -262
  13. data/lib/rubocop/config_obsoletion/changed_enforced_styles.rb +33 -0
  14. data/lib/rubocop/config_obsoletion/changed_parameter.rb +21 -0
  15. data/lib/rubocop/config_obsoletion/cop_rule.rb +34 -0
  16. data/lib/rubocop/config_obsoletion/extracted_cop.rb +44 -0
  17. data/lib/rubocop/config_obsoletion/parameter_rule.rb +44 -0
  18. data/lib/rubocop/config_obsoletion/removed_cop.rb +41 -0
  19. data/lib/rubocop/config_obsoletion/renamed_cop.rb +34 -0
  20. data/lib/rubocop/config_obsoletion/rule.rb +41 -0
  21. data/lib/rubocop/config_obsoletion/split_cop.rb +27 -0
  22. data/lib/rubocop/config_validator.rb +11 -4
  23. data/lib/rubocop/cop/base.rb +17 -15
  24. data/lib/rubocop/cop/cop.rb +2 -2
  25. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +6 -8
  26. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
  27. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  28. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
  29. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +19 -3
  30. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  31. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +26 -0
  32. data/lib/rubocop/cop/layout/line_length.rb +6 -16
  33. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  34. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
  35. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
  36. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
  37. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
  38. data/lib/rubocop/cop/layout/space_before_brackets.rb +62 -0
  39. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
  40. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  41. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
  42. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
  43. data/lib/rubocop/cop/lint/deprecated_constants.rb +75 -0
  44. data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
  45. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
  46. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
  47. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
  48. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +48 -0
  49. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
  50. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
  51. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  52. data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
  53. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  54. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  55. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
  56. data/lib/rubocop/cop/mixin/comments_help.rb +1 -10
  57. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_help.rb +4 -1
  59. data/lib/rubocop/cop/naming/accessor_method_name.rb +15 -1
  60. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
  61. data/lib/rubocop/cop/naming/variable_name.rb +2 -0
  62. data/lib/rubocop/cop/naming/variable_number.rb +1 -8
  63. data/lib/rubocop/cop/registry.rb +10 -0
  64. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
  65. data/lib/rubocop/cop/style/character_literal.rb +10 -11
  66. data/lib/rubocop/cop/style/collection_methods.rb +14 -1
  67. data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
  68. data/lib/rubocop/cop/style/empty_literal.rb +6 -2
  69. data/lib/rubocop/cop/style/endless_method.rb +102 -0
  70. data/lib/rubocop/cop/style/float_division.rb +44 -1
  71. data/lib/rubocop/cop/style/for.rb +2 -0
  72. data/lib/rubocop/cop/style/hash_except.rb +95 -0
  73. data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
  74. data/lib/rubocop/cop/style/if_inside_else.rb +8 -3
  75. data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -0
  76. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  77. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
  78. data/lib/rubocop/cop/style/lambda_call.rb +2 -1
  79. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -0
  80. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
  81. data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
  82. data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
  83. data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
  84. data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
  85. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
  86. data/lib/rubocop/cop/style/perl_backrefs.rb +86 -9
  87. data/lib/rubocop/cop/style/raise_args.rb +5 -2
  88. data/lib/rubocop/cop/style/redundant_argument.rb +21 -2
  89. data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
  90. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  91. data/lib/rubocop/cop/style/redundant_return.rb +1 -1
  92. data/lib/rubocop/cop/style/single_line_block_params.rb +30 -7
  93. data/lib/rubocop/cop/style/single_line_methods.rb +33 -2
  94. data/lib/rubocop/cop/style/sole_nested_conditional.rb +25 -9
  95. data/lib/rubocop/cop/style/special_global_vars.rb +1 -13
  96. data/lib/rubocop/cop/style/string_concatenation.rb +26 -1
  97. data/lib/rubocop/cop/style/string_literals.rb +14 -8
  98. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -3
  99. data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
  100. data/lib/rubocop/cop/util.rb +3 -1
  101. data/lib/rubocop/ext/regexp_node.rb +31 -9
  102. data/lib/rubocop/ext/regexp_parser.rb +21 -3
  103. data/lib/rubocop/formatter/emacs_style_formatter.rb +2 -0
  104. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  105. data/lib/rubocop/formatter/tap_formatter.rb +2 -0
  106. data/lib/rubocop/lockfile.rb +40 -0
  107. data/lib/rubocop/options.rb +9 -9
  108. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  109. data/lib/rubocop/rspec/expect_offense.rb +34 -22
  110. data/lib/rubocop/runner.rb +16 -1
  111. data/lib/rubocop/target_finder.rb +4 -2
  112. data/lib/rubocop/target_ruby.rb +47 -11
  113. data/lib/rubocop/util.rb +16 -0
  114. data/lib/rubocop/version.rb +8 -2
  115. metadata +42 -9
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # 1. This cop matches for method names only and hence cannot tell apart
13
13
  # methods with same name in different classes.
14
14
  # 2. This cop is limited to methods with single parameter.
15
- # 3. This cop is unsafe if certain special global variables (e.g. `$;`) are set.
15
+ # 3. This cop is unsafe if certain special global variables (e.g. `$;`, `$/`) are set.
16
16
  # That depends on the nature of the target methods, of course.
17
17
  #
18
18
  # Method names and their redundant arguments can be configured like this:
@@ -20,6 +20,8 @@ module RuboCop
20
20
  # Methods:
21
21
  # join: ''
22
22
  # split: ' '
23
+ # chomp: "\n"
24
+ # chomp!: "\n"
23
25
  # foo: 2
24
26
  #
25
27
  # @example
@@ -28,6 +30,8 @@ module RuboCop
28
30
  # [1, 2, 3].join("")
29
31
  # string.split(" ")
30
32
  # "first\nsecond".split(" ")
33
+ # string.chomp("\n")
34
+ # string.chomp!("\n")
31
35
  # A.foo(2)
32
36
  #
33
37
  # # good
@@ -35,8 +39,13 @@ module RuboCop
35
39
  # [1, 2, 3].join
36
40
  # string.split
37
41
  # "first second".split
42
+ # string.chomp
43
+ # string.chomp!
38
44
  # A.foo
39
45
  class RedundantArgument < Base
46
+ include RangeHelp
47
+ extend AutoCorrector
48
+
40
49
  MSG = 'Argument %<arg>s is redundant because it is implied by default.'
41
50
 
42
51
  def on_send(node)
@@ -44,7 +53,9 @@ module RuboCop
44
53
  return if node.arguments.count != 1
45
54
  return unless redundant_argument?(node)
46
55
 
47
- add_offense(node, message: format(MSG, arg: node.arguments.first.source))
56
+ add_offense(node, message: format(MSG, arg: node.arguments.first.source)) do |corrector|
57
+ corrector.remove(argument_range(node))
58
+ end
48
59
  end
49
60
 
50
61
  private
@@ -69,6 +80,14 @@ module RuboCop
69
80
  Parser::CurrentRuby.new(builder).parse(buffer)
70
81
  end
71
82
  end
83
+
84
+ def argument_range(node)
85
+ if node.parenthesized?
86
+ range_between(node.loc.begin.begin_pos, node.loc.end.end_pos)
87
+ else
88
+ range_with_surrounding_space(range: node.first_argument.source_range, newlines: false)
89
+ end
90
+ end
72
91
  end
73
92
  end
74
93
  end
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop check for uses of Object#freeze on immutable objects.
6
+ # This cop check for uses of `Object#freeze` on immutable objects.
7
+ #
8
+ # NOTE: Regexp and Range literals are frozen objects since Ruby 3.0.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -37,8 +39,10 @@ module RuboCop
37
39
 
38
40
  return true if node.immutable_literal?
39
41
 
40
- FROZEN_STRING_LITERAL_TYPES.include?(node.type) &&
41
- frozen_string_literals_enabled?
42
+ return true if FROZEN_STRING_LITERAL_TYPES.include?(node.type) &&
43
+ frozen_string_literals_enabled?
44
+
45
+ target_ruby_version >= 3.0 && (node.regexp_type? || node.range_type?)
42
46
  end
43
47
 
44
48
  def strip_parenthesis(node)
@@ -52,7 +56,7 @@ module RuboCop
52
56
  def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
53
57
  {
54
58
  (begin (send {float int} {:+ :- :* :** :/ :% :<<} _))
55
- (begin (send !(str _) {:+ :- :* :** :/ :%} {float int}))
59
+ (begin (send !{(str _) array} {:+ :- :* :** :/ :%} {float int}))
56
60
  (begin (send _ {:== :=== :!= :<= :>= :< :>} _))
57
61
  (send (const {nil? cbase} :ENV) :[] _)
58
62
  (send _ {:count :length :size} ...)
@@ -80,14 +80,30 @@ module RuboCop
80
80
  delimiters.include?(char)
81
81
  end
82
82
 
83
- def each_escape(node)
84
- node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
85
- yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
86
-
87
- if expr.type == :set
88
- char_class_depth + (event == :enter ? 1 : -1)
89
- else
90
- char_class_depth
83
+ if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0')
84
+ def each_escape(node)
85
+ node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
86
+ yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
87
+
88
+ if expr.type == :set
89
+ char_class_depth + (event == :enter ? 1 : -1)
90
+ else
91
+ char_class_depth
92
+ end
93
+ end
94
+ end
95
+ # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped.
96
+ # It's for compatibility with regexp_arser 1.8 and will never be maintained.
97
+ else
98
+ def each_escape(node)
99
+ node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
100
+ yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
101
+
102
+ if expr.type == :set
103
+ char_class_depth + (event == :enter ? 1 : -1)
104
+ else
105
+ char_class_depth
106
+ end
91
107
  end
92
108
  end
93
109
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
  end
67
67
 
68
68
  def correct_with_arguments(return_node, corrector)
69
- if return_node.arguments.size > 1
69
+ if return_node.children.size > 1
70
70
  add_brackets(corrector, return_node)
71
71
  elsif hash_without_braces?(return_node.first_argument)
72
72
  add_braces(corrector, return_node.first_argument)
@@ -29,6 +29,8 @@ module RuboCop
29
29
  # c + d
30
30
  # end
31
31
  class SingleLineBlockParams < Base
32
+ extend AutoCorrector
33
+
32
34
  MSG = 'Name `%<method>s` block params `|%<params>s|`.'
33
35
 
34
36
  def on_block(node)
@@ -37,20 +39,41 @@ module RuboCop
37
39
  return unless eligible_method?(node)
38
40
  return unless eligible_arguments?(node)
39
41
 
40
- return if args_match?(node.send_node.method_name, node.arguments)
42
+ method_name = node.send_node.method_name
43
+ return if args_match?(method_name, node.arguments)
44
+
45
+ preferred_block_arguments = build_preferred_arguments_map(node, target_args(method_name))
46
+ joined_block_arguments = preferred_block_arguments.values.join(', ')
41
47
 
42
- message = message(node.arguments)
48
+ message = format(MSG, method: method_name, params: joined_block_arguments)
43
49
 
44
- add_offense(node.arguments, message: message)
50
+ add_offense(node.arguments, message: message) do |corrector|
51
+ autocorrect(corrector, node, preferred_block_arguments, joined_block_arguments)
52
+ end
45
53
  end
46
54
 
47
55
  private
48
56
 
49
- def message(node)
50
- method_name = node.parent.send_node.method_name
51
- arguments = target_args(method_name).join(', ')
57
+ def build_preferred_arguments_map(node, preferred_arguments)
58
+ preferred_arguments_map = {}
59
+ node.arguments.each_with_index do |current_lvar, index|
60
+ preferred_argument = preferred_arguments[index]
61
+ current_argument = current_lvar.source
62
+ preferred_argument = "_#{preferred_argument}" if current_argument.start_with?('_')
63
+ preferred_arguments_map[current_argument] = preferred_argument
64
+ end
65
+
66
+ preferred_arguments_map
67
+ end
68
+
69
+ def autocorrect(corrector, node, preferred_block_arguments, joined_block_arguments)
70
+ corrector.replace(node.arguments, "|#{joined_block_arguments}|")
52
71
 
53
- format(MSG, method: method_name, params: arguments)
72
+ node.each_descendant(:lvar) do |lvar|
73
+ if (preferred_lvar = preferred_block_arguments[lvar.source])
74
+ corrector.replace(lvar, preferred_lvar)
75
+ end
76
+ end
54
77
  end
55
78
 
56
79
  def eligible_arguments?(node)
@@ -6,6 +6,12 @@ module RuboCop
6
6
  # This cop checks for single-line method definitions that contain a body.
7
7
  # It will accept single-line methods with no body.
8
8
  #
9
+ # Endless methods added in Ruby 3.0 are also accepted by this cop.
10
+ #
11
+ # If `Style/EndlessMethod` is enabled with `EnforcedStyle: allow` or
12
+ # `allow_always`, single-line methods will be auto-corrected to endless
13
+ # methods if there is only one statement in the body.
14
+ #
9
15
  # @example
10
16
  # # bad
11
17
  # def some_method; body end
@@ -15,6 +21,7 @@ module RuboCop
15
21
  # # good
16
22
  # def self.resource_class=(klass); end
17
23
  # def @table.columns; end
24
+ # def some_method() = body
18
25
  #
19
26
  # @example AllowIfMethodIsEmpty: true (default)
20
27
  # # good
@@ -32,6 +39,7 @@ module RuboCop
32
39
 
33
40
  def on_def(node)
34
41
  return unless node.single_line?
42
+ return if node.endless?
35
43
  return if allow_empty? && !node.body
36
44
 
37
45
  add_offense(node) do |corrector|
@@ -43,6 +51,28 @@ module RuboCop
43
51
  private
44
52
 
45
53
  def autocorrect(corrector, node)
54
+ if correct_to_endless?(node.body)
55
+ correct_to_endless(corrector, node)
56
+ else
57
+ correct_to_multiline(corrector, node)
58
+ end
59
+ end
60
+
61
+ def allow_empty?
62
+ cop_config['AllowIfMethodIsEmpty']
63
+ end
64
+
65
+ def correct_to_endless?(body_node)
66
+ endless_method_config = config.for_cop('Style/EndlessMethod')
67
+
68
+ return false unless endless_method_config['Enabled']
69
+ return false if endless_method_config['EnforcedStyle'] == 'disallow'
70
+ return false unless body_node
71
+
72
+ !(body_node.begin_type? || body_node.kwbegin_type?)
73
+ end
74
+
75
+ def correct_to_multiline(corrector, node)
46
76
  each_part(node.body) do |part|
47
77
  LineBreakCorrector.break_line_before(
48
78
  range: part, node: node, corrector: corrector,
@@ -58,8 +88,9 @@ module RuboCop
58
88
  move_comment(node, corrector)
59
89
  end
60
90
 
61
- def allow_empty?
62
- cop_config['AllowIfMethodIsEmpty']
91
+ def correct_to_endless(corrector, node)
92
+ replacement = "def #{node.method_name}(#{node.arguments.source}) = #{node.body.source}"
93
+ corrector.replace(node, replacement)
63
94
  end
64
95
 
65
96
  def each_part(body)
@@ -68,22 +68,22 @@ module RuboCop
68
68
  corrector.insert_before(node.condition, '!')
69
69
  end
70
70
 
71
+ corrector.wrap(node.condition, '(', ')') if node.condition.or_type?
72
+
71
73
  and_operator = if_branch.unless? ? ' && !' : ' && '
72
74
  if if_branch.modifier_form?
73
- correct_for_gurad_condition_style(corrector, node, if_branch, and_operator)
75
+ correct_for_guard_condition_style(corrector, node, if_branch, and_operator)
74
76
  else
75
77
  correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
78
+ correct_for_comment(corrector, node, if_branch)
76
79
  end
77
-
78
- correct_for_comment(corrector, node, if_branch)
79
80
  end
80
81
 
81
- def correct_for_gurad_condition_style(corrector, node, if_branch, and_operator)
82
- corrector.insert_after(node.condition, "#{and_operator}#{if_branch.condition.source}")
82
+ def correct_for_guard_condition_style(corrector, node, if_branch, and_operator)
83
+ condition = if_branch.condition
84
+ corrector.insert_after(node.condition, replacement_condition(and_operator, condition))
83
85
 
84
- range = range_between(
85
- if_branch.loc.keyword.begin_pos, if_branch.condition.source_range.end_pos
86
- )
86
+ range = range_between(if_branch.loc.keyword.begin_pos, condition.source_range.end_pos)
87
87
  corrector.remove(range_with_surrounding_space(range: range, newlines: false))
88
88
  corrector.remove(if_branch.loc.keyword)
89
89
  end
@@ -94,15 +94,31 @@ module RuboCop
94
94
  )
95
95
  corrector.replace(range, and_operator)
96
96
  corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
97
+ corrector.wrap(if_branch.condition, '(', ')') if wrap_condition?(if_branch.condition)
97
98
  end
98
99
 
99
100
  def correct_for_comment(corrector, node, if_branch)
100
- comments = processed_source.comments_before_line(if_branch.source_range.line)
101
+ return if config.for_cop('Style/IfUnlessModifier')['Enabled']
102
+
103
+ comments = processed_source.ast_with_comments[if_branch]
101
104
  comment_text = comments.map(&:text).join("\n") << "\n"
102
105
 
103
106
  corrector.insert_before(node.loc.keyword, comment_text) unless comments.empty?
104
107
  end
105
108
 
109
+ def wrap_condition?(node)
110
+ node.or_type? ||
111
+ (node.send_type? && node.arguments.any? && !node.parenthesized?)
112
+ end
113
+
114
+ def replacement_condition(and_operator, condition)
115
+ if wrap_condition?(condition)
116
+ "#{and_operator}(#{condition.source})"
117
+ else
118
+ "#{and_operator}#{condition.source}"
119
+ end
120
+ end
121
+
106
122
  def allow_modifier?
107
123
  cop_config['AllowModifier']
108
124
  end
@@ -26,10 +26,6 @@ module RuboCop
26
26
  # puts $LAST_MATCH_INFO
27
27
  # puts $IGNORECASE
28
28
  # puts $ARGV # or ARGV
29
- # puts $MATCH
30
- # puts $PREMATCH
31
- # puts $POSTMATCH
32
- # puts $LAST_PAREN_MATCH
33
29
  #
34
30
  # @example EnforcedStyle: use_perl_names
35
31
  # # good
@@ -51,10 +47,6 @@ module RuboCop
51
47
  # puts $~
52
48
  # puts $=
53
49
  # puts $*
54
- # puts $&
55
- # puts $`
56
- # puts $'
57
- # puts $+
58
50
  #
59
51
  class SpecialGlobalVars < Base
60
52
  include ConfigurableEnforcedStyle
@@ -85,11 +77,7 @@ module RuboCop
85
77
  :$? => [:$CHILD_STATUS],
86
78
  :$~ => [:$LAST_MATCH_INFO],
87
79
  :$= => [:$IGNORECASE],
88
- :$* => %i[$ARGV ARGV],
89
- :$& => [:$MATCH],
90
- :$` => [:$PREMATCH],
91
- :$' => [:$POSTMATCH],
92
- :$+ => [:$LAST_PAREN_MATCH]
80
+ :$* => %i[$ARGV ARGV]
93
81
  }
94
82
 
95
83
  PERL_VARS =
@@ -11,6 +11,10 @@ module RuboCop
11
11
  # In those cases, it might be useful to extract statements to local
12
12
  # variables or methods which you can then interpolate in a string.
13
13
  #
14
+ # NOTE: When concatenation between two strings is broken over multiple
15
+ # lines, this cop does not register an offense; instead,
16
+ # `Style/LineEndConcatenation` will pick up the offense if enabled.
17
+ #
14
18
  # @example
15
19
  # # bad
16
20
  # email_with_name = user.name + ' <' + user.email + '>'
@@ -19,6 +23,10 @@ module RuboCop
19
23
  # email_with_name = "#{user.name} <#{user.email}>"
20
24
  # email_with_name = format('%s <%s>', user.name, user.email)
21
25
  #
26
+ # # accepted, line-end concatenation
27
+ # name = 'First' +
28
+ # 'Last'
29
+ #
22
30
  class StringConcatenation < Base
23
31
  include Util
24
32
  extend AutoCorrector
@@ -39,6 +47,7 @@ module RuboCop
39
47
 
40
48
  def on_send(node)
41
49
  return unless string_concatenation?(node)
50
+ return if line_end_concatenation?(node)
42
51
 
43
52
  topmost_plus_node = find_topmost_plus_node(node)
44
53
 
@@ -58,6 +67,16 @@ module RuboCop
58
67
 
59
68
  private
60
69
 
70
+ def line_end_concatenation?(node)
71
+ # If the concatenation happens at the end of the line,
72
+ # and both the receiver and argument are strings, allow
73
+ # `Style/LineEndConcatenation` to handle it instead.
74
+ node.receiver.str_type? &&
75
+ node.first_argument.str_type? &&
76
+ node.multiline? &&
77
+ node.source =~ /\+\s*\n/
78
+ end
79
+
61
80
  def find_topmost_plus_node(node)
62
81
  current = node
63
82
  while (parent = current.parent) && plus_node?(parent)
@@ -106,7 +125,13 @@ module RuboCop
106
125
  end
107
126
  end
108
127
 
109
- "\"#{interpolated_parts.join}\""
128
+ "\"#{handle_quotes(interpolated_parts).join}\""
129
+ end
130
+
131
+ def handle_quotes(parts)
132
+ parts.map do |part|
133
+ part == '"' ? '\"' : part
134
+ end
110
135
  end
111
136
 
112
137
  def single_quoted?(str_node)
@@ -26,9 +26,10 @@ module RuboCop
26
26
  # "Just some text"
27
27
  # "No special chars or interpolation"
28
28
  # "Every string in #{project} uses double_quotes"
29
- class StringLiterals < Cop
29
+ class StringLiterals < Base
30
30
  include ConfigurableEnforcedStyle
31
31
  include StringLiteralsHelp
32
+ extend AutoCorrector
32
33
 
33
34
  MSG_INCONSISTENT = 'Inconsistent quote style.'
34
35
 
@@ -46,7 +47,7 @@ module RuboCop
46
47
  quote_styles = detect_quote_styles(node)
47
48
 
48
49
  if quote_styles.size > 1
49
- add_offense(node, message: MSG_INCONSISTENT)
50
+ register_offense(node, message: MSG_INCONSISTENT)
50
51
  else
51
52
  check_multiline_quote_style(node, quote_styles[0])
52
53
  end
@@ -54,11 +55,17 @@ module RuboCop
54
55
  ignore_node(node)
55
56
  end
56
57
 
57
- def autocorrect(node)
58
- StringLiteralCorrector.correct(node, style)
58
+ private
59
+
60
+ def autocorrect(corrector, node)
61
+ StringLiteralCorrector.correct(corrector, node, style)
59
62
  end
60
63
 
61
- private
64
+ def register_offense(node, message: nil)
65
+ add_offense(node, message: message || message(node)) do |corrector|
66
+ autocorrect(corrector, node)
67
+ end
68
+ end
62
69
 
63
70
  def all_string_literals?(nodes)
64
71
  nodes.all? { |n| n.str_type? || n.dstr_type? }
@@ -99,14 +106,13 @@ module RuboCop
99
106
  end
100
107
 
101
108
  def check_multiline_quote_style(node, quote)
102
- range = node.source_range
103
109
  children = node.children
104
110
  if unexpected_single_quotes?(quote)
105
111
  all_children_with_quotes = children.all? { |c| wrong_quotes?(c) }
106
- add_offense(node, location: range) if all_children_with_quotes
112
+ register_offense(node) if all_children_with_quotes
107
113
  elsif unexpected_double_quotes?(quote) &&
108
114
  !accept_child_double_quotes?(children)
109
- add_offense(node, location: range)
115
+ register_offense(node)
110
116
  end
111
117
  end
112
118