Class: RuboCop::Cop::Registry

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/rubocop/cop/registry.rb

Overview

Registry that tracks all cops by their badge and department.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cops = [], options = {}) ⇒ Registry

Returns a new instance of Registry.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rubocop/cop/registry.rb', line 51

def initialize(cops = [], options = {})
  @departments = Set.new
  # Maps each badge to its cop class, or to the fully qualified constant name (a `String`)
  # when the cop is registered for lazy loading and its file is not loaded yet.
  # A single insertion-ordered map keeps the registration order stable no matter when
  # a lazy-loaded cop's file gets loaded, because reassigning an existing key preserves
  # its position; the cop execution order depends on this order.
  @cops_by_badge = {}

  @enrollment_queue = cops
  @options = options

  @enabled_cache = {}.compare_by_identity
  @disabled_cache = {}.compare_by_identity
  @disabled_names_cache = {}.compare_by_identity
  @warnings = {}
end

Class Attribute Details

.globalObject (readonly)

Returns the value of attribute global.



369
370
371
# File 'lib/rubocop/cop/registry.rb', line 369

def global
  @global
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



49
50
51
# File 'lib/rubocop/cop/registry.rb', line 49

def options
  @options
end

#warningsObject (readonly)

Returns the value of attribute warnings.



49
50
51
# File 'lib/rubocop/cop/registry.rb', line 49

def warnings
  @warnings
end

Class Method Details

.allObject



22
23
24
# File 'lib/rubocop/cop/registry.rb', line 22

def self.all
  global.without_department(:Test).cops
end

.qualified_cop?(name) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
47
# File 'lib/rubocop/cop/registry.rb', line 44

def self.qualified_cop?(name)
  badge = Badge.parse(name)
  global.qualify_badge(badge).first == badge
end

.qualified_cop_name(name, origin, warn: true, correct_namespace: true) ⇒ Object



26
27
28
# File 'lib/rubocop/cop/registry.rb', line 26

def self.qualified_cop_name(name, origin, warn: true, correct_namespace: true)
  global.qualified_cop_name(name, origin, warn: warn, correct_namespace: correct_namespace)
end

.reset!Object



40
41
42
# File 'lib/rubocop/cop/registry.rb', line 40

def self.reset!
  @global = new
end

.with_temporary_global(temp_global = global.dup) ⇒ Object

Changes momentarily the global registry Intended for testing purposes



32
33
34
35
36
37
38
# File 'lib/rubocop/cop/registry.rb', line 32

def self.with_temporary_global(temp_global = global.dup)
  previous = @global
  @global = temp_global
  yield
ensure
  @global = previous
end

Instance Method Details

#==(other) ⇒ Object



298
299
300
# File 'lib/rubocop/cop/registry.rb', line 298

def ==(other)
  cops == other.cops
end

#contains_cop_matching?(names) ⇒ Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/rubocop/cop/registry.rb', line 138

def contains_cop_matching?(names)
  registered_badges.any? { |badge| badge.match_name?(names) }
end

#copsObject



230
231
232
233
234
# File 'lib/rubocop/cop/registry.rb', line 230

def cops
  clear_enrollment_queue
  load_all_lazy_cops
  @cops_by_badge.values
end

#cops_for_department(department) ⇒ Object



288
289
290
# File 'lib/rubocop/cop/registry.rb', line 288

def cops_for_department(department)
  with_department(department.to_sym).cops
end

#department?(name) ⇒ Boolean

Returns Checks if given name is department.

Returns:

  • (Boolean)

    Checks if given name is department



134
135
136
# File 'lib/rubocop/cop/registry.rb', line 134

def department?(name)
  departments.include?(name.to_sym)
end

#department_missing?(badge, name) ⇒ Boolean

Returns:

  • (Boolean)


196
197
198
# File 'lib/rubocop/cop/registry.rb', line 196

def department_missing?(badge, name)
  !badge.qualified? && unqualified_cop_names.include?(name)
end

#departmentsArray<Symbol>

Returns list of departments for current cops.

Returns:

  • (Array<Symbol>)

    list of departments for current cops.



104
105
106
107
# File 'lib/rubocop/cop/registry.rb', line 104

def departments
  clear_enrollment_queue
  @departments.to_a
end

#disabled(config) ⇒ Object



253
254
255
# File 'lib/rubocop/cop/registry.rb', line 253

def disabled(config)
  @disabled_cache[config] ||= reject { |cop| enabled?(cop, config) }
end

#disabled_names(config) ⇒ Array<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the names of the disabled cops without loading any of them.

Returns:



261
262
263
264
265
266
# File 'lib/rubocop/cop/registry.rb', line 261

def disabled_names(config)
  @disabled_names_cache[config] ||= registered_badges.filter_map do |badge|
    name = badge.to_s
    name unless enabled_cop_name?(name, config)
  end
end

#dismiss(cop) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/rubocop/cop/registry.rb', line 90

def dismiss(cop)
  dismissed = !@enrollment_queue.delete(cop).nil?

  # A lazy-loaded cop that was not enlisted yet is dismissed by removing its registration.
  # Anonymous cop classes cannot have a badge.
  if !cop.name.nil? && @cops_by_badge[cop.badge].is_a?(String)
    @cops_by_badge.delete(cop.badge)
    dismissed = true
  end

  raise "Cop #{cop} could not be dismissed" unless dismissed
end

#each(&block) ⇒ Object



314
315
316
# File 'lib/rubocop/cop/registry.rb', line 314

def each(&block)
  cops.each(&block)
end

#enabled(config) ⇒ Array<Class>

Returns the enabled cop classes, loading lazy-loaded cops only when they are enabled.

Returns:

  • (Array<Class>)


244
245
246
247
248
249
250
251
# File 'lib/rubocop/cop/registry.rb', line 244

def enabled(config)
  @enabled_cache[config] ||= registered_badges.filter_map do |badge|
    next unless enabled_cop_name?(badge.to_s, config)

    cop_or_name = @cops_by_badge[badge]
    cop_or_name.is_a?(String) ? load_lazy_cop(badge) : cop_or_name
  end
end

#enabled?(cop, config) ⇒ Boolean

Returns:

  • (Boolean)


268
269
270
# File 'lib/rubocop/cop/registry.rb', line 268

def enabled?(cop, config)
  enabled_cop_name?(cop.cop_name, config)
end

#enabled_pending_cop?(cop_cfg, config, cop = nil) ⇒ Boolean

Parameters:

  • cop (Class, String, nil) (defaults to: nil)

    the cop class or the qualified cop name

Returns:

  • (Boolean)


273
274
275
276
277
278
279
280
281
# File 'lib/rubocop/cop/registry.rb', line 273

def enabled_pending_cop?(cop_cfg, config, cop = nil)
  return false if @options[:disable_pending_cops]
  return false unless cop_cfg.fetch('Enabled') == 'pending'
  return true if @options[:enable_pending_cops]
  return config.enabled_new_cops? unless cop

  cop_name = cop.is_a?(String) ? cop : cop.cop_name
  config.enabled_new_cop?(cop_name)
end

#enlist(cop) ⇒ Object



86
87
88
# File 'lib/rubocop/cop/registry.rb', line 86

def enlist(cop)
  @enrollment_queue << cop
end

#filter_by_badge(options = {}) ⇒ Registry

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new registry containing the cops whose badge satisfies the given block, keeping lazy-loaded cops unloaded.

Returns:



124
125
126
127
128
129
130
131
# File 'lib/rubocop/cop/registry.rb', line 124

def filter_by_badge(options = {})
  clear_enrollment_queue
  copy = self.class.new([], options)
  @cops_by_badge.each do |badge, cop_or_name|
    copy.add_entry(badge, cop_or_name) if yield(badge)
  end
  copy
end

#find_by_cop_name(cop_name) ⇒ Class?

Parameters:

Returns:

  • (Class, nil)


320
321
322
323
324
325
326
# File 'lib/rubocop/cop/registry.rb', line 320

def find_by_cop_name(cop_name)
  clear_enrollment_queue
  badge = Badge.parse(cop_name)
  cop_or_name = @cops_by_badge[badge]

  cop_or_name.is_a?(String) ? load_lazy_cop(badge) : cop_or_name
end

#find_cops_by_directive(directive) ⇒ Object

When a cop name is given returns a single-element array with the cop class. When a department name is given returns an array with all the cop classes for that department.



331
332
333
334
# File 'lib/rubocop/cop/registry.rb', line 331

def find_cops_by_directive(directive)
  cop = find_by_cop_name(directive)
  cop ? [cop] : cops_for_department(directive)
end

#freezeObject



351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/rubocop/cop/registry.rb', line 351

def freeze
  clear_enrollment_queue
  load_all_lazy_cops
  unqualified_cop_names # build cache

  # Freeze the structural collections as well, so that a cop class defined after
  # the freeze fails fast at its `enlist`/`lazy_load` call site instead of corrupting
  # the registry and failing much later.
  @cops_by_badge.freeze
  @departments.freeze
  @enrollment_queue.freeze

  super
end

#lazy_load(badge, constant_name) ⇒ Object

Registers a cop by its badge and constant name without loading the class. The constant is resolved (loading the cop's file through autoload) only when the cop class itself is needed.

Parameters:

  • badge (Badge, String)

    the badge, or the qualified cop name

  • constant_name (String)

    the fully qualified constant name



75
76
77
78
79
80
81
82
83
84
# File 'lib/rubocop/cop/registry.rb', line 75

def lazy_load(badge, constant_name)
  badge = Badge.parse(badge) if badge.is_a?(String)

  # File any cops enlisted earlier first, so that the registration
  # order follows the call order.
  clear_enrollment_queue

  @departments << badge.department
  @cops_by_badge[badge] = constant_name unless @cops_by_badge[badge].is_a?(Class)
end

#lengthObject



236
237
238
239
# File 'lib/rubocop/cop/registry.rb', line 236

def length
  clear_enrollment_queue
  @cops_by_badge.size
end

#load_all_lazy_copsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Loads all cops that are registered for lazy loading. Useful when lazily registered cops must not outlive the current global registry, e.g. before a temporary global registry is discarded in tests.



341
342
343
344
345
346
347
348
349
# File 'lib/rubocop/cop/registry.rb', line 341

def load_all_lazy_cops
  # Take a snapshot because loading a cop can load (and thereby register)
  # another lazy-loaded cop, e.g. its superclass.
  badges = @cops_by_badge.keys

  badges.each do |badge|
    load_lazy_cop(badge) if @cops_by_badge[badge].is_a?(String)
  end
end

#namesObject



283
284
285
286
# File 'lib/rubocop/cop/registry.rb', line 283

def names
  clear_enrollment_queue
  @cops_by_badge.keys.map(&:to_s)
end

#names_for_department(department) ⇒ Object



292
293
294
295
296
# File 'lib/rubocop/cop/registry.rb', line 292

def names_for_department(department)
  department = department.to_sym

  registered_badges.filter_map { |badge| badge.to_s if badge.department == department }
end


200
201
202
203
204
205
206
# File 'lib/rubocop/cop/registry.rb', line 200

def print_department_missing_warning(name, path)
  message = "no department given for #{name}."
  if path.end_with?('.rb')
    message += ' Run `rubocop -a --only Migration/DepartmentName` to fix.'
  end
  emit_warning(path, message)
end

#qualified_cop_name(name, path, warn: true, correct_namespace: true) ⇒ String

Note:

Emits a warning if the provided name has an incorrect namespace

Convert a user provided cop name into a properly namespaced name

Examples:

gives back a correctly qualified cop name


registry = RuboCop::Cop::Registry
registry.qualified_cop_name('Layout/EndOfLine', '') # => 'Layout/EndOfLine'

fixes incorrect namespaces


registry = RuboCop::Cop::Registry
registry.qualified_cop_name('Lint/EndOfLine', '') # => 'Layout/EndOfLine'

namespaces bare cop identifiers


registry = RuboCop::Cop::Registry
registry.qualified_cop_name('EndOfLine', '') # => 'Layout/EndOfLine'

passes back unrecognized cop names


registry = RuboCop::Cop::Registry
registry.qualified_cop_name('NotACop', '') # => 'NotACop'

Parameters:

  • name (String)

    Cop name extracted from config

  • path (String, nil)

    Path of file that name was extracted from

  • warn (Boolean) (defaults to: true)

    Print a warning if no department given for name

Returns:

  • (String)

    Qualified cop name

Raises:

  • (AmbiguousCopName)

    if a bare identifier with two possible namespaces is provided



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/rubocop/cop/registry.rb', line 174

def qualified_cop_name(name, path, warn: true, correct_namespace: true)
  badge = Badge.parse(name)
  print_department_missing_warning(name, path) if warn && department_missing?(badge, name)
  return name if registered?(badge)
  # A name qualified with the wrong department is returned as is when
  # namespace correction is not wanted, so the caller can surface the
  # mistake instead of silently acting on the corrected name.
  return name if badge.qualified? && !correct_namespace

  resolve_cop_name(badge, name, path, warn)
end

#qualify_badge(badge) ⇒ Object



216
217
218
219
220
221
# File 'lib/rubocop/cop/registry.rb', line 216

def qualify_badge(badge)
  clear_enrollment_queue
  @departments
    .map { |department| badge.with_department(department) }
    .select { |potential_badge| registered?(potential_badge) }
end

#resolve_cop_name(badge, name, path, warn) ⇒ Object



186
187
188
189
190
191
192
193
194
# File 'lib/rubocop/cop/registry.rb', line 186

def resolve_cop_name(badge, name, path, warn)
  potential_badges = qualify_badge(badge)

  case potential_badges.size
  when 0 then name # No namespace found. Deal with it later in caller.
  when 1 then resolve_badge(badge, potential_badges.first, path, warn: warn)
  else raise AmbiguousCopName.new(badge, path, potential_badges)
  end
end

#select(&block) ⇒ Object



310
311
312
# File 'lib/rubocop/cop/registry.rb', line 310

def select(&block)
  cops.select(&block)
end

#sort!Object



302
303
304
305
306
307
308
# File 'lib/rubocop/cop/registry.rb', line 302

def sort!
  clear_enrollment_queue
  load_all_lazy_cops
  @cops_by_badge = @cops_by_badge.sort_by { |badge, _cop| badge.cop_name }.to_h

  self
end

#to_hHash{String => Array<Class>}

Returns:

  • (Hash{String => Array<Class>})


224
225
226
227
228
# File 'lib/rubocop/cop/registry.rb', line 224

def to_h
  clear_enrollment_queue
  load_all_lazy_cops
  @cops_by_badge.to_h { |_badge, cop| [cop.cop_name, [cop]] }
end

#unqualified_cop_namesObject



208
209
210
211
212
213
214
# File 'lib/rubocop/cop/registry.rb', line 208

def unqualified_cop_names
  clear_enrollment_queue

  @unqualified_cop_names ||= @cops_by_badge.keys.to_set do |badge|
    File.basename(badge.to_s)
  end << 'RedundantCopDisableDirective'
end

#warnings?(path) ⇒ Boolean

Returns:

  • (Boolean)


372
373
374
# File 'lib/rubocop/cop/registry.rb', line 372

def warnings?(path)
  @warnings[path]
end

#with_department(department) ⇒ Registry

Returns Cops for that specific department.

Returns:

  • (Registry)

    Cops for that specific department.



110
111
112
# File 'lib/rubocop/cop/registry.rb', line 110

def with_department(department)
  filter_by_badge { |badge| badge.department == department }
end

#without_department(department) ⇒ Registry

Returns Cops not for a specific department.

Returns:

  • (Registry)

    Cops not for a specific department.



115
116
117
# File 'lib/rubocop/cop/registry.rb', line 115

def without_department(department)
  filter_by_badge { |badge| badge.department != department }
end