codeowners-parser 0.1.0

Parser, matcher, and validator for GitHub CODEOWNERS files. Position-aware parsing for editor tooling, fast glob matching, and rule-subsumption analysis for dead-code detection.
Documentation
  • Coverage
  • 100%
    49 out of 49 items documented3 out of 23 items with examples
  • Size
  • Source code size: 108.6 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 778.0 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 6s Average build duration of successful builds.
  • all releases: 6s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • radiosilence/codeowners-lsp
    2 2 4
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • radiosilence

codeowners-parser

Crates.io Docs.rs MIT licensed

Parse, match against, and validate GitHub CODEOWNERS files — with the position information editor tooling needs.

Why this crate

The Rust ecosystem's original codeowners crate was abandoned in 2018 with two open RUSTSEC advisories in its transitive dependencies (RUSTSEC-2022-0013, RUSTSEC-2022-0006) and no LSP-grade features. This crate is extracted from 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, go-to-definition, and semantic tokens.
  • Match file paths against rules using GitHub's glob semantics (*, **, ?, anchored /prefix, trailing-slash directories). Each pattern is compiled once and dispatches to a specialized matcher based on shape — *.rs becomes a raw ends_with on bytes, 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).

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

[dependencies]
codeowners-parser = "0.1"
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());

Position-aware parsing (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);  // just past "*.rs"
assert_eq!(line.owners_start, 5); // 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 and reuse:

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

pattern_subsumes(a, b) — is everything a matches also matched by b? If yes, and b comes after a in the file, a is dead code.

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. Owners and patterns are returned as String / Vec<String>. The alternative (&str tied to the input) is hostile for interactive tooling that edits CODEOWNERS content.
  • Pattern compilation caches per call. If you need pattern reuse across paths, hold a CompiledPattern yourself via CompiledPattern::new.
  • Glob semantics match git check-ignore closely, not gitignore's full spec. CODEOWNERS is a subset: no [...] character classes, no ! negation. validate_pattern rejects those.

Feature flags

None. The crate has four runtime dependencies: fast-glob, glob, once_cell, and regex — all common and well-maintained.

Minimum supported Rust version

Rust 1.70 (the current stable-minus-a-while). Bumping MSRV is a minor-version change; we will not bump it casually.

Related

  • codeowners-lsp — the language server this was extracted from. Uses this crate plus a GitHub client, diagnostic engine, file cache, and the full LSP plumbing.
  • codeowners-cli — the CLI, shipped alongside the LSP.

License

MIT