Skip to content

Commit c209281

Browse files
authored
perf(fmt): move gitignore matching to Rust (#318)
1 parent 3165f64 commit c209281

10 files changed

Lines changed: 590 additions & 131 deletions

File tree

crates/rstack-binding/src/lib.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
use std::path::Path;
44

5-
use napi::Error;
5+
use napi::{bindgen_prelude::Uint8Array, Error, Status};
66
use napi_derive::napi;
7-
use rstack_ignore::{IgnoreMatcher as CoreIgnoreMatcher, IgnoreSource as CoreIgnoreSource};
7+
use rstack_ignore::{
8+
GitIgnoreMatcher as CoreGitIgnoreMatcher, IgnoreMatcher as CoreIgnoreMatcher,
9+
IgnoreSource as CoreIgnoreSource,
10+
};
811

912
/// A Gitignore-compatible pattern source received from JavaScript.
1013
#[napi(object, object_to_js = false)]
@@ -42,3 +45,95 @@ impl IgnoreMatcher {
4245
self.inner.is_ignored(Path::new(&file_path), is_directory)
4346
}
4447
}
48+
49+
/// JavaScript-facing hierarchy for repository `.gitignore` files.
50+
#[napi]
51+
pub struct GitIgnoreMatcher {
52+
inner: CoreGitIgnoreMatcher,
53+
}
54+
55+
impl Default for GitIgnoreMatcher {
56+
fn default() -> Self {
57+
Self {
58+
inner: CoreGitIgnoreMatcher::new(),
59+
}
60+
}
61+
}
62+
63+
#[napi]
64+
impl GitIgnoreMatcher {
65+
/// Creates an empty matcher whose sources can be added during directory traversal.
66+
#[napi(constructor)]
67+
pub fn new() -> Self {
68+
Self::default()
69+
}
70+
71+
/// Compiles or replaces rules rooted at a repository-relative POSIX directory.
72+
#[napi]
73+
pub fn add_source(&mut self, relative_root: String, patterns: String) -> napi::Result<bool> {
74+
self.inner
75+
.add_source(&relative_root, &patterns)
76+
.map_err(|error| {
77+
Error::from_reason(format!("Failed to compile .gitignore patterns: {error}"))
78+
})
79+
}
80+
81+
/// Returns whether one repository-relative POSIX path is ignored.
82+
#[napi]
83+
pub fn is_ignored(&mut self, relative_path: String, is_directory: bool) -> bool {
84+
self.inner.is_ignored(&relative_path, is_directory)
85+
}
86+
87+
/// Matches one directory's entries in a native call and returns one byte per name.
88+
#[napi]
89+
pub fn is_ignored_batch(
90+
&mut self,
91+
relative_parent: String,
92+
names: Vec<String>,
93+
directory_flags: Uint8Array,
94+
) -> napi::Result<Uint8Array> {
95+
if names.len() != directory_flags.len() {
96+
return Err(Error::new(
97+
Status::InvalidArg,
98+
"Name and directory flag counts must match.",
99+
));
100+
}
101+
102+
Ok(self
103+
.inner
104+
.is_ignored_batch(&relative_parent, &names, directory_flags.as_ref())
105+
.into())
106+
}
107+
108+
/// Matches up to 32 entries while avoiding per-directory typed-array allocation.
109+
#[napi]
110+
pub fn is_ignored_batch_mask(
111+
&mut self,
112+
relative_parent: String,
113+
names: Vec<String>,
114+
directory_mask: u32,
115+
) -> napi::Result<u32> {
116+
if names.len() > u32::BITS as usize {
117+
return Err(Error::new(
118+
Status::InvalidArg,
119+
"A bit-mask batch cannot contain more than 32 names.",
120+
));
121+
}
122+
123+
Ok(self
124+
.inner
125+
.is_ignored_batch_mask(&relative_parent, &names, directory_mask))
126+
}
127+
128+
/// Matches a single directory entry without constructing a JavaScript array.
129+
#[napi]
130+
pub fn is_ignored_child(
131+
&mut self,
132+
relative_parent: String,
133+
name: String,
134+
is_directory: bool,
135+
) -> bool {
136+
self.inner
137+
.is_ignored_child(&relative_parent, &name, is_directory)
138+
}
139+
}

0 commit comments

Comments
 (0)