Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 26 additions & 57 deletions src/stdlib/filesystem.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::helpers::{check_arg_count, check_arg_range, expect_text};
use super::helpers::{
check_arg_count, check_arg_range, expect_text, unary_path_bool_op, unary_path_string_op,
};
use crate::interpreter::error::RuntimeError;
use crate::interpreter::value::Value;
use std::cell::RefCell;
Expand Down Expand Up @@ -123,31 +125,23 @@ pub fn native_path_join(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_path_basename(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("path_basename", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

let basename = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");

Ok(Value::Text(Arc::from(basename)))
unary_path_string_op("path_basename", args, |path| {
Arc::from(
path.file_name()
.and_then(|name| name.to_str())
.unwrap_or(""),
)
})
}

pub fn native_path_dirname(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("path_dirname", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

let dirname = path
.parent()
.and_then(|parent| parent.to_str())
.unwrap_or("");

Ok(Value::Text(Arc::from(dirname)))
unary_path_string_op("path_dirname", args, |path| {
Arc::from(
path.parent()
.and_then(|parent| parent.to_str())
.unwrap_or(""),
)
})
}

pub fn native_makedirs(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down Expand Up @@ -211,30 +205,15 @@ pub fn native_file_mtime(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_path_exists(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("path_exists", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

Ok(Value::Bool(path.exists()))
unary_path_bool_op("path_exists", args, |path| path.exists())
}

pub fn native_is_file(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("is_file", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

Ok(Value::Bool(path.is_file()))
unary_path_bool_op("is_file", args, |path| path.is_file())
}

pub fn native_is_dir(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("is_dir", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

Ok(Value::Bool(path.is_dir()))
unary_path_bool_op("is_dir", args, |path| path.is_dir())
}

pub fn native_count_lines(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down Expand Up @@ -281,25 +260,15 @@ pub fn native_count_lines(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_path_extension(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("path_extension", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");

Ok(Value::Text(Arc::from(extension)))
unary_path_string_op("path_extension", args, |path| {
Arc::from(path.extension().and_then(|ext| ext.to_str()).unwrap_or(""))
})
}

pub fn native_path_stem(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("path_stem", &args, 1)?;

let path_str = expect_text(&args[0])?;
let path = Path::new(path_str.as_ref());

let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");

Ok(Value::Text(Arc::from(stem)))
unary_path_string_op("path_stem", args, |path| {
Arc::from(path.file_stem().and_then(|s| s.to_str()).unwrap_or(""))
})
}

pub fn native_file_size(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down
49 changes: 49 additions & 0 deletions src/stdlib/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,3 +581,52 @@ where
let list = expect_list(&args[0])?;
op(list, val)
}

/// Helper for unary path operations that return a boolean (&Path -> bool).
///
/// Centralizes argument validation, text extraction, Path conversion, and result wrapping.
///
/// # Arguments
///
/// * `func_name` - Name of the function for error messages
/// * `args` - Arguments passed to the function
/// * `op` - The boolean operation to perform on the path
pub fn unary_path_bool_op<F>(
func_name: &str,
args: Vec<Value>,
op: F,
) -> Result<Value, RuntimeError>
where
F: FnOnce(&std::path::Path) -> bool,
{
check_arg_count(func_name, &args, 1)?;
let path_str = expect_text(&args[0])?;
let path = std::path::Path::new(path_str.as_ref());
Ok(Value::Bool(op(path)))
}

/// Helper for unary path operations that return a string (&Path -> Result<Arc<str>, RuntimeError>).
///
/// Centralizes argument validation, text extraction, Path conversion, and result wrapping.
/// Note that the path operation itself may return an empty string or an error if the operation
/// fails (e.g., getting parent of root).
Comment on lines +608 to +612

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment for unary_path_string_op says the operation is &Path -> Result<Arc<str>, RuntimeError> and mentions it may return an error, but the function signature doesn’t allow the closure to return Result or propagate errors. Update the docs to match the current API, or change the helper to accept FnOnce(&Path) -> Result<impl Into<Arc<str>>, RuntimeError> and propagate the error.

Suggested change
/// Helper for unary path operations that return a string (&Path -> Result<Arc<str>, RuntimeError>).
///
/// Centralizes argument validation, text extraction, Path conversion, and result wrapping.
/// Note that the path operation itself may return an empty string or an error if the operation
/// fails (e.g., getting parent of root).
/// Helper for unary path operations that return a string (`&Path -> impl Into<Arc<str>>`).
///
/// Centralizes argument validation, text extraction, `Path` conversion, and result wrapping.
/// The path operation itself does not return a `Result`; if it may fail, it should handle
/// the failure internally (for example, by returning an empty string or an error message).

Copilot uses AI. Check for mistakes.
///
/// # Arguments
///
/// * `func_name` - Name of the function for error messages
/// * `args` - Arguments passed to the function
/// * `op` - The operation to perform on the path that yields a string representation
pub fn unary_path_string_op<F, R>(
func_name: &str,
args: Vec<Value>,
op: F,
) -> Result<Value, RuntimeError>
where
F: FnOnce(&std::path::Path) -> R,
R: Into<Arc<str>>,
{
check_arg_count(func_name, &args, 1)?;
let path_str = expect_text(&args[0])?;
let path = std::path::Path::new(path_str.as_ref());
Ok(Value::Text(op(path).into()))
}
3 changes: 3 additions & 0 deletions test_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn test() {
println!("test");
}
Comment on lines +1 to +3

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file appears to be an orphan Rust source file at the repository root and won’t be picked up by Cargo as a unit test or module. If it’s intended as a test, it should live under tests/ (as an integration test) or be referenced from an existing crate module with a #[test] function; otherwise it should be removed to avoid accumulating dead files.

Suggested change
fn test() {
println!("test");
}

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Stray test_helpers.rs file committed at repo root violates project structure

A new file test_helpers.rs has been added at the repository root. This file contains only a trivial unused function (fn test() { println!("test"); }) and is not referenced by any module or build configuration. According to AGENTS.md and CLAUDE.md, Rust unit/integration tests must be located in the tests/ directory, not the repo root. There is already a proper tests/test_helpers.rs module used by multiple integration tests. This appears to be an accidentally committed file.

Prompt for agents
Delete the stray file test_helpers.rs from the repository root. It serves no purpose, is not referenced by any code, and violates the project structure guidelines (tests belong in tests/ directory). The proper test helper module already exists at tests/test_helpers.rs.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1 to +3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for any calls to the `test` function in the codebase.

# Search for invocations of the test() function
rg -nP '\btest\s*\(' --type rust -C3

Repository: WebFirstLanguage/wfl

Length of output: 2538


🏁 Script executed:

find . -name "test_helpers.rs" -type f

Repository: WebFirstLanguage/wfl

Length of output: 105


🏁 Script executed:

echo "=== Root level test_helpers.rs ===" && cat ./test_helpers.rs && echo -e "\n=== Tests directory test_helpers.rs ===" && cat ./tests/test_helpers.rs

Repository: WebFirstLanguage/wfl

Length of output: 10942


Remove the unused test() function—this file should not be at the repository root.

This file at the root level is dead code that serves no purpose. The function contains only a println! statement with no actual test logic or utility functionality. Per the project guidelines, test files should be placed in the tests/ directory with feature-oriented naming (e.g., file_io_test.rs).

Additionally:

  • The function is never invoked anywhere in the codebase.
  • It lacks a #[test] attribute and proper test assertions.
  • If intended as a helper module, it provides no actual utilities (see the legitimate tests/test_helpers.rs for reference).

Delete this file before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_helpers.rs` around lines 1 - 3, Delete the unused top-level file and its
dead function: remove the file containing fn test() { println!("test"); } (the
unused symbol "test") from the repository root; do not replace it—if helper
logic is needed move real helpers into tests/test_helpers.rs with proper #[test]
functions and assertions and feature-oriented naming instead of keeping this
file.

Loading