123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Generator::RegistrationInjector Private

Relationships & Source Files
Inherits: Object
Defined in: lib/rubocop/cop/generator/registration_injector.rb

Overview

A class that injects a register_cop directive into the cop’s department module so that the new cop is registered for lazy loading. The directive is injected in alphabetical order among the existing directives without reordering them, since the registration order defines the cop execution order. When the department module file does not exist yet, it is created, and a require for it is injected into the root RuboCop file.

Anything unexpected in the department module, such as a commented-out or otherwise unparsable register_cop line, raises an error instead of silently producing a broken registration.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(source_path:, badge:, root_file_path: 'lib/rubocop.rb', output: $stdout) ⇒ RegistrationInjector

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 25

def initialize(source_path:, badge:, root_file_path: 'lib/rubocop.rb', output: $stdout)
  @source_path = Pathname(source_path)
  @badge = badge
  @root_file_path = root_file_path
  @department_file_path = Pathname("#{@source_path.dirname}.rb")
  @output = output
end

Instance Attribute Details

#badge (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 44

attr_reader :source_path, :badge, :root_file_path, :department_file_path, :output

#department_file_path (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 44

attr_reader :source_path, :badge, :root_file_path, :department_file_path, :output

#output (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 44

attr_reader :source_path, :badge, :root_file_path, :department_file_path, :output

#root_file_path (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 44

attr_reader :source_path, :badge, :root_file_path, :department_file_path, :output

#source_path (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 44

attr_reader :source_path, :badge, :root_file_path, :department_file_path, :output

Instance Method Details

#cop_path (private)

The directive path is relative to the department module file’s directory, which dir resolves to at load time.

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 99

def cop_path
  source_path.relative_path_from(department_file_path.dirname).to_s.delete_suffix('.rb')
end

#create_department_file (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 57

def create_department_file
  File.write(department_file_path, department_module_source)

  output.puts "[create] #{department_file_path}"
end

#department_module_source (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 152

def department_module_source
  modules = %w[RuboCop Cop] + badge.department.to_s.split('/')
  body_indent = '  ' * modules.size

  lines = ['# frozen_string_literal: true', '']
  lines.concat(module_opening_lines(modules))
  lines << "#{body_indent}extend LazyLoader"
  lines << ''
  lines << "#{body_indent}#{directive}"
  lines.concat(module_closing_lines(modules))

  "#{lines.join("\n")}\n"
end

#department_require_index(lines, require_directive) (private)

The department requires form the contiguous block right before the rubocop/cop/team require in the root RuboCop file.

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 120

def department_require_index(lines, require_directive)
  anchor = lines.index("require_relative 'rubocop/cop/team'\n")
  unless anchor
    raise Error, "could not find the department require block in #{root_file_path}"
  end

  block_end = anchor - 1
  block_end -= 1 while lines[block_end] == "\n"
  block_start = block_end
  block_start -= 1 while lines[block_start - 1]&.match?(DEPARTMENT_REQUIRE_PATTERN)

  (block_start..block_end).find do |index|
    lines[index] > require_directive
  end || (block_end + 1)
end

#directive (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 114

def directive
  "register_cop :#{badge.cop_name}, \"\#{__dir__}/#{cop_path}\""
end

#first_directive_position(lines) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 136

def first_directive_position(lines)
  extend_lines = lines.filter_map.with_index do |line, index|
    [index, EXTEND_PATTERN.match(line)[:indent]] if EXTEND_PATTERN.match?(line)
  end

  unless extend_lines.size == 1
    raise Error, "expected `extend LazyLoader` in #{department_file_path}; " \
                 'add it or register the cop manually'
  end

  index, indent = extend_lines.first
  blank_after_extend = lines[index + 1] == "\n"

  [index + (blank_after_extend ? 2 : 1), indent]
end

#inject

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 33

def inject
  if File.exist?(department_file_path)
    inject_directive
  else
    create_department_file
    inject_department_require
  end
end

#inject_department_require (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 63

def inject_department_require
  require_path = department_file_path.sub_ext('').to_s.delete_prefix('lib/')
  require_directive = "require_relative '#{require_path}'\n"

  lines = File.readlines(root_file_path)
  return if lines.include?(require_directive)

  lines.insert(department_require_index(lines, require_directive), require_directive)
  File.write(root_file_path, lines.join)

  output.puts "[modify] #{root_file_path} - `#{require_directive.strip}` was injected."
end

#inject_directive (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 46

def inject_directive
  lines = File.readlines(department_file_path)
  entries = parse_entries(lines)
  return if entries.any? { |entry| entry[:path] == cop_path }

  line, indent = insertion_point(lines, entries)
  lines.insert(line, "#{indent}#{directive}\n")

  write_department_file(lines)
end

#insertion_point(lines, entries) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 103

def insertion_point(lines, entries)
  if entries.empty?
    first_directive_position(lines)
  else
    successor = entries.find { |entry| entry[:path] > cop_path }
    entry = successor || entries.last

    [entry[:line] + (successor ? 0 : 1), entry[:indent]]
  end
end

#module_closing_lines(modules) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 182

def module_closing_lines(modules)
  (modules.size - 1).downto(0).map { |nesting| "#{'  ' * nesting}end" }
end

#module_opening_lines(modules) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 166

def module_opening_lines(modules)
  modules.flat_map.with_index do |mod, nesting|
    indent = '  ' * nesting
    lines = []

    if mod == modules.last
      lines << "#{indent}# Cops for the `#{badge.department}` department. " \
               "The department's cops are"
      lines << "#{indent}# registered for lazy loading and their files are " \
               'loaded on demand.'
    end

    lines << "#{indent}module #{mod}"
  end
end

#parse_entries(lines) ⇒ Array<Hash> (private)

Returns:

  • (Array<Hash>)

    the register_cop entries with their line numbers

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 83

def parse_entries(lines)
  lines.filter_map.with_index do |line, index|
    match = ENTRY_PATTERN.match(line)

    if match
      { line: index, indent: match[:indent], constant: match[:constant],
        path: match[:path] }
    elsif line.include?('register_cop')
      raise Error, "unexpected `register_cop` line in #{department_file_path}:" \
                   "#{index + 1}; fix it or register the cop manually: #{line}"
    end
  end
end

#write_department_file(lines) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/generator/registration_injector.rb', line 76

def write_department_file(lines)
  File.write(department_file_path, lines.join)

  output.puts "[modify] #{department_file_path} - `#{directive}` was injected."
end