rubocop 0.53.0 → 0.54.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +16 -3
  4. data/config/enabled.yml +10 -0
  5. data/lib/rubocop.rb +2 -0
  6. data/lib/rubocop/cli.rb +3 -1
  7. data/lib/rubocop/config.rb +41 -4
  8. data/lib/rubocop/config_loader.rb +1 -2
  9. data/lib/rubocop/config_loader_resolver.rb +5 -5
  10. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  11. data/lib/rubocop/cop/layout/else_alignment.rb +14 -10
  12. data/lib/rubocop/cop/layout/empty_comment.rb +22 -2
  13. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -1
  14. data/lib/rubocop/cop/layout/indent_heredoc.rb +21 -4
  15. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +3 -3
  16. data/lib/rubocop/cop/naming/constant_name.rb +13 -4
  17. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -2
  18. data/lib/rubocop/cop/rails/http_status.rb +181 -0
  19. data/lib/rubocop/cop/rails/skips_model_validations.rb +2 -2
  20. data/lib/rubocop/cop/style/documentation.rb +7 -0
  21. data/lib/rubocop/cop/style/empty_line_after_guard_clause.rb +16 -1
  22. data/lib/rubocop/cop/style/format_string_token.rb +8 -0
  23. data/lib/rubocop/cop/style/inline_comment.rb +3 -1
  24. data/lib/rubocop/cop/style/inverse_methods.rb +22 -5
  25. data/lib/rubocop/cop/style/redundant_begin.rb +20 -0
  26. data/lib/rubocop/cop/style/safe_navigation.rb +2 -1
  27. data/lib/rubocop/cop/style/trivial_accessors.rb +3 -0
  28. data/lib/rubocop/cop/style/unpack_first.rb +67 -0
  29. data/lib/rubocop/cop/util.rb +3 -11
  30. data/lib/rubocop/formatter/offense_count_formatter.rb +17 -0
  31. data/lib/rubocop/magic_comment.rb +1 -1
  32. data/lib/rubocop/target_finder.rb +1 -1
  33. data/lib/rubocop/version.rb +1 -1
  34. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d333d0b4cd8b310bb068ffc26f524a194930c15a
4
- data.tar.gz: 4f8e00e088adff5221918b4b1d91b7ada3f4e346
3
+ metadata.gz: b90da169ae8cd99e01c43d41be0a4d43911dcf77
4
+ data.tar.gz: 5c1db29014d29765ee9400b93d695a0beee04e4a
5
5
  SHA512:
6
- metadata.gz: 34e929b669ad6134d57b4d57f4adb78aab3e3bf9d230678c2a95625b9d356fb29d45e44ee3992bd6cc767d790c182b99f8fbcbb6ae9c1f6f1a018601c840ac0a
7
- data.tar.gz: ffe1738566882f2890c9532b4414e5733312e2d50fe7d112dcb30c91983a8ace89659f897e8fc497d27460886c98a84f4b7805aaea045a03903b9ccc3fd304da
6
+ metadata.gz: 750f559ae4fe5bdc11721d45907baf604f37b182602045ef38c62ca277302e3834a99bb7dab80ddd5eb70be9d358b309aeb37129a85aefeb0234ffdb54848f04
7
+ data.tar.gz: f3f987115edf268066df468d4077c1f1e160dce133b277b6ac6a261cf26a7e26e41f6f54fe24eecf626858931016a7562e9386b7f2a3180f1c31f0500dfe2f67
data/README.md CHANGED
@@ -52,7 +52,7 @@ haven't reached version 1.0 yet). To prevent an unwanted RuboCop update you
52
52
  might want to use a conservative version locking in your `Gemfile`:
53
53
 
54
54
  ```rb
55
- gem 'rubocop', '~> 0.53.0', require: false
55
+ gem 'rubocop', '~> 0.54.0', require: false
56
56
  ```
57
57
 
58
58
  ## Quickstart
@@ -115,9 +115,15 @@ AllCops:
115
115
  AllowSymlinksInCacheRootDirectory: false
116
116
  # What MRI version of the Ruby interpreter is the inspected code intended to
117
117
  # run on? (If there is more than one, set this to the lowest version.)
118
- # If a value is specified for TargetRubyVersion then it is used.
119
- # Else if .ruby-version exists and it contains an MRI version it is used.
120
- # Otherwise we fallback to the oldest officially supported Ruby version (2.1).
118
+ # If a value is specified for TargetRubyVersion then it is used. Acceptable
119
+ # values are specificed as a float (i.e. 2.5); the teeny version of Ruby
120
+ # should not be included. If the project specifies a Ruby version in the
121
+ # .ruby-version file, Gemfile or gems.rb file, RuboCop will try to determine
122
+ # the desired version of Ruby by inspecting the .ruby-version file first,
123
+ # followed by the Gemfile.lock or gems.locked file. (Although the Ruby version
124
+ # is specified in the Gemfile or gems.rb file, RuboCop reads the final value
125
+ # from the lock file.) If the Ruby version is still unresolved, RuboCop will
126
+ # use the oldest officially supported Ruby version (currently Ruby 2.1).
121
127
  TargetRubyVersion: ~
122
128
  # What version of Rails is the inspected code using? If a value is specified
123
129
  # for TargetRailsVersion then it is used. Acceptable values are specificed
@@ -762,6 +768,7 @@ Naming/UncommunicativeMethodParamName:
762
768
  AllowedNames:
763
769
  - io
764
770
  - id
771
+ - to
765
772
  # Blacklisted names that will register an offense
766
773
  ForbiddenNames: []
767
774
 
@@ -1666,6 +1673,12 @@ Rails/HasManyOrHasOneDependent:
1666
1673
  Include:
1667
1674
  - app/models/**/*.rb
1668
1675
 
1676
+ Rails/HttpStatus:
1677
+ EnforcedStyle: symbolic
1678
+ SupportedStyles:
1679
+ - numeric
1680
+ - symbolic
1681
+
1669
1682
  Rails/InverseOf:
1670
1683
  Include:
1671
1684
  - app/models/**/*.rb
@@ -1169,6 +1169,10 @@ Rails/HttpPositionalArguments:
1169
1169
  - 'spec/**/*'
1170
1170
  - 'test/**/*'
1171
1171
 
1172
+ Rails/HttpStatus:
1173
+ Description: 'Enforces use of symbolic or numeric value to define HTTP status.'
1174
+ Enabled: true
1175
+
1172
1176
  Rails/InverseOf:
1173
1177
  Description: 'Checks for associations where the inverse cannot be determined automatically.'
1174
1178
  Enabled: true
@@ -2006,6 +2010,12 @@ Style/UnneededPercentQ:
2006
2010
  StyleGuide: '#percent-q'
2007
2011
  Enabled: true
2008
2012
 
2013
+ Style/UnpackFirst:
2014
+ Description: >-
2015
+ Checks for accessing the first element of `String#unpack`
2016
+ instead of using `unpack1`
2017
+ Enabled: true
2018
+
2009
2019
  Style/VariableInterpolation:
2010
2020
  Description: >-
2011
2021
  Don't interpolate global, instance and class variables
@@ -522,6 +522,7 @@ require_relative 'rubocop/cop/style/unless_else'
522
522
  require_relative 'rubocop/cop/style/unneeded_capital_w'
523
523
  require_relative 'rubocop/cop/style/unneeded_interpolation'
524
524
  require_relative 'rubocop/cop/style/unneeded_percent_q'
525
+ require_relative 'rubocop/cop/style/unpack_first'
525
526
  require_relative 'rubocop/cop/style/variable_interpolation'
526
527
  require_relative 'rubocop/cop/style/when_then'
527
528
  require_relative 'rubocop/cop/style/while_until_do'
@@ -550,6 +551,7 @@ require_relative 'rubocop/cop/rails/find_each'
550
551
  require_relative 'rubocop/cop/rails/has_and_belongs_to_many'
551
552
  require_relative 'rubocop/cop/rails/has_many_or_has_one_dependent'
552
553
  require_relative 'rubocop/cop/rails/http_positional_arguments'
554
+ require_relative 'rubocop/cop/rails/http_status'
553
555
  require_relative 'rubocop/cop/rails/inverse_of'
554
556
  require_relative 'rubocop/cop/rails/lexically_scoped_action_filter'
555
557
  require_relative 'rubocop/cop/rails/not_null_column'
@@ -41,7 +41,7 @@ module RuboCop
41
41
  execute_runners(paths)
42
42
  rescue RuboCop::ConfigNotFoundError => e
43
43
  warn e.message
44
- e.status
44
+ STATUS_ERROR
45
45
  rescue RuboCop::Error => e
46
46
  warn Rainbow("Error: #{e.message}").red
47
47
  STATUS_ERROR
@@ -258,6 +258,8 @@ module RuboCop
258
258
  warn <<-WARNING.strip_indent
259
259
  Errors are usually caused by RuboCop bugs.
260
260
  Please, report your problems to RuboCop's issue tracker.
261
+ #{Gem.loaded_specs['rubocop'].metadata['bug_tracker_uri']}
262
+
261
263
  Mention the following information in the issue report:
262
264
  #{RuboCop::Version.version(true)}
263
265
  WARNING
@@ -425,6 +425,10 @@ module RuboCop
425
425
  @target_ruby_version_source = :ruby_version_file
426
426
 
427
427
  target_ruby_version_from_version_file
428
+ elsif target_ruby_version_from_bundler_lock_file
429
+ @target_ruby_version_source = :bundler_lock_file
430
+
431
+ target_ruby_version_from_bundler_lock_file
428
432
  else
429
433
  DEFAULT_RUBY_VERSION
430
434
  end
@@ -557,6 +561,8 @@ module RuboCop
557
561
  case @target_ruby_version_source
558
562
  when :ruby_version_file
559
563
  "`#{RUBY_VERSION_FILENAME}`"
564
+ when :bundler_lock_file
565
+ "`#{bundler_lock_file_path}`"
560
566
  when :rubocop_yml
561
567
  "`TargetRubyVersion` parameter (in #{smart_loaded_path})"
562
568
  end
@@ -577,6 +583,37 @@ module RuboCop
577
583
  end
578
584
  end
579
585
 
586
+ def target_ruby_version_from_bundler_lock_file
587
+ @target_ruby_version_from_bundler_lock_file ||=
588
+ read_ruby_version_from_bundler_lock_file
589
+ end
590
+
591
+ def read_ruby_version_from_bundler_lock_file
592
+ lock_file_path = bundler_lock_file_path
593
+ return nil unless lock_file_path
594
+
595
+ in_ruby_section = false
596
+ File.foreach(lock_file_path) do |line|
597
+ # If ruby is in Gemfile.lock or gems.lock, there should be two lines
598
+ # towards the bottom of the file that look like:
599
+ # RUBY VERSION
600
+ # ruby W.X.YpZ
601
+ # We ultimately want to match the "ruby W.X.Y.pZ" line, but there's
602
+ # extra logic to make sure we only start looking once we've seen the
603
+ # "RUBY VERSION" line.
604
+ in_ruby_section ||= line.match(/^\s*RUBY\s*VERSION\s*$/)
605
+ next unless in_ruby_section
606
+ # We currently only allow this feature to work with MRI ruby. If jruby
607
+ # (or something else) is used by the project, it's lock file will have a
608
+ # line that looks like:
609
+ # RUBY VERSION
610
+ # ruby W.X.YpZ (jruby x.x.x.x)
611
+ # The regex won't match in this situation.
612
+ result = line.match(/^\s*ruby\s+(\d+\.\d+)[p.\d]*\s*$/)
613
+ return result.captures.first.to_f if result
614
+ end
615
+ end
616
+
580
617
  def target_rails_version_from_bundler_lock_file
581
618
  @target_rails_version_from_bundler_lock_file ||=
582
619
  read_rails_version_from_bundler_lock_file
@@ -587,9 +624,9 @@ module RuboCop
587
624
  return nil unless lock_file_path
588
625
 
589
626
  File.foreach(lock_file_path) do |line|
590
- # If rails is in the Gemfile, the lock file should have a line like:
627
+ # If rails is in Gemfile.lock or gems.lock, there should be a line like:
591
628
  # rails (X.X.X)
592
- result = line.match(/^\s+rails\s+\((\d\.\d\.\d)/)
629
+ result = line.match(/^\s+rails\s+\((\d+\.\d+)/)
593
630
  return result.captures.first.to_f if result
594
631
  end
595
632
  end
@@ -598,8 +635,8 @@ module RuboCop
598
635
  return nil unless loaded_path
599
636
  base_path = base_dir_for_path_parameters
600
637
  ['gems.locked', 'Gemfile.lock'].each do |file_name|
601
- path = File.join(base_path, file_name)
602
- return path if File.file?(path)
638
+ path = find_file_upwards(file_name, base_path)
639
+ return path if path
603
640
  end
604
641
  nil
605
642
  end
@@ -35,7 +35,6 @@ module RuboCop
35
35
  end
36
36
 
37
37
  def load_file(file)
38
- return if file.nil?
39
38
  path = File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
40
39
 
41
40
  hash = load_yaml_configuration(path)
@@ -47,7 +46,7 @@ module RuboCop
47
46
  target_ruby_version_to_f!(hash)
48
47
 
49
48
  resolver.resolve_inheritance_from_gems(hash, hash.delete('inherit_gem'))
50
- resolver.resolve_inheritance(path, hash, file)
49
+ resolver.resolve_inheritance(path, hash, file, debug?)
51
50
 
52
51
  hash.delete('inherit_from')
53
52
  hash.delete('inherit_mode')
@@ -17,7 +17,7 @@ module RuboCop
17
17
  end
18
18
  end
19
19
 
20
- def resolve_inheritance(path, hash, file)
20
+ def resolve_inheritance(path, hash, file, debug)
21
21
  inherited_files = Array(hash['inherit_from'])
22
22
  base_configs(path, inherited_files, file)
23
23
  .reverse.each_with_index do |base_config, index|
@@ -25,7 +25,7 @@ module RuboCop
25
25
  next unless v.is_a?(Hash)
26
26
  if hash.key?(k)
27
27
  v = merge(v, hash[k],
28
- cop_name: k, file: file,
28
+ cop_name: k, file: file, debug: debug,
29
29
  inherited_file: inherited_files[index],
30
30
  inherit_mode: determine_inherit_mode(hash, k))
31
31
  end
@@ -86,7 +86,7 @@ module RuboCop
86
86
  result[key] = merge(base_hash[key], derived_hash[key])
87
87
  elsif should_union?(base_hash, key, opts[:inherit_mode])
88
88
  result[key] = base_hash[key] | derived_hash[key]
89
- else
89
+ elsif opts[:debug]
90
90
  warn_on_duplicate_setting(base_hash, derived_hash, key, opts)
91
91
  end
92
92
  end
@@ -112,9 +112,9 @@ module RuboCop
112
112
  return if base_hash[key].is_a?(Array) &&
113
113
  inherit_mode && inherit_mode.include?(key)
114
114
 
115
- warn("#{PathUtil.smart_path(opts[:file])}: " \
115
+ puts "#{PathUtil.smart_path(opts[:file])}: " \
116
116
  "#{opts[:cop_name]}:#{key} overrides " \
117
- "the same parameter in #{opts[:inherited_file]}")
117
+ "the same parameter in #{opts[:inherited_file]}"
118
118
  end
119
119
 
120
120
  def determine_inherit_mode(hash, key)
@@ -58,7 +58,7 @@ module RuboCop
58
58
 
59
59
  def previous_declaration(node)
60
60
  declarations = gem_declarations(processed_source.ast)
61
- node_index = declarations.find_index(node)
61
+ node_index = declarations.map(&:location).find_index(node.location)
62
62
  declarations.to_a[node_index - 1]
63
63
  end
64
64
 
@@ -40,7 +40,7 @@ module RuboCop
40
40
  return if ignored_node?(node)
41
41
  return unless node.else? && begins_its_line?(node.loc.else)
42
42
 
43
- check_alignment(base_range(node, base), node.loc.else)
43
+ check_alignment(base_range_of_if(node, base), node.loc.else)
44
44
 
45
45
  return unless node.elsif_conditional?
46
46
 
@@ -50,14 +50,7 @@ module RuboCop
50
50
  def on_rescue(node)
51
51
  return unless node.loc.respond_to?(:else) && node.loc.else
52
52
 
53
- parent = node.parent
54
- parent = parent.parent if parent.ensure_type?
55
- base = case parent.type
56
- when :def, :defs then base_for_method_definition(parent)
57
- when :kwbegin then parent.loc.begin
58
- else node.loc.keyword
59
- end
60
- check_alignment(base, node.loc.else)
53
+ check_alignment(base_range_of_rescue(node), node.loc.else)
61
54
  end
62
55
 
63
56
  def on_case(node)
@@ -77,7 +70,7 @@ module RuboCop
77
70
  ignore_node(node)
78
71
  end
79
72
 
80
- def base_range(node, base)
73
+ def base_range_of_if(node, base)
81
74
  if base
82
75
  base.source_range
83
76
  else
@@ -86,6 +79,17 @@ module RuboCop
86
79
  end
87
80
  end
88
81
 
82
+ def base_range_of_rescue(node)
83
+ parent = node.parent
84
+ parent = parent.parent if parent.ensure_type?
85
+ case parent.type
86
+ when :def, :defs then base_for_method_definition(parent)
87
+ when :kwbegin then parent.loc.begin
88
+ when :block then parent.send_node.source_range
89
+ else node.loc.keyword
90
+ end
91
+ end
92
+
89
93
  def base_for_method_definition(node)
90
94
  parent = node.parent
91
95
  if parent && parent.send_type?
@@ -87,8 +87,14 @@ module RuboCop
87
87
 
88
88
  def autocorrect(node)
89
89
  lambda do |corrector|
90
- range = range_by_whole_lines(node.loc.expression,
91
- include_final_newline: true)
90
+ previous_token = previous_token(node)
91
+ range = if previous_token && node.loc.line == previous_token.line
92
+ range_with_surrounding_space(range: node.loc.expression,
93
+ newlines: false)
94
+ else
95
+ range_by_whole_lines(node.loc.expression,
96
+ include_final_newline: true)
97
+ end
92
98
 
93
99
  corrector.remove(range)
94
100
  end
@@ -134,6 +140,20 @@ module RuboCop
134
140
  def allow_margin_comment?
135
141
  cop_config['AllowMarginComment']
136
142
  end
143
+
144
+ def current_token(node)
145
+ processed_source.find_token do |token|
146
+ token.pos.column == node.loc.column &&
147
+ token.pos.last_column == node.loc.last_column &&
148
+ token.line == node.loc.line
149
+ end
150
+ end
151
+
152
+ def previous_token(node)
153
+ current_token = current_token(node)
154
+ index = processed_source.tokens.index(current_token)
155
+ index.zero? ? nil : processed_source.tokens[index - 1]
156
+ end
137
157
  end
138
158
  end
139
159
  end
@@ -90,7 +90,7 @@ module RuboCop
90
90
  end
91
91
 
92
92
  def body_end?(line)
93
- line =~ /^\s*end/
93
+ line =~ /^\s*end\b/
94
94
  end
95
95
 
96
96
  def message(node)
@@ -73,9 +73,11 @@ module RuboCop
73
73
  include ConfigurableEnforcedStyle
74
74
  include SafeMode
75
75
 
76
- RUBY23_MSG = 'Use %<indentation_width>d spaces for indentation in a ' \
77
- 'heredoc by using `<<~` instead of ' \
78
- '`%<current_indent_type>s`.'.freeze
76
+ RUBY23_TYPE_MSG = 'Use %<indentation_width>d spaces for indentation ' \
77
+ 'in a heredoc by using `<<~` instead of ' \
78
+ '`%<current_indent_type>s`.'.freeze
79
+ RUBY23_WIDTH_MSG = 'Use %<indentation_width>d spaces for '\
80
+ 'indentation in a heredoc.'.freeze
79
81
  LIBRARY_MSG = 'Use %<indentation_width>d spaces for indentation in a ' \
80
82
  'heredoc by using %<method>s.'.freeze
81
83
  STRIP_METHODS = {
@@ -148,13 +150,28 @@ module RuboCop
148
150
  end
149
151
 
150
152
  def ruby23_message(indentation_width, current_indent_type)
153
+ if current_indent_type == '<<~'
154
+ ruby23_width_message(indentation_width)
155
+ else
156
+ ruby23_type_message(indentation_width, current_indent_type)
157
+ end
158
+ end
159
+
160
+ def ruby23_type_message(indentation_width, current_indent_type)
151
161
  format(
152
- RUBY23_MSG,
162
+ RUBY23_TYPE_MSG,
153
163
  indentation_width: indentation_width,
154
164
  current_indent_type: current_indent_type
155
165
  )
156
166
  end
157
167
 
168
+ def ruby23_width_message(indentation_width)
169
+ format(
170
+ RUBY23_WIDTH_MSG,
171
+ indentation_width: indentation_width
172
+ )
173
+ end
174
+
158
175
  def too_long_line?(node)
159
176
  return false if config.for_cop('Metrics/LineLength')['AllowHeredoc']
160
177
  body = heredoc_body(node)
@@ -6,15 +6,15 @@ module RuboCop
6
6
  module UncommunicativeName
7
7
  CASE_MSG = 'Only use lowercase characters for %<name_type>s.'.freeze
8
8
  NUM_MSG = 'Do not end %<name_type>s with a number.'.freeze
9
- LENGTH_MSG = '%<name_type>s must be longer than %<min>s ' \
10
- 'characters.'.freeze
9
+ LENGTH_MSG = '%<name_type>s must be at least %<min>s ' \
10
+ 'characters long.'.freeze
11
11
  FORBIDDEN_MSG = 'Do not use %<name>s as a name for a ' \
12
12
  '%<name_type>s.'.freeze
13
13
 
14
14
  def check(node, args)
15
15
  args.each do |arg|
16
16
  name = arg.children.first.to_s
17
- next if arg.restarg_type? && name.empty?
17
+ next if (arg.restarg_type? || arg.kwrestarg_type?) && name.empty?
18
18
  next if allowed_names.include?(name)
19
19
  range = arg_range(arg, name.size)
20
20
  issue_offenses(node, range, name)
@@ -30,7 +30,12 @@ module RuboCop
30
30
  PATTERN
31
31
 
32
32
  def on_casgn(node)
33
- _scope, const_name, value = *node
33
+ if node.parent && node.parent.or_asgn_type?
34
+ lhs, value = *node.parent
35
+ _scope, const_name = *lhs
36
+ else
37
+ _scope, const_name, value = *node
38
+ end
34
39
 
35
40
  # We cannot know the result of method calls like
36
41
  # NewClass = something_that_returns_a_class
@@ -39,15 +44,19 @@ module RuboCop
39
44
  # SomeClass = SomeOtherClass
40
45
  # SomeClass = Class.new(...)
41
46
  # SomeClass = Struct.new(...)
42
- return if value && %i[block const casgn].include?(value.type) ||
43
- allowed_method_call_on_rhs?(value) ||
44
- class_or_struct_return_method?(value)
47
+ return if allowed_assignment?(value)
45
48
 
46
49
  add_offense(node, location: :name) if const_name !~ SNAKE_CASE
47
50
  end
48
51
 
49
52
  private
50
53
 
54
+ def allowed_assignment?(value)
55
+ value && %i[block const casgn].include?(value.type) ||
56
+ allowed_method_call_on_rhs?(value) ||
57
+ class_or_struct_return_method?(value)
58
+ end
59
+
51
60
  def allowed_method_call_on_rhs?(node)
52
61
  node && node.send_type? &&
53
62
  (node.receiver.nil? || !node.receiver.literal?)
@@ -34,7 +34,7 @@ module RuboCop
34
34
  #
35
35
  class MemoizedInstanceVariableName < Cop
36
36
  MSG = 'Memoized variable `%<var>s` does not match ' \
37
- 'method name `%<method>s`. Use `@%<method>s` instead.'.freeze
37
+ 'method name `%<method>s`. Use `@%<suggested_var>s` instead.'.freeze
38
38
 
39
39
  def self.node_pattern
40
40
  memo_assign = '(or_asgn $(ivasgn _) _)'
@@ -55,6 +55,7 @@ module RuboCop
55
55
  msg = format(
56
56
  MSG,
57
57
  var: ivar_assign.children.first.to_s,
58
+ suggested_var: method_name.to_s.delete('!?'),
58
59
  method: method_name
59
60
  )
60
61
  add_offense(node, location: ivar_assign.source_range, message: msg)
@@ -65,7 +66,7 @@ module RuboCop
65
66
 
66
67
  def matches?(method_name, ivar_assign)
67
68
  return true if ivar_assign.nil? || method_name == :initialize
68
- method_name = method_name.to_s.sub('?', '')
69
+ method_name = method_name.to_s.delete('!?')
69
70
  variable = ivar_assign.children.first
70
71
  variable_name = variable.to_s.sub('@', '')
71
72
  variable_name == method_name
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces use of symbolic or numeric value to define HTTP status.
7
+ #
8
+ # @example `EnforcedStyle: symbolic` (default)
9
+ # # bad
10
+ # render :foo, status: 200
11
+ # render json: { foo: 'bar' }, status: 200
12
+ # render plain: 'foo/bar', status: 304
13
+ # redirect_to root_url, status: 301
14
+ #
15
+ # # good
16
+ # render :foo, status: :ok
17
+ # render json: { foo: 'bar' }, status: :ok
18
+ # render plain: 'foo/bar', status: :not_modified
19
+ # redirect_to root_url, status: :moved_permanently
20
+ #
21
+ # @example `EnforcedStyle: numeric`
22
+ # # bad
23
+ # render :foo, status: :ok
24
+ # render json: { foo: 'bar' }, status: :not_found
25
+ # render plain: 'foo/bar', status: :not_modified
26
+ # redirect_to root_url, status: :moved_permanently
27
+ #
28
+ # # good
29
+ # render :foo, status: 200
30
+ # render json: { foo: 'bar' }, status: 404
31
+ # render plain: 'foo/bar', status: 304
32
+ # redirect_to root_url, status: 301
33
+ #
34
+ class HttpStatus < Cop
35
+ begin
36
+ require 'rack/utils'
37
+ RACK_LOADED = true
38
+ rescue LoadError
39
+ RACK_LOADED = false
40
+ end
41
+
42
+ include ConfigurableEnforcedStyle
43
+
44
+ def_node_matcher :http_status, <<-PATTERN
45
+ {
46
+ (send nil? {:render :redirect_to}
47
+ _
48
+ (hash
49
+ (pair
50
+ (sym :status)
51
+ ${int sym})))
52
+ (send nil? {:render}
53
+ (hash
54
+ _
55
+ (pair
56
+ (sym :status)
57
+ ${int sym})))
58
+ }
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ http_status(node) do |ast_node|
63
+ checker = checker_class.new(ast_node)
64
+ return unless checker.offensive?
65
+ add_offense(checker.node, message: checker.message)
66
+ end
67
+ end
68
+
69
+ def support_autocorrect?
70
+ RACK_LOADED
71
+ end
72
+
73
+ def autocorrect(node)
74
+ lambda do |corrector|
75
+ checker = checker_class.new(node)
76
+ corrector.replace(node.loc.expression, checker.preferred_style)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def checker_class
83
+ case style
84
+ when :symbolic
85
+ SymbolicStyleChecker
86
+ when :numeric
87
+ NumericStyleChecker
88
+ end
89
+ end
90
+
91
+ # :nodoc:
92
+ class SymbolicStyleChecker
93
+ MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
94
+ 'to define HTTP status code.'.freeze
95
+ DEFAULT_MSG = 'Prefer `symbolic` over `numeric` ' \
96
+ 'to define HTTP status code.'.freeze
97
+
98
+ attr_reader :node
99
+ def initialize(node)
100
+ @node = node
101
+ end
102
+
103
+ def offensive?
104
+ !node.sym_type? && !custom_http_status_code?
105
+ end
106
+
107
+ def message
108
+ if RACK_LOADED
109
+ format(MSG, prefer: preferred_style, current: number.to_s)
110
+ else
111
+ DEFAULT_MSG
112
+ end
113
+ end
114
+
115
+ def preferred_style
116
+ symbol.inspect
117
+ end
118
+
119
+ private
120
+
121
+ def symbol
122
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
123
+ end
124
+
125
+ def number
126
+ node.children.first
127
+ end
128
+
129
+ def custom_http_status_code?
130
+ node.int_type? &&
131
+ !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(number)
132
+ end
133
+ end
134
+
135
+ # :nodoc:
136
+ class NumericStyleChecker
137
+ MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
138
+ 'to define HTTP status code.'.freeze
139
+ DEFAULT_MSG = 'Prefer `numeric` over `symbolic` ' \
140
+ 'to define HTTP status code.'.freeze
141
+ WHITELIST_STATUS = %i[error success missing redirect].freeze
142
+
143
+ attr_reader :node
144
+ def initialize(node)
145
+ @node = node
146
+ end
147
+
148
+ def offensive?
149
+ !node.int_type? && !whitelisted_symbol?
150
+ end
151
+
152
+ def message
153
+ if RACK_LOADED
154
+ format(MSG, prefer: preferred_style, current: symbol.inspect)
155
+ else
156
+ DEFAULT_MSG
157
+ end
158
+ end
159
+
160
+ def preferred_style
161
+ number.to_s
162
+ end
163
+
164
+ private
165
+
166
+ def number
167
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
168
+ end
169
+
170
+ def symbol
171
+ node.value
172
+ end
173
+
174
+ def whitelisted_symbol?
175
+ node.sym_type? && WHITELIST_STATUS.include?(node.value)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -16,12 +16,12 @@ module RuboCop
16
16
  # person.toggle :active
17
17
  # product.touch
18
18
  # Billing.update_all("category = 'authorized', author = 'David'")
19
- # user.update_attribute(website: 'example.com')
19
+ # user.update_attribute(:website, 'example.com')
20
20
  # user.update_columns(last_request_at: Time.current)
21
21
  # Post.update_counters 5, comment_count: -1, action_count: 1
22
22
  #
23
23
  # # good
24
- # user.update_attributes(website: 'example.com')
24
+ # user.update(website: 'example.com')
25
25
  # FileUtils.touch('file')
26
26
  class SkipsModelValidations < Cop
27
27
  MSG = 'Avoid using `%<method>s` because it skips validations.'.freeze
@@ -30,6 +30,7 @@ module RuboCop
30
30
  MSG = 'Missing top-level %<type>s documentation comment.'.freeze
31
31
 
32
32
  def_node_matcher :constant_definition?, '{class module casgn}'
33
+ def_node_search :outer_module, '(const (const nil? _) _)'
33
34
 
34
35
  def on_class(node)
35
36
  _, _, body = *node
@@ -50,6 +51,8 @@ module RuboCop
50
51
  def check(node, body, type)
51
52
  return if namespace?(body)
52
53
  return if documentation_comment?(node) || nodoc_comment?(node)
54
+ return if compact_namespace?(node) &&
55
+ nodoc_comment?(outer_module(node).first)
53
56
 
54
57
  add_offense(node,
55
58
  location: :keyword,
@@ -66,6 +69,10 @@ module RuboCop
66
69
  end
67
70
  end
68
71
 
72
+ def compact_namespace?(node)
73
+ node.loc.name.source =~ /::/
74
+ end
75
+
69
76
  # First checks if the :nodoc: comment is associated with the
70
77
  # class/module. Unless the element is tagged with :nodoc:, the search
71
78
  # proceeds to check its ancestors for :nodoc: all.
@@ -43,7 +43,8 @@ module RuboCop
43
43
  def on_if(node)
44
44
  return unless contains_guard_clause?(node)
45
45
 
46
- return if node.parent.nil? || node.parent.single_line?
46
+ return if next_line_rescue_or_ensure?(node)
47
+ return if next_sibling_parent_empty_or_else?(node)
47
48
  return if next_sibling_empty_or_guard_clause?(node)
48
49
 
49
50
  return if next_line_empty?(node)
@@ -68,6 +69,20 @@ module RuboCop
68
69
  processed_source[node.last_line].blank?
69
70
  end
70
71
 
72
+ def next_line_rescue_or_ensure?(node)
73
+ parent = node.parent
74
+ parent.nil? || parent.rescue_type? || parent.ensure_type?
75
+ end
76
+
77
+ def next_sibling_parent_empty_or_else?(node)
78
+ next_sibling = node.parent.children[node.sibling_index + 1]
79
+ return true if next_sibling.nil?
80
+
81
+ parent = next_sibling.parent
82
+
83
+ parent && parent.if_type? && parent.else?
84
+ end
85
+
71
86
  def next_sibling_empty_or_guard_clause?(node)
72
87
  next_sibling = node.parent.children[node.sibling_index + 1]
73
88
  return true if next_sibling.nil?
@@ -51,6 +51,7 @@ module RuboCop
51
51
  }.freeze
52
52
 
53
53
  def on_str(node)
54
+ return if placeholder_argument?(node)
54
55
  return if node.each_ancestor(:xstr, :regexp).any?
55
56
 
56
57
  tokens(node) do |detected_style, token_range|
@@ -156,6 +157,13 @@ module RuboCop
156
157
  new_end
157
158
  )
158
159
  end
160
+
161
+ def placeholder_argument?(node)
162
+ return false unless node.parent
163
+ return true if node.parent.pair_type?
164
+
165
+ placeholder_argument?(node.parent)
166
+ end
159
167
  end
160
168
  end
161
169
  end
@@ -22,7 +22,9 @@ module RuboCop
22
22
 
23
23
  def investigate(processed_source)
24
24
  processed_source.each_comment do |comment|
25
- next if comment_line?(processed_source[comment.loc.line - 1])
25
+ next if comment_line?(processed_source[comment.loc.line - 1]) ||
26
+ comment.text.match(/\A# rubocop:(enable|disable)/)
27
+
26
28
  add_offense(comment)
27
29
  end
28
30
  end
@@ -27,18 +27,21 @@ module RuboCop
27
27
  # foo != bar
28
28
  # foo == bar
29
29
  # !!('foo' =~ /^\w+$/)
30
+ # !(foo.class < Numeric) # Checking class hierarchy is allowed
30
31
  class InverseMethods < Cop
31
32
  include IgnoredNode
32
33
 
33
34
  MSG = 'Use `%<inverse>s` instead of inverting `%<method>s`.'.freeze
35
+ CLASS_COMPARISON_METHODS = %i[<= >= < >].freeze
34
36
  EQUALITY_METHODS = %i[== != =~ !~ <= >= < >].freeze
35
37
  NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
38
+ CAMEL_CASE = /[A-Z]+[a-z]+/
36
39
 
37
40
  def_node_matcher :inverse_candidate?, <<-PATTERN
38
41
  {
39
- (send $(send (...) $_ ...) :!)
40
- (send (block $(send (...) $_) ...) :!)
41
- (send (begin $(send (...) $_ ...)) :!)
42
+ (send $(send $(...) $_ $...) :!)
43
+ (send (block $(send $(...) $_) $...) :!)
44
+ (send (begin $(send $(...) $_ $...)) :!)
42
45
  }
43
46
  PATTERN
44
47
 
@@ -52,8 +55,9 @@ module RuboCop
52
55
 
53
56
  def on_send(node)
54
57
  return if part_of_ignored_node?(node)
55
- inverse_candidate?(node) do |_method_call, method|
58
+ inverse_candidate?(node) do |_method_call, lhs, method, rhs|
56
59
  return unless inverse_methods.key?(method)
60
+ return if possible_class_hierarchy_check?(lhs, rhs, method)
57
61
  return if negated?(node)
58
62
 
59
63
  add_offense(node,
@@ -78,7 +82,7 @@ module RuboCop
78
82
  end
79
83
 
80
84
  def autocorrect(node)
81
- method_call, method = inverse_candidate?(node)
85
+ method_call, _lhs, method, _rhs = inverse_candidate?(node)
82
86
 
83
87
  if method_call && method
84
88
  lambda do |corrector|
@@ -143,6 +147,19 @@ module RuboCop
143
147
  method_call.loc.expression.end_pos,
144
148
  node.loc.expression.end_pos)
145
149
  end
150
+
151
+ # When comparing classes, `!(Integer < Numeric)` is not the same as
152
+ # `Integer > Numeric`.
153
+ def possible_class_hierarchy_check?(lhs, rhs, method)
154
+ CLASS_COMPARISON_METHODS.include?(method) &&
155
+ (camel_case_constant?(lhs) ||
156
+ (rhs.size == 1 &&
157
+ camel_case_constant?(rhs.first)))
158
+ end
159
+
160
+ def camel_case_constant?(node)
161
+ node.const_type? && node.source =~ CAMEL_CASE
162
+ end
146
163
  end
147
164
  end
148
165
  end
@@ -9,6 +9,7 @@ module RuboCop
9
9
  #
10
10
  # @example
11
11
  #
12
+ # # bad
12
13
  # def redundant
13
14
  # begin
14
15
  # ala
@@ -18,12 +19,31 @@ module RuboCop
18
19
  # end
19
20
  # end
20
21
  #
22
+ # # good
21
23
  # def preferred
22
24
  # ala
23
25
  # bala
24
26
  # rescue StandardError => e
25
27
  # something
26
28
  # end
29
+ #
30
+ # # bad
31
+ # # When using Ruby 2.5 or later.
32
+ # do_something do
33
+ # begin
34
+ # something
35
+ # rescue => ex
36
+ # anything
37
+ # end
38
+ # end
39
+ #
40
+ # # good
41
+ # # In Ruby 2.5 or later, you can omit `begin` in `do-end` block.
42
+ # do_something do
43
+ # something
44
+ # rescue => ex
45
+ # anything
46
+ # end
27
47
  class RedundantBegin < Cop
28
48
  MSG = 'Redundant `begin` block detected.'.freeze
29
49
 
@@ -228,9 +228,10 @@ module RuboCop
228
228
  start_method.each_ancestor do |ancestor|
229
229
  break unless %i[send block].include?(ancestor.type)
230
230
  next unless ancestor.send_type?
231
- break if ancestor == method_chain
232
231
 
233
232
  corrector.insert_before(ancestor.loc.dot, '&')
233
+
234
+ break if ancestor == method_chain
234
235
  end
235
236
  end
236
237
  end
@@ -39,6 +39,9 @@ module RuboCop
39
39
  alias on_defs on_def
40
40
 
41
41
  def autocorrect(node)
42
+ parent = node.parent
43
+ return if parent && parent.send_type?
44
+
42
45
  if node.def_type?
43
46
  autocorrect_instance(node)
44
47
  elsif node.defs_type? && node.children.first.self_type?
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for accessing the first element of `String#unpack`
7
+ # which can be replaced with the shorter method `unpack1`.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # 'foo'.unpack('h*').first
13
+ # 'foo'.unpack('h*')[0]
14
+ # 'foo'.unpack('h*').slice(0)
15
+ # 'foo'.unpack('h*').at(0)
16
+ # 'foo'.unpack('h*').take(1)
17
+ #
18
+ # # good
19
+ # 'foo'.unpack1('h*')
20
+ #
21
+ class UnpackFirst < Cop
22
+ extend TargetRubyVersion
23
+
24
+ minimum_target_ruby_version 2.4
25
+
26
+ MSG = 'Use `%<receiver>s.unpack1(%<format>s)` instead of '\
27
+ '`%<receiver>s.unpack(%<format>s)%<method>s`.'.freeze
28
+
29
+ def_node_matcher :unpack_and_first_element?, <<-PATTERN
30
+ {
31
+ (send $(send (...) :unpack $(...)) :first)
32
+ (send $(send (...) :unpack $(...)) {:[] :slice :at} (int 0))
33
+ (send $(send (...) :unpack $(...)) :take (int 1))
34
+ }
35
+ PATTERN
36
+
37
+ def on_send(node)
38
+ unpack_and_first_element?(node) do |unpack_call, unpack_arg|
39
+ range = first_element_range(node, unpack_call)
40
+ message = format(MSG,
41
+ receiver: unpack_call.receiver.source,
42
+ format: unpack_arg.source,
43
+ method: range.source)
44
+ add_offense(node, message: message)
45
+ end
46
+ end
47
+
48
+ def autocorrect(node)
49
+ unpack_and_first_element?(node) do |unpack_call, _unpack_arg|
50
+ lambda do |corrector|
51
+ corrector.remove(first_element_range(node, unpack_call))
52
+ corrector.replace(unpack_call.loc.selector, 'unpack1')
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def first_element_range(node, unpack_call)
60
+ Parser::Source::Range.new(node.loc.expression.source_buffer,
61
+ unpack_call.loc.expression.end_pos,
62
+ node.loc.expression.end_pos)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -121,7 +121,7 @@ module RuboCop
121
121
  .sub('Style', 'Styles')
122
122
  end
123
123
 
124
- def tokens(node) # rubocop:disable Metrics/AbcSize
124
+ def tokens(node)
125
125
  @tokens ||= {}
126
126
  return @tokens[node.object_id] if @tokens[node.object_id]
127
127
 
@@ -129,17 +129,9 @@ module RuboCop
129
129
  begin_pos = source_range.begin_pos
130
130
  end_pos = source_range.end_pos
131
131
 
132
- tokens_to_node_end = processed_source.tokens.take_while do |token|
133
- token.end_pos <= end_pos
132
+ @tokens[node.object_id] = processed_source.tokens.select do |token|
133
+ token.end_pos <= end_pos && token.begin_pos >= begin_pos
134
134
  end
135
-
136
- node_tokens = []
137
- tokens_to_node_end.reverse_each do |token|
138
- break unless token.begin_pos >= begin_pos
139
- node_tokens.unshift(token)
140
- end
141
-
142
- @tokens[node.object_id] = node_tokens
143
135
  end
144
136
 
145
137
  private
@@ -17,10 +17,27 @@ module RuboCop
17
17
  def started(target_files)
18
18
  super
19
19
  @offense_counts = Hash.new(0)
20
+
21
+ return unless output.tty?
22
+
23
+ file_phrase = target_files.count == 1 ? 'file' : 'files'
24
+
25
+ # 185/407 files |====== 45 ======> | ETA: 00:00:04
26
+ # %c / %C | %w > %i | %e
27
+ bar_format = " %c/%C #{file_phrase} |%w>%i| %e "
28
+
29
+ @progressbar = ProgressBar.create(
30
+ output: output,
31
+ total: target_files.count,
32
+ format: bar_format,
33
+ autostart: false
34
+ )
35
+ @progressbar.start
20
36
  end
21
37
 
22
38
  def file_finished(_file, offenses)
23
39
  offenses.each { |o| @offense_counts[o.cop_name] += 1 }
40
+ @progressbar.increment if instance_variable_defined?(:@progressbar)
24
41
  end
25
42
 
26
43
  def finished(_inspected_files)
@@ -190,7 +190,7 @@ module RuboCop
190
190
  class SimpleComment < MagicComment
191
191
  # Match `encoding` or `coding`
192
192
  def encoding
193
- extract(/\b(?:en)?coding: (#{TOKEN})/i)
193
+ extract(/\#* \b(?:en)?coding: (#{TOKEN})/i)
194
194
  end
195
195
 
196
196
  private
@@ -121,7 +121,7 @@ module RuboCop
121
121
  end
122
122
 
123
123
  # Most recently modified file first.
124
- target_files.sort_by! { |path| Integer(-File.mtime(path)) } if fail_fast?
124
+ target_files.sort_by! { |path| -Integer(File.mtime(path)) } if fail_fast?
125
125
 
126
126
  target_files
127
127
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '0.53.0'.freeze
6
+ STRING = '0.54.0'.freeze
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, running on ' \
9
9
  '%<ruby_engine>s %<ruby_version>s %<ruby_platform>s)'.freeze
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.53.0
4
+ version: 0.54.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-03-05 00:00:00.000000000 Z
13
+ date: 2018-03-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parallel
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '1.3'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rack
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  description: |2
126
140
  Automatic Ruby code style checking tool.
127
141
  Aims to enforce the community-driven Ruby Style Guide.
@@ -500,6 +514,7 @@ files:
500
514
  - lib/rubocop/cop/rails/has_and_belongs_to_many.rb
501
515
  - lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb
502
516
  - lib/rubocop/cop/rails/http_positional_arguments.rb
517
+ - lib/rubocop/cop/rails/http_status.rb
503
518
  - lib/rubocop/cop/rails/inverse_of.rb
504
519
  - lib/rubocop/cop/rails/lexically_scoped_action_filter.rb
505
520
  - lib/rubocop/cop/rails/not_null_column.rb
@@ -678,6 +693,7 @@ files:
678
693
  - lib/rubocop/cop/style/unneeded_capital_w.rb
679
694
  - lib/rubocop/cop/style/unneeded_interpolation.rb
680
695
  - lib/rubocop/cop/style/unneeded_percent_q.rb
696
+ - lib/rubocop/cop/style/unpack_first.rb
681
697
  - lib/rubocop/cop/style/variable_interpolation.rb
682
698
  - lib/rubocop/cop/style/when_then.rb
683
699
  - lib/rubocop/cop/style/while_until_do.rb