rubocop 0.33.0 → 0.34.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/README.md +65 -6
  4. data/config/default.yml +30 -0
  5. data/config/disabled.yml +5 -0
  6. data/config/enabled.yml +19 -3
  7. data/lib/rubocop.rb +7 -2
  8. data/lib/rubocop/cli.rb +1 -1
  9. data/lib/rubocop/config.rb +11 -6
  10. data/lib/rubocop/config_loader.rb +7 -3
  11. data/lib/rubocop/cop/commissioner.rb +6 -11
  12. data/lib/rubocop/cop/cop.rb +7 -3
  13. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -8
  14. data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
  15. data/lib/rubocop/cop/lint/end_alignment.rb +6 -6
  16. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +57 -14
  17. data/lib/rubocop/cop/metrics/method_length.rb +1 -3
  18. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
  19. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +11 -1
  20. data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
  21. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
  22. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +9 -0
  23. data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
  24. data/lib/rubocop/cop/mixin/safe_assignment.rb +2 -2
  25. data/lib/rubocop/cop/performance/case_when_splat.rb +132 -0
  26. data/lib/rubocop/cop/performance/string_replacement.rb +45 -28
  27. data/lib/rubocop/cop/rails/action_filter.rb +31 -5
  28. data/lib/rubocop/cop/rails/date.rb +6 -5
  29. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  30. data/lib/rubocop/cop/rails/time_zone.rb +12 -1
  31. data/lib/rubocop/cop/style/alias.rb +1 -0
  32. data/lib/rubocop/cop/style/block_delimiters.rb +6 -5
  33. data/lib/rubocop/cop/style/collection_methods.rb +2 -20
  34. data/lib/rubocop/cop/style/else_alignment.rb +2 -1
  35. data/lib/rubocop/cop/style/empty_line_between_defs.rb +42 -13
  36. data/lib/rubocop/cop/style/extra_spacing.rb +17 -3
  37. data/lib/rubocop/cop/style/first_parameter_indentation.rb +1 -1
  38. data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
  39. data/lib/rubocop/cop/style/indentation_width.rb +7 -1
  40. data/lib/rubocop/cop/style/initial_indentation.rb +5 -0
  41. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +1 -1
  42. data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
  43. data/lib/rubocop/cop/style/next.rb +31 -14
  44. data/lib/rubocop/cop/style/option_hash.rb +9 -1
  45. data/lib/rubocop/cop/style/parallel_assignment.rb +21 -44
  46. data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
  47. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +2 -1
  48. data/lib/rubocop/cop/style/rescue_modifier.rb +35 -3
  49. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +1 -0
  50. data/lib/rubocop/cop/style/string_methods.rb +32 -0
  51. data/lib/rubocop/cop/style/symbol_proc.rb +54 -12
  52. data/lib/rubocop/cop/style/trailing_blank_lines.rb +1 -1
  53. data/lib/rubocop/cop/team.rb +2 -2
  54. data/lib/rubocop/cop/util.rb +2 -2
  55. data/lib/rubocop/cop/variable_force.rb +10 -10
  56. data/lib/rubocop/cop/variable_force/locatable.rb +5 -5
  57. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  58. data/lib/rubocop/options.rb +24 -1
  59. data/lib/rubocop/result_cache.rb +121 -0
  60. data/lib/rubocop/runner.rb +55 -15
  61. data/lib/rubocop/target_finder.rb +1 -1
  62. data/lib/rubocop/version.rb +1 -1
  63. data/relnotes/v0.34.0.md +182 -0
  64. data/rubocop.gemspec +1 -1
  65. metadata +12 -4
@@ -46,15 +46,12 @@ module RuboCop
46
46
 
47
47
  def investigate(processed_source)
48
48
  reset_errors
49
+ remove_irrelevant_cops(processed_source.buffer.name)
49
50
  prepare(processed_source)
50
51
  invoke_custom_processing(@cops, processed_source)
51
52
  invoke_custom_processing(@forces, processed_source)
52
53
  process(processed_source.ast) if processed_source.ast
53
- @cops.each_with_object([]) do |cop, offenses|
54
- filename = processed_source.buffer.name
55
- # ignore files that are of no interest to the cop in question
56
- offenses.concat(cop.offenses) if cop.relevant_file?(filename)
57
- end
54
+ @cops.flat_map(&:offenses)
58
55
  end
59
56
 
60
57
  private
@@ -63,6 +60,10 @@ module RuboCop
63
60
  @errors = Hash.new { |hash, k| hash[k] = [] }
64
61
  end
65
62
 
63
+ def remove_irrelevant_cops(filename)
64
+ @cops.reject! { |cop| cop.excluded_file?(filename) }
65
+ end
66
+
66
67
  # TODO: Bad design.
67
68
  def prepare(processed_source)
68
69
  @cops.each { |cop| cop.processed_source = processed_source }
@@ -76,12 +77,6 @@ module RuboCop
76
77
  cops_or_forces.each do |cop|
77
78
  next unless cop.respond_to?(:investigate)
78
79
 
79
- if cop.respond_to?(:relevant_file?)
80
- # ignore files that are of no interest to the cop in question
81
- filename = processed_source.buffer.name
82
- next unless cop.relevant_file?(filename)
83
- end
84
-
85
80
  with_cop_error_handling(cop) do
86
81
  cop.investigate(processed_source)
87
82
  end
@@ -198,6 +198,10 @@ module RuboCop
198
198
  !file_name_matches_any?(file, 'Exclude', false)
199
199
  end
200
200
 
201
+ def excluded_file?(file)
202
+ !relevant_file?(file)
203
+ end
204
+
201
205
  def style_guide_url
202
206
  url = cop_config && cop_config['StyleGuide']
203
207
  (url.nil? || url.empty?) ? nil : url
@@ -249,9 +253,9 @@ module RuboCop
249
253
  if Severity::NAMES.include?(severity.to_sym)
250
254
  severity.to_sym
251
255
  else
252
- warn("Warning: Invalid severity '#{severity}'. " +
253
- "Valid severities are #{Severity::NAMES.join(', ')}."
254
- .color(:red))
256
+ message = "Warning: Invalid severity '#{severity}'. " \
257
+ "Valid severities are #{Severity::NAMES.join(', ')}."
258
+ warn(Rainbow(message).red)
255
259
  end
256
260
  end
257
261
  end
@@ -5,12 +5,12 @@ module RuboCop
5
5
  module Lint
6
6
  # This cop checks for uses of the deprecated class method usages.
7
7
  class DeprecatedClassMethods < Cop
8
- include AST::Sexp
9
-
10
8
  # Inner class to DeprecatedClassMethods.
11
9
  # This class exists to add abstraction and clean naming to the
12
10
  # objects that are going to be operated on.
13
11
  class DeprecatedClassMethod
12
+ include AST::Sexp
13
+
14
14
  attr_reader :class_constant, :deprecated_method, :replacement_method
15
15
 
16
16
  def initialize(class_constant, deprecated_method, replacement_method)
@@ -18,6 +18,13 @@ module RuboCop
18
18
  @deprecated_method = deprecated_method
19
19
  @replacement_method = replacement_method
20
20
  end
21
+
22
+ def class_nodes
23
+ @class_nodes ||= [
24
+ s(:const, nil, class_constant),
25
+ s(:const, s(:cbase), class_constant)
26
+ ]
27
+ end
21
28
  end
22
29
 
23
30
  MSG = '`%s` is deprecated in favor of `%s`.'
@@ -50,18 +57,13 @@ module RuboCop
50
57
  receiver, method_name, *_args = *node
51
58
 
52
59
  DEPRECATED_METHODS_OBJECT.each do |data|
53
- next unless class_nodes(data).include?(receiver)
60
+ next unless data.class_nodes.include?(receiver)
54
61
  next unless method_name == data.deprecated_method
55
62
 
56
63
  block.call(data)
57
64
  end
58
65
  end
59
66
 
60
- def class_nodes(data)
61
- [s(:const, nil, data.class_constant),
62
- s(:const, s(:cbase), data.class_constant)]
63
- end
64
-
65
67
  def deprecated_method(data)
66
68
  method_call(data.class_constant, data.deprecated_method)
67
69
  end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for duplicated keys in hash literals.
7
+ #
8
+ # This cop mirrors a warning in Ruby 2.2.
9
+ #
10
+ # @example
11
+ # hash = { food: 'apple', food: 'orange' }
12
+ class DuplicatedKey < Cop
13
+ MSG = 'Duplicated key in hash literal.'
14
+
15
+ LITERALS = [:sym, :str, :float, :int]
16
+
17
+ def on_hash(node)
18
+ keys = []
19
+
20
+ hash_pairs = *node
21
+ hash_pairs.each do |pair|
22
+ key, _value = *pair
23
+ if keys.include?(key) && LITERALS.include?(key.type)
24
+ add_offense(key, :expression)
25
+ elsif keys.include?(key) && key.type == :array
26
+ key.children.each do |child|
27
+ return false unless LITERALS.include?(child.type)
28
+ end
29
+ add_offense(key, :expression)
30
+ end
31
+ keys << key
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -40,6 +40,10 @@ module RuboCop
40
40
  check_offset_of_node(node)
41
41
  end
42
42
 
43
+ def on_case(node)
44
+ check_offset_of_node(node)
45
+ end
46
+
43
47
  private
44
48
 
45
49
  def check_assignment(node, rhs)
@@ -50,11 +54,11 @@ module RuboCop
50
54
 
51
55
  return unless rhs
52
56
 
53
- return unless [:if, :while, :until].include?(rhs.type)
57
+ return unless [:if, :while, :until, :case].include?(rhs.type)
54
58
  return if ternary_op?(rhs)
55
59
 
56
60
  expr = node.loc.expression
57
- if style == :variable && !line_break_before_keyword?(expr, rhs)
61
+ if variable_alignment?(expr, rhs, style)
58
62
  range = Parser::Source::Range.new(expr.source_buffer,
59
63
  expr.begin_pos,
60
64
  rhs.loc.keyword.end_pos)
@@ -68,10 +72,6 @@ module RuboCop
68
72
  ignore_node(rhs) # Don't check again.
69
73
  end
70
74
 
71
- def line_break_before_keyword?(whole_expression, rhs)
72
- rhs.loc.keyword.line > whole_expression.line
73
- end
74
-
75
75
  def autocorrect(node)
76
76
  align(node,
77
77
  style == :variable ? node.each_ancestor(:lvasgn).first : node)
@@ -12,12 +12,14 @@ module RuboCop
12
12
  # format('A value: %s and another: %i', a_value)
13
13
  #
14
14
  class FormatParameterMismatch < Cop
15
- # http://rubular.com/r/HdWs2uXZv4
15
+ # http://rubular.com/r/CvpbxkcTzy
16
16
  MSG = 'Number arguments (%i) to `%s` mismatches expected fields (%i).'
17
- FIELDS_REGEX = /%([\s#+-0\*])?([0-9]*)?(.[0-9]+)?[bBdiouxXeEfgGacps]/
17
+ # rubocop:disable Metrics/LineLength
18
+ FIELD_REGEX = /(%(([\s#+-0\*])?(\d*)?(.\d+)?(\.)?[bBdiouxXeEfgGaAcps]|%))/
19
+ NAMED_FIELD_REGEX = /%\{[_a-zA-Z][_a-zA-Z]+\}/
18
20
 
19
21
  def fields_regex
20
- FIELDS_REGEX
22
+ FIELD_REGEX
21
23
  end
22
24
 
23
25
  def on_send(node)
@@ -28,22 +30,55 @@ module RuboCop
28
30
 
29
31
  def offending_node?(node)
30
32
  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
+ if named_mode?(node)
34
+ false
35
+ else
36
+ num_of_args_for_format, num_of_expected_fields = count_matches(node)
37
+ num_of_expected_fields != num_of_args_for_format
38
+ end
33
39
  else
34
40
  false
35
41
  end
36
42
  end
37
43
 
44
+ def named_mode?(node)
45
+ receiver_node, _method_name, *args = *node
46
+
47
+ relevant_node = if sprintf?(node) || format?(node)
48
+ args.first
49
+ elsif percent?(node)
50
+ receiver_node
51
+ end
52
+
53
+ relevant_node
54
+ .loc
55
+ .expression
56
+ .source
57
+ .scan(NAMED_FIELD_REGEX).count > 0
58
+ end
59
+
60
+ def heredoc?(node)
61
+ _receiver, _name, args = *node
62
+
63
+ args.loc.expression.source[0, 2] == '<<'
64
+ end
65
+
38
66
  def count_matches(node)
39
67
  receiver_node, _method_name, *args = *node
40
68
 
41
- if sprintf?(node) || format?(node)
69
+ if (sprintf?(node) || format?(node)) && !heredoc?(node)
42
70
  number_of_args_for_format = (args.size - 1)
43
- number_of_expected_fields = expected_fields(args.first).size
71
+ number_of_expected_fields = expected_fields_count(args.first)
44
72
  elsif percent?(node)
45
- number_of_args_for_format = args.first.child_nodes.size
46
- number_of_expected_fields = expected_fields(receiver_node).size
73
+ first_child_argument = args.first
74
+
75
+ if first_child_argument.type == :array
76
+ number_of_args_for_format = args.first.child_nodes.size
77
+ number_of_expected_fields = expected_fields_count(receiver_node)
78
+ else
79
+ number_of_args_for_format = 1
80
+ number_of_expected_fields = expected_fields_count(receiver_node)
81
+ end
47
82
  end
48
83
 
49
84
  [number_of_args_for_format, number_of_expected_fields]
@@ -58,12 +93,14 @@ module RuboCop
58
93
  args.size > 1 && :str == args.first.type
59
94
  end
60
95
 
61
- def expected_fields(node)
96
+ def expected_fields_count(node)
62
97
  node
63
98
  .loc
64
99
  .expression
65
100
  .source
66
- .scan(FIELDS_REGEX)
101
+ .scan(FIELD_REGEX)
102
+ .select { |x| x.first != '%%' }
103
+ .reduce(0) { |a, e| a + (e[2] == '*' ? 2 : 1) }
67
104
  end
68
105
 
69
106
  def format?(node)
@@ -77,9 +114,15 @@ module RuboCop
77
114
  def percent?(node)
78
115
  receiver_node, method_name, *arg_nodes = *node
79
116
 
80
- method_name == :% &&
81
- ([:str, :dstr].include?(receiver_node.type) ||
82
- arg_nodes[0].type == :array)
117
+ percent = method_name == :% &&
118
+ ([:str, :dstr].include?(receiver_node.type) ||
119
+ arg_nodes[0].type == :array)
120
+
121
+ if percent && [:str, :dstr].include?(receiver_node.type)
122
+ return false if heredoc?(node)
123
+ end
124
+
125
+ percent
83
126
  end
84
127
 
85
128
  def message(node)
@@ -23,9 +23,7 @@ module RuboCop
23
23
  def code_length(node)
24
24
  lines = node.loc.expression.source.lines.to_a[1..-2] || []
25
25
 
26
- lines.reject! { |line| irrelevant_line(line) }
27
-
28
- lines.size
26
+ lines.count { |line| !irrelevant_line(line) }
29
27
  end
30
28
  end
31
29
  end
@@ -16,8 +16,7 @@ module RuboCop
16
16
  def split_comment(comment)
17
17
  match = comment.text.match(/^(# ?)([A-Za-z]+)(\s*:)?(\s+)?(\S+)?/)
18
18
  return false unless match
19
- margin, first_word, colon, space, note = *match.captures
20
- [margin, first_word, colon, space, note]
19
+ match.captures
21
20
  end
22
21
 
23
22
  def keyword_appearance?(first_word, colon, space)
@@ -6,10 +6,20 @@ module RuboCop
6
6
  # the left or to the right, amount being determined by the instance
7
7
  # variable @column_delta.
8
8
  module AutocorrectAlignment
9
+ SPACE = ' '.freeze
10
+
9
11
  def configured_indentation_width
10
12
  config.for_cop('IndentationWidth')['Width']
11
13
  end
12
14
 
15
+ def indentation(node)
16
+ offset(node) + (SPACE * configured_indentation_width)
17
+ end
18
+
19
+ def offset(node)
20
+ SPACE * node.loc.column
21
+ end
22
+
13
23
  def check_alignment(items, base_column = nil)
14
24
  base_column ||= items.first.loc.column unless items.empty?
15
25
  prev_line = -1
@@ -35,7 +45,7 @@ module RuboCop
35
45
  end
36
46
 
37
47
  def start_of_line?(loc)
38
- loc.expression.source_line[0...loc.column] =~ /^\s*$/
48
+ loc.expression.source_line[0...loc.column].blank?
39
49
  end
40
50
 
41
51
  def autocorrect(arg)
@@ -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.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?
@@ -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,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
@@ -12,11 +12,11 @@ module RuboCop
12
12
 
13
13
  child = node.children.first
14
14
  case child.type
15
- when *Util::EQUALS_ASGN_NODES
16
- true
17
15
  when :send
18
16
  _receiver, method_name, _args = *child
19
17
  method_name.to_s.end_with?('=')
18
+ when *Util::EQUALS_ASGN_NODES
19
+ true
20
20
  else
21
21
  false
22
22
  end