Skip to content

Commit f0faee7

Browse files
committed
wip(core): better lookahead for tags and tests
1 parent 45bf8a6 commit f0faee7

File tree

2 files changed

+142
-2
lines changed

2 files changed

+142
-2
lines changed

core/src/commands.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::common::find_files;
12
use crate::types::OutputFormat;
23

34
use utils::app_config::AppConfig;
@@ -31,6 +32,10 @@ pub fn codeowners_parse(
3132

3233
dbg!(&parsed_codeowners);
3334

35+
let files = find_files(path)?;
36+
37+
//dbg!(&files);
38+
3439
println!("CODEOWNERS parsing completed successfully");
3540
Ok(())
3641
}

core/src/common.rs

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ pub fn find_codeowners_files<P: AsRef<Path>>(base_path: P) -> Result<Vec<PathBuf
2626
Ok(result)
2727
}
2828

29+
// Find all files in the given directory and its subdirectories
30+
pub fn find_files<P: AsRef<Path>>(base_path: P) -> Result<Vec<PathBuf>> {
31+
let mut result = Vec::new();
32+
if let Ok(entries) = std::fs::read_dir(base_path) {
33+
for entry in entries.flatten() {
34+
let path = entry.path();
35+
if path.is_file() {
36+
result.push(path);
37+
} else if path.is_dir() {
38+
result.extend(find_files(path)?);
39+
}
40+
}
41+
}
42+
Ok(result)
43+
}
44+
2945
/// Parse CODEOWNERS
3046
pub fn parse_codeowners(source_path: &Path) -> Result<Vec<CodeownersEntry>> {
3147
let content = std::fs::read_to_string(source_path)?;
@@ -65,21 +81,27 @@ fn parse_line(line: &str, line_num: usize, source_path: &Path) -> Result<Option<
6581
i += 1;
6682
}
6783

68-
// Collect tags
84+
// Collect tags with lookahead to check for comments
6985
while i < tokens.len() {
7086
let token = tokens[i];
7187
if token.starts_with('#') {
7288
if token == "#" {
7389
// Comment starts, break
7490
break;
7591
} else {
92+
// Check if the next token is not a tag (doesn't start with '#')
93+
let next_is_non_tag = i + 1 < tokens.len() && !tokens[i + 1].starts_with('#');
94+
if next_is_non_tag {
95+
// This token is part of the comment, break
96+
break;
97+
}
7698
tags.push(Tag(token[1..].to_string()));
99+
i += 1;
77100
}
78101
} else {
79102
// Non-tag, part of comment
80103
break;
81104
}
82-
i += 1;
83105
}
84106

85107
Ok(Some(CodeownersEntry {
@@ -321,4 +343,117 @@ mod tests {
321343

322344
Ok(())
323345
}
346+
347+
#[test]
348+
fn test_parse_line_pattern_with_owners() -> Result<()> {
349+
let source_path = Path::new("/test/CODEOWNERS");
350+
let result = parse_line("*.js @qa-team @bob #test", 1, source_path)?;
351+
352+
assert!(result.is_some());
353+
let entry = result.unwrap();
354+
assert_eq!(entry.pattern, "*.js");
355+
assert_eq!(entry.owners.len(), 2);
356+
assert_eq!(entry.owners[0].identifier, "@qa-team");
357+
assert_eq!(entry.owners[1].identifier, "@bob");
358+
assert_eq!(entry.tags.len(), 1);
359+
assert_eq!(entry.tags[0].0, "test");
360+
assert_eq!(entry.line_number, 1);
361+
assert_eq!(entry.source_file, source_path);
362+
363+
Ok(())
364+
}
365+
366+
#[test]
367+
fn test_parse_line_with_path_pattern() -> Result<()> {
368+
let source_path = Path::new("/test/CODEOWNERS");
369+
let result = parse_line("/fixtures/ @alice @dave", 2, source_path)?;
370+
371+
assert!(result.is_some());
372+
let entry = result.unwrap();
373+
assert_eq!(entry.pattern, "/fixtures/");
374+
assert_eq!(entry.owners.len(), 2);
375+
assert_eq!(entry.owners[0].identifier, "@alice");
376+
assert_eq!(entry.owners[1].identifier, "@dave");
377+
assert_eq!(entry.tags.len(), 0);
378+
379+
Ok(())
380+
}
381+
382+
#[test]
383+
fn test_parse_line_comment() -> Result<()> {
384+
let source_path = Path::new("/test/CODEOWNERS");
385+
let result = parse_line("# this is a comment line", 3, source_path)?;
386+
387+
assert!(result.is_none());
388+
389+
Ok(())
390+
}
391+
392+
#[test]
393+
fn test_parse_line_with_multiple_tags_and_comment() -> Result<()> {
394+
let source_path = Path::new("/test/CODEOWNERS");
395+
let result = parse_line(
396+
"/hooks.ts @org/frontend #test #core # this is a comment",
397+
4,
398+
source_path,
399+
)?;
400+
401+
assert!(result.is_some());
402+
let entry = result.unwrap();
403+
assert_eq!(entry.pattern, "/hooks.ts");
404+
assert_eq!(entry.owners.len(), 1);
405+
assert_eq!(entry.owners[0].identifier, "@org/frontend");
406+
assert_eq!(entry.tags.len(), 2);
407+
assert_eq!(entry.tags[0].0, "test");
408+
assert_eq!(entry.tags[1].0, "core");
409+
410+
Ok(())
411+
}
412+
413+
#[test]
414+
fn test_parse_line_empty() -> Result<()> {
415+
let source_path = Path::new("/test/CODEOWNERS");
416+
let result = parse_line("", 5, source_path)?;
417+
418+
assert!(result.is_none());
419+
420+
let result = parse_line(" ", 6, source_path)?;
421+
assert!(result.is_none());
422+
423+
Ok(())
424+
}
425+
426+
#[test]
427+
fn test_parse_line_security_tag() -> Result<()> {
428+
let source_path = Path::new("/test/.husky/CODEOWNERS");
429+
let result = parse_line("pre-commit @org/security @frank #security", 2, source_path)?;
430+
431+
assert!(result.is_some());
432+
let entry = result.unwrap();
433+
assert_eq!(entry.pattern, "pre-commit");
434+
assert_eq!(entry.owners.len(), 2);
435+
assert_eq!(entry.owners[0].identifier, "@org/security");
436+
assert_eq!(entry.owners[1].identifier, "@frank");
437+
assert_eq!(entry.tags.len(), 1);
438+
assert_eq!(entry.tags[0].0, "security");
439+
440+
Ok(())
441+
}
442+
443+
#[test]
444+
fn test_parse_line_with_pound_tag_edge_case() -> Result<()> {
445+
let source_path = Path::new("/test/CODEOWNERS");
446+
447+
// Test edge case where # is followed by a space (comment marker)
448+
let result = parse_line("*.md @docs-team #not a tag", 7, source_path)?;
449+
450+
assert!(result.is_some());
451+
let entry = result.unwrap();
452+
assert_eq!(entry.pattern, "*.md");
453+
assert_eq!(entry.owners.len(), 1);
454+
assert_eq!(entry.owners[0].identifier, "@docs-team");
455+
assert_eq!(entry.tags.len(), 0); // No tags, just a comment
456+
457+
Ok(())
458+
}
324459
}

0 commit comments

Comments
 (0)