rubocop 0.32.1 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/README.md +22 -4
  4. data/config/default.yml +29 -10
  5. data/config/disabled.yml +8 -4
  6. data/config/enabled.yml +40 -1
  7. data/lib/rubocop.rb +8 -0
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader.rb +23 -2
  10. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  11. data/lib/rubocop/cop/lint/circular_argument_reference.rb +38 -0
  12. data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
  13. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +38 -21
  14. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  15. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +95 -0
  16. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  17. data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
  18. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  19. data/lib/rubocop/cop/performance/count.rb +2 -0
  20. data/lib/rubocop/cop/performance/detect.rb +11 -2
  21. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  22. data/lib/rubocop/cop/performance/string_replacement.rb +161 -0
  23. data/lib/rubocop/cop/rails/date.rb +8 -8
  24. data/lib/rubocop/cop/rails/time_zone.rb +22 -13
  25. data/lib/rubocop/cop/style/block_delimiters.rb +6 -1
  26. data/lib/rubocop/cop/style/documentation.rb +1 -1
  27. data/lib/rubocop/cop/style/extra_spacing.rb +84 -5
  28. data/lib/rubocop/cop/style/first_parameter_indentation.rb +2 -0
  29. data/lib/rubocop/cop/style/indentation_width.rb +28 -4
  30. data/lib/rubocop/cop/style/initial_indentation.rb +32 -0
  31. data/lib/rubocop/cop/style/method_call_parentheses.rb +20 -1
  32. data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
  33. data/lib/rubocop/cop/style/option_hash.rb +56 -0
  34. data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
  35. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -0
  36. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
  37. data/lib/rubocop/cop/style/redundant_return.rb +20 -3
  38. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +77 -0
  39. data/lib/rubocop/cop/style/rescue_modifier.rb +4 -28
  40. data/lib/rubocop/cop/style/send.rb +18 -0
  41. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +32 -13
  42. data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
  43. data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
  44. data/lib/rubocop/cop/style/while_until_do.rb +1 -1
  45. data/lib/rubocop/cop/style/word_array.rb +13 -1
  46. data/lib/rubocop/formatter/disabled_config_formatter.rb +54 -5
  47. data/lib/rubocop/options.rb +81 -55
  48. data/lib/rubocop/version.rb +1 -1
  49. data/relnotes/v0.33.0.md +157 -0
  50. metadata +11 -2
@@ -20,7 +20,7 @@ module RuboCop
20
20
  include OnMethodDef
21
21
  include EndKeywordAlignment
22
22
 
23
- MSG = '`end` at %d, %d is not aligned with `%s` at %d, %d'
23
+ MSG = '`end` at %d, %d is not aligned with `%s` at %d, %d.'
24
24
 
25
25
  def on_method_def(node, _method_name, _args, _body)
26
26
  check_offset_of_node(node)
@@ -28,8 +28,8 @@ module RuboCop
28
28
 
29
29
  def on_send(node)
30
30
  receiver, method_name, *args = *node
31
- return unless visibility_and_def_on_same_line?(receiver, method_name,
32
- args)
31
+ return unless modifier_and_def_on_same_line?(receiver, method_name,
32
+ args)
33
33
 
34
34
  method_def = args.first
35
35
  if style == :start_of_line
@@ -49,7 +49,11 @@ module RuboCop
49
49
  private
50
50
 
51
51
  def autocorrect(node)
52
- align(node, style == :start_of_line ? node.ancestors.first : node)
52
+ if style == :start_of_line && node.parent && node.parent.send_type?
53
+ align(node, node.parent)
54
+ else
55
+ align(node, node)
56
+ end
53
57
  end
54
58
  end
55
59
  end
@@ -7,20 +7,27 @@ module RuboCop
7
7
  class DeprecatedClassMethods < Cop
8
8
  include AST::Sexp
9
9
 
10
- MSG = '`%s` is deprecated in favor of `%s`.'
10
+ # Inner class to DeprecatedClassMethods.
11
+ # This class exists to add abstraction and clean naming to the
12
+ # objects that are going to be operated on.
13
+ class DeprecatedClassMethod
14
+ attr_reader :class_constant, :deprecated_method, :replacement_method
11
15
 
12
- DEPRECATED_METHODS = [
13
- [:File, :exists?, :exist?],
14
- [:Dir, :exists?, :exist?]
16
+ def initialize(class_constant, deprecated_method, replacement_method)
17
+ @class_constant = class_constant
18
+ @deprecated_method = deprecated_method
19
+ @replacement_method = replacement_method
20
+ end
21
+ end
22
+
23
+ MSG = '`%s` is deprecated in favor of `%s`.'
24
+ DEPRECATED_METHODS_OBJECT = [
25
+ DeprecatedClassMethod.new(:File, :exists?, :exist?),
26
+ DeprecatedClassMethod.new(:Dir, :exists?, :exist?)
15
27
  ]
16
28
 
17
29
  def on_send(node)
18
- receiver, method_name, *_args = *node
19
-
20
- DEPRECATED_METHODS.each do |data|
21
- next unless class_nodes(data).include?(receiver)
22
- next unless method_name == data[1]
23
-
30
+ check(node) do |data|
24
31
  add_offense(node, :selector,
25
32
  format(MSG,
26
33
  deprecated_method(data),
@@ -30,31 +37,41 @@ module RuboCop
30
37
 
31
38
  def autocorrect(node)
32
39
  lambda do |corrector|
33
- receiver, method_name, *_args = *node
34
-
35
- DEPRECATED_METHODS.each do |data|
36
- next unless class_nodes(data).include?(receiver)
37
- next unless method_name == data[1]
38
-
40
+ check(node) do |data|
39
41
  corrector.replace(node.loc.selector,
40
- data[2].to_s)
42
+ data.replacement_method.to_s)
41
43
  end
42
44
  end
43
45
  end
44
46
 
45
47
  private
46
48
 
49
+ def check(node, &block)
50
+ receiver, method_name, *_args = *node
51
+
52
+ DEPRECATED_METHODS_OBJECT.each do |data|
53
+ next unless class_nodes(data).include?(receiver)
54
+ next unless method_name == data.deprecated_method
55
+
56
+ block.call(data)
57
+ end
58
+ end
59
+
47
60
  def class_nodes(data)
48
- [s(:const, nil, data[0]),
49
- s(:const, s(:cbase), data[0])]
61
+ [s(:const, nil, data.class_constant),
62
+ s(:const, s(:cbase), data.class_constant)]
50
63
  end
51
64
 
52
65
  def deprecated_method(data)
53
- format('%s.%s', data[0], data[1])
66
+ method_call(data.class_constant, data.deprecated_method)
54
67
  end
55
68
 
56
69
  def replacement_method(data)
57
- format('%s.%s', data[0], data[2])
70
+ method_call(data.class_constant, data.replacement_method)
71
+ end
72
+
73
+ def method_call(class_constant, method)
74
+ format('%s.%s', class_constant, method)
58
75
  end
59
76
  end
60
77
  end
@@ -65,7 +65,7 @@ module RuboCop
65
65
 
66
66
  def method_names(body)
67
67
  body.child_nodes.map do |node|
68
- _receiver, node, body = *node if node.send_type?
68
+ _receiver, node, body = *node if node.send_type?
69
69
 
70
70
  if node.is_a? Symbol
71
71
  next if body.nil?
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This lint sees if there is a mismatch between the number of
7
+ # expected fields for format/sprintf/#% and what is actually
8
+ # passed as arguments.
9
+ #
10
+ # @example
11
+ #
12
+ # format('A value: %s and another: %i', a_value)
13
+ #
14
+ class FormatParameterMismatch < Cop
15
+ # http://rubular.com/r/HdWs2uXZv4
16
+ MSG = 'Number arguments (%i) to `%s` mismatches expected fields (%i).'
17
+ FIELDS_REGEX = /%([\s#+-0\*])?([0-9]*)?(.[0-9]+)?[bBdiouxXeEfgGacps]/
18
+
19
+ def fields_regex
20
+ FIELDS_REGEX
21
+ end
22
+
23
+ def on_send(node)
24
+ add_offense(node, :selector) if offending_node?(node)
25
+ end
26
+
27
+ private
28
+
29
+ def offending_node?(node)
30
+ if sprintf?(node) || format?(node) || percent?(node)
31
+ num_of_args_for_format, num_of_expected_fields = count_matches(node)
32
+ num_of_expected_fields != num_of_args_for_format
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def count_matches(node)
39
+ receiver_node, _method_name, *args = *node
40
+
41
+ if sprintf?(node) || format?(node)
42
+ number_of_args_for_format = (args.size - 1)
43
+ number_of_expected_fields = expected_fields(args.first).size
44
+ elsif percent?(node)
45
+ number_of_args_for_format = args.first.child_nodes.size
46
+ number_of_expected_fields = expected_fields(receiver_node).size
47
+ end
48
+
49
+ [number_of_args_for_format, number_of_expected_fields]
50
+ end
51
+
52
+ def format_method?(name, node)
53
+ receiver, method_name, *args = *node
54
+
55
+ # commands have no explicit receiver
56
+ return false unless !receiver && method_name == name
57
+
58
+ args.size > 1 && :str == args.first.type
59
+ end
60
+
61
+ def expected_fields(node)
62
+ node
63
+ .loc
64
+ .expression
65
+ .source
66
+ .scan(FIELDS_REGEX)
67
+ end
68
+
69
+ def format?(node)
70
+ format_method?(:format, node)
71
+ end
72
+
73
+ def sprintf?(node)
74
+ format_method?(:sprintf, node)
75
+ end
76
+
77
+ def percent?(node)
78
+ receiver_node, method_name, *arg_nodes = *node
79
+
80
+ method_name == :% &&
81
+ ([:str, :dstr].include?(receiver_node.type) ||
82
+ arg_nodes[0].type == :array)
83
+ end
84
+
85
+ def message(node)
86
+ _receiver, method_name, *_args = *node
87
+ num_args_for_format, num_expected_fields = count_matches(node)
88
+
89
+ method_name = 'String#%' if '%' == method_name.to_s
90
+ format(MSG, num_args_for_format, method_name, num_expected_fields)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -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
 
@@ -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
@@ -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
 
@@ -68,6 +68,8 @@ module RuboCop
68
68
  if selector.is_a?(Symbol)
69
69
  if expression && expression.parent.loc.respond_to?(:selector)
70
70
  expression.parent.loc.selector
71
+ else
72
+ left.loc.selector if left.loc.respond_to?(:selector)
71
73
  end
72
74
  else
73
75
  _enumerable, selector, params = *expression
@@ -22,21 +22,24 @@ module RuboCop
22
22
  REVERSE_MSG = 'Use `reverse.%s` instead of `%s.%s`.'
23
23
 
24
24
  SELECT_METHODS = [:select, :find_all]
25
+ DANGEROUS_METHODS = [:first, :last]
25
26
 
26
27
  def on_send(node)
27
28
  receiver, second_method = *node
28
- return unless second_method == :first || second_method == :last
29
29
  return if receiver.nil?
30
+ return unless DANGEROUS_METHODS.include?(second_method)
30
31
 
31
32
  receiver, _args, body = *receiver if receiver.block_type?
32
33
 
33
- _, first_method, args = *receiver
34
+ caller, first_method, args = *receiver
34
35
 
35
36
  # check that we have usual block or block pass
36
37
  return if body.nil? && (args.nil? || !args.block_pass_type?)
37
38
 
38
39
  return unless SELECT_METHODS.include?(first_method)
39
40
 
41
+ return if lazy?(caller)
42
+
40
43
  range = receiver.loc.selector.join(node.loc.selector)
41
44
 
42
45
  message = second_method == :last ? REVERSE_MSG : MSG
@@ -70,6 +73,12 @@ module RuboCop
70
73
  config.for_cop('Style/CollectionMethods') \
71
74
  ['PreferredMethods']['detect'] || 'detect'
72
75
  end
76
+
77
+ def lazy?(node)
78
+ return false if node.nil?
79
+ receiver, method, _args = *node
80
+ method == :lazy && !receiver.nil?
81
+ end
73
82
  end
74
83
  end
75
84
  end
@@ -18,11 +18,11 @@ module RuboCop
18
18
  MSG = 'Use `flat_map` instead of `%s...%s`.'
19
19
  FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
20
20
  'and `flatten` can be used to flatten ' \
21
- 'multiple levels'
21
+ 'multiple levels.'
22
22
  FLATTEN = [:flatten, :flatten!]
23
23
 
24
24
  def on_send(node)
25
- left, second_method, flatten_param = *node
25
+ left, second_method, flatten_param = *node
26
26
  return unless FLATTEN.include?(second_method)
27
27
  flatten_level, = *flatten_param
28
28
  expression, = *left
@@ -44,7 +44,7 @@ module RuboCop
44
44
  end
45
45
 
46
46
  def autocorrect(node)
47
- receiver, _flatten, flatten_param = *node
47
+ receiver, _flatten, flatten_param = *node
48
48
  flatten_level, = *flatten_param
49
49
  return if flatten_level.nil?
50
50
 
@@ -0,0 +1,161 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `gsub` can be replaced by
7
+ # `tr` or `delete`.
8
+ #
9
+ # @example
10
+ # @bad
11
+ # 'abc'.gsub('b', 'd')
12
+ # 'abc'.gsub('a', '')
13
+ # 'abc'.gsub(/a/, 'd')
14
+ # 'abc'.gsub!('a', 'd')
15
+ #
16
+ # @good
17
+ # 'abc'.gsub(/.*/, 'a')
18
+ # 'abc'.gsub(/a+/, 'd')
19
+ # 'abc'.tr('b', 'd')
20
+ # 'a b c'.delete(' ')
21
+ class StringReplacement < Cop
22
+ MSG = 'Use `%s` instead of `%s`.'
23
+ DETERMINISTIC_REGEX = /^[\w\s\-,."']+$/
24
+ REGEXP_CONSTRUCTOR_METHODS = [:new, :compile]
25
+ GSUB_METHODS = [:gsub, :gsub!]
26
+ DETERMINISTIC_TYPES = [:regexp, :str, :send]
27
+
28
+ def on_send(node)
29
+ _string, method, first_param, second_param = *node
30
+ return unless GSUB_METHODS.include?(method)
31
+ return unless second_param && second_param.str_type?
32
+ return unless DETERMINISTIC_TYPES.include?(first_param.type)
33
+
34
+ first_source = first_source(first_param)
35
+ second_source, = *second_param
36
+
37
+ return if first_source.nil?
38
+
39
+ if regex?(first_param)
40
+ return unless first_source =~ DETERMINISTIC_REGEX
41
+ end
42
+
43
+ return if first_source.length != 1
44
+ return unless second_source.length <= 1
45
+
46
+ message = message(method, first_source, second_source)
47
+ add_offense(node, range(node), message)
48
+ end
49
+
50
+ def autocorrect(node)
51
+ _string, method, first_param, second_param = *node
52
+ first_source = first_source(first_param)
53
+ second_source, = *second_param
54
+ replacement_method = replacement_method(first_source, second_source)
55
+
56
+ lambda do |corrector|
57
+ replacement =
58
+ if second_source.empty? && first_source.length == 1
59
+ "#{replacement_method}#{'!' if bang_method?(method)}" \
60
+ "(#{escape(first_source)})"
61
+ else
62
+ "#{replacement_method}#{'!' if bang_method?(method)}" \
63
+ "(#{escape(first_source)}, #{escape(second_source)})"
64
+ end
65
+
66
+ corrector.replace(range(node), replacement)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def first_source(first_param)
73
+ case first_param.type
74
+ when :regexp, :send
75
+ return nil unless regex?(first_param)
76
+
77
+ source, = extract_source(first_param)
78
+ when :str
79
+ source, = *first_param
80
+ end
81
+
82
+ source
83
+ end
84
+
85
+ def extract_source(node)
86
+ case node.type
87
+ when :regexp
88
+ source_from_regex_literal(node)
89
+ when :send
90
+ source_from_regex_constructor(node)
91
+ end
92
+ end
93
+
94
+ def source_from_regex_literal(node)
95
+ regex, = *node
96
+ source, = *regex
97
+ source
98
+ end
99
+
100
+ def source_from_regex_constructor(node)
101
+ _const, _init, regex = *node
102
+ case regex.type
103
+ when :regexp
104
+ source_from_regex_literal(regex)
105
+ when :str
106
+ source, = *regex
107
+ source
108
+ end
109
+ end
110
+
111
+ def regex?(node)
112
+ return true if node.regexp_type?
113
+
114
+ const, init, = *node
115
+ _, klass = *const
116
+
117
+ klass == :Regexp && REGEXP_CONSTRUCTOR_METHODS.include?(init)
118
+ end
119
+
120
+ def range(node)
121
+ Parser::Source::Range.new(node.loc.expression.source_buffer,
122
+ node.loc.selector.begin_pos,
123
+ node.loc.expression.end_pos)
124
+ end
125
+
126
+ def replacement_method(first_source, second_source)
127
+ if second_source.empty? && first_source.length == 1
128
+ 'delete'
129
+ else
130
+ 'tr'
131
+ end
132
+ end
133
+
134
+ def message(method, first_source, second_source)
135
+ replacement_method = replacement_method(first_source, second_source)
136
+
137
+ format(MSG,
138
+ "#{replacement_method}#{'!' if bang_method?(method)}",
139
+ method)
140
+ end
141
+
142
+ def bang_method?(method)
143
+ method.to_s.end_with?('!')
144
+ end
145
+
146
+ def escape(string)
147
+ if require_double_quotes?(string)
148
+ string.inspect
149
+ else
150
+ "'#{string}'"
151
+ end
152
+ end
153
+
154
+ def require_double_quotes?(string)
155
+ /'/ =~ string.inspect ||
156
+ StringHelp::ESCAPED_CHAR_REGEXP =~ string.inspect
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end