code_ownership 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +101 -0
- data/bin/codeownership +5 -0
- data/lib/code_ownership/cli.rb +60 -0
- data/lib/code_ownership/private/configuration.rb +37 -0
- data/lib/code_ownership/private/ownership_mappers/file_annotations.rb +119 -0
- data/lib/code_ownership/private/ownership_mappers/interface.rb +50 -0
- data/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb +121 -0
- data/lib/code_ownership/private/ownership_mappers/package_ownership.rb +121 -0
- data/lib/code_ownership/private/ownership_mappers/team_globs.rb +68 -0
- data/lib/code_ownership/private/parse_js_packages.rb +59 -0
- data/lib/code_ownership/private/team_plugins/github.rb +24 -0
- data/lib/code_ownership/private/team_plugins/ownership.rb +17 -0
- data/lib/code_ownership/private/validations/files_have_owners.rb +34 -0
- data/lib/code_ownership/private/validations/files_have_unique_owners.rb +32 -0
- data/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb +85 -0
- data/lib/code_ownership/private/validations/interface.rb +18 -0
- data/lib/code_ownership/private.rb +124 -0
- data/lib/code_ownership.rb +129 -0
- data/sorbet/config +4 -0
- data/sorbet/rbi/gems/bigrails-teams@0.1.0.rbi +120 -0
- data/sorbet/rbi/gems/parse_packwerk@0.7.0.rbi +111 -0
- data/sorbet/rbi/todo.rbi +6 -0
- metadata +182 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: true
|
4
|
+
|
5
|
+
module CodeOwnership
|
6
|
+
module Private
|
7
|
+
# Modeled off of ParsePackwerk
|
8
|
+
module ParseJsPackages
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
ROOT_PACKAGE_NAME = 'root'
|
12
|
+
PACKAGE_JSON_NAME = T.let('package.json', String)
|
13
|
+
METADATA = 'metadata'
|
14
|
+
|
15
|
+
class Package < T::Struct
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
const :name, String
|
19
|
+
const :metadata, T::Hash[String, T.untyped]
|
20
|
+
|
21
|
+
sig { params(pathname: Pathname).returns(Package) }
|
22
|
+
def self.from(pathname)
|
23
|
+
package_loaded_json = JSON.parse(pathname.read)
|
24
|
+
|
25
|
+
package_name = if pathname.dirname == Pathname.new('.')
|
26
|
+
ROOT_PACKAGE_NAME
|
27
|
+
else
|
28
|
+
pathname.dirname.cleanpath.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
new(
|
32
|
+
name: package_name,
|
33
|
+
metadata: package_loaded_json[METADATA] || {}
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { returns(Pathname) }
|
38
|
+
def directory
|
39
|
+
root_pathname = Pathname.new('.')
|
40
|
+
name == ROOT_PACKAGE_NAME ? root_pathname.cleanpath : root_pathname.join(name).cleanpath
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
sig do
|
45
|
+
returns(T::Array[Package])
|
46
|
+
end
|
47
|
+
def self.all
|
48
|
+
package_glob_patterns = Private.configuration.js_package_paths.map do |pathspec|
|
49
|
+
File.join(pathspec, PACKAGE_JSON_NAME)
|
50
|
+
end
|
51
|
+
|
52
|
+
# The T.unsafe is because the upstream RBI is wrong for Pathname.glob
|
53
|
+
T.unsafe(Pathname).glob(package_glob_patterns).map(&:cleanpath).map do |path|
|
54
|
+
Package.from(path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module CodeOwnership
|
4
|
+
module Private
|
5
|
+
module TeamPlugins
|
6
|
+
class Github < Teams::Plugin
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
GithubStruct = Struct.new(:team, :do_not_add_to_codeowners_file)
|
11
|
+
|
12
|
+
sig { returns(GithubStruct) }
|
13
|
+
def github
|
14
|
+
raw_github = @team.raw_hash['github'] || {}
|
15
|
+
|
16
|
+
GithubStruct.new(
|
17
|
+
raw_github['team'],
|
18
|
+
raw_github['do_not_add_to_codeowners_file'] || false
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module CodeOwnership
|
4
|
+
module Private
|
5
|
+
module TeamPlugins
|
6
|
+
class Ownership < Teams::Plugin
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
sig { returns(T::Array[String]) }
|
11
|
+
def owned_globs
|
12
|
+
@team.raw_hash['owned_globs'] || []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module CodeOwnership
|
4
|
+
module Private
|
5
|
+
module Validations
|
6
|
+
class FilesHaveOwners
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
include Interface
|
10
|
+
|
11
|
+
sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
12
|
+
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
|
+
allow_list = Dir.glob(Private.configuration.unowned_globs)
|
14
|
+
files_by_mapper = Private.files_by_mapper(files)
|
15
|
+
files_not_mapped_at_all = files_by_mapper.select { |_file, mapper_descriptions| mapper_descriptions.count == 0 }.keys
|
16
|
+
|
17
|
+
files_without_owners = files_not_mapped_at_all - allow_list
|
18
|
+
|
19
|
+
errors = T.let([], T::Array[String])
|
20
|
+
|
21
|
+
if files_without_owners.any?
|
22
|
+
errors << <<~MSG
|
23
|
+
Some files are missing ownership:
|
24
|
+
|
25
|
+
#{files_without_owners.map { |file| "- #{file}" }.join("\n")}
|
26
|
+
MSG
|
27
|
+
end
|
28
|
+
|
29
|
+
errors
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module CodeOwnership
|
4
|
+
module Private
|
5
|
+
module Validations
|
6
|
+
class FilesHaveUniqueOwners
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
include Interface
|
10
|
+
|
11
|
+
sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
12
|
+
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
|
+
files_by_mapper = Private.files_by_mapper(files)
|
14
|
+
|
15
|
+
files_mapped_by_multiple_mappers = files_by_mapper.select { |_file, mapper_descriptions| mapper_descriptions.count > 1 }.to_h
|
16
|
+
|
17
|
+
errors = T.let([], T::Array[String])
|
18
|
+
|
19
|
+
if files_mapped_by_multiple_mappers.any?
|
20
|
+
errors << <<~MSG
|
21
|
+
Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways.
|
22
|
+
|
23
|
+
#{files_mapped_by_multiple_mappers.map { |file, descriptions| "- #{file} (#{descriptions.join(', ')})" }.join("\n")}
|
24
|
+
MSG
|
25
|
+
end
|
26
|
+
|
27
|
+
errors
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module CodeOwnership
|
4
|
+
module Private
|
5
|
+
module Validations
|
6
|
+
class GithubCodeownersUpToDate
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
include Interface
|
10
|
+
|
11
|
+
sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
12
|
+
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
|
+
return [] if Private.configuration.skip_codeowners_validation
|
14
|
+
|
15
|
+
codeowners_filepath = Pathname.pwd.join('.github/CODEOWNERS')
|
16
|
+
FileUtils.mkdir_p(codeowners_filepath.dirname) if !codeowners_filepath.dirname.exist?
|
17
|
+
|
18
|
+
header = <<~HEADER
|
19
|
+
# STOP! - DO NOT EDIT THIS FILE MANUALLY
|
20
|
+
# This file was automatically generated by "bin/codeownership validate".
|
21
|
+
#
|
22
|
+
# CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub
|
23
|
+
# teams. This is useful when developers create Pull Requests since the
|
24
|
+
# code/file owner is notified. Reference GitHub docs for more details:
|
25
|
+
# https://help.github.com/en/articles/about-code-owners
|
26
|
+
HEADER
|
27
|
+
|
28
|
+
contents = [
|
29
|
+
header,
|
30
|
+
*codeowners_file_lines,
|
31
|
+
nil, # For end-of-file newline
|
32
|
+
].join("\n")
|
33
|
+
|
34
|
+
codeowners_up_to_date = codeowners_filepath.exist? && codeowners_filepath.read == contents
|
35
|
+
|
36
|
+
errors = T.let([], T::Array[String])
|
37
|
+
|
38
|
+
if !codeowners_up_to_date
|
39
|
+
if autocorrect
|
40
|
+
codeowners_filepath.write(contents)
|
41
|
+
if stage_changes
|
42
|
+
`git add #{codeowners_filepath}`
|
43
|
+
end
|
44
|
+
else
|
45
|
+
errors << "CODEOWNERS out of date. Ensure pre-commit hook is set up correctly and used. You can also run bin/codeownership validate to update the CODEOWNERS file\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
errors
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Generate the contents of a CODEOWNERS file that GitHub can use to
|
55
|
+
# automatically assign reviewers
|
56
|
+
# https://help.github.com/articles/about-codeowners/
|
57
|
+
sig { returns(T::Array[String]) }
|
58
|
+
def codeowners_file_lines
|
59
|
+
github_team_map = Teams.all.each_with_object({}) do |team, map|
|
60
|
+
team_github = TeamPlugins::Github.for(team).github
|
61
|
+
next if team_github.do_not_add_to_codeowners_file
|
62
|
+
|
63
|
+
map[team.name] = team_github.team
|
64
|
+
end
|
65
|
+
|
66
|
+
Private.mappers.flat_map do |mapper|
|
67
|
+
codeowners_lines = mapper.codeowners_lines_to_owners.filter_map do |line, team|
|
68
|
+
team_mapping = github_team_map[team&.name]
|
69
|
+
next unless team_mapping
|
70
|
+
|
71
|
+
"/#{line} #{team_mapping}"
|
72
|
+
end
|
73
|
+
next [] if codeowners_lines.empty?
|
74
|
+
|
75
|
+
[
|
76
|
+
'',
|
77
|
+
"# #{mapper.description}",
|
78
|
+
*codeowners_lines.sort,
|
79
|
+
]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module CodeOwnership
|
4
|
+
module Private
|
5
|
+
module Validations
|
6
|
+
module Interface
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
interface!
|
11
|
+
|
12
|
+
sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
13
|
+
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: strict
|
4
|
+
|
5
|
+
require 'code_ownership/private/configuration'
|
6
|
+
require 'code_ownership/private/team_plugins/ownership'
|
7
|
+
require 'code_ownership/private/team_plugins/github'
|
8
|
+
require 'code_ownership/private/parse_js_packages'
|
9
|
+
require 'code_ownership/private/validations/interface'
|
10
|
+
require 'code_ownership/private/validations/files_have_owners'
|
11
|
+
require 'code_ownership/private/validations/github_codeowners_up_to_date'
|
12
|
+
require 'code_ownership/private/validations/files_have_unique_owners'
|
13
|
+
require 'code_ownership/private/ownership_mappers/interface'
|
14
|
+
require 'code_ownership/private/ownership_mappers/file_annotations'
|
15
|
+
require 'code_ownership/private/ownership_mappers/team_globs'
|
16
|
+
require 'code_ownership/private/ownership_mappers/package_ownership'
|
17
|
+
require 'code_ownership/private/ownership_mappers/js_package_ownership'
|
18
|
+
|
19
|
+
module CodeOwnership
|
20
|
+
module Private
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { returns(Private::Configuration) }
|
24
|
+
def self.configuration
|
25
|
+
@configuration ||= T.let(@configuration, T.nilable(Private::Configuration))
|
26
|
+
@configuration ||= Private::Configuration.fetch
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { void }
|
30
|
+
def self.bust_caches!
|
31
|
+
@configuration = nil
|
32
|
+
@tracked_files = nil
|
33
|
+
@files_by_mapper = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).void }
|
37
|
+
def self.validate!(files:, autocorrect: true, stage_changes: true)
|
38
|
+
validators = [
|
39
|
+
Validations::FilesHaveOwners.new,
|
40
|
+
Validations::FilesHaveUniqueOwners.new,
|
41
|
+
Validations::GithubCodeownersUpToDate.new,
|
42
|
+
]
|
43
|
+
|
44
|
+
errors = validators.flat_map do |validator|
|
45
|
+
validator.validation_errors(
|
46
|
+
files: files,
|
47
|
+
autocorrect: autocorrect,
|
48
|
+
stage_changes: stage_changes
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
if errors.any?
|
53
|
+
errors << 'See https://github.com/bigrails/code_ownership/README.md for more details'
|
54
|
+
raise InvalidCodeOwnershipConfigurationError.new(errors.join("\n")) # rubocop:disable Style/RaiseArgs
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { returns(T::Array[Private::OwnershipMappers::Interface]) }
|
59
|
+
def self.mappers
|
60
|
+
[
|
61
|
+
file_annotations_mapper,
|
62
|
+
Private::OwnershipMappers::TeamGlobs.new,
|
63
|
+
Private::OwnershipMappers::PackageOwnership.new,
|
64
|
+
Private::OwnershipMappers::JsPackageOwnership.new,
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { returns(Private::OwnershipMappers::FileAnnotations) }
|
69
|
+
def self.file_annotations_mapper
|
70
|
+
@file_annotations_mapper = T.let(@file_annotations_mapper, T.nilable(Private::OwnershipMappers::FileAnnotations))
|
71
|
+
@file_annotations_mapper ||= Private::OwnershipMappers::FileAnnotations.new
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a string version of the relative path to a Rails constant,
|
75
|
+
# or nil if it can't find something
|
76
|
+
sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(String)) }
|
77
|
+
def self.path_from_klass(klass)
|
78
|
+
if klass
|
79
|
+
path = Object.const_source_location(klass.to_s)&.first
|
80
|
+
(path && Pathname.new(path).relative_path_from(Pathname.pwd).to_s) || nil
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# The output of this function is string pathnames relative to the root.
|
88
|
+
#
|
89
|
+
sig { returns(T::Array[String]) }
|
90
|
+
def self.tracked_files
|
91
|
+
@tracked_files ||= T.let(@tracked_files, T.nilable(T::Array[String]))
|
92
|
+
@tracked_files ||= Dir.glob(configuration.owned_globs)
|
93
|
+
end
|
94
|
+
|
95
|
+
sig { params(team_name: String, location_of_reference: String).returns(Teams::Team) }
|
96
|
+
def self.find_team!(team_name, location_of_reference)
|
97
|
+
found_team = Teams.find(team_name)
|
98
|
+
if found_team.nil?
|
99
|
+
raise StandardError, "Could not find team with name: `#{team_name}` in #{location_of_reference}. Make sure the team is one of `#{Teams.all.map(&:name).sort}`"
|
100
|
+
else
|
101
|
+
found_team
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
sig { params(files: T::Array[String]).returns(T::Hash[String, T::Array[String]]) }
|
106
|
+
def self.files_by_mapper(files)
|
107
|
+
@files_by_mapper ||= T.let(@files_by_mapper, T.nilable(T::Hash[String, T::Array[String]]))
|
108
|
+
@files_by_mapper ||= begin
|
109
|
+
files_by_mapper = files.map { |file| [file, []] }.to_h
|
110
|
+
|
111
|
+
Private.mappers.each do |mapper|
|
112
|
+
mapper.map_files_to_owners(files).each do |file, _team|
|
113
|
+
files_by_mapper[file] ||= []
|
114
|
+
T.must(files_by_mapper[file]) << mapper.description
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
files_by_mapper
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private_constant :Private
|
124
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: strict
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
require 'teams'
|
7
|
+
require 'sorbet-runtime'
|
8
|
+
require 'json'
|
9
|
+
require 'parse_packwerk'
|
10
|
+
require 'code_ownership/cli'
|
11
|
+
require 'code_ownership/private'
|
12
|
+
|
13
|
+
module CodeOwnership
|
14
|
+
extend self
|
15
|
+
extend T::Sig
|
16
|
+
extend T::Helpers
|
17
|
+
|
18
|
+
requires_ancestor { Kernel }
|
19
|
+
|
20
|
+
sig { params(file: String).returns(T.nilable(Teams::Team)) }
|
21
|
+
def for_file(file)
|
22
|
+
@for_file ||= T.let(@for_file, T.nilable(T::Hash[String, T.nilable(Teams::Team)]))
|
23
|
+
@for_file ||= {}
|
24
|
+
|
25
|
+
return nil if file.start_with?('./')
|
26
|
+
return @for_file[file] if @for_file.key?(file)
|
27
|
+
|
28
|
+
owner = T.let(nil, T.nilable(Teams::Team))
|
29
|
+
|
30
|
+
Private.mappers.each do |mapper|
|
31
|
+
owner = mapper.map_file_to_owner(file)
|
32
|
+
break if owner
|
33
|
+
end
|
34
|
+
|
35
|
+
@for_file[file] = owner
|
36
|
+
end
|
37
|
+
|
38
|
+
class InvalidCodeOwnershipConfigurationError < StandardError
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(filename: String).void }
|
42
|
+
def self.remove_file_annotation!(filename)
|
43
|
+
Private.file_annotations_mapper.remove_file_annotation!(filename)
|
44
|
+
end
|
45
|
+
|
46
|
+
sig do
|
47
|
+
params(
|
48
|
+
files: T::Array[String],
|
49
|
+
autocorrect: T::Boolean,
|
50
|
+
stage_changes: T::Boolean
|
51
|
+
).void
|
52
|
+
end
|
53
|
+
def validate!(
|
54
|
+
files: Private.tracked_files,
|
55
|
+
autocorrect: true,
|
56
|
+
stage_changes: true
|
57
|
+
)
|
58
|
+
tracked_file_subset = Private.tracked_files & files
|
59
|
+
Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Given a backtrace from either `Exception#backtrace` or `caller`, find the
|
63
|
+
# first line that corresponds to a file with assigned ownership
|
64
|
+
sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::Teams::Team]).returns(T.nilable(::Teams::Team)) }
|
65
|
+
def for_backtrace(backtrace, excluded_teams: [])
|
66
|
+
return unless backtrace
|
67
|
+
|
68
|
+
# The pattern for a backtrace hasn't changed in forever and is considered
|
69
|
+
# stable: https://github.com/ruby/ruby/blob/trunk/vm_backtrace.c#L303-L317
|
70
|
+
#
|
71
|
+
# This pattern matches a line like the following:
|
72
|
+
#
|
73
|
+
# ./app/controllers/some_controller.rb:43:in `block (3 levels) in create'
|
74
|
+
#
|
75
|
+
backtrace_line = %r{\A(#{Pathname.pwd}/|\./)?
|
76
|
+
(?<file>.+) # Matches 'app/controllers/some_controller.rb'
|
77
|
+
:
|
78
|
+
(?<line>\d+) # Matches '43'
|
79
|
+
:in\s
|
80
|
+
`(?<function>.*)' # Matches "`block (3 levels) in create'"
|
81
|
+
\z}x
|
82
|
+
|
83
|
+
backtrace.each do |line|
|
84
|
+
match = line.match(backtrace_line)
|
85
|
+
|
86
|
+
if match
|
87
|
+
team = CodeOwnership.for_file(T.must(match[:file]))
|
88
|
+
if team && !excluded_teams.include?(team)
|
89
|
+
return team
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(::Teams::Team)) }
|
97
|
+
def for_class(klass)
|
98
|
+
@memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(::Teams::Team)]))
|
99
|
+
@memoized_values ||= {}
|
100
|
+
# We use key because the memoized value could be `nil`
|
101
|
+
if !@memoized_values.key?(klass.to_s)
|
102
|
+
path = Private.path_from_klass(klass)
|
103
|
+
return nil if path.nil?
|
104
|
+
|
105
|
+
value_to_memoize = for_file(path)
|
106
|
+
@memoized_values[klass.to_s] = value_to_memoize
|
107
|
+
value_to_memoize
|
108
|
+
else
|
109
|
+
@memoized_values[klass.to_s]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
sig { params(package: ParsePackwerk::Package).returns(T.nilable(::Teams::Team)) }
|
114
|
+
def for_package(package)
|
115
|
+
Private::OwnershipMappers::PackageOwnership.new.owner_for_package(package)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generally, you should not ever need to do this, because once your ruby process loads, cached content should not change.
|
119
|
+
# Namely, the set of files, packages, and directories which are tracked for ownership should not change.
|
120
|
+
# The primary reason this is helpful is for clients of CodeOwnership who want to test their code, and each test context
|
121
|
+
# has different ownership and tracked files.
|
122
|
+
sig { void }
|
123
|
+
def self.bust_caches!
|
124
|
+
@for_file = nil
|
125
|
+
@memoized_values = nil
|
126
|
+
Private.bust_caches!
|
127
|
+
Private.mappers.each(&:bust_caches!)
|
128
|
+
end
|
129
|
+
end
|
data/sorbet/config
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
# DO NOT EDIT MANUALLY
|
4
|
+
# This is an autogenerated file for types exported from the `bigrails-teams` gem.
|
5
|
+
# Please instead update this file by running `bin/tapioca gem bigrails-teams`.
|
6
|
+
|
7
|
+
module Teams
|
8
|
+
class << self
|
9
|
+
sig { returns(T::Array[::Teams::Team]) }
|
10
|
+
def all; end
|
11
|
+
|
12
|
+
sig { void }
|
13
|
+
def bust_caches!; end
|
14
|
+
|
15
|
+
sig { params(name: ::String).returns(T.nilable(::Teams::Team)) }
|
16
|
+
def find(name); end
|
17
|
+
|
18
|
+
sig { params(dir: ::String).returns(T::Array[::Teams::Team]) }
|
19
|
+
def for_directory(dir); end
|
20
|
+
|
21
|
+
sig { params(string: ::String).returns(::String) }
|
22
|
+
def tag_value_for(string); end
|
23
|
+
|
24
|
+
sig { params(teams: T::Array[::Teams::Team]).returns(T::Array[::String]) }
|
25
|
+
def validation_errors(teams); end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Teams::IncorrectPublicApiUsageError < ::StandardError; end
|
30
|
+
|
31
|
+
class Teams::Plugin
|
32
|
+
abstract!
|
33
|
+
|
34
|
+
sig { params(team: ::Teams::Team).void }
|
35
|
+
def initialize(team); end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
sig { returns(T::Array[T.class_of(Teams::Plugin)]) }
|
39
|
+
def all_plugins; end
|
40
|
+
|
41
|
+
sig { params(team: ::Teams::Team).returns(T.attached_class) }
|
42
|
+
def for(team); end
|
43
|
+
|
44
|
+
sig { params(base: T.untyped).void }
|
45
|
+
def inherited(base); end
|
46
|
+
|
47
|
+
sig { params(team: ::Teams::Team, key: ::String).returns(::String) }
|
48
|
+
def missing_key_error_message(team, key); end
|
49
|
+
|
50
|
+
sig { params(teams: T::Array[::Teams::Team]).returns(T::Array[::String]) }
|
51
|
+
def validation_errors(teams); end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
sig { params(team: ::Teams::Team).returns(T.attached_class) }
|
56
|
+
def register_team(team); end
|
57
|
+
|
58
|
+
sig { returns(T::Hash[T.nilable(::String), T::Hash[::Class, ::Teams::Plugin]]) }
|
59
|
+
def registry; end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Teams::Plugins; end
|
64
|
+
|
65
|
+
class Teams::Plugins::Identity < ::Teams::Plugin
|
66
|
+
sig { returns(::Teams::Plugins::Identity::IdentityStruct) }
|
67
|
+
def identity; end
|
68
|
+
|
69
|
+
class << self
|
70
|
+
sig { override.params(teams: T::Array[::Teams::Team]).returns(T::Array[::String]) }
|
71
|
+
def validation_errors(teams); end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Teams::Plugins::Identity::IdentityStruct < ::Struct
|
76
|
+
def name; end
|
77
|
+
def name=(_); end
|
78
|
+
|
79
|
+
class << self
|
80
|
+
def [](*_arg0); end
|
81
|
+
def inspect; end
|
82
|
+
def members; end
|
83
|
+
def new(*_arg0); end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Teams::Team
|
88
|
+
sig { params(config_yml: T.nilable(::String), raw_hash: T::Hash[T.untyped, T.untyped]).void }
|
89
|
+
def initialize(config_yml:, raw_hash:); end
|
90
|
+
|
91
|
+
sig { params(other: ::Object).returns(T::Boolean) }
|
92
|
+
def ==(other); end
|
93
|
+
|
94
|
+
sig { returns(T.nilable(::String)) }
|
95
|
+
def config_yml; end
|
96
|
+
|
97
|
+
def eql?(*args, &blk); end
|
98
|
+
|
99
|
+
sig { returns(::Integer) }
|
100
|
+
def hash; end
|
101
|
+
|
102
|
+
sig { returns(::String) }
|
103
|
+
def name; end
|
104
|
+
|
105
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
106
|
+
def raw_hash; end
|
107
|
+
|
108
|
+
sig { returns(::String) }
|
109
|
+
def to_tag; end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(::Teams::Team) }
|
113
|
+
def from_hash(raw_hash); end
|
114
|
+
|
115
|
+
sig { params(config_yml: ::String).returns(::Teams::Team) }
|
116
|
+
def from_yml(config_yml); end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
Teams::UNKNOWN_TEAM_STRING = T.let(T.unsafe(nil), String)
|