# typed: strict module CodeOwnership module Private module Validations class GithubCodeownersUpToDate extend T::Sig extend T::Helpers include Validator sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } def validation_errors(files:, autocorrect: true, stage_changes: true) return [] if Private.configuration.skip_codeowners_validation codeowners_filepath = Pathname.pwd.join('.github/CODEOWNERS') FileUtils.mkdir_p(codeowners_filepath.dirname) if !codeowners_filepath.dirname.exist? header = <<~HEADER # STOP! - DO NOT EDIT THIS FILE MANUALLY # This file was automatically generated by "bin/codeownership validate". # # CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub # teams. This is useful when developers create Pull Requests since the # code/file owner is notified. Reference GitHub docs for more details: # https://help.github.com/en/articles/about-code-owners HEADER expected_content_lines = [ *header.split("\n"), nil, # For line between header and codeowners_file_lines *codeowners_file_lines, nil, # For end-of-file newline ] expected_contents = expected_content_lines.join("\n") actual_contents = codeowners_filepath.exist? ? codeowners_filepath.read : "" actual_content_lines = actual_contents.split("\n") codeowners_up_to_date = actual_contents == expected_contents errors = T.let([], T::Array[String]) if !codeowners_up_to_date if autocorrect codeowners_filepath.write(expected_contents) if stage_changes `git add #{codeowners_filepath}` end else # If there is no current file or its empty, display a shorter message. missing_lines = expected_content_lines - actual_content_lines extra_lines = actual_content_lines - expected_content_lines missing_lines_text = if missing_lines.any? <<~COMMENT CODEOWNERS should contain the following lines, but does not: #{(expected_content_lines - actual_content_lines).map { |line| "- \"#{line}\""}.join("\n")} COMMENT end extra_lines_text = if extra_lines.any? <<~COMMENT CODEOWNERS should not contain the following lines, but it does: #{(actual_content_lines - expected_content_lines).map { |line| "- \"#{line}\""}.join("\n")} COMMENT end diff_text = if missing_lines_text && extra_lines_text "#{missing_lines_text}\n#{extra_lines_text}".chomp elsif missing_lines_text missing_lines_text elsif extra_lines_text extra_lines_text else "" end if actual_contents == "" errors << <<~CODEOWNERS_ERROR CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file CODEOWNERS_ERROR else errors << <<~CODEOWNERS_ERROR CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file #{diff_text.chomp} CODEOWNERS_ERROR end end end errors end private # Generate the contents of a CODEOWNERS file that GitHub can use to # automatically assign reviewers # https://help.github.com/articles/about-codeowners/ sig { returns(T::Array[String]) } def codeowners_file_lines github_team_map = CodeTeams.all.each_with_object({}) do |team, map| team_github = TeamPlugins::Github.for(team).github next if team_github.do_not_add_to_codeowners_file map[team.name] = team_github.team end Mapper.all.flat_map do |mapper| codeowners_lines = mapper.codeowners_lines_to_owners.filter_map do |line, team| team_mapping = github_team_map[team&.name] next unless team_mapping "/#{line} #{team_mapping}" end next [] if codeowners_lines.empty? [ '', "# #{mapper.description}", *codeowners_lines.sort, ] end end end end end end