1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! # codeowners-parser
//!
//! Parse, match against, and validate GitHub CODEOWNERS files — with the
//! position information editor tooling needs.
//!
//! This crate exists because the Rust ecosystem's original `codeowners` crate
//! was abandoned in 2018 with two open RUSTSEC advisories in its transitive
//! deps, and nothing public replaced it with LSP-grade features. This one is
//! extracted from [`codeowners-lsp`](https://github.com/radiosilence/codeowners-lsp),
//! where it has been hardened against real-world CODEOWNERS files from large
//! monorepos.
//!
//! ## What it does
//!
//! - **Parse** CODEOWNERS into structured lines, with optional character
//! offsets for every pattern, owner, and inline comment — exactly what an
//! LSP needs for hover, rename, and go-to-definition.
//! - **Match** file paths against rules using GitHub's glob semantics
//! (`*`, `**`, `?`, anchored `/prefix`, trailing-slash directories).
//! The matcher compiles each pattern once and picks a specialized
//! implementation based on shape — `*.rs` becomes a raw `ends_with` check,
//! not a regex.
//! - **Resolve ownership** for a path following "last match wins" semantics.
//! Pre-parse once for hot loops via [`check_file_ownership_parsed`].
//! - **Detect dead rules** via [`pattern_subsumes`] — used by linters to find
//! patterns that will never match because a later rule shadows them.
//! - **Validate** owner format (`@user`, `@org/team`, `email@host`) and glob
//! syntax — with the specific subset of globs CODEOWNERS actually supports
//! (no `[...]` character classes, no `!` negation).
//! - **Locate** the CODEOWNERS file in a repo (`.github/CODEOWNERS`,
//! `CODEOWNERS`, or `docs/CODEOWNERS`) via [`find_codeowners`].
//!
//! ## What it doesn't do
//!
//! - No file-system enumeration. Matchers take a `file_path: &str`; callers
//! supply paths however they like (walkdir, git ls-files, HTTP API, etc.).
//! - No GitHub API calls. Validation is syntactic — `@ghost` is accepted
//! even though GitHub would reject it.
//! - No diagnostic rendering. You get `Option<String>` from validators and
//! structured results from matchers; turn those into whatever diagnostic
//! format your tool needs.
//!
//! ## Quick start
//!
//! ```
//! use codeowners_parser::{check_file_ownership, validate_owner, validate_pattern};
//!
//! let codeowners = "\
//! # Default owners
//! * @core-team
//! /docs/ @docs-team
//! *.rs @rust-team @reviewer
//! ";
//!
//! // Who owns a file?
//! let result = check_file_ownership(codeowners, "src/lib.rs").unwrap();
//! assert_eq!(result.pattern, "*.rs");
//! assert_eq!(result.owners, vec!["@rust-team", "@reviewer"]);
//!
//! // Validate owner syntax
//! assert!(validate_owner("@rust-team").is_none());
//! assert!(validate_owner("@rust_team").is_some()); // underscore not allowed
//! assert!(validate_owner("user@example.com").is_none());
//!
//! // Validate glob syntax
//! assert!(validate_pattern("*.rs").is_none());
//! assert!(validate_pattern("").is_some());
//! ```
//!
//! ## Parsing with positions (for editor tooling)
//!
//! ```
//! use codeowners_parser::parser::{parse_codeowners_file_with_positions, CodeownersLine};
//!
//! let lines = parse_codeowners_file_with_positions("*.rs @owner\n");
//! let line = &lines[0];
//!
//! assert_eq!(line.line_number, 0);
//! assert_eq!(line.pattern_start, 0);
//! assert_eq!(line.pattern_end, 4); // points just past "*.rs"
//! assert_eq!(line.owners_start, 5); // points at "@owner"
//!
//! if let CodeownersLine::Rule { pattern, owners } = &line.content {
//! assert_eq!(pattern, "*.rs");
//! assert_eq!(owners, &vec!["@owner".to_string()]);
//! }
//! ```
//!
//! ## Hot-loop ownership checks
//!
//! When you need to check many paths against the same CODEOWNERS content,
//! parse once with [`parser::parse_codeowners_file_with_positions`] and reuse
//! the result, rather than paying to re-parse per path.
//!
//! [`check_file_ownership_parsed`] still classifies each pattern on every
//! call. If that shows up in a profile, hoist the work yourself by compiling
//! the patterns once into [`pattern::CompiledPattern`] and matching against
//! those.
//!
//! ```
//! use codeowners_parser::{check_file_ownership_parsed, parser::parse_codeowners_file_with_positions};
//!
//! let parsed = parse_codeowners_file_with_positions(
//! "*.rs @rust\n*.md @docs\n/src/ @core\n",
//! );
//!
//! for path in ["src/main.rs", "README.md", "src/lib.rs"] {
//! if let Some(result) = check_file_ownership_parsed(&parsed, path) {
//! println!("{}: {} (from `{}`)", path, result.owners.join(" "), result.pattern);
//! }
//! }
//! ```
//!
//! ## Detecting dead rules
//!
//! Linters can spot patterns that are shadowed by later rules. The call
//! is `pattern_subsumes(a, b)` — "is everything `a` matches also matched
//! by `b`?". If a later rule subsumes an earlier one, the earlier rule is
//! dead code (last match wins).
//!
//! ```
//! use codeowners_parser::pattern_subsumes;
//!
//! // `*` subsumes everything — any rule before a final `*` is dead.
//! assert!(pattern_subsumes("*.rs", "*"));
//! assert!(pattern_subsumes("/src/", "*"));
//!
//! // `/src/lib/` is subsumed by `/src/` (parent directory contains child).
//! assert!(pattern_subsumes("/src/lib/", "/src/"));
//!
//! // Anchored dir is subsumed by the unanchored version (unanchored matches more).
//! assert!(pattern_subsumes("/docs/", "docs/"));
//! assert!(!pattern_subsumes("docs/", "/docs/"));
//! ```
//!
//! ## Design notes
//!
//! - **Zero-copy is not a goal.** We return `String` / `Vec<String>` for
//! owners and patterns. The alternative (`&str` tied to input lifetime)
//! is hostile for interactive tooling that edits CODEOWNERS content.
//! - **Patterns are not compiled behind your back.** The matching helpers
//! classify the pattern string each time they are called. For repeated
//! matching, build a [`pattern::CompiledPattern::new`] once and keep it.
//! - **Glob semantics match `git check-ignore` closely**, not gitignore's
//! full spec. CODEOWNERS is a subset: no `[...]` character classes,
//! no `!` negation. Patterns containing `[` or `!` at the start are
//! rejected by [`validate_pattern`].
pub use ;
pub use ;
pub use ;
pub use ;