code_ownership 1.36.1 → 1.36.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 668edc0471999590cd95e9953151e6e30d02bf64b2bb14e544e5f7c56099c998
4
- data.tar.gz: bbdff6a9af05c8026b33c206d63f083d1c9ed82831bd7b1f3cf3707aa0f54a67
3
+ metadata.gz: eb464ff5e39c0c9f2712a7af6498f30b3b42e3c456e7da52a5209d7f30467346
4
+ data.tar.gz: 5fd3f8974a041ff298fbdaceca0b3eccee027f4d9c94bdfaa344ae9b8ade72d5
5
5
  SHA512:
6
- metadata.gz: 54a6b78ddb108894b20ddee99d0a6637770485b27c154b6676021f512d2aae39159d052e8f2c7986b71c824eb9f3f7bc4a3b9d5bc0c9e1848e7bceecee3e7728
7
- data.tar.gz: b4f39290cfc23f31b73642d3d6a5747ec79efc07210541c4b18c913f448c1df32efe6b4657e91f736055ab30ecf72b4910c22a0266503842aff84ad0e375be62
6
+ metadata.gz: ef51049577f74ab3faed01b4a37df119eaf577465fa10f342b79c0fabf7bc89b59ed993f8c1d8f687c53a3957745dfe579430e081473b9eb63f9b3cbe1f8eb19
7
+ data.tar.gz: ba03e839b81e0dcdfd1c07356b99dbf850b9abddaa83049c2c74e458012c4b81bdfaead12ed6c59fc744848261e078d715f6512df876d1d1fa238362910bfcb9
@@ -14,7 +14,7 @@ module CodeOwnership
14
14
  for_file(argv)
15
15
  elsif command == 'for_team'
16
16
  for_team(argv)
17
- elsif [nil, "help"].include?(command)
17
+ elsif [nil, 'help'].include?(command)
18
18
  puts <<~USAGE
19
19
  Usage: bin/codeownership <subcommand>
20
20
 
@@ -27,7 +27,6 @@ module CodeOwnership
27
27
  else
28
28
  puts "'#{command}' is not a code_ownership command. See `bin/codeownership help`."
29
29
  end
30
-
31
30
  end
32
31
 
33
32
  def self.validate!(argv)
@@ -53,16 +52,16 @@ module CodeOwnership
53
52
  exit
54
53
  end
55
54
  end
56
- args = parser.order!(argv) {}
55
+ args = parser.order!(argv)
57
56
  parser.parse!(args)
58
57
 
59
58
  files = if options[:diff]
60
- ENV.fetch('CODEOWNERS_GIT_STAGED_FILES') { `git diff --staged --name-only` }.split("\n").select do |file|
61
- File.exist?(file)
62
- end
63
- else
64
- nil
65
- end
59
+ ENV.fetch('CODEOWNERS_GIT_STAGED_FILES') { `git diff --staged --name-only` }.split("\n").select do |file|
60
+ File.exist?(file)
61
+ end
62
+ else
63
+ nil
64
+ end
66
65
 
67
66
  CodeOwnership.validate!(
68
67
  files: files,
@@ -79,7 +78,7 @@ module CodeOwnership
79
78
  # Long-term, we probably want to use something like `thor` so we don't have to implement logic
80
79
  # like this. In the short-term, this is a simple way for us to use the built-in OptionParser
81
80
  # while having an ergonomic CLI.
82
- files = argv.select { |arg| !arg.start_with?('--') }
81
+ files = argv.reject { |arg| arg.start_with?('--') }
83
82
 
84
83
  parser = OptionParser.new do |opts|
85
84
  opts.banner = 'Usage: bin/codeownership for_file [options]'
@@ -93,22 +92,22 @@ module CodeOwnership
93
92
  exit
94
93
  end
95
94
  end
96
- args = parser.order!(argv) {}
95
+ args = parser.order!(argv)
97
96
  parser.parse!(args)
98
97
 
99
98
  if files.count != 1
100
- raise "Please pass in one file. Use `bin/codeownership for_file --help` for more info"
99
+ raise 'Please pass in one file. Use `bin/codeownership for_file --help` for more info'
101
100
  end
102
-
101
+
103
102
  team = CodeOwnership.for_file(files.first)
104
103
 
105
- team_name = team&.name || "Unowned"
106
- team_yml = team&.config_yml || "Unowned"
104
+ team_name = team&.name || 'Unowned'
105
+ team_yml = team&.config_yml || 'Unowned'
107
106
 
108
107
  if options[:json]
109
108
  json = {
110
109
  team_name: team_name,
111
- team_yml: team_yml,
110
+ team_yml: team_yml
112
111
  }
113
112
 
114
113
  puts json.to_json
@@ -121,8 +120,6 @@ module CodeOwnership
121
120
  end
122
121
 
123
122
  def self.for_team(argv)
124
- options = {}
125
-
126
123
  parser = OptionParser.new do |opts|
127
124
  opts.banner = 'Usage: bin/codeownership for_team \'Team Name\''
128
125
 
@@ -131,14 +128,14 @@ module CodeOwnership
131
128
  exit
132
129
  end
133
130
  end
134
- teams = argv.select { |arg| !arg.start_with?('--') }
135
- args = parser.order!(argv) {}
131
+ teams = argv.reject { |arg| arg.start_with?('--') }
132
+ args = parser.order!(argv)
136
133
  parser.parse!(args)
137
134
 
138
135
  if teams.count != 1
139
- raise "Please pass in one team. Use `bin/codeownership for_team --help` for more info"
136
+ raise 'Please pass in one team. Use `bin/codeownership for_team --help` for more info'
140
137
  end
141
-
138
+
142
139
  puts CodeOwnership.for_team(teams.first)
143
140
  end
144
141
 
@@ -17,8 +17,8 @@ module CodeOwnership
17
17
  def self.fetch
18
18
  config_hash = YAML.load_file('config/code_ownership.yml')
19
19
 
20
- if config_hash.key?("require")
21
- config_hash["require"].each do |require_directive|
20
+ if config_hash.key?('require')
21
+ config_hash['require'].each do |require_directive|
22
22
  Private::ExtensionLoader.load(require_directive)
23
23
  end
24
24
  end
@@ -29,21 +29,19 @@ module CodeOwnership
29
29
  # This should be fast when run with ONE file
30
30
  #
31
31
  sig do
32
- abstract.params(file: String).
33
- returns(T.nilable(::CodeTeams::Team))
34
- end
35
- def map_file_to_owner(file)
32
+ abstract.params(file: String)
33
+ .returns(T.nilable(::CodeTeams::Team))
36
34
  end
35
+ def map_file_to_owner(file); end
37
36
 
38
37
  #
39
38
  # This should be fast when run with MANY files
40
39
  #
41
40
  sig do
42
- abstract.params(files: T::Array[String]).
43
- returns(T::Hash[String, ::CodeTeams::Team])
44
- end
45
- def globs_to_owner(files)
41
+ abstract.params(files: T::Array[String])
42
+ .returns(T::Hash[String, ::CodeTeams::Team])
46
43
  end
44
+ def globs_to_owner(files); end
47
45
 
48
46
  #
49
47
  # This should be fast when run with MANY files
@@ -51,16 +49,13 @@ module CodeOwnership
51
49
  sig do
52
50
  abstract.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
53
51
  end
54
- def update_cache(cache, files)
55
- end
52
+ def update_cache(cache, files); end
56
53
 
57
54
  sig { abstract.returns(String) }
58
- def description
59
- end
55
+ def description; end
60
56
 
61
57
  sig { abstract.void }
62
- def bust_caches!
63
- end
58
+ def bust_caches!; end
64
59
 
65
60
  sig { returns(Private::GlobCache) }
66
61
  def self.to_glob_cache
@@ -72,6 +67,7 @@ module CodeOwnership
72
67
 
73
68
  mapped_files.each do |glob, owner|
74
69
  next if owner.nil?
70
+
75
71
  glob_to_owner_map_by_mapper_description.fetch(mapper.description)[glob] = owner
76
72
  end
77
73
  end
@@ -13,15 +13,15 @@ module CodeOwnership
13
13
 
14
14
  sig { returns(T::Array[String]) }
15
15
  def self.actual_contents_lines
16
- if !path.exist?
17
- [""]
18
- else
16
+ if path.exist?
19
17
  content = path.read
20
18
  lines = path.read.split("\n")
21
19
  if content.end_with?("\n")
22
- lines << ""
20
+ lines << ''
23
21
  end
24
22
  lines
23
+ else
24
+ ['']
25
25
  end
26
26
  end
27
27
 
@@ -53,7 +53,7 @@ module CodeOwnership
53
53
 
54
54
  cache.each do |mapper_description, ownership_map_cache|
55
55
  ownership_entries = []
56
- sorted_ownership_map_cache = ownership_map_cache.sort_by do |glob, team|
56
+ sorted_ownership_map_cache = ownership_map_cache.sort_by do |glob, _team|
57
57
  glob
58
58
  end
59
59
  sorted_ownership_map_cache.to_h.each do |path, code_team|
@@ -64,22 +64,23 @@ module CodeOwnership
64
64
  # 1) It allows the CODEOWNERS file to be used as a cache for validations
65
65
  # 2) It allows users to specifically see what their team will not be notified about.
66
66
  entry = if ignored_teams.include?(code_team.name)
67
- "# /#{path} #{team_mapping}"
68
- else
69
- "/#{path} #{team_mapping}"
70
- end
67
+ "# /#{path} #{team_mapping}"
68
+ else
69
+ "/#{path} #{team_mapping}"
70
+ end
71
71
  ownership_entries << entry
72
72
  end
73
73
 
74
74
  next if ownership_entries.none?
75
+
75
76
  codeowners_file_lines += ['', "# #{mapper_description}", *ownership_entries.sort]
76
77
  end
77
78
 
78
79
  [
79
80
  *header.split("\n"),
80
- "", # For line between header and codeowners_file_lines
81
+ '', # For line between header and codeowners_file_lines
81
82
  *codeowners_file_lines,
82
- "", # For end-of-file newline
83
+ '' # For end-of-file newline
83
84
  ]
84
85
  end
85
86
 
@@ -123,20 +124,22 @@ module CodeOwnership
123
124
  mapper_descriptions = Set.new(Mapper.all.map(&:description))
124
125
 
125
126
  path.readlines.each do |line|
126
- line_with_no_comment = line.chomp.gsub("# ", "")
127
+ line_with_no_comment = line.chomp.gsub('# ', '')
127
128
  if mapper_descriptions.include?(line_with_no_comment)
128
129
  current_mapper = line_with_no_comment
129
130
  else
130
131
  next if current_mapper.nil?
131
- next if line.chomp == ""
132
+ next if line.chomp == ''
133
+
132
134
  # The codeowners file stores paths relative to the root of directory
133
135
  # Since a `/` means root of the file system from the perspective of ruby,
134
136
  # we remove that beginning slash so we can correctly glob the files out.
135
- normalized_line = line.gsub(/^# /, '').gsub(/^\//, '')
137
+ normalized_line = line.gsub(/^# /, '').gsub(%r{^/}, '')
136
138
  split_line = normalized_line.split
137
139
  # Most lines will be in the format: /path/to/file my-github-team
138
140
  # This will skip over lines that are not of the correct form
139
141
  next if split_line.count > 2
142
+
140
143
  entry, github_team = split_line
141
144
  code_team = github_team_to_code_team_map[T.must(github_team)]
142
145
  # If a GitHub team is changed and a user runs `bin/codeownership validate`, we won't be able to identify
@@ -144,6 +147,7 @@ module CodeOwnership
144
147
  # Therefore, if we can't determine the team, we just skip it.
145
148
  # This affects how complete the cache is, but that will still be caught by `bin/codeownership validate`.
146
149
  next if code_team.nil?
150
+
147
151
  raw_cache_contents[current_mapper] ||= {}
148
152
  raw_cache_contents.fetch(current_mapper)[T.must(entry)] = github_team_to_code_team_map.fetch(T.must(github_team))
149
153
  end
@@ -12,7 +12,7 @@ module CodeOwnership
12
12
  def load(require_directive)
13
13
  # We want to transform the require directive to behave differently
14
14
  # if it's a specific local file being required versus a gem
15
- if require_directive.start_with?(".")
15
+ if require_directive.start_with?('.')
16
16
  require File.join(Pathname.pwd, require_directive)
17
17
  else
18
18
  require require_directive
@@ -34,14 +34,15 @@ module CodeOwnership
34
34
 
35
35
  sig { params(files: T::Array[String]).returns(FilesByMapper) }
36
36
  def mapper_descriptions_that_map_files(files)
37
- if files.count > 100
38
- # When looking at many files, expanding the cache out using Dir.glob and checking for intersections is faster
39
- files_by_mappers = files.map{ |f| [f, Set.new([]) ]}.to_h
40
- files_by_mappers.merge(files_by_mappers_via_expanded_cache)
41
- else
42
- # When looking at few files, using File.fnmatch is faster
43
- files_by_mappers_via_file_fnmatch(files)
37
+ files_by_mappers = files.to_h { |f| [f, Set.new([])] }
38
+
39
+ files_by_mappers_via_expanded_cache.each do |file, mappers|
40
+ mappers.each do |mapper|
41
+ T.must(files_by_mappers[file]) << mapper if files_by_mappers[file]
42
+ end
44
43
  end
44
+
45
+ files_by_mappers
45
46
  end
46
47
 
47
48
  private
@@ -53,25 +54,19 @@ module CodeOwnership
53
54
  @expanded_cache ||= begin
54
55
  expanded_cache = {}
55
56
  @raw_cache_contents.each do |mapper_description, globs_by_owner|
56
- expanded_cache[mapper_description] = {}
57
- globs_by_owner.each do |glob, owner|
58
- Dir.glob(glob).each do |file, owner|
59
- expanded_cache[mapper_description][file] = owner
60
- end
61
- end
57
+ expanded_cache[mapper_description] = OwnerAssigner.assign_owners(globs_by_owner)
62
58
  end
63
-
64
59
  expanded_cache
65
60
  end
66
61
  end
67
62
 
68
63
  sig { returns(FilesByMapper) }
69
64
  def files_by_mappers_via_expanded_cache
70
- @files_by_mappers ||= T.let(@files_by_mappers, T.nilable(FilesByMapper))
71
- @files_by_mappers ||= begin
65
+ @files_by_mappers_via_expanded_cache ||= T.let(@files_by_mappers_via_expanded_cache, T.nilable(FilesByMapper))
66
+ @files_by_mappers_via_expanded_cache ||= begin
72
67
  files_by_mappers = T.let({}, FilesByMapper)
73
68
  expanded_cache.each do |mapper_description, file_by_owner|
74
- file_by_owner.each do |file, owner|
69
+ file_by_owner.each_key do |file|
75
70
  files_by_mappers[file] ||= Set.new([])
76
71
  files_by_mappers.fetch(file) << mapper_description
77
72
  end
@@ -80,31 +75,6 @@ module CodeOwnership
80
75
  files_by_mappers
81
76
  end
82
77
  end
83
-
84
- sig { params(files: T::Array[String]).returns(FilesByMapper) }
85
- def files_by_mappers_via_file_fnmatch(files)
86
- files_by_mappers = T.let({}, FilesByMapper)
87
-
88
- files.each do |file|
89
- files_by_mappers[file] ||= Set.new([])
90
- @raw_cache_contents.each do |mapper_description, globs_by_owner|
91
- # As much as I'd like to *not* special case the file annotations mapper, using File.fnmatch? on the thousands of files mapped by the
92
- # file annotations mapper is a lot of unnecessary extra work.
93
- # Therefore we can just check if the file is in the globs directly for file annotations, otherwise use File.fnmatch
94
- if mapper_description == OwnershipMappers::FileAnnotations::DESCRIPTION
95
- files_by_mappers.fetch(file) << mapper_description if globs_by_owner[file]
96
- else
97
- globs_by_owner.each do |glob, owner|
98
- if File.fnmatch?(glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
99
- files_by_mappers.fetch(file) << mapper_description
100
- end
101
- end
102
- end
103
- end
104
- end
105
-
106
- files_by_mappers
107
- end
108
78
  end
109
79
  end
110
80
  end
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CodeOwnership
5
+ module Private
6
+ class OwnerAssigner
7
+ extend T::Sig
8
+
9
+ sig { params(globs_to_owning_team_map: GlobsToOwningTeamMap).returns(GlobsToOwningTeamMap) }
10
+ def self.assign_owners(globs_to_owning_team_map)
11
+ globs_to_owning_team_map.each_with_object({}) do |(glob, owner), mapping|
12
+ # addresses the case where a directory name includes regex characters
13
+ # such as `app/services/[test]/some_other_file.ts`
14
+ mapping[glob] = owner if File.exist?(glob)
15
+ Dir.glob(glob).each do |file|
16
+ mapping[file] ||= owner
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -14,8 +14,8 @@ module CodeOwnership
14
14
  @@directory_cache = T.let({}, T::Hash[String, T.nilable(CodeTeams::Team)]) # rubocop:disable Style/ClassVars
15
15
 
16
16
  sig do
17
- override.params(file: String).
18
- returns(T.nilable(::CodeTeams::Team))
17
+ override.params(file: String)
18
+ .returns(T.nilable(::CodeTeams::Team))
19
19
  end
20
20
  def map_file_to_owner(file)
21
21
  map_file_to_relevant_owner(file)
@@ -37,8 +37,8 @@ module CodeOwnership
37
37
  # subset of files, but rather we want code ownership for all files.
38
38
  #
39
39
  sig do
40
- override.params(files: T::Array[String]).
41
- returns(T::Hash[String, ::CodeTeams::Team])
40
+ override.params(files: T::Array[String])
41
+ .returns(T::Hash[String, ::CodeTeams::Team])
42
42
  end
43
43
  def globs_to_owner(files)
44
44
  # The T.unsafe is because the upstream RBI is wrong for Pathname.glob
@@ -97,7 +97,7 @@ module CodeOwnership
97
97
  (path_components.length - 1).downto(0).each do |i|
98
98
  team = get_team_from_codeowners_file_within_directory(
99
99
  Pathname.new(File.join(*T.unsafe(path_components[0...i])))
100
- )
100
+ )
101
101
  return team unless team.nil?
102
102
  end
103
103
 
@@ -121,7 +121,7 @@ module CodeOwnership
121
121
  @@directory_cache[potential_codeowners_file_name] = nil
122
122
  end
123
123
 
124
- return team
124
+ team
125
125
  end
126
126
  end
127
127
  end
@@ -18,24 +18,24 @@ module CodeOwnership
18
18
  extend T::Sig
19
19
  include Mapper
20
20
 
21
- TEAM_PATTERN = T.let(/\A(?:#|\/\/) @team (?<team>.*)\Z/.freeze, Regexp)
21
+ TEAM_PATTERN = T.let(%r{\A(?:#|//) @team (?<team>.*)\Z}.freeze, Regexp)
22
22
  DESCRIPTION = 'Annotations at the top of file'
23
23
 
24
24
  sig do
25
- override.params(file: String).
26
- returns(T.nilable(::CodeTeams::Team))
25
+ override.params(file: String)
26
+ .returns(T.nilable(::CodeTeams::Team))
27
27
  end
28
28
  def map_file_to_owner(file)
29
29
  file_annotation_based_owner(file)
30
30
  end
31
31
 
32
32
  sig do
33
- override.
34
- params(files: T::Array[String]).
35
- returns(T::Hash[String, ::CodeTeams::Team])
33
+ override
34
+ .params(files: T::Array[String])
35
+ .returns(T::Hash[String, ::CodeTeams::Team])
36
36
  end
37
37
  def globs_to_owner(files)
38
- files.each_with_object({}) do |filename_relative_to_root, mapping| # rubocop:disable Style/ClassVars
38
+ files.each_with_object({}) do |filename_relative_to_root, mapping|
39
39
  owner = file_annotation_based_owner(filename_relative_to_root)
40
40
  next unless owner
41
41
 
@@ -56,10 +56,10 @@ module CodeOwnership
56
56
  invalid_files = cache.keys.select do |file|
57
57
  # If a file is not tracked, it should be removed from the cache
58
58
  !Private.file_tracked?(file) ||
59
- # If a file no longer has a file annotation (i.e. `globs_to_owner` doesn't map it)
60
- # it should be removed from the cache
61
- # We make sure to only apply this to the input files since otherwise `updated_cache_for_files.key?(file)` would always return `false` when files == []
62
- (fileset.include?(file) && !updated_cache_for_files.key?(file))
59
+ # If a file no longer has a file annotation (i.e. `globs_to_owner` doesn't map it)
60
+ # it should be removed from the cache
61
+ # We make sure to only apply this to the input files since otherwise `updated_cache_for_files.key?(file)` would always return `false` when files == []
62
+ (fileset.include?(file) && !updated_cache_for_files.key?(file))
63
63
  end
64
64
 
65
65
  invalid_files.each do |invalid_file|
@@ -83,14 +83,14 @@ module CodeOwnership
83
83
  # and if the annotation isn't in the first two lines we assume it
84
84
  # doesn't exist.
85
85
 
86
- line_1 = File.foreach(filename).first
86
+ line1 = File.foreach(filename).first
87
87
 
88
- return if !line_1
88
+ return if !line1
89
89
 
90
90
  begin
91
- team = line_1[TEAM_PATTERN, :team]
92
- rescue ArgumentError => ex
93
- if ex.message.include?('invalid byte sequence')
91
+ team = line1[TEAM_PATTERN, :team]
92
+ rescue ArgumentError => e
93
+ if e.message.include?('invalid byte sequence')
94
94
  team = nil
95
95
  else
96
96
  raise
@@ -110,7 +110,7 @@ module CodeOwnership
110
110
  if file_annotation_based_owner(filename)
111
111
  filepath = Pathname.new(filename)
112
112
  lines = filepath.read.split("\n")
113
- new_lines = lines.select { |line| !line[TEAM_PATTERN] }
113
+ new_lines = lines.reject { |line| line[TEAM_PATTERN] }
114
114
  # We explicitly add a final new line since splitting by new line when reading the file lines
115
115
  # ignores new lines at the ends of files
116
116
  # We also remove leading new lines, since there is after a new line after an annotation
@@ -125,8 +125,7 @@ module CodeOwnership
125
125
  end
126
126
 
127
127
  sig { override.void }
128
- def bust_caches!
129
- end
128
+ def bust_caches!; end
130
129
  end
131
130
  end
132
131
  end
@@ -12,8 +12,8 @@ module CodeOwnership
12
12
  @@package_json_cache = T.let({}, T::Hash[String, T.nilable(ParseJsPackages::Package)]) # rubocop:disable Style/ClassVars
13
13
 
14
14
  sig do
15
- override.params(file: String).
16
- returns(T.nilable(::CodeTeams::Team))
15
+ override.params(file: String)
16
+ .returns(T.nilable(::CodeTeams::Team))
17
17
  end
18
18
  def map_file_to_owner(file)
19
19
  package = map_file_to_relevant_package(file)
@@ -39,8 +39,8 @@ module CodeOwnership
39
39
  # subset of files, but rather we want code ownership for all files.
40
40
  #
41
41
  sig do
42
- override.params(files: T::Array[String]).
43
- returns(T::Hash[String, ::CodeTeams::Team])
42
+ override.params(files: T::Array[String])
43
+ .returns(T::Hash[String, ::CodeTeams::Team])
44
44
  end
45
45
  def globs_to_owner(files)
46
46
  ParseJsPackages.all.each_with_object({}) do |package, res|
@@ -85,8 +85,8 @@ module CodeOwnership
85
85
 
86
86
  (path_components.length - 1).downto(0).each do |i|
87
87
  potential_relative_path_name = T.must(path_components[0...i]).reduce(Pathname.new('')) { |built_path, path| built_path.join(path) }
88
- potential_package_json_path = potential_relative_path_name.
89
- join(ParseJsPackages::PACKAGE_JSON_NAME)
88
+ potential_package_json_path = potential_relative_path_name
89
+ .join(ParseJsPackages::PACKAGE_JSON_NAME)
90
90
 
91
91
  potential_package_json_string = potential_package_json_path.to_s
92
92
 
@@ -10,8 +10,8 @@ module CodeOwnership
10
10
  include Mapper
11
11
 
12
12
  sig do
13
- override.params(file: String).
14
- returns(T.nilable(::CodeTeams::Team))
13
+ override.params(file: String)
14
+ .returns(T.nilable(::CodeTeams::Team))
15
15
  end
16
16
  def map_file_to_owner(file)
17
17
  package = Packs.for_file(file)
@@ -30,8 +30,8 @@ module CodeOwnership
30
30
  # subset of files, but rather we want code ownership for all files.
31
31
  #
32
32
  sig do
33
- override.params(files: T::Array[String]).
34
- returns(T::Hash[String, ::CodeTeams::Team])
33
+ override.params(files: T::Array[String])
34
+ .returns(T::Hash[String, ::CodeTeams::Team])
35
35
  end
36
36
  def globs_to_owner(files)
37
37
  Packs.all.each_with_object({}) do |package, res|
@@ -66,8 +66,7 @@ module CodeOwnership
66
66
  end
67
67
 
68
68
  sig { override.void }
69
- def bust_caches!
70
- end
69
+ def bust_caches!; end
71
70
  end
72
71
  end
73
72
  end
@@ -14,11 +14,11 @@ module CodeOwnership
14
14
  @@map_files_to_owners = {} # rubocop:disable Style/ClassVars
15
15
 
16
16
  sig do
17
- params(files: T::Array[String]).
18
- returns(T::Hash[String, ::CodeTeams::Team])
17
+ params(files: T::Array[String])
18
+ .returns(T::Hash[String, ::CodeTeams::Team])
19
19
  end
20
- def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
21
- return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
20
+ def map_files_to_owners(files)
21
+ return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count.positive?
22
22
 
23
23
  @@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
24
24
  TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
@@ -42,7 +42,7 @@ module CodeOwnership
42
42
  sig { returns(String) }
43
43
  def description
44
44
  # These are sorted only to prevent non-determinism in output between local and CI environments.
45
- sorted_contexts = mapping_contexts.sort_by{|context| context.team.config_yml.to_s }
45
+ sorted_contexts = mapping_contexts.sort_by { |context| context.team.config_yml.to_s }
46
46
  description_args = sorted_contexts.map do |context|
47
47
  "`#{context.glob}` (from `#{context.team.config_yml}`)"
48
48
  end
@@ -56,7 +56,7 @@ module CodeOwnership
56
56
  end
57
57
  def find_overlapping_globs
58
58
  mapped_files = T.let({}, T::Hash[String, T::Array[MappingContext]])
59
- CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
59
+ CodeTeams.all.each_with_object({}) do |team, _map|
60
60
  TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
61
61
  Dir.glob(glob).each do |filename|
62
62
  mapped_files[filename] ||= []
@@ -66,24 +66,22 @@ module CodeOwnership
66
66
  end
67
67
 
68
68
  overlaps = T.let([], T::Array[GlobOverlap])
69
- mapped_files.each do |filename, mapping_contexts|
69
+ mapped_files.each_value do |mapping_contexts|
70
70
  if mapping_contexts.count > 1
71
71
  overlaps << GlobOverlap.new(mapping_contexts: mapping_contexts)
72
72
  end
73
73
  end
74
74
 
75
- deduplicated_overlaps = overlaps.uniq do |glob_overlap|
75
+ overlaps.uniq do |glob_overlap|
76
76
  glob_overlap.mapping_contexts.map do |context|
77
77
  [context.glob, context.team.name]
78
78
  end
79
79
  end
80
-
81
- deduplicated_overlaps
82
80
  end
83
81
 
84
82
  sig do
85
- override.params(file: String).
86
- returns(T.nilable(::CodeTeams::Team))
83
+ override.params(file: String)
84
+ .returns(T.nilable(::CodeTeams::Team))
87
85
  end
88
86
  def map_file_to_owner(file)
89
87
  map_files_to_owners([file])[file]
@@ -97,11 +95,11 @@ module CodeOwnership
97
95
  end
98
96
 
99
97
  sig do
100
- override.params(files: T::Array[String]).
101
- returns(T::Hash[String, ::CodeTeams::Team])
98
+ override.params(files: T::Array[String])
99
+ .returns(T::Hash[String, ::CodeTeams::Team])
102
100
  end
103
101
  def globs_to_owner(files)
104
- CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
102
+ CodeTeams.all.each_with_object({}) do |team, map|
105
103
  TeamPlugins::Ownership.for(team).owned_globs.each do |owned_glob|
106
104
  map[owned_glob] = team
107
105
  end
@@ -128,7 +126,7 @@ module CodeOwnership
128
126
  errors << <<~MSG
129
127
  `owned_globs` cannot overlap between teams. The following globs overlap:
130
128
 
131
- #{overlapping_globs.map { |overlap| "- #{overlap.description}"}.join("\n")}
129
+ #{overlapping_globs.map { |overlap| "- #{overlap.description}" }.join("\n")}
132
130
  MSG
133
131
  end
134
132
 
@@ -13,11 +13,11 @@ module CodeOwnership
13
13
  @@map_files_to_owners = {} # rubocop:disable Style/ClassVars
14
14
 
15
15
  sig do
16
- params(files: T::Array[String]).
17
- returns(T::Hash[String, ::CodeTeams::Team])
16
+ params(files: T::Array[String])
17
+ .returns(T::Hash[String, ::CodeTeams::Team])
18
18
  end
19
- def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
20
- return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
19
+ def map_files_to_owners(files)
20
+ return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count.positive?
21
21
 
22
22
  @@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
23
23
  map[team.config_yml] = team
@@ -25,19 +25,19 @@ module CodeOwnership
25
25
  end
26
26
 
27
27
  sig do
28
- override.params(file: String).
29
- returns(T.nilable(::CodeTeams::Team))
28
+ override.params(file: String)
29
+ .returns(T.nilable(::CodeTeams::Team))
30
30
  end
31
31
  def map_file_to_owner(file)
32
32
  map_files_to_owners([file])[file]
33
33
  end
34
34
 
35
35
  sig do
36
- override.params(files: T::Array[String]).
37
- returns(T::Hash[String, ::CodeTeams::Team])
36
+ override.params(files: T::Array[String])
37
+ .returns(T::Hash[String, ::CodeTeams::Team])
38
38
  end
39
39
  def globs_to_owner(files)
40
- CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
40
+ CodeTeams.all.each_with_object({}) do |team, map|
41
41
  map[team.config_yml] = team
42
42
  end
43
43
  end
@@ -23,10 +23,10 @@ module CodeOwnership
23
23
  package_loaded_json = JSON.parse(pathname.read)
24
24
 
25
25
  package_name = if pathname.dirname == Pathname.new('.')
26
- ROOT_PACKAGE_NAME
27
- else
28
- pathname.dirname.cleanpath.to_s
29
- end
26
+ ROOT_PACKAGE_NAME
27
+ else
28
+ pathname.dirname.cleanpath.to_s
29
+ end
30
30
 
31
31
  new(
32
32
  name: package_name,
@@ -41,7 +41,7 @@ module CodeOwnership
41
41
  Please either make the JSON in that file valid or specify `js_package_paths` in config/code_ownership.yml.
42
42
  MESSAGE
43
43
 
44
- raise InvalidCodeOwnershipConfigurationError.new(error_message)
44
+ raise InvalidCodeOwnershipConfigurationError, error_message
45
45
  end
46
46
 
47
47
  sig { returns(Pathname) }
@@ -1,4 +1,3 @@
1
-
2
1
  # typed: strict
3
2
  # frozen_string_literal: true
4
3
 
@@ -12,8 +12,8 @@ module CodeOwnership
12
12
  def validation_errors(files:, autocorrect: true, stage_changes: true)
13
13
  cache = Private.glob_cache
14
14
  file_mappings = cache.mapper_descriptions_that_map_files(files)
15
- files_not_mapped_at_all = file_mappings.select do |file, mapper_descriptions|
16
- mapper_descriptions.count == 0
15
+ files_not_mapped_at_all = file_mappings.select do |_file, mapper_descriptions|
16
+ mapper_descriptions.count.zero?
17
17
  end
18
18
 
19
19
  errors = T.let([], T::Array[String])
@@ -22,7 +22,7 @@ module CodeOwnership
22
22
  errors << <<~MSG
23
23
  Some files are missing ownership:
24
24
 
25
- #{files_not_mapped_at_all.map { |file, mappers| "- #{file}" }.join("\n")}
25
+ #{files_not_mapped_at_all.map { |file, _mappers| "- #{file}" }.join("\n")}
26
26
  MSG
27
27
  end
28
28
 
@@ -12,7 +12,7 @@ module CodeOwnership
12
12
  def validation_errors(files:, autocorrect: true, stage_changes: true)
13
13
  cache = Private.glob_cache
14
14
  file_mappings = cache.mapper_descriptions_that_map_files(files)
15
- files_mapped_by_multiple_mappers = file_mappings.select do |file, mapper_descriptions|
15
+ files_mapped_by_multiple_mappers = file_mappings.select do |_file, mapper_descriptions|
16
16
  mapper_descriptions.count > 1
17
17
  end
18
18
 
@@ -25,7 +25,7 @@ module CodeOwnership
25
25
  `git add #{CodeownersFile.path}`
26
26
  end
27
27
  # If there is no current file or its empty, display a shorter message.
28
- elsif actual_content_lines == [""]
28
+ elsif actual_content_lines == ['']
29
29
  errors << <<~CODEOWNERS_ERROR
30
30
  CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file
31
31
  CODEOWNERS_ERROR
@@ -34,33 +34,33 @@ module CodeOwnership
34
34
  extra_lines = actual_content_lines - expected_content_lines
35
35
 
36
36
  missing_lines_text = if missing_lines.any?
37
- <<~COMMENT
38
- CODEOWNERS should contain the following lines, but does not:
39
- #{(missing_lines).map { |line| "- \"#{line}\""}.join("\n")}
40
- COMMENT
41
- end
37
+ <<~COMMENT
38
+ CODEOWNERS should contain the following lines, but does not:
39
+ #{missing_lines.map { |line| "- \"#{line}\"" }.join("\n")}
40
+ COMMENT
41
+ end
42
42
 
43
43
  extra_lines_text = if extra_lines.any?
44
- <<~COMMENT
45
- CODEOWNERS should not contain the following lines, but it does:
46
- #{(extra_lines).map { |line| "- \"#{line}\""}.join("\n")}
47
- COMMENT
48
- end
44
+ <<~COMMENT
45
+ CODEOWNERS should not contain the following lines, but it does:
46
+ #{extra_lines.map { |line| "- \"#{line}\"" }.join("\n")}
47
+ COMMENT
48
+ end
49
49
 
50
50
  diff_text = if missing_lines_text && extra_lines_text
51
- "#{missing_lines_text}\n#{extra_lines_text}".chomp
52
- elsif missing_lines_text
53
- missing_lines_text
54
- elsif extra_lines_text
55
- extra_lines_text
56
- else
57
- <<~TEXT
58
- There may be extra lines, or lines are out of order.
59
- You can try to regenerate the CODEOWNERS file from scratch:
60
- 1) `rm .github/CODEOWNERS`
61
- 2) `bin/codeownership validate`
62
- TEXT
63
- end
51
+ "#{missing_lines_text}\n#{extra_lines_text}".chomp
52
+ elsif missing_lines_text
53
+ missing_lines_text
54
+ elsif extra_lines_text
55
+ extra_lines_text
56
+ else
57
+ <<~TEXT
58
+ There may be extra lines, or lines are out of order.
59
+ You can try to regenerate the CODEOWNERS file from scratch:
60
+ 1) `rm .github/CODEOWNERS`
61
+ 2) `bin/codeownership validate`
62
+ TEXT
63
+ end
64
64
 
65
65
  errors << <<~CODEOWNERS_ERROR
66
66
  CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file
@@ -8,6 +8,7 @@ require 'code_ownership/private/team_plugins/github'
8
8
  require 'code_ownership/private/codeowners_file'
9
9
  require 'code_ownership/private/parse_js_packages'
10
10
  require 'code_ownership/private/glob_cache'
11
+ require 'code_ownership/private/owner_assigner'
11
12
  require 'code_ownership/private/validations/files_have_owners'
12
13
  require 'code_ownership/private/validations/github_codeowners_up_to_date'
13
14
  require 'code_ownership/private/validations/files_have_unique_owners'
@@ -91,14 +92,13 @@ module CodeOwnership
91
92
  # However, globbing out can take 5 or more seconds on a large repository, dramatically slowing down
92
93
  # invocations to `bin/codeownership validate --diff`.
93
94
  # Using `File.fnmatch?` is a lot faster!
94
- in_owned_globs = configuration.owned_globs.all? do |owned_glob|
95
+ in_owned_globs = configuration.owned_globs.any? do |owned_glob|
95
96
  File.fnmatch?(owned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
96
97
  end
97
98
 
98
99
  in_unowned_globs = configuration.unowned_globs.any? do |unowned_glob|
99
100
  File.fnmatch?(unowned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
100
101
  end
101
-
102
102
  in_owned_globs && !in_unowned_globs && File.exist?(file)
103
103
  end
104
104
 
@@ -115,13 +115,11 @@ module CodeOwnership
115
115
  sig { returns(GlobCache) }
116
116
  def self.glob_cache
117
117
  @glob_cache ||= T.let(@glob_cache, T.nilable(GlobCache))
118
- @glob_cache ||= begin
119
- if CodeownersFile.use_codeowners_cache?
120
- CodeownersFile.to_glob_cache
121
- else
122
- Mapper.to_glob_cache
123
- end
124
- end
118
+ @glob_cache ||= if CodeownersFile.use_codeowners_cache?
119
+ CodeownersFile.to_glob_cache
120
+ else
121
+ Mapper.to_glob_cache
122
+ end
125
123
  end
126
124
  end
127
125
 
@@ -8,8 +8,7 @@ module CodeOwnership
8
8
  interface!
9
9
 
10
10
  sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
11
- def validation_errors(files:, autocorrect: true, stage_changes: true)
12
- end
11
+ def validation_errors(files:, autocorrect: true, stage_changes: true); end
13
12
 
14
13
  class << self
15
14
  extend T::Sig
@@ -18,7 +18,8 @@ if defined?(Packwerk)
18
18
  end
19
19
 
20
20
  module CodeOwnership
21
- extend self
21
+ module_function
22
+
22
23
  extend T::Sig
23
24
  extend T::Helpers
24
25
 
@@ -39,7 +40,7 @@ module CodeOwnership
39
40
 
40
41
  Mapper.all.each do |mapper|
41
42
  owner = mapper.map_file_to_owner(file)
42
- break if owner
43
+ break if owner # TODO: what if there are multiple owners? Should we respond with an error instead of the first match?
43
44
  end
44
45
 
45
46
  @for_file[file] = owner
@@ -57,6 +58,7 @@ module CodeOwnership
57
58
  ownership_for_mapper = []
58
59
  glob_to_owning_team_map.each do |glob, owning_team|
59
60
  next if owning_team != team
61
+
60
62
  ownership_for_mapper << "- #{glob}"
61
63
  end
62
64
 
@@ -66,7 +68,7 @@ module CodeOwnership
66
68
  ownership_information += ownership_for_mapper.sort
67
69
  end
68
70
 
69
- ownership_information << ""
71
+ ownership_information << ''
70
72
  end
71
73
 
72
74
  ownership_information.join("\n")
@@ -84,7 +86,7 @@ module CodeOwnership
84
86
  params(
85
87
  autocorrect: T::Boolean,
86
88
  stage_changes: T::Boolean,
87
- files: T.nilable(T::Array[String]),
89
+ files: T.nilable(T::Array[String])
88
90
  ).void
89
91
  end
90
92
  def validate!(
@@ -95,10 +97,10 @@ module CodeOwnership
95
97
  Private.load_configuration!
96
98
 
97
99
  tracked_file_subset = if files
98
- files.select{|f| Private.file_tracked?(f)}
99
- else
100
- Private.tracked_files
101
- end
100
+ files.select { |f| Private.file_tracked?(f) }
101
+ else
102
+ Private.tracked_files
103
+ end
102
104
 
103
105
  Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
104
106
  end
@@ -150,7 +152,7 @@ module CodeOwnership
150
152
 
151
153
  [
152
154
  CodeOwnership.for_file(file),
153
- file,
155
+ file
154
156
  ]
155
157
  end
156
158
  end
@@ -161,15 +163,15 @@ module CodeOwnership
161
163
  @memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)]))
162
164
  @memoized_values ||= {}
163
165
  # We use key because the memoized value could be `nil`
164
- if !@memoized_values.key?(klass.to_s)
166
+ if @memoized_values.key?(klass.to_s)
167
+ @memoized_values[klass.to_s]
168
+ else
165
169
  path = Private.path_from_klass(klass)
166
170
  return nil if path.nil?
167
171
 
168
172
  value_to_memoize = for_file(path)
169
173
  @memoized_values[klass.to_s] = value_to_memoize
170
174
  value_to_memoize
171
- else
172
- @memoized_values[klass.to_s]
173
175
  end
174
176
  end
175
177
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_ownership
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.36.1
4
+ version: 1.36.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-12 00:00:00.000000000 Z
11
+ date: 2024-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams
@@ -44,16 +44,16 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.5.10821
47
+ version: 0.5.11249
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 0.5.10821
54
+ version: 0.5.11249
55
55
  - !ruby/object:Gem::Dependency
56
- name: pry
56
+ name: debug
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rake
70
+ name: packwerk
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,21 +81,21 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rspec
84
+ name: railties
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '3.0'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '3.0'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: sorbet
98
+ name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: tapioca
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -123,7 +137,7 @@ dependencies:
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
- name: packwerk
140
+ name: sorbet
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - ">="
@@ -137,7 +151,7 @@ dependencies:
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
- name: railties
154
+ name: tapioca
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
157
  - - ">="
@@ -168,6 +182,7 @@ files:
168
182
  - lib/code_ownership/private/codeowners_file.rb
169
183
  - lib/code_ownership/private/extension_loader.rb
170
184
  - lib/code_ownership/private/glob_cache.rb
185
+ - lib/code_ownership/private/owner_assigner.rb
171
186
  - lib/code_ownership/private/ownership_mappers/directory_ownership.rb
172
187
  - lib/code_ownership/private/ownership_mappers/file_annotations.rb
173
188
  - lib/code_ownership/private/ownership_mappers/js_package_ownership.rb
@@ -190,7 +205,7 @@ metadata:
190
205
  source_code_uri: https://github.com/rubyatscale/code_ownership
191
206
  changelog_uri: https://github.com/rubyatscale/code_ownership/releases
192
207
  allowed_push_host: https://rubygems.org
193
- post_install_message:
208
+ post_install_message:
194
209
  rdoc_options: []
195
210
  require_paths:
196
211
  - lib
@@ -198,15 +213,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
213
  requirements:
199
214
  - - ">="
200
215
  - !ruby/object:Gem::Version
201
- version: '0'
216
+ version: '2.6'
202
217
  required_rubygems_version: !ruby/object:Gem::Requirement
203
218
  requirements:
204
219
  - - ">="
205
220
  - !ruby/object:Gem::Version
206
221
  version: '0'
207
222
  requirements: []
208
- rubygems_version: 3.1.6
209
- signing_key:
223
+ rubygems_version: 3.5.11
224
+ signing_key:
210
225
  specification_version: 4
211
226
  summary: A gem to help engineering teams declare ownership of code
212
227
  test_files: []