@@ -6,8 +6,6 @@ use std::{
66
77use fast_glob:: glob_match;
88use glob:: glob;
9- use lazy_static:: lazy_static;
10- use regex:: Regex ;
119
1210use crate :: { config:: Config , project:: Team } ;
1311
@@ -106,7 +104,7 @@ pub fn find_file_owners(project_root: &Path, config: &Config, file_path: &Path)
106104}
107105
108106fn build_teams_by_name_map ( teams : & [ Team ] ) -> HashMap < String , Team > {
109- let mut map = HashMap :: new ( ) ;
107+ let mut map = HashMap :: with_capacity ( teams . len ( ) * 2 ) ;
110108 for team in teams {
111109 map. insert ( team. name . clone ( ) , team. clone ( ) ) ;
112110 map. insert ( team. github_team . clone ( ) , team. clone ( ) ) ;
@@ -117,7 +115,7 @@ fn build_teams_by_name_map(teams: &[Team]) -> HashMap<String, Team> {
117115fn load_teams ( project_root : & Path , team_file_globs : & [ String ] ) -> std:: result:: Result < Vec < Team > , String > {
118116 let mut teams: Vec < Team > = Vec :: new ( ) ;
119117 for glob_str in team_file_globs {
120- let absolute_glob = format ! ( "{}/{}" , project_root. display ( ) , glob_str) ;
118+ let absolute_glob = project_root. join ( glob_str) . to_string_lossy ( ) . into_owned ( ) ;
121119 let paths = glob ( & absolute_glob) . map_err ( |e| e. to_string ( ) ) ?;
122120 for path in paths. flatten ( ) {
123121 match Team :: from_team_file_path ( path. clone ( ) ) {
@@ -132,30 +130,48 @@ fn load_teams(project_root: &Path, team_file_globs: &[String]) -> std::result::R
132130 Ok ( teams)
133131}
134132
135- lazy_static ! {
136- // Allow optional leading whitespace before the comment marker
137- static ref TOP_OF_FILE_TEAM_AT_REGEX : Option <Regex > = Regex :: new( r#"^\s*(?:#|//)\s*@team\s+(.+)$"# ) . ok( ) ;
138- static ref TOP_OF_FILE_TEAM_COLON_REGEX : Option <Regex > = Regex :: new( r#"(?i)^\s*(?:#|//)\s*team\s*:\s*(.+)$"# ) . ok( ) ;
139- }
133+ // no regex: parse cheaply with ASCII-aware checks
140134
141135fn read_top_of_file_team ( path : & Path ) -> Option < String > {
142- let content = fs:: read_to_string ( path) . ok ( ) ?;
143- let line = content. lines ( ) . next ( ) ?;
136+ use std:: io:: { BufRead , BufReader } ;
137+
138+ let file = std:: fs:: File :: open ( path) . ok ( ) ?;
139+ let mut reader = BufReader :: new ( file) ;
140+ let mut line = String :: new ( ) ;
141+ reader. read_line ( & mut line) . ok ( ) ?;
142+
143+ // trim trailing newlines
144+ let mut s: & str = line. trim_end_matches ( [ '\r' , '\n' ] ) ;
145+ // allow optional leading whitespace
146+ s = s. trim_start ( ) ;
147+
148+ // strip comment leader
149+ if let Some ( rest) = s. strip_prefix ( "//" ) {
150+ s = rest;
151+ } else if let Some ( rest) = s. strip_prefix ( '#' ) {
152+ s = rest;
153+ } else {
154+ return None ;
155+ }
144156
145- if let Some ( re ) = & * TOP_OF_FILE_TEAM_AT_REGEX {
146- if let Some ( cap ) = re . captures ( line ) {
147- if let Some ( m ) = cap . get ( 1 ) {
148- return Some ( m . as_str ( ) . to_string ( ) ) ;
149- }
150- }
157+ // optional spaces after comment leader
158+ s = s . trim_start ( ) ;
159+
160+ // @team <Name>
161+ if let Some ( rest ) = s . strip_prefix ( "@team" ) {
162+ return Some ( rest . trim_start ( ) . to_string ( ) ) ;
151163 }
152- if let Some ( re) = & * TOP_OF_FILE_TEAM_COLON_REGEX {
153- if let Some ( cap) = re. captures ( line) {
154- if let Some ( m) = cap. get ( 1 ) {
155- return Some ( m. as_str ( ) . to_string ( ) ) ;
156- }
164+
165+ // team: <Name> (case-insensitive label, optional spaces around ':')
166+ let prefix = "team" ;
167+ if s. len ( ) >= prefix. len ( ) && s[ ..prefix. len ( ) ] . eq_ignore_ascii_case ( prefix) {
168+ let mut rest = & s[ prefix. len ( ) ..] ;
169+ rest = rest. trim_start ( ) ;
170+ if let Some ( after_colon) = rest. strip_prefix ( ':' ) {
171+ return Some ( after_colon. trim_start ( ) . to_string ( ) ) ;
157172 }
158173 }
174+
159175 None
160176}
161177
@@ -167,34 +183,32 @@ fn most_specific_directory_owner(
167183 let mut current = project_root. join ( relative_file_path) ;
168184 let mut best: Option < ( String , Source ) > = None ;
169185 loop {
170- let parent_opt = current. parent ( ) . map ( |p| p. to_path_buf ( ) ) ;
171- let Some ( parent) = parent_opt else { break } ;
172- let codeowner_path = parent. join ( ".codeowner" ) ;
186+ if !current. pop ( ) {
187+ break ;
188+ }
189+ let codeowner_path = current. join ( ".codeowner" ) ;
173190 if let Ok ( owner_str) = fs:: read_to_string ( & codeowner_path) {
174191 let owner = owner_str. trim ( ) ;
175192 if let Some ( team) = teams_by_name. get ( owner) {
176- let relative_dir = parent
193+ let relative_dir = current
177194 . strip_prefix ( project_root)
178- . unwrap_or ( parent . as_path ( ) )
195+ . unwrap_or ( current . as_path ( ) )
179196 . to_string_lossy ( )
180197 . to_string ( ) ;
181198 let candidate = ( team. name . clone ( ) , Source :: Directory ( relative_dir) ) ;
182199 match & best {
183200 None => best = Some ( candidate) ,
184201 Some ( ( _, existing_source) ) => {
185- let existing_len = source_directory_depth ( existing_source) ;
186- let candidate_len = source_directory_depth ( & candidate. 1 ) ;
187- if candidate_len > existing_len {
202+ if candidate. 1 . len ( ) > existing_source. len ( ) {
188203 best = Some ( candidate) ;
189204 }
190205 }
191206 }
192207 }
193208 }
194- if parent == project_root {
209+ if current == project_root {
195210 break ;
196211 }
197- current = parent. clone ( ) ;
198212 }
199213 best
200214}
@@ -207,17 +221,18 @@ fn nearest_package_owner(
207221) -> Option < ( String , Source ) > {
208222 let mut current = project_root. join ( relative_file_path) ;
209223 loop {
210- let parent_opt = current. parent ( ) . map ( |p| p. to_path_buf ( ) ) ;
211- let Some ( parent) = parent_opt else { break } ;
212- let parent_rel = parent. strip_prefix ( project_root) . unwrap_or ( parent. as_path ( ) ) ;
224+ if !current. pop ( ) {
225+ break ;
226+ }
227+ let parent_rel = current. strip_prefix ( project_root) . unwrap_or ( current. as_path ( ) ) ;
213228 if let Some ( rel_str) = parent_rel. to_str ( ) {
214229 if glob_list_matches ( rel_str, & config. ruby_package_paths ) {
215- let pkg_yml = parent . join ( "package.yml" ) ;
230+ let pkg_yml = current . join ( "package.yml" ) ;
216231 if pkg_yml. exists ( ) {
217232 if let Ok ( owner) = read_ruby_package_owner ( & pkg_yml) {
218233 if let Some ( team) = teams_by_name. get ( & owner) {
219234 let package_path = parent_rel. join ( "package.yml" ) ;
220- let package_glob = format ! ( "{}/**/**" , rel_str ) ;
235+ let package_glob = format ! ( "{rel_str }/**/**" ) ;
221236 return Some ( (
222237 team. name . clone ( ) ,
223238 Source :: Package ( package_path. to_string_lossy ( ) . to_string ( ) , package_glob) ,
@@ -227,12 +242,12 @@ fn nearest_package_owner(
227242 }
228243 }
229244 if glob_list_matches ( rel_str, & config. javascript_package_paths ) {
230- let pkg_json = parent . join ( "package.json" ) ;
245+ let pkg_json = current . join ( "package.json" ) ;
231246 if pkg_json. exists ( ) {
232247 if let Ok ( owner) = read_js_package_owner ( & pkg_json) {
233248 if let Some ( team) = teams_by_name. get ( & owner) {
234249 let package_path = parent_rel. join ( "package.json" ) ;
235- let package_glob = format ! ( "{}/**/**" , rel_str ) ;
250+ let package_glob = format ! ( "{rel_str }/**/**" ) ;
236251 return Some ( (
237252 team. name . clone ( ) ,
238253 Source :: Package ( package_path. to_string_lossy ( ) . to_string ( ) , package_glob) ,
@@ -242,20 +257,14 @@ fn nearest_package_owner(
242257 }
243258 }
244259 }
245- if parent == project_root {
260+ if current == project_root {
246261 break ;
247262 }
248- current = parent;
249263 }
250264 None
251265}
252266
253- fn source_directory_depth ( source : & Source ) -> usize {
254- match source {
255- Source :: Directory ( path) => path. matches ( '/' ) . count ( ) ,
256- _ => 0 ,
257- }
258- }
267+ // removed: use `Source::len()` instead
259268
260269fn glob_list_matches ( path : & str , globs : & [ String ] ) -> bool {
261270 globs. iter ( ) . any ( |g| glob_match ( g, path) )
@@ -401,20 +410,12 @@ mod tests {
401410 // Ruby package
402411 let ruby_pkg = project_root. join ( "packs/payroll" ) ;
403412 std:: fs:: create_dir_all ( & ruby_pkg) . unwrap ( ) ;
404- std:: fs:: write (
405- ruby_pkg. join ( "package.yml" ) ,
406- "---\n owner: Payroll\n " ,
407- )
408- . unwrap ( ) ;
413+ std:: fs:: write ( ruby_pkg. join ( "package.yml" ) , "---\n owner: Payroll\n " ) . unwrap ( ) ;
409414
410415 // JS package
411416 let js_pkg = project_root. join ( "frontend/flow" ) ;
412417 std:: fs:: create_dir_all ( & js_pkg) . unwrap ( ) ;
413- std:: fs:: write (
414- js_pkg. join ( "package.json" ) ,
415- r#"{"metadata": {"owner": "UX"}}"# ,
416- )
417- . unwrap ( ) ;
418+ std:: fs:: write ( js_pkg. join ( "package.json" ) , r#"{"metadata": {"owner": "UX"}}"# ) . unwrap ( ) ;
418419
419420 // Teams map
420421 let mut tbn: HashMap < String , Team > = HashMap :: new ( ) ;
0 commit comments