rubocop 0.53.0 → 0.54.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 (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