Skip to content

Commit 542a207

Browse files
author
Alex Evanczuk
authored
Auto-register new validators and mappers (#37)
* Auto-register mappers and validators * combine team glob validation into mapper
1 parent 0fdc6a9 commit 542a207

File tree

9 files changed

+120
-125
lines changed

9 files changed

+120
-125
lines changed

lib/code_ownership.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def for_file(file)
2727

2828
owner = T.let(nil, T.nilable(CodeTeams::Team))
2929

30-
Private.mappers.each do |mapper|
30+
Private::OwnershipMappers::Interface.all.each do |mapper|
3131
owner = mapper.map_file_to_owner(file)
3232
break if owner
3333
end
@@ -41,7 +41,7 @@ def for_team(team)
4141
ownership_information = T.let([], T::Array[String])
4242

4343
ownership_information << "# Code Ownership Report for `#{team.name}` Team"
44-
Private.mappers.each do |mapper|
44+
Private::OwnershipMappers::Interface.all.each do |mapper|
4545
ownership_information << "## #{mapper.description}"
4646
codeowners_lines = mapper.codeowners_lines_to_owners
4747
ownership_for_mapper = []
@@ -69,7 +69,7 @@ class InvalidCodeOwnershipConfigurationError < StandardError
6969

7070
sig { params(filename: String).void }
7171
def self.remove_file_annotation!(filename)
72-
Private.file_annotations_mapper.remove_file_annotation!(filename)
72+
Private::OwnershipMappers::FileAnnotations.new.remove_file_annotation!(filename)
7373
end
7474

7575
sig do
@@ -172,6 +172,6 @@ def self.bust_caches!
172172
@for_file = nil
173173
@memoized_values = nil
174174
Private.bust_caches!
175-
Private.mappers.each(&:bust_caches!)
175+
Private::OwnershipMappers::Interface.all.each(&:bust_caches!)
176176
end
177177
end

lib/code_ownership/private.rb

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
require 'code_ownership/private/validations/files_have_owners'
1111
require 'code_ownership/private/validations/github_codeowners_up_to_date'
1212
require 'code_ownership/private/validations/files_have_unique_owners'
13-
require 'code_ownership/private/validations/no_overlapping_globs'
1413
require 'code_ownership/private/ownership_mappers/interface'
1514
require 'code_ownership/private/ownership_mappers/file_annotations'
1615
require 'code_ownership/private/ownership_mappers/team_globs'
@@ -37,14 +36,7 @@ def self.bust_caches!
3736

3837
sig { params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).void }
3938
def self.validate!(files:, autocorrect: true, stage_changes: true)
40-
validators = [
41-
Validations::FilesHaveOwners.new,
42-
Validations::FilesHaveUniqueOwners.new,
43-
Validations::GithubCodeownersUpToDate.new,
44-
Validations::NoOverlappingGlobs.new,
45-
]
46-
47-
errors = validators.flat_map do |validator|
39+
errors = Validations::Interface.all.flat_map do |validator|
4840
validator.validation_errors(
4941
files: files,
5042
autocorrect: autocorrect,
@@ -58,23 +50,6 @@ def self.validate!(files:, autocorrect: true, stage_changes: true)
5850
end
5951
end
6052

61-
sig { returns(T::Array[Private::OwnershipMappers::Interface]) }
62-
def self.mappers
63-
[
64-
file_annotations_mapper,
65-
Private::OwnershipMappers::TeamGlobs.new,
66-
Private::OwnershipMappers::PackageOwnership.new,
67-
Private::OwnershipMappers::JsPackageOwnership.new,
68-
Private::OwnershipMappers::TeamYmlOwnership.new,
69-
]
70-
end
71-
72-
sig { returns(Private::OwnershipMappers::FileAnnotations) }
73-
def self.file_annotations_mapper
74-
@file_annotations_mapper = T.let(@file_annotations_mapper, T.nilable(Private::OwnershipMappers::FileAnnotations))
75-
@file_annotations_mapper ||= Private::OwnershipMappers::FileAnnotations.new
76-
end
77-
7853
# Returns a string version of the relative path to a Rails constant,
7954
# or nil if it can't find something
8055
sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(String)) }
@@ -112,7 +87,7 @@ def self.files_by_mapper(files)
11287
@files_by_mapper ||= begin
11388
files_by_mapper = files.map { |file| [file, []] }.to_h
11489

115-
Private.mappers.each do |mapper|
90+
Private::OwnershipMappers::Interface.all.each do |mapper|
11691
mapper.map_files_to_owners(files).each do |file, _team|
11792
files_by_mapper[file] ||= []
11893
T.must(files_by_mapper[file]) << mapper.description

lib/code_ownership/private/ownership_mappers/interface.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@ module Interface
1111

1212
interface!
1313

14+
class << self
15+
extend T::Sig
16+
17+
sig { params(base: Class).void }
18+
def included(base)
19+
@mappers ||= T.let(@mappers, T.nilable(T::Array[Class]))
20+
@mappers ||= []
21+
@mappers << base
22+
end
23+
24+
sig { returns(T::Array[Interface]) }
25+
def all
26+
T.unsafe(@mappers).map(&:new)
27+
end
28+
end
29+
1430
#
1531
# This should be fast when run with ONE file
1632
#

lib/code_ownership/private/ownership_mappers/team_globs.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module OwnershipMappers
88
class TeamGlobs
99
extend T::Sig
1010
include Interface
11+
include Validations::Interface
1112

1213
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
1314
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
@@ -114,6 +115,23 @@ def bust_caches!
114115
def description
115116
'Team-specific owned globs'
116117
end
118+
119+
sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
120+
def validation_errors(files:, autocorrect: true, stage_changes: true)
121+
overlapping_globs = OwnershipMappers::TeamGlobs.new.find_overlapping_globs
122+
123+
errors = T.let([], T::Array[String])
124+
125+
if overlapping_globs.any?
126+
errors << <<~MSG
127+
`owned_globs` cannot overlap between teams. The following globs overlap:
128+
129+
#{overlapping_globs.map { |overlap| "- #{overlap.description}"}.join("\n")}
130+
MSG
131+
end
132+
133+
errors
134+
end
117135
end
118136
end
119137
end

lib/code_ownership/private/validations/github_codeowners_up_to_date.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def codeowners_file_lines
105105
map[team.name] = team_github.team
106106
end
107107

108-
Private.mappers.flat_map do |mapper|
108+
Private::OwnershipMappers::Interface.all.flat_map do |mapper|
109109
codeowners_lines = mapper.codeowners_lines_to_owners.filter_map do |line, team|
110110
team_mapping = github_team_map[team&.name]
111111
next unless team_mapping

lib/code_ownership/private/validations/interface.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ module Interface
1212
sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
1313
def validation_errors(files:, autocorrect: true, stage_changes: true)
1414
end
15+
16+
class << self
17+
extend T::Sig
18+
19+
sig { params(base: Class).void }
20+
def included(base)
21+
@validators ||= T.let(@validators, T.nilable(T::Array[Class]))
22+
@validators ||= []
23+
@validators << base
24+
end
25+
26+
sig { returns(T::Array[Interface]) }
27+
def all
28+
T.unsafe(@validators).map(&:new)
29+
end
30+
end
1531
end
1632
end
1733
end

lib/code_ownership/private/validations/no_overlapping_globs.rb

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,71 @@
11
module CodeOwnership
22
RSpec.describe Private::OwnershipMappers::TeamGlobs do
3-
before do
4-
create_configuration
5-
write_file('config/teams/bar.yml', <<~CONTENTS)
6-
name: Bar
7-
owned_globs:
8-
- app/services/bar_stuff/**/**
9-
- frontend/javascripts/bar_stuff/**/**
10-
CONTENTS
11-
12-
write_file('app/services/bar_stuff/thing.rb')
13-
write_file('frontend/javascripts/bar_stuff/thing.jsx')
14-
end
3+
before { create_configuration }
4+
5+
describe 'CodeOwnership.for_file' do
6+
before do
7+
write_file('config/teams/bar.yml', <<~CONTENTS)
8+
name: Bar
9+
owned_globs:
10+
- app/services/bar_stuff/**/**
11+
- frontend/javascripts/bar_stuff/**/**
12+
CONTENTS
13+
14+
write_file('app/services/bar_stuff/thing.rb')
15+
write_file('frontend/javascripts/bar_stuff/thing.jsx')
16+
end
1517

16-
it 'can find the owner of ruby files in owned_globs' do
17-
expect(CodeOwnership.for_file('app/services/bar_stuff/thing.rb').name).to eq 'Bar'
18+
it 'can find the owner of ruby files in owned_globs' do
19+
expect(CodeOwnership.for_file('app/services/bar_stuff/thing.rb').name).to eq 'Bar'
20+
end
21+
22+
it 'can find the owner of javascript files in owned_globs' do
23+
expect(CodeOwnership.for_file('frontend/javascripts/bar_stuff/thing.jsx').name).to eq 'Bar'
24+
end
1825
end
1926

20-
it 'can find the owner of javascript files in owned_globs' do
21-
expect(CodeOwnership.for_file('frontend/javascripts/bar_stuff/thing.jsx').name).to eq 'Bar'
27+
describe 'CodeOwnership.validate!' do
28+
context 'two teams own the same exact glob' do
29+
before do
30+
write_file('config/code_ownership.yml', <<~YML)
31+
owned_globs:
32+
- '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
33+
YML
34+
35+
write_file('packs/my_pack/owned_file.rb')
36+
write_file('frontend/javascripts/blah/my_file.rb')
37+
write_file('frontend/javascripts/blah/subdir/my_file.rb')
38+
39+
write_file('config/teams/bar.yml', <<~CONTENTS)
40+
name: Bar
41+
owned_globs:
42+
- packs/**/**
43+
- frontend/javascripts/blah/subdir/my_file.rb
44+
CONTENTS
45+
46+
write_file('config/teams/foo.yml', <<~CONTENTS)
47+
name: Foo
48+
owned_globs:
49+
- packs/**/**
50+
- frontend/javascripts/blah/**/**
51+
CONTENTS
52+
end
53+
54+
it 'lets the user know that `owned_globs` can not overlap' do
55+
expect { CodeOwnership.validate! }.to raise_error do |e|
56+
expect(e).to be_a CodeOwnership::InvalidCodeOwnershipConfigurationError
57+
puts e.message
58+
expect(e.message).to eq <<~EXPECTED.chomp
59+
`owned_globs` cannot overlap between teams. The following globs overlap:
60+
61+
- `packs/**/**` (from `config/teams/bar.yml`), `packs/**/**` (from `config/teams/foo.yml`)
62+
- `frontend/javascripts/blah/subdir/my_file.rb` (from `config/teams/bar.yml`), `frontend/javascripts/blah/**/**` (from `config/teams/foo.yml`)
63+
64+
See https://github.com/rubyatscale/code_ownership#README.md for more details
65+
EXPECTED
66+
end
67+
end
68+
end
2269
end
2370
end
2471
end

spec/lib/code_ownership/private/validations/no_overlapping_globs_spec.rb

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)