rubocop 1.86.1 → 1.87.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +15 -1
  3. data/lib/rubocop/cli/command/auto_generate_config.rb +27 -1
  4. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  5. data/lib/rubocop/cli/command/show_docs_url.rb +3 -7
  6. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  7. data/lib/rubocop/cli.rb +6 -7
  8. data/lib/rubocop/comment_config.rb +12 -15
  9. data/lib/rubocop/config_loader.rb +17 -2
  10. data/lib/rubocop/config_loader_resolver.rb +11 -3
  11. data/lib/rubocop/config_store.rb +1 -1
  12. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  13. data/lib/rubocop/cop/base.rb +8 -2
  14. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
  16. data/lib/rubocop/cop/correctors.rb +28 -0
  17. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  18. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -4
  20. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -3
  21. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  22. data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
  23. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +14 -5
  25. data/lib/rubocop/cop/layout/end_alignment.rb +2 -2
  26. data/lib/rubocop/cop/layout/indentation_width.rb +13 -1
  27. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  28. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +26 -1
  29. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
  31. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  32. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  33. data/lib/rubocop/cop/lint/constant_reassignment.rb +36 -4
  34. data/lib/rubocop/cop/lint/constant_resolution.rb +5 -5
  35. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -1
  36. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  37. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -1
  38. data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
  39. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  40. data/lib/rubocop/cop/lint/number_conversion.rb +5 -5
  41. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  42. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  43. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +2 -2
  44. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +3 -3
  45. data/lib/rubocop/cop/lint/require_relative_self_path.rb +3 -1
  46. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  47. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -1
  48. data/lib/rubocop/cop/lint/unreachable_code.rb +2 -2
  49. data/lib/rubocop/cop/lint/useless_assignment.rb +3 -8
  50. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +1 -1
  51. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +18 -7
  52. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  53. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  54. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
  55. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  56. data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
  57. data/lib/rubocop/cop/mixin.rb +86 -0
  58. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  59. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  60. data/lib/rubocop/cop/naming/predicate_method.rb +2 -2
  61. data/lib/rubocop/cop/naming/predicate_prefix.rb +1 -1
  62. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  63. data/lib/rubocop/cop/offense.rb +8 -0
  64. data/lib/rubocop/cop/registry.rb +42 -25
  65. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  66. data/lib/rubocop/cop/style/alias.rb +10 -1
  67. data/lib/rubocop/cop/style/character_literal.rb +2 -2
  68. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -0
  69. data/lib/rubocop/cop/style/copyright.rb +21 -10
  70. data/lib/rubocop/cop/style/date_time.rb +2 -2
  71. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  72. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  73. data/lib/rubocop/cop/style/file_write.rb +18 -16
  74. data/lib/rubocop/cop/style/format_string.rb +4 -3
  75. data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
  76. data/lib/rubocop/cop/style/hash_lookup_method.rb +12 -7
  77. data/lib/rubocop/cop/style/if_inside_else.rb +15 -2
  78. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  79. data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
  80. data/lib/rubocop/cop/style/module_member_existence_check.rb +6 -3
  81. data/lib/rubocop/cop/style/reduce_to_hash.rb +16 -0
  82. data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
  83. data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
  84. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
  85. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  86. data/lib/rubocop/cop/style/regexp_literal.rb +31 -2
  87. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
  88. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  89. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  90. data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
  91. data/lib/rubocop/cop/style/symbol_proc.rb +3 -3
  92. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
  93. data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
  94. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  95. data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
  96. data/lib/rubocop/cop/team.rb +86 -35
  97. data/lib/rubocop/file_patterns.rb +9 -1
  98. data/lib/rubocop/formatter/disabled_config_formatter.rb +4 -1
  99. data/lib/rubocop/lsp/runtime.rb +1 -2
  100. data/lib/rubocop/options.rb +26 -4
  101. data/lib/rubocop/project_index_loader.rb +66 -0
  102. data/lib/rubocop/rspec/shared_contexts.rb +21 -0
  103. data/lib/rubocop/runner.rb +123 -57
  104. data/lib/rubocop/target_finder.rb +13 -6
  105. data/lib/rubocop/version.rb +20 -2
  106. data/lib/rubocop.rb +8 -96
  107. metadata +8 -3
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common helpers for cops that consult the project-wide static-analysis index
6
+ # via `Cop::Base#project_index`.
7
+ #
8
+ # Mixed-in cops gain the `external_dependency_checksum` override that invalidates
9
+ # the `ResultCache` whenever the indexed project files change on disk.
10
+ # To run index-backed analysis, cops should simply check whether `project_index` is non-nil;
11
+ # the runner only exposes a non-nil index when the user opted in via `AllCops/UseProjectIndex`
12
+ # and the underlying gem is available.
13
+ module ProjectIndexHelp
14
+ BUILTIN_DOCUMENT_URI = 'rubydex:built-in'
15
+ FILE_URI_PREFIX = 'file://'
16
+ # Matches the spurious leading slash before a Windows drive letter that
17
+ # remains after stripping `file://` from a `file:///C:/...` URI.
18
+ WINDOWS_DRIVE_PREFIX = %r{\A/(?=[A-Za-z]:[/\\])}.freeze
19
+
20
+ def external_dependency_checksum
21
+ return nil unless project_index
22
+
23
+ @external_dependency_checksum ||= Digest::SHA1.hexdigest(
24
+ project_index_signature.join("\n")
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def project_index_signature
31
+ project_index.documents.filter_map do |doc|
32
+ uri = doc.uri
33
+ next if uri == BUILTIN_DOCUMENT_URI
34
+
35
+ path = uri.delete_prefix(FILE_URI_PREFIX).sub(WINDOWS_DRIVE_PREFIX, '')
36
+ mtime, size = begin
37
+ stat = File.stat(path)
38
+ [stat.mtime.to_f, stat.size]
39
+ rescue StandardError
40
+ [0, 0]
41
+ end
42
+
43
+ "#{path}:#{mtime}:#{size}"
44
+ end.sort
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop # rubocop:disable Style/Documentation
5
+ # Autoloads mixin modules included by cops. Mixins are autoloaded to reduce the number of
6
+ # requires because they're used only when the relevant cop class is loaded.
7
+
8
+ autoload :ArrayMinSize, 'rubocop/cop/mixin/array_min_size'
9
+ autoload :ArraySyntax, 'rubocop/cop/mixin/array_syntax'
10
+ autoload :Alignment, 'rubocop/cop/mixin/alignment'
11
+ autoload :AllowedIdentifiers, 'rubocop/cop/mixin/allowed_identifiers'
12
+ autoload :AllowedMethods, 'rubocop/cop/mixin/allowed_methods'
13
+ autoload :AllowedPattern, 'rubocop/cop/mixin/allowed_pattern'
14
+ autoload :AllowedReceivers, 'rubocop/cop/mixin/allowed_receivers'
15
+ autoload :ForbiddenIdentifiers, 'rubocop/cop/mixin/forbidden_identifiers'
16
+ autoload :ForbiddenPattern, 'rubocop/cop/mixin/forbidden_pattern'
17
+ autoload :AutoCorrector, 'rubocop/cop/mixin/auto_corrector' # rubocop:todo Naming/InclusiveLanguage
18
+ autoload :CheckAssignment, 'rubocop/cop/mixin/check_assignment'
19
+ autoload :CheckLineBreakable, 'rubocop/cop/mixin/check_line_breakable'
20
+ autoload :CheckSingleLineSuitability, 'rubocop/cop/mixin/check_single_line_suitability'
21
+ autoload :ConfigurableMax, 'rubocop/cop/mixin/configurable_max'
22
+ autoload :CodeLength, 'rubocop/cop/mixin/code_length'
23
+ autoload :ConfigurableEnforcedStyle, 'rubocop/cop/mixin/configurable_enforced_style'
24
+ autoload :ConfigurableFormatting, 'rubocop/cop/mixin/configurable_formatting'
25
+ autoload :ConfigurableNaming, 'rubocop/cop/mixin/configurable_naming'
26
+ autoload :ConfigurableNumbering, 'rubocop/cop/mixin/configurable_numbering'
27
+ autoload :DigHelp, 'rubocop/cop/mixin/dig_help'
28
+ autoload :DocumentationComment, 'rubocop/cop/mixin/documentation_comment'
29
+ autoload :Duplication, 'rubocop/cop/mixin/duplication'
30
+ autoload :RangeHelp, 'rubocop/cop/mixin/range_help'
31
+ autoload :ProjectIndexHelp, 'rubocop/cop/mixin/project_index_help'
32
+ autoload :AnnotationComment, 'rubocop/cop/mixin/annotation_comment'
33
+ autoload :EmptyParameter, 'rubocop/cop/mixin/empty_parameter'
34
+ autoload :EndKeywordAlignment, 'rubocop/cop/mixin/end_keyword_alignment'
35
+ autoload :EndlessMethodRewriter, 'rubocop/cop/mixin/endless_method_rewriter'
36
+ autoload :EnforceSuperclass, 'rubocop/cop/mixin/enforce_superclass'
37
+ autoload :FirstElementLineBreak, 'rubocop/cop/mixin/first_element_line_break'
38
+ autoload :FrozenStringLiteral, 'rubocop/cop/mixin/frozen_string_literal'
39
+ autoload :GemDeclaration, 'rubocop/cop/mixin/gem_declaration'
40
+ autoload :GemspecHelp, 'rubocop/cop/mixin/gemspec_help'
41
+ autoload :HashAlignmentStyles, 'rubocop/cop/mixin/hash_alignment_styles'
42
+ autoload :HashSubset, 'rubocop/cop/mixin/hash_subset'
43
+ autoload :HashTransformMethod, 'rubocop/cop/mixin/hash_transform_method'
44
+ autoload :IntegerNode, 'rubocop/cop/mixin/integer_node'
45
+ autoload :Interpolation, 'rubocop/cop/mixin/interpolation'
46
+ autoload :LineLengthHelp, 'rubocop/cop/mixin/line_length_help'
47
+ autoload :MatchRange, 'rubocop/cop/mixin/match_range'
48
+ autoload :HashShorthandSyntax, 'rubocop/cop/mixin/hash_shorthand_syntax'
49
+ autoload :MethodComplexity, 'rubocop/cop/mixin/method_complexity'
50
+ autoload :MethodPreference, 'rubocop/cop/mixin/method_preference'
51
+ autoload :MinBodyLength, 'rubocop/cop/mixin/min_body_length'
52
+ autoload :MinBranchesCount, 'rubocop/cop/mixin/min_branches_count'
53
+ autoload :MultilineElementIndentation, 'rubocop/cop/mixin/multiline_element_indentation'
54
+ autoload :MultilineElementLineBreaks, 'rubocop/cop/mixin/multiline_element_line_breaks'
55
+ autoload :MultilineExpressionIndentation, 'rubocop/cop/mixin/multiline_expression_indentation'
56
+ autoload :MultilineLiteralBraceLayout, 'rubocop/cop/mixin/multiline_literal_brace_layout'
57
+ autoload :NegativeConditional, 'rubocop/cop/mixin/negative_conditional'
58
+ autoload :Heredoc, 'rubocop/cop/mixin/heredoc'
59
+ autoload :NilMethods, 'rubocop/cop/mixin/nil_methods'
60
+ autoload :OnNormalIfUnless, 'rubocop/cop/mixin/on_normal_if_unless'
61
+ autoload :OrderedGemNode, 'rubocop/cop/mixin/ordered_gem_node'
62
+ autoload :Parentheses, 'rubocop/cop/mixin/parentheses'
63
+ autoload :PercentArray, 'rubocop/cop/mixin/percent_array'
64
+ autoload :PercentLiteral, 'rubocop/cop/mixin/percent_literal'
65
+ autoload :PrecedingFollowingAlignment, 'rubocop/cop/mixin/preceding_following_alignment'
66
+ autoload :PreferredDelimiters, 'rubocop/cop/mixin/preferred_delimiters'
67
+ autoload :RationalLiteral, 'rubocop/cop/mixin/rational_literal'
68
+ autoload :RequireLibrary, 'rubocop/cop/mixin/require_library'
69
+ autoload :RescueNode, 'rubocop/cop/mixin/rescue_node'
70
+ autoload :SafeAssignment, 'rubocop/cop/mixin/safe_assignment'
71
+ autoload :SpaceAfterPunctuation, 'rubocop/cop/mixin/space_after_punctuation'
72
+ autoload :SpaceBeforePunctuation, 'rubocop/cop/mixin/space_before_punctuation'
73
+ autoload :SurroundingSpace, 'rubocop/cop/mixin/surrounding_space'
74
+ autoload :StatementModifier, 'rubocop/cop/mixin/statement_modifier'
75
+ autoload :StringHelp, 'rubocop/cop/mixin/string_help'
76
+ autoload :StringLiteralsHelp, 'rubocop/cop/mixin/string_literals_help'
77
+ autoload :SymbolHelp, 'rubocop/cop/mixin/symbol_help'
78
+ autoload :TargetRubyVersion, 'rubocop/cop/mixin/target_ruby_version'
79
+ autoload :TrailingBody, 'rubocop/cop/mixin/trailing_body'
80
+ autoload :TrailingComma, 'rubocop/cop/mixin/trailing_comma'
81
+ autoload :UncommunicativeName, 'rubocop/cop/mixin/uncommunicative_name'
82
+ autoload :VisibilityHelp, 'rubocop/cop/mixin/visibility_help'
83
+ autoload :CommentsHelp, 'rubocop/cop/mixin/comments_help'
84
+ autoload :DefNode, 'rubocop/cop/mixin/def_node'
85
+ end
86
+ end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Naming
6
6
  # Makes sure that certain binary operator methods have their
7
- # sole parameter named `other`.
7
+ # sole parameter named `other`.
8
8
  #
9
9
  # @example
10
10
  #
@@ -109,7 +109,7 @@ module RuboCop
109
109
  # @_foo = calculate_expensive_thing
110
110
  # end
111
111
  #
112
- # @example EnforcedStyleForLeadingUnderscores :optional
112
+ # @example EnforcedStyleForLeadingUnderscores: optional
113
113
  # # bad
114
114
  # def foo
115
115
  # @something ||= calculate_expensive_thing
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Checks that predicate methods end with `?` and non-predicate methods do not.
7
7
  #
8
8
  # The names of predicate methods (methods that return a boolean value) should end
9
- # in a question mark. Methods that don't return a boolean, shouldn't
9
+ # in a question mark. Methods that don't return a boolean shouldn't
10
10
  # end in a question mark.
11
11
  #
12
12
  # The cop assesses a predicate method as one that returns boolean values. Likewise,
@@ -22,7 +22,7 @@ module RuboCop
22
22
  # return values are detected.
23
23
  #
24
24
  # The cop also has `AllowedMethods` configuration in order to prevent the cop from
25
- # registering an offense from a method name that does not confirm to the naming
25
+ # registering an offense from a method name that does not conform to the naming
26
26
  # guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
27
27
  # configuration to allow method names by regular expression.
28
28
  #
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # they end with a `?`. These methods should be changed to remove the
18
18
  # prefix.
19
19
  #
20
- # When `UseSorbetSigs` set to true (optional), the cop will only report
20
+ # When `UseSorbetSigs` is set to true (optional), the cop will only report
21
21
  # offenses if the method has a Sorbet `sig` with a return type of
22
22
  # `T::Boolean`. Dynamic methods are not supported with this configuration.
23
23
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Naming
6
- # Makes sure that rescued exceptions variables are named as
6
+ # Makes sure that rescued exception variables are named as
7
7
  # expected.
8
8
  #
9
9
  # The `PreferredName` config option takes a `String`. It represents
@@ -98,6 +98,14 @@ module RuboCop
98
98
  freeze
99
99
  end
100
100
 
101
+ def marshal_dump
102
+ [@severity, @location, @message, @cop_name, @status]
103
+ end
104
+
105
+ def marshal_load(array)
106
+ @severity, @location, @message, @cop_name, @status = array
107
+ end
108
+
101
109
  # @api public
102
110
  #
103
111
  # @!attribute [r] correctable?
@@ -49,9 +49,9 @@ module RuboCop
49
49
  attr_reader :options, :warnings
50
50
 
51
51
  def initialize(cops = [], options = {})
52
- @registry = {}
53
- @departments = {}
54
- @cops_by_cop_name = Hash.new { |hash, key| hash[key] = [] }
52
+ @departments = Set.new
53
+ @cops_by_badge = {}
54
+ @lazy_loaded_cops_by_badge = {}
55
55
 
56
56
  @enrollment_queue = cops
57
57
  @options = options
@@ -61,6 +61,12 @@ module RuboCop
61
61
  @warnings = {}
62
62
  end
63
63
 
64
+ def lazy_load(cop_name, constant_name)
65
+ badge = Badge.parse(cop_name)
66
+ @departments << badge.department
67
+ @lazy_loaded_cops_by_badge[badge] = constant_name
68
+ end
69
+
64
70
  def enlist(cop)
65
71
  @enrollment_queue << cop
66
72
  end
@@ -72,22 +78,17 @@ module RuboCop
72
78
  # @return [Array<Symbol>] list of departments for current cops.
73
79
  def departments
74
80
  clear_enrollment_queue
75
- @departments.keys
81
+ @departments.to_a
76
82
  end
77
83
 
78
84
  # @return [Registry] Cops for that specific department.
79
85
  def with_department(department)
80
- clear_enrollment_queue
81
- with(@departments.fetch(department, []))
86
+ with(cops.select { |cop| cop.department == department })
82
87
  end
83
88
 
84
89
  # @return [Registry] Cops not for a specific department.
85
90
  def without_department(department)
86
- clear_enrollment_queue
87
- without_department = @departments.dup
88
- without_department.delete(department)
89
-
90
- with(without_department.values.flatten)
91
+ with(cops.reject { |cop| cop.department == department })
91
92
  end
92
93
 
93
94
  # @return [Boolean] Checks if given name is department
@@ -160,31 +161,33 @@ module RuboCop
160
161
  def unqualified_cop_names
161
162
  clear_enrollment_queue
162
163
  @unqualified_cop_names ||=
163
- Set.new(@cops_by_cop_name.keys.map { |qn| File.basename(qn) }) <<
164
- 'RedundantCopDisableDirective'
164
+ (@cops_by_badge.keys | @lazy_loaded_cops_by_badge.keys)
165
+ .to_set { |badge| File.basename(badge.to_s) } << 'RedundantCopDisableDirective'
165
166
  end
166
167
 
167
168
  def qualify_badge(badge)
168
169
  clear_enrollment_queue
169
170
  @departments
170
- .map { |department, _| badge.with_department(department) }
171
+ .map { |department| badge.with_department(department) }
171
172
  .select { |potential_badge| registered?(potential_badge) }
172
173
  end
173
174
 
174
175
  # @return [Hash{String => Array<Class>}]
175
176
  def to_h
176
177
  clear_enrollment_queue
177
- @cops_by_cop_name
178
+ load_all_lazy_cops
179
+ @cops_by_badge.to_h { |_badge, cop| [cop.cop_name, [cop]] }
178
180
  end
179
181
 
180
182
  def cops
181
183
  clear_enrollment_queue
182
- @registry.values
184
+ load_all_lazy_cops
185
+ @cops_by_badge.values
183
186
  end
184
187
 
185
188
  def length
186
189
  clear_enrollment_queue
187
- @registry.size
190
+ @cops_by_badge.size + @lazy_loaded_cops_by_badge.size
188
191
  end
189
192
 
190
193
  def enabled(config)
@@ -219,7 +222,8 @@ module RuboCop
219
222
  end
220
223
 
221
224
  def names
222
- cops.map(&:cop_name)
225
+ clear_enrollment_queue
226
+ @cops_by_badge.keys.map(&:to_s) | @lazy_loaded_cops_by_badge.keys.map(&:to_s)
223
227
  end
224
228
 
225
229
  def cops_for_department(department)
@@ -236,7 +240,8 @@ module RuboCop
236
240
 
237
241
  def sort!
238
242
  clear_enrollment_queue
239
- @registry = @registry.sort_by { |badge, _| badge.cop_name }.to_h
243
+ load_all_lazy_cops
244
+ @cops_by_badge = @cops_by_badge.sort_by { |badge, _cop| badge.cop_name }.to_h
240
245
 
241
246
  self
242
247
  end
@@ -252,7 +257,9 @@ module RuboCop
252
257
  # @param [String] cop_name
253
258
  # @return [Class, nil]
254
259
  def find_by_cop_name(cop_name)
255
- to_h[cop_name].first
260
+ clear_enrollment_queue
261
+ badge = Badge.parse(cop_name)
262
+ @cops_by_badge[badge] || load_lazy_cop(badge)
256
263
  end
257
264
 
258
265
  # When a cop name is given returns a single-element array with the cop class.
@@ -265,6 +272,7 @@ module RuboCop
265
272
 
266
273
  def freeze
267
274
  clear_enrollment_queue
275
+ load_all_lazy_cops
268
276
  unqualified_cop_names # build cache
269
277
  super
270
278
  end
@@ -289,14 +297,23 @@ module RuboCop
289
297
  return if @enrollment_queue.empty?
290
298
 
291
299
  @enrollment_queue.each do |cop|
292
- @registry[cop.badge] = cop
293
- @departments[cop.department] ||= []
294
- @departments[cop.department] << cop
295
- @cops_by_cop_name[cop.cop_name] << cop
300
+ @cops_by_badge[cop.badge] = cop
301
+ @departments << cop.department
296
302
  end
297
303
  @enrollment_queue = []
298
304
  end
299
305
 
306
+ def load_all_lazy_cops
307
+ @lazy_loaded_cops_by_badge.each_key { |badge| load_lazy_cop(badge) }
308
+ end
309
+
310
+ def load_lazy_cop(badge)
311
+ constant_name = @lazy_loaded_cops_by_badge.delete(badge)
312
+ return unless constant_name
313
+
314
+ @cops_by_badge[badge] = Kernel.const_get(constant_name)
315
+ end
316
+
300
317
  def with(cops)
301
318
  self.class.new(cops)
302
319
  end
@@ -318,7 +335,7 @@ module RuboCop
318
335
 
319
336
  def registered?(badge)
320
337
  clear_enrollment_queue
321
- @registry.key?(badge)
338
+ @cops_by_badge.key?(badge) || @lazy_loaded_cops_by_badge.key?(badge)
322
339
  end
323
340
  end
324
341
  end
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # a subprocess is created in the same way as `Kernel#open`, and its output is returned.
11
11
  # `Kernel#open` may allow unintentional command injection, which is the reason these
12
12
  # `IO` methods are a security risk.
13
- # Consider to use `File.read` to disable the behavior of subprocess invocation.
13
+ # Consider using `File.read` to disable the behavior of subprocess invocation.
14
14
  #
15
15
  # @safety
16
16
  # This cop is unsafe because false positive will occur if the variable passed as
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # is resolved at runtime).
11
11
  # It also flags uses of `alias :symbol` rather than `alias bareword`.
12
12
  #
13
- # However, it will always enforce `method_alias` when used `alias`
13
+ # However, it will always enforce `alias_method` when `alias` is used
14
14
  # in an instance method definition and in a singleton method definition.
15
15
  # If used in a block, always enforce `alias_method`
16
16
  # unless it is an `instance_eval` block.
@@ -45,6 +45,7 @@ module RuboCop
45
45
  return unless node.command?(:alias_method)
46
46
  return unless style == :prefer_alias && alias_keyword_possible?(node)
47
47
  return unless node.arguments.count == 2
48
+ return if alias_method_value_used?(node)
48
49
 
49
50
  msg = format(MSG_ALIAS_METHOD, current: lexical_scope_type(node))
50
51
  add_offense(node.loc.selector, message: msg) do |corrector|
@@ -80,6 +81,14 @@ module RuboCop
80
81
  scope_type(node) != :dynamic && node.arguments.all?(&:sym_type?)
81
82
  end
82
83
 
84
+ # `alias_method` is a method call whose return value can be used
85
+ # (e.g., as an argument to `public`/`module_function`, or as an assignment),
86
+ # but `alias` is a keyword statement that cannot appear in such positions.
87
+ # Detect these positions so the conversion does not produce a syntax error.
88
+ def alias_method_value_used?(node)
89
+ node.argument? || node.parent&.assignment?
90
+ end
91
+
83
92
  def alias_method_possible?(node)
84
93
  scope_type(node) != :instance_eval &&
85
94
  node.children.none?(&:gvar_type?) &&
@@ -8,8 +8,8 @@ module RuboCop
8
8
  # essentially one-character strings, so this syntax
9
9
  # is mostly redundant at this point.
10
10
  #
11
- # ? character literal can be used to express meta and control character.
12
- # That's a good use case of ? literal so it doesn't count it as an offense.
11
+ # A `?` character literal can be used to express meta and control characters.
12
+ # That's a good use case of a `?` literal so it doesn't count as an offense.
13
13
  #
14
14
  # @example
15
15
  # # bad
@@ -181,6 +181,7 @@ module RuboCop
181
181
 
182
182
  def check_style(node, body, style)
183
183
  return if node.identifier.namespace&.cbase_type?
184
+ return unless const_namespace?(node.identifier.namespace)
184
185
 
185
186
  if style == :nested
186
187
  check_nested_style(node)
@@ -189,6 +190,13 @@ module RuboCop
189
190
  end
190
191
  end
191
192
 
193
+ def const_namespace?(node)
194
+ return true if node.nil? || node.cbase_type?
195
+ return false unless node.const_type?
196
+
197
+ const_namespace?(node.namespace)
198
+ end
199
+
192
200
  def check_nested_style(node)
193
201
  return unless compact_node_name?(node)
194
202
  return if node.parent&.type?(:class, :module)
@@ -46,15 +46,16 @@ module RuboCop
46
46
  token = insert_notice_before(processed_source)
47
47
  range = token.nil? ? range_between(0, 0) : token.pos
48
48
 
49
- corrector.insert_before(range, "#{autocorrect_notice}\n")
49
+ corrector.insert_before(range, "#{normalized_autocorrect_notice}\n")
50
50
  end
51
51
 
52
- def notice
53
- cop_config['Notice']
54
- end
52
+ def normalized_autocorrect_notice
53
+ autocorrect_notice.lines.map do |line|
54
+ next line if line.start_with?('#')
55
+ next "#\n" if line.chomp.empty?
55
56
 
56
- def autocorrect_notice
57
- cop_config['AutocorrectNotice']
57
+ "# #{line}"
58
+ end.join
58
59
  end
59
60
 
60
61
  def verify_autocorrect_notice!
@@ -62,8 +63,7 @@ module RuboCop
62
63
  raise Warning, "#{cop_name}: #{AUTOCORRECT_EMPTY_WARNING}"
63
64
  end
64
65
 
65
- regex = Regexp.new(notice)
66
- return if autocorrect_notice.gsub(/^# */, '').match?(regex)
66
+ return if normalized_autocorrect_notice.gsub(/^# */, '').match?(notice_regexp)
67
67
 
68
68
  message = "AutocorrectNotice '#{autocorrect_notice}' must match Notice /#{notice}/"
69
69
  raise Warning, "#{cop_name}: #{message}"
@@ -91,18 +91,29 @@ module RuboCop
91
91
  end
92
92
 
93
93
  def notice_found?(processed_source)
94
- notice_regexp = Regexp.new(notice.lines.map(&:strip).join)
95
94
  multiline_notice = +''
96
95
  processed_source.tokens.each do |token|
97
96
  break unless token.comment?
98
97
 
99
- multiline_notice << token.text.sub(/\A# */, '')
98
+ multiline_notice << token.text.sub(/\A# */, '') << "\n"
100
99
 
101
100
  break if notice_regexp.match?(token.text)
102
101
  end
103
102
 
104
103
  multiline_notice.match?(notice_regexp)
105
104
  end
105
+
106
+ def notice_regexp
107
+ @notice_regexp ||= Regexp.new(notice.sub(/\A(?:\\A|\^)?#(?:\\s[*+?]?|\s)*/, ''))
108
+ end
109
+
110
+ def notice
111
+ cop_config['Notice']
112
+ end
113
+
114
+ def autocorrect_notice
115
+ cop_config['AutocorrectNotice']
116
+ end
106
117
  end
107
118
  end
108
119
  end
@@ -3,8 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for consistent usage of the `DateTime` class over the
7
- # `Time` class. This cop is disabled by default since these classes,
6
+ # Checks for consistent usage of the `Time` class over the
7
+ # `DateTime` class. This cop is disabled by default since these classes,
8
8
  # although highly overlapping, have particularities that make them not
9
9
  # replaceable in certain situations when dealing with multiple timezones
10
10
  # and/or DST.
@@ -6,7 +6,7 @@ module RuboCop
6
6
  module Cop
7
7
  module Style
8
8
  # Detects comments to enable/disable RuboCop.
9
- # This is useful if want to make sure that every RuboCop error gets fixed
9
+ # This is useful if you want to make sure that every RuboCop error gets fixed
10
10
  # and not quickly disabled with a comment.
11
11
  #
12
12
  # Specific cops can be allowed with the `AllowedCops` configuration. Note that
@@ -161,7 +161,12 @@ module RuboCop
161
161
  source = source.gsub(COMMENT_REGEXP, '')
162
162
  return if source.blank?
163
163
 
164
- /\s*#{Regexp.escape(source.strip)}/
164
+ # Treat `\#` (an escaped interpolation marker in the heredoc) as matching
165
+ # either `\#` or `#` in the comment, since the comment may show either
166
+ # the literal source form or the runtime appearance.
167
+ segments = source.strip.split('\\#', -1).map { |segment| Regexp.escape(segment) }
168
+
169
+ /\s*#{segments.join('\\\\?#')}/
165
170
  end
166
171
  end
167
172
  end
@@ -104,28 +104,30 @@ module RuboCop
104
104
 
105
105
  def replacement(mode, filename, content, write_node)
106
106
  replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
107
+ heredoc = heredoc_in_write(write_node)
108
+ return replacement unless heredoc
107
109
 
108
- if heredoc?(write_node)
109
- first_argument = write_node.body.first_argument
110
+ <<~REPLACEMENT.chomp
111
+ #{replacement}
112
+ #{heredoc_range(heredoc).source}
113
+ REPLACEMENT
114
+ end
110
115
 
111
- <<~REPLACEMENT.chomp
112
- #{replacement}
113
- #{heredoc_range(first_argument).source}
114
- REPLACEMENT
115
- else
116
- replacement
117
- end
116
+ def heredoc_in_write(write_node)
117
+ return unless write_node.block_type? && (first_argument = write_node.body.first_argument)
118
+
119
+ find_heredoc(first_argument)
118
120
  end
119
121
 
120
- def heredoc?(write_node)
121
- write_node.block_type? && (first_argument = write_node.body.first_argument) &&
122
- first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
122
+ def heredoc_range(heredoc)
123
+ range_between(heredoc.loc.heredoc_body.begin_pos, heredoc.loc.heredoc_end.end_pos)
123
124
  end
124
125
 
125
- def heredoc_range(first_argument)
126
- range_between(
127
- first_argument.loc.heredoc_body.begin_pos, first_argument.loc.heredoc_end.end_pos
128
- )
126
+ def find_heredoc(node)
127
+ return node if node.respond_to?(:heredoc?) && node.heredoc?
128
+ return if node.send_type? && !(receiver = node.receiver)
129
+
130
+ find_heredoc(receiver)
129
131
  end
130
132
  end
131
133
  end
@@ -11,9 +11,10 @@ module RuboCop
11
11
  # if the first argument is a string literal and if the second
12
12
  # argument is an array literal.
13
13
  #
14
- # Autocorrection will be applied when using argument is a literal or known built-in conversion
15
- # methods such as `to_d`, `to_f`, `to_h`, `to_i`, `to_r`, `to_s`, and `to_sym` on variables,
16
- # provided that their return value is not an array. For example, when using `to_s`,
14
+ # Autocorrection will be applied when the argument is a literal or uses a known
15
+ # built-in conversion method such as `to_d`, `to_f`, `to_h`, `to_i`, `to_r`, `to_s`,
16
+ # and `to_sym` on variables, provided that their return value is not an array.
17
+ # For example, when using `to_s`,
17
18
  # `'%s' % [1, 2, 3].to_s` can be autocorrected without any incompatibility:
18
19
  #
19
20
  # [source,ruby]
@@ -73,7 +73,7 @@ module RuboCop
73
73
  first_argument = node.first_argument
74
74
  if first_argument.hash_type?
75
75
  register_offense_for_hash(node, first_argument)
76
- elsif first_argument.splat_type?
76
+ elsif first_argument.type?(:splat, :forwarded_restarg)
77
77
  add_offense(node, message: MSG_SPLAT) unless allowed_splat_argument?
78
78
  elsif use_zip_method_without_argument?(first_argument)
79
79
  register_offense_for_zip_method(node, first_argument)
@@ -82,18 +82,23 @@ module RuboCop
82
82
  end
83
83
 
84
84
  def correct_fetch_to_brackets(corrector, node)
85
- receiver = node.receiver.source
86
85
  key = node.first_argument.source
87
- replacement = "#{receiver}[#{key}]"
88
- replacement = "(#{replacement})" if node.csend_type?
89
- corrector.replace(node, replacement)
86
+
87
+ if node.csend_type?
88
+ corrector.replace(node, "(#{node.receiver.source}[#{key}])")
89
+ else
90
+ corrector.replace(node.loc.dot.join(node.source_range.end), "[#{key}]")
91
+ end
90
92
  end
91
93
 
92
94
  def correct_brackets_to_fetch(corrector, node)
93
- receiver = node.receiver.source
94
95
  key = node.first_argument.source
95
- operator = node.csend_type? ? '&.' : '.'
96
- corrector.replace(node, "#{receiver}#{operator}fetch(#{key})")
96
+
97
+ if node.csend_type?
98
+ corrector.replace(node.loc.dot.join(node.source_range.end), "&.fetch(#{key})")
99
+ else
100
+ corrector.replace(node.loc.selector.join(node.source_range.end), ".fetch(#{key})")
101
+ end
97
102
  end
98
103
  end
99
104
  end