rubocop 0.48.1 → 0.49.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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/config/default.yml +397 -357
  4. data/config/disabled.yml +29 -29
  5. data/config/enabled.yml +366 -326
  6. data/lib/rubocop.rb +85 -70
  7. data/lib/rubocop/ast/builder.rb +4 -1
  8. data/lib/rubocop/ast/node.rb +2 -2
  9. data/lib/rubocop/ast/node/and_node.rb +1 -1
  10. data/lib/rubocop/ast/node/args_node.rb +24 -0
  11. data/lib/rubocop/ast/node/block_node.rb +107 -0
  12. data/lib/rubocop/ast/node/case_node.rb +1 -1
  13. data/lib/rubocop/ast/node/ensure_node.rb +1 -1
  14. data/lib/rubocop/ast/node/for_node.rb +1 -1
  15. data/lib/rubocop/ast/node/if_node.rb +1 -1
  16. data/lib/rubocop/ast/node/mixin/parameterized_node.rb +74 -0
  17. data/lib/rubocop/ast/node/or_node.rb +1 -1
  18. data/lib/rubocop/ast/node/pair_node.rb +1 -1
  19. data/lib/rubocop/ast/node/resbody_node.rb +1 -1
  20. data/lib/rubocop/ast/node/send_node.rb +36 -57
  21. data/lib/rubocop/ast/node/super_node.rb +42 -0
  22. data/lib/rubocop/ast/node/until_node.rb +1 -1
  23. data/lib/rubocop/ast/node/when_node.rb +1 -1
  24. data/lib/rubocop/ast/node/while_node.rb +1 -1
  25. data/lib/rubocop/cli.rb +10 -0
  26. data/lib/rubocop/config.rb +23 -7
  27. data/lib/rubocop/config_loader.rb +19 -3
  28. data/lib/rubocop/cop/badge.rb +1 -1
  29. data/lib/rubocop/cop/bundler/duplicated_gem.rb +2 -2
  30. data/lib/rubocop/cop/commissioner.rb +1 -1
  31. data/lib/rubocop/cop/cop.rb +10 -0
  32. data/lib/rubocop/cop/{style → layout}/access_modifier_indentation.rb +33 -3
  33. data/lib/rubocop/cop/{style → layout}/align_array.rb +16 -1
  34. data/lib/rubocop/cop/{style → layout}/align_hash.rb +1 -1
  35. data/lib/rubocop/cop/{style → layout}/align_parameters.rb +29 -1
  36. data/lib/rubocop/cop/{style → layout}/block_end_newline.rb +10 -5
  37. data/lib/rubocop/cop/{style → layout}/case_indentation.rb +64 -1
  38. data/lib/rubocop/cop/{style → layout}/closing_parenthesis_indentation.rb +2 -2
  39. data/lib/rubocop/cop/{style → layout}/comment_indentation.rb +1 -1
  40. data/lib/rubocop/cop/{style → layout}/dot_position.rb +1 -1
  41. data/lib/rubocop/cop/{style → layout}/else_alignment.rb +1 -1
  42. data/lib/rubocop/cop/{style → layout}/empty_line_after_magic_comment.rb +1 -1
  43. data/lib/rubocop/cop/{style → layout}/empty_line_between_defs.rb +1 -1
  44. data/lib/rubocop/cop/{style → layout}/empty_lines.rb +1 -1
  45. data/lib/rubocop/cop/{style → layout}/empty_lines_around_access_modifier.rb +2 -7
  46. data/lib/rubocop/cop/{style → layout}/empty_lines_around_begin_body.rb +1 -1
  47. data/lib/rubocop/cop/{style → layout}/empty_lines_around_block_body.rb +2 -4
  48. data/lib/rubocop/cop/{style → layout}/empty_lines_around_class_body.rb +1 -1
  49. data/lib/rubocop/cop/{style → layout}/empty_lines_around_exception_handling_keywords.rb +1 -1
  50. data/lib/rubocop/cop/{style → layout}/empty_lines_around_method_body.rb +1 -1
  51. data/lib/rubocop/cop/{style → layout}/empty_lines_around_module_body.rb +1 -1
  52. data/lib/rubocop/cop/{style → layout}/end_of_line.rb +1 -1
  53. data/lib/rubocop/cop/{style → layout}/extra_spacing.rb +1 -1
  54. data/lib/rubocop/cop/{style → layout}/first_array_element_line_break.rb +1 -1
  55. data/lib/rubocop/cop/{style → layout}/first_hash_element_line_break.rb +1 -1
  56. data/lib/rubocop/cop/{style → layout}/first_method_argument_line_break.rb +1 -1
  57. data/lib/rubocop/cop/{style → layout}/first_method_parameter_line_break.rb +1 -1
  58. data/lib/rubocop/cop/{style → layout}/first_parameter_indentation.rb +1 -1
  59. data/lib/rubocop/cop/{style → layout}/indent_array.rb +1 -1
  60. data/lib/rubocop/cop/{style → layout}/indent_assignment.rb +1 -1
  61. data/lib/rubocop/cop/{style → layout}/indent_hash.rb +2 -2
  62. data/lib/rubocop/cop/{style → layout}/indent_heredoc.rb +3 -3
  63. data/lib/rubocop/cop/{style → layout}/indentation_consistency.rb +1 -1
  64. data/lib/rubocop/cop/{style → layout}/indentation_width.rb +10 -12
  65. data/lib/rubocop/cop/{style → layout}/initial_indentation.rb +1 -1
  66. data/lib/rubocop/cop/{style → layout}/leading_comment_space.rb +1 -1
  67. data/lib/rubocop/cop/{style → layout}/multiline_array_brace_layout.rb +1 -1
  68. data/lib/rubocop/cop/{style → layout}/multiline_assignment_layout.rb +1 -1
  69. data/lib/rubocop/cop/{style → layout}/multiline_block_layout.rb +21 -36
  70. data/lib/rubocop/cop/{style → layout}/multiline_hash_brace_layout.rb +5 -1
  71. data/lib/rubocop/cop/{style → layout}/multiline_method_call_brace_layout.rb +1 -1
  72. data/lib/rubocop/cop/{style → layout}/multiline_method_call_indentation.rb +3 -3
  73. data/lib/rubocop/cop/{style → layout}/multiline_method_definition_brace_layout.rb +1 -1
  74. data/lib/rubocop/cop/{style → layout}/multiline_operation_indentation.rb +6 -5
  75. data/lib/rubocop/cop/{style → layout}/rescue_ensure_alignment.rb +1 -1
  76. data/lib/rubocop/cop/{style → layout}/space_after_colon.rb +2 -2
  77. data/lib/rubocop/cop/{style → layout}/space_after_comma.rb +2 -2
  78. data/lib/rubocop/cop/{style → layout}/space_after_method_name.rb +1 -1
  79. data/lib/rubocop/cop/{style → layout}/space_after_not.rb +1 -1
  80. data/lib/rubocop/cop/{style → layout}/space_after_semicolon.rb +2 -2
  81. data/lib/rubocop/cop/{style → layout}/space_around_block_parameters.rb +7 -5
  82. data/lib/rubocop/cop/{style → layout}/space_around_equals_in_parameter_default.rb +1 -1
  83. data/lib/rubocop/cop/{style → layout}/space_around_keyword.rb +1 -1
  84. data/lib/rubocop/cop/{style → layout}/space_around_operators.rb +6 -2
  85. data/lib/rubocop/cop/{style → layout}/space_before_block_braces.rb +6 -2
  86. data/lib/rubocop/cop/{style → layout}/space_before_comma.rb +1 -1
  87. data/lib/rubocop/cop/{style → layout}/space_before_comment.rb +1 -1
  88. data/lib/rubocop/cop/{style → layout}/space_before_first_arg.rb +4 -2
  89. data/lib/rubocop/cop/{style → layout}/space_before_semicolon.rb +1 -1
  90. data/lib/rubocop/cop/{style → layout}/space_in_lambda_literal.rb +1 -1
  91. data/lib/rubocop/cop/{style → layout}/space_inside_array_percent_literal.rb +1 -1
  92. data/lib/rubocop/cop/{style → layout}/space_inside_block_braces.rb +3 -4
  93. data/lib/rubocop/cop/{style → layout}/space_inside_brackets.rb +1 -1
  94. data/lib/rubocop/cop/{style → layout}/space_inside_hash_literal_braces.rb +1 -1
  95. data/lib/rubocop/cop/{style → layout}/space_inside_parens.rb +1 -1
  96. data/lib/rubocop/cop/{style → layout}/space_inside_percent_literal_delimiters.rb +8 -7
  97. data/lib/rubocop/cop/{style → layout}/space_inside_range_literal.rb +1 -1
  98. data/lib/rubocop/cop/{style → layout}/space_inside_string_interpolation.rb +1 -1
  99. data/lib/rubocop/cop/{style → layout}/tab.rb +1 -1
  100. data/lib/rubocop/cop/{style → layout}/trailing_blank_lines.rb +1 -1
  101. data/lib/rubocop/cop/{style → layout}/trailing_whitespace.rb +2 -2
  102. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  103. data/lib/rubocop/cop/lint/ambiguous_operator.rb +4 -4
  104. data/lib/rubocop/cop/lint/debugger.rb +0 -15
  105. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -1
  106. data/lib/rubocop/cop/lint/rescue_type.rb +81 -0
  107. data/lib/rubocop/cop/lint/script_permission.rb +42 -0
  108. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -1
  109. data/lib/rubocop/cop/message_annotator.rb +23 -13
  110. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  111. data/lib/rubocop/cop/mixin/array_min_size.rb +59 -0
  112. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +10 -11
  113. data/lib/rubocop/cop/mixin/def_node.rb +1 -1
  114. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -1
  115. data/lib/rubocop/cop/mixin/enforce_superclass.rb +36 -0
  116. data/lib/rubocop/cop/mixin/hash_alignment.rb +1 -1
  117. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +7 -3
  118. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  119. data/lib/rubocop/cop/performance/caller.rb +41 -0
  120. data/lib/rubocop/cop/performance/compare_with_block.rb +60 -14
  121. data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
  122. data/lib/rubocop/cop/performance/redundant_merge.rb +2 -0
  123. data/lib/rubocop/cop/rails/action_filter.rb +1 -3
  124. data/lib/rubocop/cop/rails/application_job.rb +32 -0
  125. data/lib/rubocop/cop/rails/application_record.rb +32 -0
  126. data/lib/rubocop/cop/rails/blank.rb +9 -3
  127. data/lib/rubocop/cop/rails/output_safety.rb +59 -15
  128. data/lib/rubocop/cop/rails/present.rb +9 -3
  129. data/lib/rubocop/cop/rails/relative_date_constant.rb +35 -4
  130. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -18
  131. data/lib/rubocop/cop/rails/save_bang.rb +7 -2
  132. data/lib/rubocop/cop/rails/skips_model_validations.rb +7 -0
  133. data/lib/rubocop/cop/registry.rb +4 -3
  134. data/lib/rubocop/cop/security/eval.rb +9 -3
  135. data/lib/rubocop/cop/style/and_or.rb +1 -1
  136. data/lib/rubocop/cop/style/block_delimiters.rb +11 -17
  137. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +1 -1
  138. data/lib/rubocop/cop/style/collection_methods.rb +1 -3
  139. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  140. data/lib/rubocop/cop/style/copyright.rb +2 -2
  141. data/lib/rubocop/cop/style/documentation_method.rb +1 -1
  142. data/lib/rubocop/cop/style/each_for_simple_loop.rb +2 -1
  143. data/lib/rubocop/cop/style/each_with_object.rb +10 -6
  144. data/lib/rubocop/cop/style/empty_case_condition.rb +2 -2
  145. data/lib/rubocop/cop/style/for.rb +4 -5
  146. data/lib/rubocop/cop/style/format_string.rb +49 -0
  147. data/lib/rubocop/cop/style/format_string_token.rb +141 -0
  148. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  149. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -2
  150. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -1
  151. data/lib/rubocop/cop/style/inverse_methods.rb +10 -1
  152. data/lib/rubocop/cop/style/lambda.rb +9 -9
  153. data/lib/rubocop/cop/style/line_end_concatenation.rb +4 -0
  154. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -3
  155. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +1 -2
  156. data/lib/rubocop/cop/style/method_name.rb +8 -2
  157. data/lib/rubocop/cop/style/mixin_grouping.rb +41 -3
  158. data/lib/rubocop/cop/style/multiline_block_chain.rb +7 -11
  159. data/lib/rubocop/cop/style/multiple_comparison.rb +77 -0
  160. data/lib/rubocop/cop/style/next.rb +11 -22
  161. data/lib/rubocop/cop/style/parallel_assignment.rb +10 -19
  162. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -2
  163. data/lib/rubocop/cop/style/self_assignment.rb +4 -0
  164. data/lib/rubocop/cop/style/single_line_block_params.rb +23 -17
  165. data/lib/rubocop/cop/style/symbol_array.rb +24 -13
  166. data/lib/rubocop/cop/style/symbol_proc.rb +4 -0
  167. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  168. data/lib/rubocop/cop/style/unneeded_interpolation.rb +4 -0
  169. data/lib/rubocop/cop/style/word_array.rb +33 -53
  170. data/lib/rubocop/cop/style/yoda_condition.rb +78 -0
  171. data/lib/rubocop/cop/team.rb +1 -14
  172. data/lib/rubocop/cop/util.rb +16 -0
  173. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -11
  174. data/lib/rubocop/node_pattern.rb +52 -52
  175. data/lib/rubocop/options.rb +25 -0
  176. data/lib/rubocop/path_util.rb +17 -1
  177. data/lib/rubocop/result_cache.rb +8 -7
  178. data/lib/rubocop/rspec/expect_offense.rb +167 -0
  179. data/lib/rubocop/rspec/shared_examples.rb +0 -8
  180. data/lib/rubocop/rspec/support.rb +1 -0
  181. data/lib/rubocop/runner.rb +12 -2
  182. data/lib/rubocop/target_finder.rb +5 -0
  183. data/lib/rubocop/version.rb +1 -1
  184. metadata +101 -72
@@ -12,6 +12,7 @@ module RuboCop
12
12
  # array.sort { |a, b| a.foo <=> b.foo }
13
13
  # array.max { |a, b| a.foo <=> b.foo }
14
14
  # array.min { |a, b| a.foo <=> b.foo }
15
+ # array.sort { |a, b| a[:foo] <=> b[:foo] }
15
16
  #
16
17
  # @good
17
18
  # array.sort_by(&:foo)
@@ -21,37 +22,82 @@ module RuboCop
21
22
  # end
22
23
  # array.max_by(&:foo)
23
24
  # array.min_by(&:foo)
25
+ # array.sort_by { |a| a[:foo] }
24
26
  class CompareWithBlock < Cop
25
- MSG = 'Use `%s_by(&:%s)` instead of ' \
26
- '`%s { |%s, %s| %s.%s <=> %s.%s }`.'.freeze
27
+ MSG = 'Use `%s_by%s` instead of ' \
28
+ '`%s { |%s, %s| %s <=> %s }`.'.freeze
27
29
 
28
30
  def_node_matcher :compare?, <<-END
29
- (block $(send _ {:sort :min :max}) (args (arg $_a) (arg $_b)) (send (send (lvar _a) $_m) :<=> (send (lvar _b) $_m)))
31
+ (block
32
+ $(send _ {:sort :min :max})
33
+ (args (arg $_a) (arg $_b))
34
+ $send)
35
+ END
36
+
37
+ def_node_matcher :replaceable_body?, <<-END
38
+ (send
39
+ (send (lvar %1) $_method $...)
40
+ :<=>
41
+ (send (lvar %2) _method $...))
30
42
  END
31
43
 
32
44
  def on_block(node)
33
- compare?(node) do |send, var_a, var_b, method|
34
- range = compare_range(send, node)
35
- compare_method = send.method_name
36
- add_offense(node, range,
37
- format(MSG, compare_method, method,
38
- compare_method, var_a, var_b,
39
- var_a, method, var_b, method))
45
+ compare?(node) do |send, var_a, var_b, body|
46
+ replaceable_body?(body, var_a, var_b) do |method, args_a, args_b|
47
+ return unless slow_compare?(method, args_a, args_b)
48
+ range = compare_range(send, node)
49
+ add_offense(node, range,
50
+ message(send, method, var_a, var_b, args_a))
51
+ end
40
52
  end
41
53
  end
42
54
 
43
55
  def autocorrect(node)
44
- send, = *node
45
-
46
56
  lambda do |corrector|
47
- method = node.children.last.children.last.children.last
57
+ send, var_a, var_b, body = compare?(node)
58
+ method, arg, = replaceable_body?(body, var_a, var_b)
59
+ replacement =
60
+ if method == :[]
61
+ "#{send.method_name}_by { |a| a[#{arg.first.source}] }"
62
+ else
63
+ "#{send.method_name}_by(&:#{method})"
64
+ end
48
65
  corrector.replace(compare_range(send, node),
49
- "#{send.method_name}_by(&:#{method})")
66
+ replacement)
50
67
  end
51
68
  end
52
69
 
53
70
  private
54
71
 
72
+ def slow_compare?(method, args_a, args_b)
73
+ return false unless args_a == args_b
74
+ if method == :[]
75
+ return false unless args_a.size == 1
76
+ key = args_a.first
77
+ return false unless %i[sym str int].include?(key.type)
78
+ else
79
+ return false unless args_a.empty?
80
+ end
81
+ true
82
+ end
83
+
84
+ def message(send, method, var_a, var_b, args)
85
+ compare_method = send.method_name
86
+ if method == :[]
87
+ key = args.first
88
+ instead = " { |a| a[#{key.source}] }"
89
+ str_a = "#{var_a}[#{key.source}]"
90
+ str_b = "#{var_b}[#{key.source}]"
91
+ else
92
+ instead = "(&:#{method})"
93
+ str_a = "#{var_a}.#{method}"
94
+ str_b = "#{var_b}.#{method}"
95
+ end
96
+ format(MSG, compare_method, instead,
97
+ compare_method, var_a, var_b,
98
+ str_a, str_b)
99
+ end
100
+
55
101
  def compare_range(send, node)
56
102
  range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
57
103
  end
@@ -23,8 +23,8 @@ module RuboCop
23
23
  # var2 = ...
24
24
  # str.end_with?(var1, var2)
25
25
  class DoubleStartEndWith < Cop
26
- MSG = 'Use `%{receiver}.%{method}(%{combined_args})` ' \
27
- 'instead of `%{original_code}`.'.freeze
26
+ MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
27
+ 'instead of `%<original_code>s`.'.freeze
28
28
 
29
29
  def on_or(node)
30
30
  receiver,
@@ -21,6 +21,8 @@ module RuboCop
21
21
 
22
22
  def on_send(node)
23
23
  each_redundant_merge(node) do |receiver, pairs|
24
+ return if pairs.any?(&:kwsplat_type?)
25
+
24
26
  assignments = to_assignments(receiver, pairs).join('; ')
25
27
  message = format(MSG, assignments, node.source)
26
28
  add_offense(node, :expression, message)
@@ -51,9 +51,7 @@ module RuboCop
51
51
  minimum_target_rails_version 4.0
52
52
 
53
53
  def on_block(node)
54
- method, _args, _body = *node
55
-
56
- check_method_node(method)
54
+ check_method_node(node.send_node)
57
55
  end
58
56
 
59
57
  def on_send(node)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that jobs subclass ApplicationJob with Rails 5.0.
7
+ #
8
+ # @example
9
+ #
10
+ # # good
11
+ # class Rails5Job < ApplicationJob
12
+ # ...
13
+ # end
14
+ #
15
+ # # bad
16
+ # class Rails4Job < ActiveJob::Base
17
+ # ...
18
+ # end
19
+ class ApplicationJob < Cop
20
+ extend TargetRailsVersion
21
+
22
+ minimum_target_rails_version 5.0
23
+
24
+ MSG = 'Jobs should subclass `ApplicationJob`.'.freeze
25
+ SUPERCLASS = 'ApplicationJob'.freeze
26
+ BASE_PATTERN = '(const (const nil :ActiveJob) :Base)'.freeze
27
+
28
+ include RuboCop::Cop::EnforceSuperclass
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that models subclass ApplicationRecord with Rails 5.0.
7
+ #
8
+ # @example
9
+ #
10
+ # # good
11
+ # class Rails5Model < ApplicationRecord
12
+ # ...
13
+ # end
14
+ #
15
+ # # bad
16
+ # class Rails4Model < ActiveRecord::Base
17
+ # ...
18
+ # end
19
+ class ApplicationRecord < Cop
20
+ extend TargetRailsVersion
21
+
22
+ minimum_target_rails_version 5.0
23
+
24
+ MSG = 'Models should subclass `ApplicationRecord`.'.freeze
25
+ SUPERCLASS = 'ApplicationRecord'.freeze
26
+ BASE_PATTERN = '(const (const nil :ActiveRecord) :Base)'.freeze
27
+
28
+ include RuboCop::Cop::EnforceSuperclass
29
+ end
30
+ end
31
+ end
32
+ end
@@ -39,7 +39,7 @@ module RuboCop
39
39
  # end
40
40
  class Blank < Cop
41
41
  MSG_NIL_OR_EMPTY = 'Use `%s.blank?` instead of `%s`.'.freeze
42
- MSG_NOT_PRESENT = 'Use `%s.blank?` instead of `%s`.'.freeze
42
+ MSG_NOT_PRESENT = 'Use `%s` instead of `%s`.'.freeze
43
43
  MSG_UNLESS_PRESENT = 'Use `if %s.blank?` instead of `%s`.'.freeze
44
44
 
45
45
  def_node_matcher :nil_or_empty?, <<-PATTERN
@@ -69,7 +69,9 @@ module RuboCop
69
69
  not_present?(node) do |receiver|
70
70
  add_offense(node,
71
71
  :expression,
72
- format(MSG_NOT_PRESENT, receiver.source, node.source))
72
+ format(MSG_NOT_PRESENT,
73
+ replacement(receiver),
74
+ node.source))
73
75
  end
74
76
  end
75
77
 
@@ -113,7 +115,7 @@ module RuboCop
113
115
  range = node.loc.expression
114
116
  end
115
117
 
116
- corrector.replace(range, "#{variable1.source}.blank?")
118
+ corrector.replace(range, replacement(variable1))
117
119
  end
118
120
  end
119
121
 
@@ -126,6 +128,10 @@ module RuboCop
126
128
  node.loc.expression.begin.join(method_call.loc.expression)
127
129
  end
128
130
  end
131
+
132
+ def replacement(node)
133
+ node.respond_to?(:source) ? "#{node.source}.blank?" : 'blank?'
134
+ end
129
135
  end
130
136
  end
131
137
  end
@@ -3,38 +3,78 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of output safety calls like html_safe and
7
- # raw.
6
+ # This cop checks for the use of output safety calls like html_safe,
7
+ # raw, and safe_concat. These methods do not escape content. They
8
+ # simply return a SafeBuffer containing the content as is. Instead,
9
+ # use safe_join to join content and escape it and concat to
10
+ # concatenate content and escape it, ensuring its safety.
8
11
  #
9
12
  # @example
13
+ # user_content = "<b>hi</b>"
14
+ #
10
15
  # # bad
11
- # "<p>#{text}</p>".html_safe
16
+ # "<p>#{user_content}</p>".html_safe
17
+ # => ActiveSupport::SafeBuffer
18
+ # "<p><b>hi</b></p>"
12
19
  #
13
20
  # # good
14
- # content_tag(:p, text)
21
+ # content_tag(:p, user_content)
22
+ # => ActiveSupport::SafeBuffer
23
+ # "<p>&lt;b&gt;hi&lt;/b&gt;</p>"
15
24
  #
16
25
  # # bad
17
26
  # out = ""
18
- # out << content_tag(:li, "one")
19
- # out << content_tag(:li, "two")
27
+ # out << "<li>#{user_content}</li>"
28
+ # out << "<li>#{user_content}</li>"
20
29
  # out.html_safe
30
+ # => ActiveSupport::SafeBuffer
31
+ # "<li><b>hi</b></li><li><b>hi</b></li>"
21
32
  #
22
33
  # # good
23
34
  # out = []
24
- # out << content_tag(:li, "one")
25
- # out << content_tag(:li, "two")
35
+ # out << content_tag(:li, user_content)
36
+ # out << content_tag(:li, user_content)
26
37
  # safe_join(out)
38
+ # => ActiveSupport::SafeBuffer
39
+ # "<li>&lt;b&gt;hi&lt;/b&gt;</li><li>&lt;b&gt;hi&lt;/b&gt;</li>"
40
+ #
41
+ # # bad
42
+ # out = "<h1>trusted content</h1>".html_safe
43
+ # out.safe_concat(user_content)
44
+ # => ActiveSupport::SafeBuffer
45
+ # "<h1>trusted_content</h1><b>hi</b>"
46
+ #
47
+ # # good
48
+ # out = "<h1>trusted content</h1>".html_safe
49
+ # out.concat(user_content)
50
+ # => ActiveSupport::SafeBuffer
51
+ # "<h1>trusted_content</h1>&lt;b&gt;hi&lt;/b&gt;"
52
+ #
53
+ # # safe, though maybe not good style
54
+ # out = "trusted content"
55
+ # result = out.concat(user_content)
56
+ # => String "trusted content<b>hi</b>"
57
+ # # because when rendered in ERB the String will be escaped:
58
+ # <%= result %>
59
+ # => trusted content&lt;b&gt;hi&lt;/b&gt;
60
+ #
61
+ # # bad
62
+ # (user_content + " " + content_tag(:span, user_content)).html_safe
63
+ # => ActiveSupport::SafeBuffer
64
+ # "<b>hi</b> <span><b>hi</b></span>"
65
+ #
66
+ # # good
67
+ # safe_join([user_content, " ", content_tag(:span, user_content)])
68
+ # => ActiveSupport::SafeBuffer
69
+ # "&lt;b&gt;hi&lt;/b&gt; <span>&lt;b&gt;hi&lt;/b&gt;</span>"
27
70
  #
28
71
  class OutputSafety < Cop
29
- MSG = 'Tagging a string as html safe may be a security risk, ' \
30
- 'prefer `safe_join` or other Rails tag helpers instead.'.freeze
72
+ MSG = 'Tagging a string as html safe may be a security risk.'.freeze
31
73
 
32
74
  def on_send(node)
33
- ignore_node(node) if node.method?(:safe_join)
34
-
35
- return unless !part_of_ignored_node?(node) &&
36
- (looks_like_rails_html_safe?(node) ||
37
- looks_like_rails_raw?(node))
75
+ return unless looks_like_rails_html_safe?(node) ||
76
+ looks_like_rails_raw?(node) ||
77
+ looks_like_rails_safe_concat?(node)
38
78
 
39
79
  add_offense(node, :selector)
40
80
  end
@@ -48,6 +88,10 @@ module RuboCop
48
88
  def looks_like_rails_raw?(node)
49
89
  node.command?(:raw) && node.arguments.one?
50
90
  end
91
+
92
+ def looks_like_rails_safe_concat?(node)
93
+ node.method?(:safe_concat) && node.arguments.one?
94
+ end
51
95
  end
52
96
  end
53
97
  end
@@ -35,7 +35,7 @@ module RuboCop
35
35
  # # good
36
36
  # something if foo.present?
37
37
  class Present < Cop
38
- MSG_NOT_BLANK = 'Use `%s.present?` instead of `%s`.'.freeze
38
+ MSG_NOT_BLANK = 'Use `%s` instead of `%s`.'.freeze
39
39
  MSG_EXISTS_AND_NOT_EMPTY = 'Use `%s.present?` instead of `%s`.'.freeze
40
40
  MSG_UNLESS_BLANK = 'Use `if %s.present?` instead of `%s`.'.freeze
41
41
 
@@ -65,7 +65,9 @@ module RuboCop
65
65
  not_blank?(node) do |receiver|
66
66
  add_offense(node,
67
67
  :expression,
68
- format(MSG_NOT_BLANK, receiver.source, node.source))
68
+ format(MSG_NOT_BLANK,
69
+ replacement(receiver),
70
+ node.source))
69
71
  end
70
72
  end
71
73
 
@@ -118,7 +120,7 @@ module RuboCop
118
120
  range = node.loc.expression
119
121
  end
120
122
 
121
- corrector.replace(range, "#{variable1.source}.present?")
123
+ corrector.replace(range, replacement(variable1))
122
124
  end
123
125
  end
124
126
 
@@ -131,6 +133,10 @@ module RuboCop
131
133
  node.loc.expression.begin.join(method_call.loc.expression)
132
134
  end
133
135
  end
136
+
137
+ def replacement(node)
138
+ node.respond_to?(:source) ? "#{node.source}.present?" : 'present?'
139
+ end
134
140
  end
135
141
  end
136
142
  end
@@ -27,16 +27,47 @@ module RuboCop
27
27
  def on_casgn(node)
28
28
  _scope, _constant, rhs = *node
29
29
 
30
- return if rhs.lambda_or_proc?
30
+ # rhs would be nil in a or_asgn node
31
+ return unless rhs
31
32
 
32
- bad_node = node.descendants.find { |n| relative_date_method?(n) }
33
- return unless bad_node
33
+ check_node(rhs)
34
+ end
35
+
36
+ def on_masgn(node)
37
+ lhs, rhs = *node
38
+
39
+ return unless rhs && rhs.array_type?
34
40
 
35
- add_offense(node, :expression, format(MSG, bad_node.method_name))
41
+ lhs.children.zip(rhs.children).each do |(name, value)|
42
+ check_node(value) if name.casgn_type?
43
+ end
44
+ end
45
+
46
+ def on_or_asgn(node)
47
+ lhs, rhs = *node
48
+
49
+ return unless lhs.casgn_type?
50
+
51
+ check_node(rhs)
36
52
  end
37
53
 
38
54
  private
39
55
 
56
+ def check_node(node)
57
+ return unless node.irange_type? ||
58
+ node.erange_type? ||
59
+ node.send_type?
60
+
61
+ # for range nodes we need to check both their boundaries
62
+ nodes = node.send_type? ? [node] : node.children
63
+
64
+ nodes.each do |n|
65
+ if relative_date_method?(n)
66
+ add_offense(node.parent, :expression, format(MSG, n.method_name))
67
+ end
68
+ end
69
+ end
70
+
40
71
  def relative_date_method?(node)
41
72
  node.send_type? &&
42
73
  RELATIVE_DATE_METHODS.include?(node.method_name) &&
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # # bad
11
11
  # def change
12
12
  # change_table :users do |t|
13
- # t.column :name, :string
13
+ # t.remove :name
14
14
  # end
15
15
  # end
16
16
  #
@@ -90,12 +90,49 @@ module RuboCop
90
90
  # remove_foreign_key :accounts, :branches
91
91
  # end
92
92
  #
93
+ # @example
94
+ # # change_table
95
+ #
96
+ # # bad
97
+ # def change
98
+ # change_table :users do |t|
99
+ # t.remove :name
100
+ # t.change_default :authorized, 1
101
+ # t.change :price, :string
102
+ # end
103
+ # end
104
+ #
105
+ # # good
106
+ # def change
107
+ # change_table :users do |t|
108
+ # t.string :name
109
+ # end
110
+ # end
111
+ #
112
+ # # good
113
+ # def change
114
+ # reversible do |dir|
115
+ # change_table :users do |t|
116
+ # dir.up do
117
+ # t.change :price, :string
118
+ # end
119
+ #
120
+ # dir.down do
121
+ # t.change :price, :integer
122
+ # end
123
+ # end
124
+ # end
125
+ # end
126
+ #
93
127
  # @see http://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
94
128
  class ReversibleMigration < Cop
95
129
  MSG = '%s is not reversible.'.freeze
130
+ IRREVERSIBLE_CHANGE_TABLE_CALLS = %i[
131
+ change change_default remove
132
+ ].freeze
96
133
 
97
134
  def_node_matcher :irreversible_schema_statement_call, <<-END
98
- (send nil ${:change_table :change_table_comment :execute :remove_belongs_to} ...)
135
+ (send nil ${:change_table_comment :execute :remove_belongs_to} ...)
99
136
  END
100
137
 
101
138
  def_node_matcher :drop_table_call, <<-END
@@ -114,6 +151,10 @@ module RuboCop
114
151
  (send nil :remove_foreign_key _ $_)
115
152
  END
116
153
 
154
+ def_node_matcher :change_table_call, <<-END
155
+ (send nil :change_table $_ ...)
156
+ END
157
+
117
158
  def on_send(node)
118
159
  return unless within_change_method?(node)
119
160
  return if within_reversible_block?(node)
@@ -125,6 +166,13 @@ module RuboCop
125
166
  check_remove_foreign_key_node(node)
126
167
  end
127
168
 
169
+ def on_block(node)
170
+ return unless within_change_method?(node)
171
+ return if within_reversible_block?(node)
172
+
173
+ check_change_table_node(node.send_node, node.body)
174
+ end
175
+
128
176
  private
129
177
 
130
178
  def check_irreversible_schema_statement_node(node)
@@ -177,28 +225,44 @@ module RuboCop
177
225
  end
178
226
  end
179
227
 
180
- def within_change_method?(node)
181
- parent = node.parent
182
- while parent
183
- if parent.def_type?
184
- method_name, = *parent
185
- return true if method_name == :change
228
+ def check_change_table_node(node, block)
229
+ change_table_call(node) do |arg|
230
+ if target_rails_version < 4.0
231
+ add_offense(
232
+ node, :expression,
233
+ format(MSG, 'change_table')
234
+ )
235
+ elsif block.send_type?
236
+ check_change_table_offense(arg, block)
237
+ else
238
+ block.each_child_node do |child_node|
239
+ check_change_table_offense(arg, child_node)
240
+ end
186
241
  end
187
- parent = parent.parent
188
242
  end
189
- false
243
+ end
244
+
245
+ def check_change_table_offense(receiver, node)
246
+ method_name = node.method_name
247
+ return if receiver != node.receiver &&
248
+ !IRREVERSIBLE_CHANGE_TABLE_CALLS.include?(method_name)
249
+ add_offense(
250
+ node, :expression,
251
+ format(MSG, "change_table(with #{method_name})")
252
+ )
253
+ end
254
+
255
+ def within_change_method?(node)
256
+ node.each_ancestor(:def).any? do |ancestor|
257
+ method_name, = *ancestor
258
+ method_name == :change
259
+ end
190
260
  end
191
261
 
192
262
  def within_reversible_block?(node)
193
- parent = node.parent
194
- while parent
195
- if parent.block_type?
196
- _, block_name = *parent.to_a.first
197
- return true if block_name == :reversible
198
- end
199
- parent = parent.parent
263
+ node.each_ancestor(:block).any? do |ancestor|
264
+ ancestor.block_type? && ancestor.send_node.method?(:reversible)
200
265
  end
201
- false
202
266
  end
203
267
 
204
268
  def all_hash_key?(args, *keys)