rubocop 0.31.0 → 0.35.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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +199 -38
  4. data/config/default.yml +91 -12
  5. data/config/disabled.yml +45 -4
  6. data/config/enabled.yml +107 -9
  7. data/lib/rubocop/ast_node.rb +48 -0
  8. data/lib/rubocop/cli.rb +11 -1
  9. data/lib/rubocop/comment_config.rb +4 -1
  10. data/lib/rubocop/config.rb +26 -17
  11. data/lib/rubocop/config_loader.rb +61 -14
  12. data/lib/rubocop/cop/commissioner.rb +7 -12
  13. data/lib/rubocop/cop/cop.rb +43 -20
  14. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  15. data/lib/rubocop/cop/lint/circular_argument_reference.rb +69 -0
  16. data/lib/rubocop/cop/lint/debugger.rb +9 -48
  17. data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
  18. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +42 -23
  19. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
  20. data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
  21. data/lib/rubocop/cop/lint/end_alignment.rb +33 -13
  22. data/lib/rubocop/cop/lint/eval.rb +6 -2
  23. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +175 -0
  24. data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -5
  25. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +10 -0
  26. data/lib/rubocop/cop/lint/nested_method_definition.rb +31 -0
  27. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +19 -1
  28. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  29. data/lib/rubocop/cop/lint/space_before_first_arg.rb +1 -1
  30. data/lib/rubocop/cop/lint/unneeded_disable.rb +72 -0
  31. data/lib/rubocop/cop/lint/unused_block_argument.rb +6 -0
  32. data/lib/rubocop/cop/lint/unused_method_argument.rb +8 -0
  33. data/lib/rubocop/cop/metrics/abc_size.rb +17 -6
  34. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  35. data/lib/rubocop/cop/metrics/method_length.rb +1 -3
  36. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  37. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  38. data/lib/rubocop/cop/mixin/access_modifier_node.rb +1 -1
  39. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
  40. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +28 -4
  41. data/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb +26 -3
  42. data/lib/rubocop/cop/mixin/check_assignment.rb +2 -3
  43. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +59 -12
  44. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -1
  45. data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
  46. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
  47. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +10 -1
  48. data/lib/rubocop/cop/mixin/first_element_line_break.rb +41 -0
  49. data/lib/rubocop/cop/mixin/if_node.rb +10 -0
  50. data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
  51. data/lib/rubocop/cop/mixin/negative_conditional.rb +1 -1
  52. data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
  53. data/lib/rubocop/cop/mixin/safe_assignment.rb +3 -14
  54. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +8 -1
  55. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +8 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +4 -7
  57. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  59. data/lib/rubocop/cop/mixin/surrounding_space.rb +5 -4
  60. data/lib/rubocop/cop/offense.rb +16 -3
  61. data/lib/rubocop/cop/performance/case_when_splat.rb +160 -0
  62. data/lib/rubocop/cop/performance/count.rb +35 -30
  63. data/lib/rubocop/cop/performance/detect.rb +16 -3
  64. data/lib/rubocop/cop/performance/fixed_size.rb +50 -0
  65. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  66. data/lib/rubocop/cop/performance/sample.rb +103 -59
  67. data/lib/rubocop/cop/performance/size.rb +2 -1
  68. data/lib/rubocop/cop/performance/string_replacement.rb +187 -0
  69. data/lib/rubocop/cop/rails/action_filter.rb +31 -5
  70. data/lib/rubocop/cop/rails/date.rb +15 -14
  71. data/lib/rubocop/cop/rails/pluralization_grammar.rb +97 -0
  72. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  73. data/lib/rubocop/cop/rails/time_zone.rb +46 -18
  74. data/lib/rubocop/cop/style/alias.rb +1 -0
  75. data/lib/rubocop/cop/style/align_hash.rb +8 -15
  76. data/lib/rubocop/cop/style/align_parameters.rb +19 -7
  77. data/lib/rubocop/cop/style/and_or.rb +42 -13
  78. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +2 -1
  79. data/lib/rubocop/cop/style/block_comments.rb +4 -2
  80. data/lib/rubocop/cop/style/block_delimiters.rb +69 -24
  81. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +40 -12
  82. data/lib/rubocop/cop/style/case_indentation.rb +18 -4
  83. data/lib/rubocop/cop/style/collection_methods.rb +2 -20
  84. data/lib/rubocop/cop/style/command_literal.rb +2 -10
  85. data/lib/rubocop/cop/style/comment_annotation.rb +29 -8
  86. data/lib/rubocop/cop/style/copyright.rb +5 -3
  87. data/lib/rubocop/cop/style/documentation.rb +21 -12
  88. data/lib/rubocop/cop/style/dot_position.rb +6 -0
  89. data/lib/rubocop/cop/style/double_negation.rb +4 -15
  90. data/lib/rubocop/cop/style/each_with_object.rb +17 -4
  91. data/lib/rubocop/cop/style/else_alignment.rb +2 -1
  92. data/lib/rubocop/cop/style/empty_else.rb +25 -0
  93. data/lib/rubocop/cop/style/empty_line_between_defs.rb +39 -14
  94. data/lib/rubocop/cop/style/encoding.rb +10 -4
  95. data/lib/rubocop/cop/style/extra_spacing.rb +126 -5
  96. data/lib/rubocop/cop/style/first_array_element_line_break.rb +41 -0
  97. data/lib/rubocop/cop/style/first_hash_element_line_break.rb +35 -0
  98. data/lib/rubocop/cop/style/first_method_argument_line_break.rb +37 -0
  99. data/lib/rubocop/cop/style/first_method_parameter_line_break.rb +42 -0
  100. data/lib/rubocop/cop/style/first_parameter_indentation.rb +5 -3
  101. data/lib/rubocop/cop/style/for.rb +2 -1
  102. data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
  103. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -5
  104. data/lib/rubocop/cop/style/indent_hash.rb +67 -37
  105. data/lib/rubocop/cop/style/indentation_width.rb +36 -10
  106. data/lib/rubocop/cop/style/initial_indentation.rb +37 -0
  107. data/lib/rubocop/cop/style/leading_comment_space.rb +3 -2
  108. data/lib/rubocop/cop/style/method_call_parentheses.rb +28 -1
  109. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -7
  110. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +21 -24
  111. data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
  112. data/lib/rubocop/cop/style/nested_modifier.rb +97 -0
  113. data/lib/rubocop/cop/style/next.rb +50 -15
  114. data/lib/rubocop/cop/style/non_nil_check.rb +12 -8
  115. data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
  116. data/lib/rubocop/cop/style/option_hash.rb +64 -0
  117. data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
  118. data/lib/rubocop/cop/style/parallel_assignment.rb +218 -0
  119. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
  120. data/lib/rubocop/cop/style/predicate_name.rb +7 -2
  121. data/lib/rubocop/cop/style/redundant_begin.rb +2 -13
  122. data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
  123. data/lib/rubocop/cop/style/redundant_return.rb +32 -3
  124. data/lib/rubocop/cop/style/regexp_literal.rb +2 -10
  125. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +81 -0
  126. data/lib/rubocop/cop/style/rescue_modifier.rb +30 -22
  127. data/lib/rubocop/cop/style/send.rb +18 -0
  128. data/lib/rubocop/cop/style/signal_exception.rb +24 -11
  129. data/lib/rubocop/cop/style/single_line_methods.rb +8 -9
  130. data/lib/rubocop/cop/style/single_space_before_first_arg.rb +1 -1
  131. data/lib/rubocop/cop/style/space_around_operators.rb +2 -0
  132. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +61 -0
  133. data/lib/rubocop/cop/style/special_global_vars.rb +4 -2
  134. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +108 -0
  135. data/lib/rubocop/cop/style/string_methods.rb +32 -0
  136. data/lib/rubocop/cop/style/struct_inheritance.rb +11 -10
  137. data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
  138. data/lib/rubocop/cop/style/symbol_proc.rb +62 -13
  139. data/lib/rubocop/cop/style/trailing_blank_lines.rb +9 -1
  140. data/lib/rubocop/cop/style/trailing_comma.rb +17 -7
  141. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -2
  142. data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
  143. data/lib/rubocop/cop/style/unneeded_percent_q.rb +31 -20
  144. data/lib/rubocop/cop/style/variable_name.rb +5 -0
  145. data/lib/rubocop/cop/style/while_until_do.rb +1 -1
  146. data/lib/rubocop/cop/style/word_array.rb +15 -2
  147. data/lib/rubocop/cop/team.rb +25 -5
  148. data/lib/rubocop/cop/util.rb +7 -2
  149. data/lib/rubocop/cop/variable_force/locatable.rb +6 -6
  150. data/lib/rubocop/cop/variable_force.rb +10 -10
  151. data/lib/rubocop/formatter/base_formatter.rb +1 -1
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +70 -8
  153. data/lib/rubocop/formatter/formatter_set.rb +27 -1
  154. data/lib/rubocop/formatter/progress_formatter.rb +10 -2
  155. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
  156. data/lib/rubocop/node_pattern.rb +390 -0
  157. data/lib/rubocop/options.rb +148 -81
  158. data/lib/rubocop/processed_source.rb +7 -2
  159. data/lib/rubocop/rake_task.rb +1 -1
  160. data/lib/rubocop/remote_config.rb +60 -0
  161. data/lib/rubocop/result_cache.rb +123 -0
  162. data/lib/rubocop/runner.rb +85 -22
  163. data/lib/rubocop/target_finder.rb +4 -4
  164. data/lib/rubocop/token.rb +2 -1
  165. data/lib/rubocop/version.rb +1 -1
  166. data/lib/rubocop/warning.rb +11 -0
  167. data/lib/rubocop.rb +32 -3
  168. data/relnotes/v0.32.0.md +139 -0
  169. data/relnotes/v0.32.1.md +122 -0
  170. data/relnotes/v0.33.0.md +157 -0
  171. data/relnotes/v0.34.0.md +182 -0
  172. data/relnotes/v0.34.1.md +129 -0
  173. data/relnotes/v0.34.2.md +139 -0
  174. data/relnotes/v0.35.0.md +210 -0
  175. data/rubocop.gemspec +4 -4
  176. metadata +50 -12
  177. data/lib/rubocop/cop/performance/parallel_assignment.rb +0 -79
@@ -5,28 +5,75 @@ module RuboCop
5
5
  # Handles `EnforcedStyle` configuration parameters.
6
6
  module ConfigurableEnforcedStyle
7
7
  def opposite_style_detected
8
- self.config_to_allow_offenses ||=
9
- { parameter_name => alternative_style.to_s }
10
- both_styles_detected if config_to_allow_offenses['Enabled']
8
+ style_detected(alternative_style)
11
9
  end
12
10
 
13
11
  def correct_style_detected
14
- # Enabled:true indicates, later when the opposite style is detected,
15
- # that the correct style is used somewhere.
16
- self.config_to_allow_offenses ||= { 'Enabled' => true }
17
- both_styles_detected if config_to_allow_offenses[parameter_name]
12
+ style_detected(style)
18
13
  end
19
14
 
20
- def both_styles_detected
21
- # Both correct and opposite styles exist.
22
- self.config_to_allow_offenses = { 'Enabled' => false }
15
+ def unexpected_style_detected(unexpected)
16
+ style_detected(unexpected)
17
+ end
18
+
19
+ def ambiguous_style_detected(*possibilities)
20
+ style_detected(possibilities)
23
21
  end
24
22
 
25
- def unrecognized_style_detected
26
- # All we can do is to disable.
23
+ def style_detected(detected)
24
+ # `detected` can be a single style, or an Array of possible styles
25
+ # (if there is more than one which matches the observed code)
26
+
27
+ return if no_acceptable_style?
28
+
29
+ if detected.is_a?(Array)
30
+ detected.map!(&:to_s)
31
+ else
32
+ detected = detected.to_s
33
+ end
34
+
35
+ if !detected_style # we haven't observed any specific style yet
36
+ self.detected_style = detected
37
+ elsif detected_style.is_a?(Array)
38
+ self.detected_style &= [*detected]
39
+ elsif detected.is_a?(Array)
40
+ no_acceptable_style! unless detected.include?(detected_style)
41
+ else
42
+ no_acceptable_style! unless detected_style == detected
43
+ end
44
+ end
45
+
46
+ def no_acceptable_style?
47
+ config_to_allow_offenses['Enabled'] == false
48
+ end
49
+
50
+ def no_acceptable_style!
27
51
  self.config_to_allow_offenses = { 'Enabled' => false }
28
52
  end
29
53
 
54
+ def detected_style
55
+ config_to_allow_offenses[parameter_name]
56
+ end
57
+
58
+ def detected_style=(style)
59
+ if style.nil?
60
+ no_acceptable_style!
61
+ elsif style.is_a?(Array)
62
+ if style.empty?
63
+ no_acceptable_style!
64
+ elsif style.one?
65
+ config_to_allow_offenses[parameter_name] = style[0]
66
+ else
67
+ config_to_allow_offenses[parameter_name] = style
68
+ end
69
+ else
70
+ config_to_allow_offenses[parameter_name] = style
71
+ end
72
+ end
73
+
74
+ alias_method :conflicting_styles_detected, :no_acceptable_style!
75
+ alias_method :unrecognized_style_detected, :no_acceptable_style!
76
+
30
77
  def style
31
78
  s = cop_config[parameter_name]
32
79
  if cop_config['SupportedStyles'].include?(s)
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # appropriate value with --auto-gen-config.
7
7
  module ConfigurableMax
8
8
  def max=(value)
9
- cfg = self.config_to_allow_offenses ||= {}
9
+ cfg = config_to_allow_offenses
10
10
  value = [cfg[parameter_name], value].max if cfg[parameter_name]
11
11
  cfg[parameter_name] = value
12
12
  end
@@ -13,7 +13,7 @@ module RuboCop
13
13
  def check_name(node, name, name_range)
14
14
  return if operator?(name)
15
15
 
16
- if valid_name?(name)
16
+ if valid_name?(node, name)
17
17
  correct_style_detected
18
18
  else
19
19
  add_offense(node, name_range, message(style)) do
@@ -22,9 +22,20 @@ module RuboCop
22
22
  end
23
23
  end
24
24
 
25
- def valid_name?(name)
25
+ def valid_name?(node, name)
26
26
  pattern = (style == :snake_case ? SNAKE_CASE : CAMEL_CASE)
27
- name.match(pattern)
27
+ name.match(pattern) || class_emitter_method?(node, name)
28
+ end
29
+
30
+ # A class emitter method is a singleton method in a class/module, where
31
+ # the method has the same name as a class defined in the class/module.
32
+ def class_emitter_method?(node, name)
33
+ return false unless node.defs_type?
34
+ return false unless node.parent
35
+
36
+ node.parent.children.compact.any? do |c|
37
+ c.class_type? && c.loc.name.is?(name.to_s)
38
+ end
28
39
  end
29
40
  end
30
41
  end
@@ -34,9 +34,7 @@ module RuboCop
34
34
  def check_source(start_line, end_line)
35
35
  case style
36
36
  when :no_empty_lines
37
- check_both(start_line, end_line, MSG_EXTRA) do |line|
38
- line.empty?
39
- end
37
+ check_both(start_line, end_line, MSG_EXTRA, &:empty?)
40
38
  when :empty_lines
41
39
  check_both(start_line, end_line, MSG_MISSING) do |line|
42
40
  !line.empty?
@@ -6,7 +6,7 @@ module RuboCop
6
6
  module EndKeywordAlignment
7
7
  include ConfigurableEnforcedStyle
8
8
 
9
- MSG = '`end` at %d, %d is not aligned with `%s` at %d, %d'
9
+ MSG = '`end` at %d, %d is not aligned with `%s` at %d, %d.'
10
10
 
11
11
  private
12
12
 
@@ -38,6 +38,15 @@ module RuboCop
38
38
  'AlignWith'
39
39
  end
40
40
 
41
+ def variable_alignment?(whole_expression, rhs, end_alignment_style)
42
+ end_alignment_style == :variable &&
43
+ !line_break_before_keyword?(whole_expression, rhs)
44
+ end
45
+
46
+ def line_break_before_keyword?(whole_expression, rhs)
47
+ rhs.loc.line > whole_expression.line
48
+ end
49
+
41
50
  def align(node, alignment_node)
42
51
  source_buffer = node.loc.expression.source_buffer
43
52
  begin_pos = node.loc.end.begin_pos
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for checking for a line break before the first
6
+ # element in a multi-line collection.
7
+ module FirstElementLineBreak
8
+ def autocorrect(node)
9
+ ->(corrector) { corrector.insert_before(node.loc.expression, "\n") }
10
+ end
11
+
12
+ private
13
+
14
+ def check_method_line_break(node, children)
15
+ return if children.empty?
16
+
17
+ return unless method_uses_parens?(node, children.first)
18
+
19
+ check_children_line_break(node, children)
20
+ end
21
+
22
+ def method_uses_parens?(node, limit)
23
+ source = node.loc.expression.source_line[0...limit.loc.column]
24
+ source =~ /\s*\(\s*$/
25
+ end
26
+
27
+ def check_children_line_break(node, children, start = node)
28
+ return if children.size < 2
29
+
30
+ line = start.loc.line
31
+ min = children.min_by { |n| n.loc.first_line }
32
+ return if line != min.loc.first_line
33
+
34
+ max = children.max_by { |n| n.loc.last_line }
35
+ return if line == max.loc.last_line
36
+
37
+ add_offense(min, :expression, self.class::MSG)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -22,6 +22,16 @@ module RuboCop
22
22
  def if_else?(node)
23
23
  node.loc.respond_to?(:else) && node.loc.else
24
24
  end
25
+
26
+ def if_node_parts(node)
27
+ case node.loc.keyword.source
28
+ when 'if', 'elsif' then condition, body, else_clause = *node
29
+ when 'unless' then condition, else_clause, body = *node
30
+ else condition, body = *node
31
+ end
32
+
33
+ [condition, body, else_clause]
34
+ end
25
35
  end
26
36
  end
27
37
  end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common code for cops that deal with preferred methods.
6
+ module MethodPreference
7
+ def preferred_method(method)
8
+ preferred_methods[method.to_sym]
9
+ end
10
+
11
+ def preferred_methods
12
+ @preferred_methods ||=
13
+ begin
14
+ # Make sure default configuration 'foo' => 'bar' is removed from
15
+ # the total configuration if there is a 'bar' => 'foo' override.
16
+ default = default_cop_config['PreferredMethods']
17
+ merged = cop_config['PreferredMethods']
18
+ overrides = merged.values - default.values
19
+ merged.reject { |key, _| overrides.include?(key) }.symbolize_keys
20
+ end
21
+ end
22
+
23
+ def default_cop_config
24
+ ConfigLoader.default_configuration[cop_name]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -8,7 +8,7 @@ module RuboCop
8
8
  def check_negative_conditional(node)
9
9
  condition, _body, _rest = *node
10
10
 
11
- # Look at last expression of contents if there's a parenthesis
11
+ # Look at last expression of contents if there are parentheses
12
12
  # around condition.
13
13
  condition = condition.children.last while condition.type == :begin
14
14
  return unless condition.type == :send
@@ -19,12 +19,11 @@ module RuboCop
19
19
  # Returns true for constructs such as
20
20
  # private def my_method
21
21
  # which are allowed in Ruby 2.1 and later.
22
- def visibility_and_def_on_same_line?(receiver, method_name, args)
22
+ def modifier_and_def_on_same_line?(receiver, method_name, args)
23
23
  !receiver &&
24
- [:public, :protected, :private,
25
- :private_class_method, :public_class_method,
26
- :module_function].include?(method_name) &&
27
- args.size == 1 && [:def, :defs].include?(args.first.type)
24
+ method_name != :def &&
25
+ args.size == 1 &&
26
+ [:def, :defs].include?(args.first.type)
28
27
  end
29
28
  end
30
29
  end
@@ -6,21 +6,10 @@ module RuboCop
6
6
  # putting parentheses around an assignment to indicate "I know I'm using an
7
7
  # assignment as a condition. It's not a mistake."
8
8
  module SafeAssignment
9
- def safe_assignment?(node)
10
- return false unless node.type == :begin
11
- return false unless node.children.size == 1
9
+ extend NodePattern::Macros
12
10
 
13
- child = node.children.first
14
- case child.type
15
- when *Util::EQUALS_ASGN_NODES
16
- true
17
- when :send
18
- _receiver, method_name, _args = *child
19
- method_name.to_s.end_with?('=')
20
- else
21
- false
22
- end
23
- end
11
+ def_node_matcher :safe_assignment?,
12
+ '(begin {equals_asgn? asgn_method_call?})'
24
13
 
25
14
  def safe_assignment_allowed?
26
15
  cop_config['AllowSafeAssignment']
@@ -11,12 +11,19 @@ module RuboCop
11
11
  processed_source.tokens.each_cons(2) do |t1, t2|
12
12
  next unless kind(t1) && t1.pos.line == t2.pos.line &&
13
13
  t2.pos.column == t1.pos.column + offset &&
14
- ![:tRPAREN, :tRBRACK, :tPIPE].include?(t2.type)
14
+ ![:tRPAREN, :tRBRACK, :tPIPE].include?(t2.type) &&
15
+ !(t2.type == :tRCURLY && space_forbidden_before_rcurly?)
15
16
 
16
17
  add_offense(t1, t1.pos, format(MSG, kind(t1)))
17
18
  end
18
19
  end
19
20
 
21
+ def space_forbidden_before_rcurly?
22
+ cfg = config.for_cop('Style/SpaceInsideBlockBraces')
23
+ style = cfg['Enabled'] ? cfg['EnforcedStyle'] : 'space'
24
+ style == 'no_space'
25
+ end
26
+
20
27
  # The normal offset, i.e., the distance from the punctuation
21
28
  # token where a space should be, is 1.
22
29
  def offset
@@ -10,7 +10,8 @@ module RuboCop
10
10
  def investigate(processed_source)
11
11
  processed_source.tokens.each_cons(2) do |t1, t2|
12
12
  next unless kind(t2) && t1.pos.line == t2.pos.line &&
13
- t2.pos.begin_pos > t1.pos.end_pos
13
+ t2.pos.begin_pos > t1.pos.end_pos &&
14
+ !(t1.type == :tLCURLY && space_required_after_lcurly?)
14
15
  buffer = processed_source.buffer
15
16
  pos_before_punctuation = Parser::Source::Range.new(buffer,
16
17
  t1.pos.end_pos,
@@ -22,6 +23,12 @@ module RuboCop
22
23
  end
23
24
  end
24
25
 
26
+ def space_required_after_lcurly?
27
+ cfg = config.for_cop('Style/SpaceInsideBlockBraces')
28
+ style = cfg['Enabled'] ? cfg['EnforcedStyle'] : 'space'
29
+ style == 'space'
30
+ end
31
+
25
32
  def autocorrect(pos_before_punctuation)
26
33
  ->(corrector) { corrector.remove(pos_before_punctuation) }
27
34
  end
@@ -7,13 +7,10 @@ module RuboCop
7
7
  include IfNode
8
8
 
9
9
  def fit_within_line_as_modifier_form?(node)
10
- case node.loc.keyword.source
11
- when 'if' then cond, body, _else = *node
12
- when 'unless' then cond, _else, body = *node
13
- else cond, body = *node
14
- end
10
+ cond, body, _else = if_node_parts(node)
15
11
 
16
12
  return false if length(node) > 3
13
+ return false if body && body.begin_type? # multiple statements
17
14
 
18
15
  body_length = body_length(body)
19
16
 
@@ -33,7 +30,7 @@ module RuboCop
33
30
  end
34
31
 
35
32
  def max_line_length
36
- cop_config && cop_config['MaxLineLength'] ||
33
+ cop_config['MaxLineLength'] ||
37
34
  config.for_cop('Metrics/LineLength')['Max']
38
35
  end
39
36
 
@@ -58,7 +55,7 @@ module RuboCop
58
55
  end
59
56
 
60
57
  def comment_lines
61
- @comment_lines ||= processed_source.comments.map(&:location).map(&:line)
58
+ @comment_lines ||= processed_source.comments.map { |c| c.location.line }
62
59
  end
63
60
  end
64
61
  end
@@ -31,7 +31,7 @@ module RuboCop
31
31
  def inside_interpolation?(node)
32
32
  # A :begin node inside a :dstr node is an interpolation.
33
33
  begin_found = false
34
- node.each_ancestor.find do |a|
34
+ node.each_ancestor.any? do |a|
35
35
  begin_found = true if a.type == :begin
36
36
  begin_found && a.type == :dstr
37
37
  end
@@ -12,7 +12,7 @@ module RuboCop
12
12
  if style == :single_quotes
13
13
  src !~ /'/ && src !~ StringHelp::ESCAPED_CHAR_REGEXP
14
14
  else
15
- src !~ /" | \\/x
15
+ src !~ /" | \\ | \#/x
16
16
  end
17
17
  end
18
18
 
@@ -17,13 +17,13 @@ module RuboCop
17
17
 
18
18
  def index_of_first_token(node)
19
19
  b = node.loc.expression.begin
20
- token_table[[b.line, b.column]]
20
+ token_table[b.line][b.column]
21
21
  end
22
22
 
23
23
  def index_of_last_token(node)
24
24
  e = node.loc.expression.end
25
- (0...e.column).to_a.reverse.find do |c|
26
- ix = token_table[[e.line, c]]
25
+ (0...e.column).to_a.reverse_each do |c|
26
+ ix = token_table[e.line][c]
27
27
  return ix if ix
28
28
  end
29
29
  end
@@ -32,7 +32,8 @@ module RuboCop
32
32
  @token_table ||= begin
33
33
  table = {}
34
34
  @processed_source.tokens.each_with_index do |t, ix|
35
- table[[t.pos.line, t.pos.column]] = ix
35
+ table[t.pos.line] ||= {}
36
+ table[t.pos.line][t.pos.column] = ix
36
37
  end
37
38
  table
38
39
  end
@@ -56,16 +56,29 @@ module RuboCop
56
56
  #
57
57
  # @return [Boolean]
58
58
  # whether this offense is automatically corrected.
59
- attr_reader :corrected
59
+ def corrected
60
+ @status == :unsupported ? nil : @status == :corrected
61
+ end
60
62
  alias_method :corrected?, :corrected
61
63
 
64
+ # @api public
65
+ #
66
+ # @!attribute [r] disabled?
67
+ #
68
+ # @return [Boolean]
69
+ # whether this offense was locally disabled where it occurred
70
+ def disabled?
71
+ @status == :disabled
72
+ end
73
+
62
74
  # @api private
63
- def initialize(severity, location, message, cop_name, corrected = false)
75
+ def initialize(severity, location, message, cop_name,
76
+ status = :uncorrected)
64
77
  @severity = RuboCop::Cop::Severity.new(severity)
65
78
  @location = location
66
79
  @message = message.freeze
67
80
  @cop_name = cop_name.freeze
68
- @corrected = corrected
81
+ @status = status
69
82
  freeze
70
83
  end
71
84
 
@@ -0,0 +1,160 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # Place `when` conditions that use splat at the end
7
+ # of the list of `when` branches.
8
+ #
9
+ # Ruby has to allocate memory for the splat expansion every time
10
+ # that the `case` `when` statement is run. Since Ruby does not support
11
+ # fall through inside of `case` `when`, like some other languages do,
12
+ # the order of the `when` branches does not matter. By placing any
13
+ # splat expansions at the end of the list of `when` branches we will
14
+ # reduce the number of times that memory has to be allocated for
15
+ # the expansion.
16
+ #
17
+ # This is not a guaranteed performance improvement. If the data being
18
+ # processed by the `case` condition is normalized in a manner that favors
19
+ # hitting a condition in the splat expansion, it is possible that
20
+ # moving the splat condition to the end will use more memory,
21
+ # and run slightly slower.
22
+ #
23
+ # @example
24
+ # # bad
25
+ # case foo
26
+ # when *condition
27
+ # bar
28
+ # when baz
29
+ # foobar
30
+ # end
31
+ #
32
+ # case foo
33
+ # when *[1, 2, 3, 4]
34
+ # bar
35
+ # when 5
36
+ # baz
37
+ # end
38
+ #
39
+ # # good
40
+ # case foo
41
+ # when baz
42
+ # foobar
43
+ # when *condition
44
+ # bar
45
+ # end
46
+ #
47
+ # case foo
48
+ # when 1, 2, 3, 4
49
+ # bar
50
+ # when 5
51
+ # baz
52
+ # end
53
+ class CaseWhenSplat < Cop
54
+ include AutocorrectAlignment
55
+
56
+ MSG = 'Place `when` conditions with a splat ' \
57
+ 'at the end of the `when` branches.'.freeze
58
+ ARRAY_MSG = 'Do not expand array literals in `when` conditions.'.freeze
59
+ OPEN_BRACKET = '['.freeze
60
+ PERCENT_W = '%w'.freeze
61
+ PERCENT_CAPITAL_W = '%W'.freeze
62
+ PERCENT_I = '%i'.freeze
63
+ PERCENT_CAPITAL_I = '%I'.freeze
64
+
65
+ def on_case(node)
66
+ _case_branch, *when_branches, _else_branch = *node
67
+ when_conditions =
68
+ when_branches.each_with_object([]) do |branch, conditions|
69
+ condition, = *branch
70
+ conditions << condition
71
+ end
72
+
73
+ splat_offenses(when_conditions).reverse_each do |condition|
74
+ range = condition.parent.loc.keyword.join(condition.loc.expression)
75
+ variable, = *condition
76
+ message = variable.array_type? ? ARRAY_MSG : MSG
77
+ add_offense(condition.parent, range, message)
78
+ end
79
+ end
80
+
81
+ def autocorrect(node)
82
+ condition, = *node
83
+ variable, = *condition
84
+ if variable.array_type?
85
+ correct_array_literal(condition, variable)
86
+ else
87
+ reorder_splat_condition(node)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def splat_offenses(when_conditions)
94
+ found_non_splat = false
95
+ when_conditions.reverse.each_with_object([]) do |condition, result|
96
+ found_non_splat ||= true if error_condition?(condition)
97
+
98
+ next unless condition.splat_type?
99
+ result << condition if found_non_splat
100
+ end
101
+ end
102
+
103
+ def error_condition?(condition)
104
+ variable, = *condition
105
+
106
+ (condition.splat_type? && variable.array_type?) ||
107
+ !condition.splat_type?
108
+ end
109
+
110
+ def correct_array_literal(condition, variable)
111
+ lambda do |corrector|
112
+ array_start = variable.loc.begin.source
113
+
114
+ if array_start.start_with?(OPEN_BRACKET)
115
+ corrector.remove(condition.loc.operator)
116
+ corrector.remove(variable.loc.begin)
117
+ corrector.remove(variable.loc.end)
118
+ else
119
+ corrector.replace(condition.loc.expression,
120
+ expand_percent_array(variable))
121
+ end
122
+ end
123
+ end
124
+
125
+ def reorder_splat_condition(node)
126
+ _case_branch, *when_branches, _else_branch = *node.parent
127
+ current_index = when_branches.index { |branch| branch == node }
128
+ next_branch = when_branches[current_index + 1]
129
+ correction = "\n#{offset(node)}#{node.loc.expression.source}"
130
+ range =
131
+ Parser::Source::Range.new(node.parent,
132
+ node.loc.expression.begin_pos,
133
+ next_branch.loc.expression.begin_pos)
134
+
135
+ lambda do |corrector|
136
+ corrector.remove(range)
137
+ corrector.insert_after(when_branches.last.loc.expression,
138
+ correction)
139
+ end
140
+ end
141
+
142
+ def expand_percent_array(array)
143
+ array_start = array.loc.begin.source
144
+ elements = *array
145
+ elements = elements.map { |e| e.loc.expression.source }
146
+
147
+ if array_start.start_with?(PERCENT_W)
148
+ "'#{elements.join("', '")}'"
149
+ elsif array_start.start_with?(PERCENT_CAPITAL_W)
150
+ %("#{elements.join('", "')}")
151
+ elsif array_start.start_with?(PERCENT_I)
152
+ ":#{elements.join(', :')}"
153
+ elsif array_start.start_with?(PERCENT_CAPITAL_I)
154
+ %(:"#{elements.join('", :"')}")
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end