From 18b22ae2164ceacddaf20f8311973e8595de4e7a Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:57:18 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`feat/ca?= =?UTF-8?q?rbon-dev`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @CoderSerio. * https://github.com/CoderSerio/hyper-fs/pull/2#issuecomment-3626534299 The following files were modified: * `benchmark/bench.ts` * `benchmark/rm.ts` * `src/readdir.rs` * `src/rm.rs` --- benchmark/bench.ts | 7 +++- benchmark/rm.ts | 34 ++++++++++++++- src/readdir.rs | 27 +++++++++++- src/rm.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 165 insertions(+), 4 deletions(-) diff --git a/benchmark/bench.ts b/benchmark/bench.ts index f222877..c4ecb3e 100644 --- a/benchmark/bench.ts +++ b/benchmark/bench.ts @@ -4,6 +4,11 @@ import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) +/** + * Discovers and runs benchmark files in the current directory, optionally filtered by the first command-line argument. + * + * Selects files ending with `.ts` (excluding `bench.ts` and declaration files ending with `.d.ts`). If a filter string is provided as the first CLI argument, only files whose names include that substring (case-insensitive) are selected. Logs a message and exits if no files match, logs the number of files found, and imports each matched file to execute its benchmark; errors importing individual files are logged. + */ async function runBenchmarks() { const args = process.argv.slice(2) const filter = args[0] @@ -38,4 +43,4 @@ async function runBenchmarks() { } } -runBenchmarks() +runBenchmarks() \ No newline at end of file diff --git a/benchmark/rm.ts b/benchmark/rm.ts index 7251394..0253116 100644 --- a/benchmark/rm.ts +++ b/benchmark/rm.ts @@ -12,6 +12,15 @@ if (fs.existsSync(baseDir)) { } fs.mkdirSync(baseDir) +/** + * Create a flat directory containing a specified number of files. + * + * Ensures the target directory exists and writes `count` files named + * `file-0.txt` through `file-(count-1).txt`, each containing the string "content". + * + * @param dir - The directory path where files will be created + * @param count - The number of files to create (non-negative integer) + */ function createFlatStructure(dir: string, count: number) { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) for (let i = 0; i < count; i++) { @@ -19,6 +28,15 @@ function createFlatStructure(dir: string, count: number) { } } +/** + * Creates a chain of nested subdirectories under the given root and places a file in each level. + * + * Ensures the root directory exists, then creates `depth` nested directories named `depth-0`, `depth-1`, ..., + * and writes a file named `file.txt` containing "content" into each created subdirectory. + * + * @param dir - The root directory under which the nested structure will be created + * @param depth - The number of nested subdirectory levels to create + */ function createDeepStructure(dir: string, depth: number) { let current = dir if (!fs.existsSync(current)) fs.mkdirSync(current, { recursive: true }) @@ -29,6 +47,15 @@ function createDeepStructure(dir: string, depth: number) { } } +/** + * Runs a benchmark group that compares multiple rmSync implementations and prints a Mitata-like comparison table. + * + * Executes a warmup run then performs 10 timed iterations per implementation using the provided setup function to create + * each test directory (setup time is excluded from measurements). For each implementation it computes the average time + * in milliseconds and prints each implementation's average alongside a ratio compared to the first (baseline). + * + * @param setupFn - A function that creates the test directory structure at the given path before removal + */ async function runGroup(groupName: string, setupFn: (dir: string) => void) { console.log(`\n${groupName}`) @@ -84,6 +111,11 @@ async function runGroup(groupName: string, setupFn: (dir: string) => void) { }) } +/** + * Execute the benchmark suite for the two test scenarios and remove temporary data. + * + * Runs the "Flat directory (2000 files)" and "Deep nested directory (depth 100)" benchmark groups sequentially, then deletes the temporary base directory if it exists. + */ async function run() { await runGroup('Flat directory (2000 files)', (dir) => createFlatStructure(dir, 2000)) await runGroup('Deep nested directory (depth 100)', (dir) => createDeepStructure(dir, 100)) @@ -94,4 +126,4 @@ async function run() { } } -run() +run() \ No newline at end of file diff --git a/src/readdir.rs b/src/readdir.rs index 0089e15..332214e 100644 --- a/src/readdir.rs +++ b/src/readdir.rs @@ -39,6 +39,31 @@ pub struct Dirent { } // #[napi] // marco: expose the function to Node +/// Read directory entries from `path_str` according to the provided `options`. +/// +/// The function performs a non-recursive or recursive directory listing based on `options.recursive`. +/// Hidden entries may be skipped when `options.skip_hidden` is true. When `options.with_file_types` +/// is true the results include `Dirent` objects with `name`, `parent_path`, and `is_dir`; otherwise +/// the results are plain file path strings (non-recursive: entry names; recursive: paths relative +/// to the provided root). +/// +/// # Returns +/// +/// `Ok(Either::A(Vec))` with entry names or relative paths when `with_file_types` is false, +/// or `Ok(Either::B(Vec))` with `Dirent` objects when `with_file_types` is true. Returns +/// `Err` when the path does not exist or an underlying IO error occurs (error message contains +/// the underlying reason). +/// +/// # Examples +/// +/// ``` +/// // Non-recursive list of names +/// let res = ls(".".to_string(), None).unwrap(); +/// match res { +/// Either::A(names) => println!("names: {:?}", names), +/// Either::B(dirents) => println!("dirents: {:?}", dirents), +/// } +/// ``` fn ls( path_str: String, options: Option, @@ -184,4 +209,4 @@ impl Task for ReaddirTask { #[napi(js_name = "readdir")] pub fn readdir(path: String, options: Option) -> AsyncTask { AsyncTask::new(ReaddirTask { path, options }) -} +} \ No newline at end of file diff --git a/src/rm.rs b/src/rm.rs index c938344..c4373dc 100644 --- a/src/rm.rs +++ b/src/rm.rs @@ -30,6 +30,43 @@ pub struct RmOptions { pub concurrency: Option, } +/// Recursively removes the file or directory at `path` according to `opts`. +/// +/// If `path` is a directory and `opts.recursive` is `true`, the directory's +/// contents are removed first (optionally in parallel when `opts.concurrency` +/// is greater than 1) and then the directory itself is removed. If the path +/// is a directory and `opts.recursive` is `false`, the function attempts to +/// remove the directory and maps "directory not empty" conditions to an +/// `ENOTEMPTY`-style error message. If the path is not a directory, the file +/// is removed. +/// +/// # Parameters +/// +/// - `path`: filesystem path to remove. +/// - `opts`: removal options; `recursive` controls directory recursion and +/// `concurrency` (when > 1) enables parallel traversal. +/// +/// # Returns +/// +/// `Ok(())` on successful removal, or an `napi::Error` created from the +/// underlying I/O error on failure. +/// +/// # Examples +/// +/// ```no_run +/// use std::path::Path; +/// +/// let opts = RmOptions { +/// force: None, +/// max_retries: None, +/// recursive: Some(true), +/// retry_delay: None, +/// concurrency: None, +/// }; +/// +/// // Remove the current directory contents (for demonstration; be careful) +/// let _ = remove_recursive(Path::new("."), &opts).unwrap(); +/// ``` fn remove_recursive(path: &Path, opts: &RmOptions) -> Result<()> { let meta = fs::symlink_metadata(path).map_err(|e| Error::from_reason(e.to_string()))?; @@ -72,6 +109,31 @@ fn remove_recursive(path: &Path, opts: &RmOptions) -> Result<()> { Ok(()) } +/// Remove the filesystem entry at `path_str` using the provided rm-style options. +/// +/// The empty string for `path_str` is treated as `"."`. When `options` is `None`, +/// defaults are used (force = false, recursive = false, other fields unset). +/// If `options.force` is true and the path does not exist, the call succeeds silently. +/// +/// # Parameters +/// +/// - `path_str` — Path to remove; `""` is interpreted as the current directory (`"."`). +/// - `options` — Optional `RmOptions` controlling behavior (e.g. `force`, `recursive`, `concurrency`). +/// +/// # Returns +/// +/// `Ok(())` on successful removal, or an `Err` containing a `napi::Error` describing the failure. +/// +/// # Examples +/// +/// ``` +/// // remove a single file (non-recursive) +/// let _ = remove("tmp/file.txt".to_string(), None); +/// +/// // remove or ignore missing path +/// let opts = RmOptions { force: Some(true), recursive: Some(false), max_retries: None, retry_delay: None, concurrency: None }; +/// let _ = remove("tmp/missing".to_string(), Some(opts)); +/// ``` fn remove(path_str: String, options: Option) -> Result<()> { let search_path_str = if path_str.is_empty() { "." } else { &path_str }; let path = Path::new(search_path_str); @@ -110,21 +172,58 @@ impl Task for RmTask { type Output = (); type JsValue = (); + /// Performs the removal operation described by this task. + /// + /// # Examples + /// + /// ```no_run + /// let mut task = RmTask { path: ".".to_string(), options: None }; + /// let result = task.compute(); + /// assert!(result.is_ok()); + /// ``` fn compute(&mut self) -> Result { remove(self.path.clone(), self.options.clone()) } + /// Convert the completed task result into a JavaScript value for the N-API environment. + /// + /// This implementation produces no JavaScript value and signals successful resolution. + /// + /// # Returns + /// + /// `Ok(())` indicating the task resolved with no value to return to JavaScript. fn resolve(&mut self, _env: Env, _output: Self::Output) -> Result { Ok(()) } } +/// Creates an asynchronous remove task for the given filesystem path using the provided options. +/// +/// The returned task, when scheduled by the N-API runtime, will perform the removal work off the +/// main thread and resolve with no value. +/// +/// # Examples +/// +/// ``` +/// let task = rm("some/path".to_string(), None); +/// // `task` can be returned to JavaScript or scheduled with the napi runtime. +/// ``` #[napi(js_name = "rm")] pub fn rm(path: String, options: Option) -> AsyncTask { AsyncTask::new(RmTask { path, options }) } +/// Synchronously removes the filesystem entry at the given path using the provided options. +/// +/// Returns `Ok(())` on success or an error describing the failure. +/// +/// # Examples +/// +/// ``` +/// // Remove a file or directory at "./tmp" using default options. +/// rm_sync("./tmp".to_string(), None).unwrap(); +/// ``` #[napi(js_name = "rmSync")] pub fn rm_sync(path: String, options: Option) -> Result<()> { remove(path, options) -} +} \ No newline at end of file