rubocop 1.85.0 → 1.86.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +3 -10
  3. data/lib/rubocop/cache_config.rb +1 -1
  4. data/lib/rubocop/cli/command/auto_generate_config.rb +1 -1
  5. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  6. data/lib/rubocop/cli/command/show_docs_url.rb +1 -1
  7. data/lib/rubocop/config.rb +1 -1
  8. data/lib/rubocop/config_finder.rb +1 -1
  9. data/lib/rubocop/config_store.rb +1 -1
  10. data/lib/rubocop/config_validator.rb +1 -1
  11. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  12. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  13. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  14. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  16. data/lib/rubocop/cop/layout/end_alignment.rb +3 -2
  17. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +28 -3
  18. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  19. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  20. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  21. data/lib/rubocop/cop/lint/duplicate_methods.rb +50 -8
  22. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  23. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  24. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  25. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  26. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +16 -0
  27. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  28. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  29. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  30. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  31. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +6 -5
  32. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
  33. data/lib/rubocop/cop/registry.rb +20 -13
  34. data/lib/rubocop/cop/style/and_or.rb +1 -0
  35. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  36. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  37. data/lib/rubocop/cop/style/block_delimiters.rb +23 -31
  38. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  39. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  40. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  41. data/lib/rubocop/cop/style/empty_class_definition.rb +24 -2
  42. data/lib/rubocop/cop/style/file_open.rb +28 -7
  43. data/lib/rubocop/cop/style/global_vars.rb +1 -1
  44. data/lib/rubocop/cop/style/hash_lookup_method.rb +7 -0
  45. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -0
  46. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  47. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  48. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  49. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  50. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  51. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  52. data/lib/rubocop/cop/style/numeric_literals.rb +1 -1
  53. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  54. data/lib/rubocop/cop/style/reduce_to_hash.rb +15 -0
  55. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  56. data/lib/rubocop/cop/style/redundant_parentheses.rb +19 -22
  57. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  58. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  59. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  60. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  61. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  62. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  63. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  64. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  65. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  66. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  67. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  68. data/lib/rubocop/formatter.rb +22 -21
  69. data/lib/rubocop/lsp/routes.rb +10 -3
  70. data/lib/rubocop/mcp/server.rb +25 -1
  71. data/lib/rubocop/path_util.rb +14 -2
  72. data/lib/rubocop/plugin/loader.rb +1 -1
  73. data/lib/rubocop/result_cache.rb +22 -10
  74. data/lib/rubocop/rspec/shared_contexts.rb +11 -2
  75. data/lib/rubocop/runner.rb +8 -3
  76. data/lib/rubocop/server/core.rb +2 -0
  77. data/lib/rubocop/target_finder.rb +1 -1
  78. data/lib/rubocop/version.rb +2 -2
  79. metadata +3 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 540ad681621feef2b8ffb784afa823f9f03df707e480c371da9b95576c42a0c0
4
- data.tar.gz: 2e346929c88929a4c923c3f4f5907b986580fbcd36de82687433ef22e5648f9b
3
+ metadata.gz: a8991e66e8594a8ba30b4122a2990df1d7f610e3e51515b016e68432bcd94855
4
+ data.tar.gz: dadc1936594395ec71c4b628867d1ed2e2f585fcafedb886e5493d0b81c4cb2f
5
5
  SHA512:
6
- metadata.gz: 6c7d48381d4f7121e7e17303ad5ce20c74d24e04b71d9b6d57a89d9c8c435da93e1db46b2a9b574dcdd363dfaad4e568c02f24ad1c13bfc0481e73109b06c8a5
7
- data.tar.gz: a04e081927cf6dca0c9775a741aa589ae5878cda28fbf8c7e69545970089a48908b56b82872416680acc4461b7ab6261191ea35190d994dc3f9bb3a3b57e0018
6
+ metadata.gz: f95a7d048724e297f77b42444bd2eae15889bfa1b9b815fabbde131af75e99e114ab6ba2b4c0d078c937be9abd899ec7d9fb0650354b4bfd3cfb5a554f4bcfc3
7
+ data.tar.gz: ed088266152e384f4c2c82dead8363ced3c990b6492a4b4652f98a548ec16b0c57da73e1295dbbc302b00c5affd3fe2e540d9c012221068449d733d1c812139c
data/config/default.yml CHANGED
@@ -3,20 +3,16 @@
3
3
  AllCops:
4
4
  RubyInterpreters:
5
5
  - ruby
6
- - macruby
7
6
  - rake
8
7
  - jruby
9
- - rbx
10
8
  # Include common Ruby source files.
11
9
  Include:
12
10
  - '**/*.rb'
13
11
  - '**/*.arb'
14
12
  - '**/*.axlsx'
15
13
  - '**/*.builder'
16
- - '**/*.fcgi'
17
14
  - '**/*.gemfile'
18
15
  - '**/*.gemspec'
19
- - '**/*.god'
20
16
  - '**/*.jb'
21
17
  - '**/*.jbuilder'
22
18
  - '**/*.mspec'
@@ -25,15 +21,12 @@ AllCops:
25
21
  - '**/*.podspec'
26
22
  - '**/*.rabl'
27
23
  - '**/*.rake'
28
- - '**/*.rbuild'
29
24
  - '**/*.rbw'
30
- - '**/*.rbx'
31
25
  - '**/*.ru'
32
26
  - '**/*.ruby'
33
27
  - '**/*.schema'
34
28
  - '**/*.spec'
35
29
  - '**/*.thor'
36
- - '**/*.watchr'
37
30
  - '**/.irbrc'
38
31
  - '**/.pryrc'
39
32
  - '**/.simplecov'
@@ -43,7 +36,6 @@ AllCops:
43
36
  - '**/Brewfile'
44
37
  - '**/Buildfile'
45
38
  - '**/Capfile'
46
- - '**/Cheffile'
47
39
  - '**/Dangerfile'
48
40
  - '**/Deliverfile'
49
41
  - '**/Fastfile'
@@ -60,7 +52,6 @@ AllCops:
60
52
  - '**/Snapfile'
61
53
  - '**/Steepfile'
62
54
  - '**/Thorfile'
63
- - '**/Vagabondfile'
64
55
  - '**/Vagrantfile'
65
56
  Exclude:
66
57
  - 'node_modules/**/*'
@@ -117,7 +108,7 @@ AllCops:
117
108
  # line option.
118
109
  UseCache: true
119
110
  # Threshold for how many files can be stored in the result cache before some
120
- # of the files are automatically removed.
111
+ # of the files are automatically removed. Set to false to disable cache pruning.
121
112
  MaxFilesInCache: 20000
122
113
  # The cache will be stored in "rubocop_cache" under this directory. If
123
114
  # CacheRootDirectory is ~ (nil), which it is by default, the root will be
@@ -3993,11 +3984,13 @@ Style/EmptyClassDefinition:
3993
3984
  Description: 'Enforces consistent style for empty class definitions.'
3994
3985
  Enabled: pending
3995
3986
  VersionAdded: '1.84'
3987
+ VersionChanged: '1.86'
3996
3988
  EnforcedStyle: class_keyword
3997
3989
  SupportedStyles:
3998
3990
  - class_keyword
3999
3991
  - class_new
4000
3992
  - class_definition # Deprecated.
3993
+ AllowedParentClasses: []
4001
3994
 
4002
3995
  Style/EmptyElse:
4003
3996
  Description: 'Avoid empty else-clauses.'
@@ -35,7 +35,7 @@ module RuboCop
35
35
  root_dir do
36
36
  next cache_root_override if cache_root_override
37
37
 
38
- config_path = ConfigFinder.find_config_path(Dir.pwd)
38
+ config_path = ConfigFinder.find_config_path(PathUtil.pwd)
39
39
  file_contents = File.read(config_path)
40
40
 
41
41
  # Returns early if `CacheRootDirectory` is not used before requiring `erb` or `yaml`.
@@ -153,7 +153,7 @@ module RuboCop
153
153
  def relative_path_to_todo_from_options_config
154
154
  return AUTO_GENERATED_FILE unless @options[:config]
155
155
 
156
- base = Pathname.new(Dir.pwd)
156
+ base = Pathname.new(PathUtil.pwd)
157
157
  config_dir = Pathname.new(@options[:config]).realpath.dirname
158
158
 
159
159
  # Don't have the path start with `/`
@@ -25,7 +25,7 @@ module RuboCop
25
25
  super
26
26
 
27
27
  # Load the configs so the require()s are done for custom cops
28
- @config = @config_store.for(Dir.pwd)
28
+ @config = @config_store.for(PathUtil.pwd)
29
29
 
30
30
  @cop_matchers = @options[:show_cops].map do |pattern|
31
31
  if pattern.include?('*')
@@ -46,7 +46,7 @@ module RuboCop
46
46
  registry = Cop::Registry.global
47
47
  show_all = @cop_matchers.empty?
48
48
 
49
- puts "# Available cops (#{registry.length}) + config for #{Dir.pwd}: " if show_all
49
+ puts "# Available cops (#{registry.length}) + config for #{PathUtil.pwd}: " if show_all
50
50
 
51
51
  registry.departments.sort!.each do |department|
52
52
  print_cops_of_department(registry, department, show_all)
@@ -12,7 +12,7 @@ module RuboCop
12
12
  def initialize(env)
13
13
  super
14
14
 
15
- @config = @config_store.for(Dir.pwd)
15
+ @config = @config_store.for(PathUtil.pwd)
16
16
  end
17
17
 
18
18
  def run
@@ -286,7 +286,7 @@ module RuboCop
286
286
  loaded_path != File.join(Dir.home, ConfigLoader::DOTFILE)
287
287
  File.expand_path(File.dirname(loaded_path))
288
288
  else
289
- Dir.pwd
289
+ PathUtil.pwd
290
290
  end
291
291
  end
292
292
 
@@ -30,7 +30,7 @@ module RuboCop
30
30
  private
31
31
 
32
32
  def find_project_root
33
- pwd = Dir.pwd
33
+ pwd = PathUtil.pwd
34
34
  gems_file = find_last_file_upwards('Gemfile', pwd) || find_last_file_upwards('gems.rb', pwd)
35
35
  return unless gems_file
36
36
 
@@ -49,7 +49,7 @@ module RuboCop
49
49
  end
50
50
 
51
51
  def for_pwd
52
- for_dir(Dir.pwd)
52
+ for_dir(PathUtil.pwd)
53
53
  end
54
54
 
55
55
  # If type (file/dir) is known beforehand,
@@ -9,7 +9,7 @@ module RuboCop
9
9
 
10
10
  # @api private
11
11
  COMMON_PARAMS = %w[Exclude Include Severity inherit_mode AutoCorrect StyleGuide Details
12
- Enabled Reference References].freeze
12
+ Enabled Reference References Safe SafeAutoCorrect].freeze
13
13
  # @api private
14
14
  INTERNAL_PARAMS = %w[Description StyleGuide
15
15
  VersionAdded VersionChanged VersionRemoved
@@ -52,7 +52,7 @@ module RuboCop
52
52
  'following the first line of a multi-line method call.'
53
53
 
54
54
  def on_send(node)
55
- return if !multiple_arguments?(node) || (node.send_type? && node.method?(:[]=)) ||
55
+ return if !multiple_arguments?(node) || (node.call_type? && node.method?(:[]=)) ||
56
56
  autocorrect_incompatible_with_other_cops?
57
57
 
58
58
  items = flattened_arguments(node)
@@ -112,7 +112,7 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def last_heredoc_line(node)
115
- if node.send_type?
115
+ if node.call_type?
116
116
  node.arguments.select { |arg| heredoc?(arg) }.map { |arg| arg.loc.heredoc_end.line }.max
117
117
  elsif heredoc?(node)
118
118
  node.loc.heredoc_end.line
@@ -60,6 +60,11 @@ module RuboCop
60
60
  END_OF_HEREDOC_LINE = 1
61
61
  SIMPLE_DIRECTIVE_COMMENT_PATTERN = /\A# *:nocov:\z/.freeze
62
62
 
63
+ # @!method guard_clause_branch?(node)
64
+ def_node_matcher :guard_clause_branch?, <<~PATTERN
65
+ {(send nil? {:raise :fail} ...) return break next}
66
+ PATTERN
67
+
63
68
  def on_if(node)
64
69
  return if correct_style?(node)
65
70
  return if multiple_statements_on_line?(node)
@@ -97,14 +102,16 @@ module RuboCop
97
102
  end
98
103
 
99
104
  def correct_style?(node)
100
- !contains_guard_clause?(node) ||
105
+ !node.if_branch&.guard_clause? ||
101
106
  next_line_rescue_or_ensure?(node) ||
102
107
  next_sibling_parent_empty_or_else?(node) ||
103
108
  next_sibling_empty_or_guard_clause?(node)
104
109
  end
105
110
 
106
111
  def contains_guard_clause?(node)
107
- node.if_branch&.guard_clause?
112
+ return false unless (branch = node.if_branch)
113
+
114
+ guard_clause_branch?(branch)
108
115
  end
109
116
 
110
117
  def next_line_empty_or_allowed_directive_comment?(line)
@@ -185,7 +185,7 @@ module RuboCop
185
185
  end
186
186
 
187
187
  def empty_line_between_macros
188
- cop_config.fetch('DefLikeMacros', []).map(&:to_sym)
188
+ @empty_line_between_macros ||= cop_config.fetch('DefLikeMacros', []).map(&:to_sym).freeze
189
189
  end
190
190
 
191
191
  def macro_candidate?(node)
@@ -66,6 +66,7 @@ module RuboCop
66
66
  extend AutoCorrector
67
67
 
68
68
  MSG = 'Add an empty line after attribute accessor.'
69
+ RESTRICT_ON_SEND = %i[attr_reader attr_writer attr_accessor attr].freeze
69
70
 
70
71
  def on_send(node)
71
72
  return unless node.attribute_accessor?
@@ -129,8 +129,9 @@ module RuboCop
129
129
  # we check if it's an if/unless/while/until.
130
130
  return unless (rhs = first_part_of_call_chain(rhs))
131
131
 
132
- # If `rhs` is a `begin` node, find the first non-`begin` child.
133
- rhs = rhs.child_nodes.first while rhs.begin_type?
132
+ # If `rhs` is a `begin` node or a logical operator,
133
+ # unwrap to find the leading conditional.
134
+ rhs = rhs.child_nodes.first while rhs.type?(:begin, :or, :and)
134
135
 
135
136
  return unless rhs.conditional?
136
137
  return if rhs.if_type? && rhs.ternary?
@@ -128,12 +128,15 @@ module RuboCop
128
128
  if hash_pair_indented?(node, pair_ancestor, given_style)
129
129
  return check_hash_pair_indented_style(rhs, pair_ancestor)
130
130
  end
131
-
132
- return false if !pair_ancestor && not_for_this_cop?(node)
131
+ return false if skip_for_context?(node, pair_ancestor)
133
132
 
134
133
  check_regular_indentation(node, lhs, rhs, given_style)
135
134
  end
136
135
 
136
+ def skip_for_context?(node, pair_ancestor)
137
+ pair_ancestor ? inside_multiline_chain_arg?(node) : not_for_this_cop?(node)
138
+ end
139
+
137
140
  def hash_pair_aligned?(pair_ancestor, given_style)
138
141
  pair_ancestor && given_style == :aligned
139
142
  end
@@ -152,7 +155,10 @@ module RuboCop
152
155
  end
153
156
 
154
157
  def check_hash_pair_indentation(node, lhs, rhs)
155
- @base = find_hash_pair_alignment_base(node) || lhs.source_range
158
+ @base = find_hash_pair_alignment_base(node)
159
+ return false if !@base && inside_multiline_chain_arg?(node)
160
+
161
+ @base ||= lhs.source_range
156
162
  return if aligned_with_first_line_dot?(node, rhs)
157
163
 
158
164
  calculate_column_delta_offense(rhs, @base.column)
@@ -166,6 +172,25 @@ module RuboCop
166
172
  first_call.loc.dot.join(first_call.loc.selector)
167
173
  end
168
174
 
175
+ def inside_multiline_chain_arg?(node)
176
+ enclosing_call = find_enclosing_chain_call(node)
177
+ return false unless enclosing_call
178
+
179
+ !same_line?(enclosing_call.loc.selector, enclosing_call.receiver.source_range)
180
+ end
181
+
182
+ def find_enclosing_chain_call(node)
183
+ hash_ancestor = find_pair_ancestor(node).parent
184
+ enclosing_call = hash_ancestor.parent
185
+ return unless hash_arg_in_chain?(enclosing_call, hash_ancestor)
186
+
187
+ enclosing_call
188
+ end
189
+
190
+ def hash_arg_in_chain?(call, hash_node)
191
+ call&.call_type? && call.receiver != hash_node && call.loc?(:dot)
192
+ end
193
+
169
194
  def aligned_with_first_line_dot?(node, rhs)
170
195
  return false unless rhs.source.start_with?('.', '&.')
171
196
 
@@ -47,9 +47,11 @@ module RuboCop
47
47
  check(node, [:operator].freeze) if node.keyword?
48
48
  end
49
49
 
50
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
50
+ def on_block(node)
51
51
  check(node, %i[begin end].freeze)
52
52
  end
53
+ alias on_numblock on_block
54
+ alias on_itblock on_block
53
55
 
54
56
  def on_break(node)
55
57
  check(node, [:keyword].freeze)
@@ -26,6 +26,7 @@ module RuboCop
26
26
 
27
27
  MSG_REQUIRE_SPACE = 'Use a space between `->` and `(` in lambda literals.'
28
28
  MSG_REQUIRE_NO_SPACE = 'Do not use spaces between `->` and `(` in lambda literals.'
29
+ RESTRICT_ON_SEND = %i[lambda].freeze
29
30
 
30
31
  def on_send(node)
31
32
  return unless arrow_lambda_with_args?(node)
@@ -6,8 +6,11 @@ module RuboCop
6
6
  # Checks for constant reassignments.
7
7
  #
8
8
  # Emulates Ruby's runtime warning "already initialized constant X"
9
- # when a constant is reassigned in the same file and namespace using the
10
- # `NAME = value` syntax.
9
+ # when a constant is reassigned in the same file and namespace.
10
+ #
11
+ # The cop tracks constants defined via `NAME = value` syntax as well as
12
+ # class/module keyword definitions. It detects reassignment when a constant
13
+ # is first defined one way and then redefined using the `NAME = value` syntax.
11
14
  #
12
15
  # The cop cannot catch all offenses, like, for example, when a constant
13
16
  # is reassigned in another file, or when using metaprogramming (`Module#const_set`).
@@ -36,6 +39,14 @@ module RuboCop
36
39
  # X = :bar
37
40
  # end
38
41
  #
42
+ # # bad
43
+ # class FooError < StandardError; end
44
+ # FooError = Class.new(RuntimeError)
45
+ #
46
+ # # bad
47
+ # module M; end
48
+ # M = 1
49
+ #
39
50
  # # good - keep only one assignment
40
51
  # X = :bar
41
52
  #
@@ -69,16 +80,30 @@ module RuboCop
69
80
 
70
81
  # @!method remove_constant(node)
71
82
  def_node_matcher :remove_constant, <<~PATTERN
72
- (send _ :remove_const
83
+ (send {nil? self} :remove_const
73
84
  ({sym str} $_))
74
85
  PATTERN
75
86
 
87
+ def on_class(node)
88
+ return unless unconditional_definition?(node)
89
+
90
+ constant_definitions[definition_name(node)] ||= :class
91
+ end
92
+
93
+ def on_module(node)
94
+ return unless unconditional_definition?(node)
95
+
96
+ constant_definitions[definition_name(node)] ||= :module
97
+ end
98
+
76
99
  def on_casgn(node)
77
100
  return unless fixed_constant_path?(node)
78
101
  return unless simple_assignment?(node)
79
- return if constant_names.add?(fully_qualified_constant_name(node))
80
102
 
81
- add_offense(node, message: format(MSG, constant: node.name))
103
+ name = fully_qualified_constant_name(node)
104
+ return constant_definitions[name] = :casgn unless constant_definitions.key?(name)
105
+
106
+ add_offense(node, message: format(MSG, constant: constant_display_name(node)))
82
107
  end
83
108
 
84
109
  def on_send(node)
@@ -90,7 +115,7 @@ module RuboCop
90
115
 
91
116
  return if namespaces.none?
92
117
 
93
- constant_names.delete(fully_qualified_name_for(namespaces, constant))
118
+ constant_definitions.delete(fully_qualified_name_for(namespaces, constant))
94
119
  end
95
120
 
96
121
  private
@@ -104,7 +129,7 @@ module RuboCop
104
129
  return true if ancestor.type?(:module, :class)
105
130
 
106
131
  ancestor.begin_type? || ancestor.literal? || ancestor.casgn_type? ||
107
- freeze_method?(ancestor)
132
+ ancestor.type?(:masgn, :mlhs) || freeze_method?(ancestor)
108
133
  end
109
134
  end
110
135
 
@@ -128,6 +153,10 @@ module RuboCop
128
153
  ['', *namespaces, constant].join('::')
129
154
  end
130
155
 
156
+ def constant_display_name(node)
157
+ [*constant_namespaces(node), node.name].join('::')
158
+ end
159
+
131
160
  def constant_namespaces(node)
132
161
  node.each_path.select(&:const_type?).map(&:short_name)
133
162
  end
@@ -139,8 +168,29 @@ module RuboCop
139
168
  .reverse
140
169
  end
141
170
 
142
- def constant_names
143
- @constant_names ||= Set.new
171
+ def unconditional_definition?(node)
172
+ node.each_ancestor.all? do |ancestor|
173
+ ancestor.type?(:begin, :module, :class)
174
+ end
175
+ end
176
+
177
+ def definition_name(node)
178
+ identifier = node.identifier
179
+
180
+ if identifier.namespace&.cbase_type?
181
+ fully_qualified_name_for([], identifier.short_name)
182
+ else
183
+ namespaces = ancestor_namespaces(node) + identifier_namespaces(identifier)
184
+ fully_qualified_name_for(namespaces, identifier.short_name)
185
+ end
186
+ end
187
+
188
+ def identifier_namespaces(identifier)
189
+ identifier.each_path.select(&:const_type?).map(&:short_name)
190
+ end
191
+
192
+ def constant_definitions
193
+ @constant_definitions ||= {}
144
194
  end
145
195
  end
146
196
  end
@@ -197,6 +197,13 @@ module RuboCop
197
197
  # @!method sym_name(node)
198
198
  def_node_matcher :sym_name, '(sym $_name)'
199
199
 
200
+ # @!method class_or_module_new_block?(node)
201
+ def_node_matcher :class_or_module_new_block?, <<~PATTERN
202
+ (block
203
+ (send (const _ {:Class :Module}) :new ...)
204
+ ...)
205
+ PATTERN
206
+
200
207
  def on_send(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
201
208
  name, original_name = alias_method?(node)
202
209
 
@@ -233,9 +240,12 @@ module RuboCop
233
240
 
234
241
  def check_self_receiver(node, name)
235
242
  enclosing = node.parent_module_name
236
- return unless enclosing
237
-
238
- found_method(node, "#{enclosing}.#{name}")
243
+ if enclosing
244
+ found_method(node, "#{enclosing}.#{name}")
245
+ elsif (anon_block = anonymous_class_block(node))
246
+ scope = qualified_name(anon_block.parent_module_name, nil, 'Object')
247
+ found_method(node, "#{scope}.#{name}", scope_id: anon_block_scope_id(anon_block))
248
+ end
239
249
  end
240
250
 
241
251
  def inside_condition?(node)
@@ -274,16 +284,45 @@ module RuboCop
274
284
  end
275
285
 
276
286
  def found_instance_method(node, name)
277
- return found_sclass_method(node, name) unless (scope = node.parent_module_name)
287
+ if (scope = node.parent_module_name)
288
+ found_method(node, "#{humanize_scope(scope)}#{name}")
289
+ elsif (anon_block = anonymous_class_block(node))
290
+ base = qualified_name(anon_block.parent_module_name, nil, 'Object')
291
+ scope = node.each_ancestor(:sclass).any? ? "#<Class:#{base}>" : base
292
+ found_method(
293
+ node, "#{humanize_scope(scope)}#{name}", scope_id: anon_block_scope_id(anon_block)
294
+ )
295
+ else
296
+ found_sclass_method(node, name)
297
+ end
298
+ end
278
299
 
279
- # Humanize the scope
300
+ def humanize_scope(scope)
280
301
  scope = scope.sub(
281
302
  /(?:(?<name>.*)::)#<Class:\k<name>>|#<Class:(?<name>.*)>(?:::)?/,
282
303
  '\k<name>.'
283
304
  )
284
- scope << '#' unless scope.end_with?('.')
305
+ scope.end_with?('.') ? scope : "#{scope}#"
306
+ end
307
+
308
+ def anonymous_class_block(node)
309
+ first_block = node.each_ancestor(:block).first
310
+ return unless class_or_module_new_block?(first_block)
311
+ return if first_block.parent&.type?(:lvasgn, :block)
312
+ return if node.each_ancestor(:sclass).any? { |s| !s.children.first.self_type? }
313
+
314
+ first_block
315
+ end
285
316
 
286
- found_method(node, "#{scope}#{name}")
317
+ def anon_block_scope_id(anon_block)
318
+ parent = anon_block.parent
319
+ return unless parent&.call_type?
320
+
321
+ if parent.receiver
322
+ "#{parent.receiver.source}.#{parent.method_name}"
323
+ else
324
+ source_location(anon_block)
325
+ end
287
326
  end
288
327
 
289
328
  def found_sclass_method(node, name)
@@ -296,8 +335,10 @@ module RuboCop
296
335
  found_method(node, "#{singleton_receiver_node.method_name}.#{name}")
297
336
  end
298
337
 
299
- def found_method(node, method_name)
338
+ # rubocop:disable Metrics/AbcSize
339
+ def found_method(node, method_name, scope_id: nil)
300
340
  key = method_key(node, method_name)
341
+ key = "#{key}@#{scope_id}" if scope_id
301
342
  scope = node.each_ancestor(:rescue, :ensure).first&.type
302
343
 
303
344
  if @definitions.key?(key)
@@ -314,6 +355,7 @@ module RuboCop
314
355
  @definitions[key] = node
315
356
  end
316
357
  end
358
+ # rubocop:enable Metrics/AbcSize
317
359
 
318
360
  def method_key(node, method_name)
319
361
  if (ancestor_def = node.each_ancestor(:any_def).first)
@@ -70,7 +70,7 @@ module RuboCop
70
70
 
71
71
  def on_if(node)
72
72
  return if node.body || same_line?(node.loc.begin, node.loc.end)
73
- return if cop_config['AllowComments'] && contains_comments?(node)
73
+ return if allow_comments?(node)
74
74
 
75
75
  range = offense_range(node)
76
76
 
@@ -83,6 +83,11 @@ module RuboCop
83
83
 
84
84
  private
85
85
 
86
+ def allow_comments?(node)
87
+ cop_config['AllowComments'] && contains_comments?(node) &&
88
+ !comments_contain_disables?(node, name)
89
+ end
90
+
86
91
  def offense_range(node)
87
92
  if node.loc.else
88
93
  node.source_range.begin.join(node.loc.else.begin)
@@ -53,11 +53,18 @@ module RuboCop
53
53
  def on_case_match(node)
54
54
  node.in_pattern_branches.each do |branch|
55
55
  next if branch.body
56
- next if cop_config['AllowComments'] && contains_comments?(branch)
56
+ next if allow_comments?(branch)
57
57
 
58
58
  add_offense(branch)
59
59
  end
60
60
  end
61
+
62
+ private
63
+
64
+ def allow_comments?(node)
65
+ cop_config['AllowComments'] && contains_comments?(node) &&
66
+ !comments_contain_disables?(node, name)
67
+ end
61
68
  end
62
69
  end
63
70
  end
@@ -50,11 +50,18 @@ module RuboCop
50
50
  def on_case(node)
51
51
  node.when_branches.each do |when_node|
52
52
  next if when_node.body
53
- next if cop_config['AllowComments'] && contains_comments?(when_node)
53
+ next if allow_comments?(when_node)
54
54
 
55
55
  add_offense(when_node)
56
56
  end
57
57
  end
58
+
59
+ private
60
+
61
+ def allow_comments?(node)
62
+ cop_config['AllowComments'] && contains_comments?(node) &&
63
+ !comments_contain_disables?(node, name)
64
+ end
58
65
  end
59
66
  end
60
67
  end
@@ -162,7 +162,7 @@ module RuboCop
162
162
  end
163
163
 
164
164
  def allow_receiver?(receiver)
165
- if receiver.numeric_type? || (receiver.send_type? &&
165
+ if receiver.numeric_type? || (receiver.call_type? &&
166
166
  (conversion_method?(receiver.method_name) ||
167
167
  allowed_method_name?(receiver.method_name)))
168
168
  true
@@ -62,6 +62,22 @@ module RuboCop
62
62
  # do_something if attrs.respond_to?(:[])
63
63
  #
64
64
  # # bad
65
+ # foo&.bar ? foo&.bar.baz : qux
66
+ #
67
+ # # good
68
+ # foo&.bar ? foo.bar.baz : qux
69
+ #
70
+ # # bad
71
+ # if foo&.bar
72
+ # foo&.bar.baz
73
+ # end
74
+ #
75
+ # # good
76
+ # if foo&.bar
77
+ # foo.bar.baz
78
+ # end
79
+ #
80
+ # # bad
65
81
  # while node&.is_a?(BeginNode)
66
82
  # node = node.parent
67
83
  # end