# frozen_string_literal: true # typed: true module CodeOwnership module Private module OwnershipMappers class TeamGlobs extend T::Sig include Interface @@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars @@map_files_to_owners = {} # rubocop:disable Style/ClassVars @@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars @@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars sig do override. params(files: T::Array[String]). returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) end def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0 @@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars TeamPlugins::Ownership.for(team).owned_globs.each do |glob| Dir.glob(glob).each do |filename| map[filename] = team end end end end class MappingContext < T::Struct const :glob, String const :team, CodeTeams::Team end class GlobOverlap < T::Struct extend T::Sig const :mapping_contexts, T::Array[MappingContext] sig { returns(String) } def description # These are sorted only to prevent non-determinism in output between local and CI environments. sorted_contexts = mapping_contexts.sort_by{|context| context.team.config_yml.to_s } description_args = sorted_contexts.map do |context| "`#{context.glob}` (from `#{context.team.config_yml}`)" end description_args.join(', ') end end sig do returns(T::Array[GlobOverlap]) end def find_overlapping_globs mapped_files = T.let({}, T::Hash[String, T::Array[MappingContext]]) CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars TeamPlugins::Ownership.for(team).owned_globs.each do |glob| Dir.glob(glob).each do |filename| mapped_files[filename] ||= [] T.must(mapped_files[filename]) << MappingContext.new(glob: glob, team: team) end end end overlaps = T.let([], T::Array[GlobOverlap]) mapped_files.each do |filename, mapping_contexts| if mapping_contexts.count > 1 overlaps << GlobOverlap.new(mapping_contexts: mapping_contexts) end end deduplicated_overlaps = overlaps.uniq do |glob_overlap| glob_overlap.mapping_contexts.map do |context| [context.glob, context.team.name] end end deduplicated_overlaps end sig do override.params(file: String). returns(T.nilable(::CodeTeams::Team)) end def map_file_to_owner(file) map_files_to_owners([file])[file] end sig do override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) end def codeowners_lines_to_owners return @@codeowners_lines_to_owners if @@codeowners_lines_to_owners&.keys && @@codeowners_lines_to_owners.keys.count > 0 @@codeowners_lines_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars TeamPlugins::Ownership.for(team).owned_globs.each do |owned_glob| map[owned_glob] = team end end end sig { override.void } def bust_caches! @@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars @@map_files_to_owners = {} # rubocop:disable Style/ClassVars end sig { override.returns(String) } def description 'Team-specific owned globs' end end end end end