class Danger::DangerMissingCodeowners

Parses the CODEOWNERS file and verifies if files have at least one owner. Works with GitHub and GitLab. Results are passed out as a table in markdown.

@example Verifying files missing codeowners.

missing_codeowners.verify

@see andre-alves/danger-missing_codeowners @tags codeowners

Attributes

files_missing_codeowners[RW]

The list of files that are missing owners.

@return [Array<String>]

max_number_of_files_to_report[RW]

The maximum number of files missing owners Danger should report. Default is 100.

@return [Int]

severity[RW]

Defines the severity level of the execution. Possible values are: 'error' or 'warning'. Default is 'error'.

@return [String]

verbose[RW]

Provides additional logging diagnostic information. Default is false.

@return [Bool]

verify_all_files[RW]

Whether all files or only ones in PR diff to be reported. Default is false.

@return [Bool]

Public Instance Methods

verify() click to toggle source

Verifies git added and modified files for missing owners. Generates a `markdown` list of warnings for the prose in a corpus of .markdown and .md files.

@return [void]

# File lib/missing_codeowners/plugin.rb, line 49
def verify
  @verify_all_files ||= false
  @max_number_of_files_to_report ||= 100
  @severity ||= "error"
  @verbose ||= false

  files = files_to_verify
  log "Files to verify:"
  log files.join("\n")

  codeowners_path = find_codeowners_file
  codeowners_lines = read_codeowners_file(codeowners_path)
  codeowners_spec = parse_codeowners_spec(codeowners_lines)
  @files_missing_codeowners = files.reject { |file| codeowners_spec.match file }

  if @files_missing_codeowners.any?
    log "Files missing CODEOWNERS:"
    log @files_missing_codeowners.join("\n")

    markdown format_missing_owners_message(@files_missing_codeowners, @max_number_of_files_to_report)
    danger_message = "Add CODEOWNERS rules to match all files."
    @severity == "error" ? (fail danger_message) : (warn danger_message)
  else
    log "No files missing CODEOWNERS."
  end

  log "-----"
end

Private Instance Methods

files_to_verify() click to toggle source
# File lib/missing_codeowners/plugin.rb, line 80
def files_to_verify
  @verify_all_files == true ? git_all_files : git_modified_files
end
find_codeowners_file() click to toggle source
# File lib/missing_codeowners/plugin.rb, line 93
def find_codeowners_file
  directories = ["", ".gitlab", ".github", "docs"]
  paths = directories.map { |dir| File.join(dir, "CODEOWNERS") }
  Dir.glob(paths).first || paths.first
end
format_missing_owners_message(files, max_count) click to toggle source
# File lib/missing_codeowners/plugin.rb, line 129
def format_missing_owners_message(files, max_count)
  message = "### Files missing CODEOWNERS\n\n".dup
  message << "| File |\n"
  message << "| ---- |\n"
  files.take(max_count).each do |file|
    message << "| #{file} |\n"
  end

  other_files_length = files.length - max_count
  if other_files_length.positive?
    message << "...and #{other_files_length} other files.\n"
  end

  message
end
git_all_files() click to toggle source
# File lib/missing_codeowners/plugin.rb, line 88
def git_all_files
  # The git object provided by Danger doesn't have ls_files
  `git ls-files`.split($/)
end
git_modified_files() click to toggle source
# File lib/missing_codeowners/plugin.rb, line 84
def git_modified_files
  git.added_files + git.modified_files
end
log(text) click to toggle source
# File lib/missing_codeowners/plugin.rb, line 145
def log(text)
  puts(text) if @verbose
end
parse_codeowners_spec(lines) click to toggle source
# File lib/missing_codeowners/plugin.rb, line 104
def parse_codeowners_spec(lines)
  patterns = []
  lines.each do |line|
    components = line.split(/\s+(@\S+|\S+@\S+)/).reject { |c| c.strip.empty? }
    if line.match(/^\s*((?:#.*)|(?:\[.*)|(?:\^.*))?$/)
      next # Comment, group or empty line
    elsif components.length < 2
      raise "[ERROR] Invalid CODEOWNERS line: '#{line}'"
    else
      pattern = components[0]

      # There is a different between .gitignore spec and CODEOWNERS in regards to nested directories
      # See frotz/ example in https://git-scm.com/docs/gitignore
      # foo/bar (CODEOWNERS) == **/foo/bar (.gitignore)
      if pattern.match(%r{^[^/*].*/.+})
        pattern = "**/#{pattern}"
      end

      patterns << pattern
      log "Adding pattern: '#{pattern}'"
    end
  end
  PathSpec.from_lines(patterns)
end
read_codeowners_file(path) click to toggle source
# File lib/missing_codeowners/plugin.rb, line 99
def read_codeowners_file(path)
  log "Reading the CODEOWNERS file from path: #{path}"
  File.readlines(path).map(&:chomp)
end