ronantakizawa/codereview-bench huggingface dataset:
Repository: casey/just
File: tests/functions.rs (Rust)

Code before:
    foo:
      echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
  ",
  stderr:
"error: Call to function `replace_regex` failed: regex parse error:
    foo\\
       ^
error: incomplete escape sequence, reached end of pattern prematurely
  |
2 |   echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
  |           ^^^^^^^^^^^^^
",
  status: EXIT_FAILURE,
}

test! {
    name: capitalize,
    justfile: "
      foo:
        echo {{ capitalize('BAR') }}
    ",
    stdout: "Bar\n",
    stderr: "echo Bar\n",
}

test! {
    name: semver_matches_lt,
    justfile: "
      foo:
        echo {{ semver_matches('0.0.1', '<0.1.0') }}
    ",
    stdout: "true\n",
    stderr: "echo true\n",
}

test! {
    name: semver_matches_lte,
    justfile: "
      foo:
        echo {{ semver_matches('0.1.0', '<=0.1.0') }}
        echo {{ semver_matches('0.1.0', '<=0.1.1') }}
    ",
    stdout: "true\ntrue\n",
    stderr: "echo true\necho true\n",
}

test! {
    name: semver_matches_lt_fail,
    justfile: "
      foo:
        echo {{ semver_matches('0.1.0', '<0.0.1') }}

Review comment (bug):
These tests are great, but I think we don't need all of them. In general, when using a library, I try to avoid writing tests which test the library itself, instead of testing how it's being used. I just kind of assume the library works, and only test the way it's being used. Since the semver library is handling all the comparisons, I think we can get away with just one test with a matching semver, one with a non-matching semver, and one with an error when the version fails to parse, and one where the req fails to parse.

Code after:
    foo:
      echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
  ",
  stderr:
"error: Call to function `replace_regex` failed: regex parse error:
    foo\\
       ^
error: incomplete escape sequence, reached end of pattern prematurely
  |
2 |   echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
  |           ^^^^^^^^^^^^^
",
  status: EXIT_FAILURE,
}

test! {
    name: capitalize,
    justfile: "
      foo:
        echo {{ capitalize('BAR') }}
    ",
    stdout: "Bar\n",
    stderr: "echo Bar\n",
}

#[test]
fn semver_matches() {
  Test::new()
    .justfile(
      "
      foo:
        echo {{ semver_matches('0.1.0', '>=0.1.0') }}
        echo {{ semver_matches('0.1.0', '=0.0.1') }}
    ",
    )
    .stdout("true\nfalse\n")
    .stderr("echo true\necho false\n")
    .run();
}

fn assert_eval_eq(expression: &str, result: &str) {
  Test::new()
    .justfile(format!("x := {expression}"))
    .args(["--evaluate", "x"])
    .stdout(result)
    .unindent_stdout(false)
    .run();
}

#[test]
fn trim_end_matches() {
Repository: casey/just
File: src/function.rs (Rust)

Code before:
  Ok(s.to_upper_camel_case())
}

fn uppercase(_context: &FunctionContext, s: &str) -> Result<String, String> {
  Ok(s.to_uppercase())
}

fn uuid(_context: &FunctionContext) -> Result<String, String> {
  Ok(uuid::Uuid::new_v4().to_string())
}

fn without_extension(_context: &FunctionContext, path: &str) -> Result<String, String> {
  let parent = Utf8Path::new(path)
    .parent()
    .ok_or_else(|| format!("Could not extract parent from `{path}`"))?;

  let file_stem = Utf8Path::new(path)
    .file_stem()
    .ok_or_else(|| format!("Could not extract file stem from `{path}`"))?;

  Ok(parent.join(file_stem).to_string())
}

/// Check whether a string processes properly as semver (e.x. "0.1.0")
/// and matches a given semver requirement (e.x. ">=0.1.0")
fn semver_matches(_context: &FunctionContext, v: &str, req: &str) -> Result<String, String> {
  Ok(
    VersionReq::parse(req.as_ref())
      .map_err(|err| err.to_string())?
      .matches(&Version::parse(v.as_ref()).map_err(|err| err.to_string())?)
      .to_string(),
  )
}


Review comment (suggestion):
Maybe a little more explicit:

```suggestion
fn semver_matches(_context: &FunctionContext, version: &str, requirement: &str) -> Result<String, String> {
```

Code after:

fn uppercase(_context: &FunctionContext, s: &str) -> Result<String, String> {
  Ok(s.to_uppercase())
}

fn uuid(_context: &FunctionContext) -> Result<String, String> {
  Ok(uuid::Uuid::new_v4().to_string())
}

fn without_extension(_context: &FunctionContext, path: &str) -> Result<String, String> {
  let parent = Utf8Path::new(path)
    .parent()
    .ok_or_else(|| format!("Could not extract parent from `{path}`"))?;

  let file_stem = Utf8Path::new(path)
    .file_stem()
    .ok_or_else(|| format!("Could not extract file stem from `{path}`"))?;

  Ok(parent.join(file_stem).to_string())
}

/// Check whether a string processes properly as semver (e.x. "0.1.0")
/// and matches a given semver requirement (e.x. ">=0.1.0")
fn semver_matches(
  _context: &FunctionContext,
  version: &str,
  requirement: &str,
) -> Result<String, String> {
  Ok(
    requirement
      .parse::<VersionReq>()
      .map_err(|err| format!("invalid semver requirement: {err}"))?
      .matches(
        &version
          .parse::<Version>()
          .map_err(|err| format!("invalid semver version: {err}"))?,
      )
      .to_string(),
  )
}
Repository: casey/just
File: src/recipe.rs (Rust)

Code before:

  pub(crate) fn min_arguments(&self) -> usize {
    self
      .parameters
      .iter()
      .filter(|p| p.default.is_none() && p.kind != ParameterKind::Star)
      .count()
  }

  pub(crate) fn max_arguments(&self) -> usize {
    if self.parameters.iter().any(|p| p.kind.is_variadic()) {
      usize::MAX - 1
    } else {
      self.parameters.len()
    }
  }

  pub(crate) fn name(&self) -> &'src str {
    self.name.lexeme()
  }

  pub(crate) fn line_number(&self) -> usize {
    self.name.line
  }

  pub(crate) fn confirm(&self) -> bool {
    if self.attributes.contains(&Attribute::Confirm) {
      eprint!("Confirm running {} (y/N): ", self.name);
      let mut buf = String::new();
      std::io::stdin().read_line(&mut buf).is_ok() && buf.trim_end().eq_ignore_ascii_case("y")
    } else {
      true
    }
  }

  pub(crate) fn public(&self) -> bool {
    !self.private && !self.attributes.contains(&Attribute::Private)
  }

  pub(crate) fn change_directory(&self) -> bool {
    !self.attributes.contains(&Attribute::NoCd)
  }

  pub(crate) fn enabled(&self) -> bool {
    let windows = self.attributes.contains(&Attribute::Windows);
    let linux = self.attributes.contains(&Attribute::Linux);
    let macos = self.attributes.contains(&Attribute::Macos);
    let unix = self.attributes.contains(&Attribute::Unix);

    (!windows && !linux && !macos && !unix)
      || (cfg!(target_os = "windows") && windows)

Review comment (bug):
This function should be fallible, since `read_line` can fail, so it should return `RunResult<bool>`

Code after:

  pub(crate) fn min_arguments(&self) -> usize {
    self
      .parameters
      .iter()
      .filter(|p| p.default.is_none() && p.kind != ParameterKind::Star)
      .count()
  }

  pub(crate) fn max_arguments(&self) -> usize {
    if self.parameters.iter().any(|p| p.kind.is_variadic()) {
      usize::MAX - 1
    } else {
      self.parameters.len()
    }
  }

  pub(crate) fn name(&self) -> &'src str {
    self.name.lexeme()
  }

  pub(crate) fn line_number(&self) -> usize {
    self.name.line
  }

  pub(crate) fn confirm(&self) -> RunResult<'src, bool> {
    if self.attributes.contains(&Attribute::Confirm) {
      eprint!("Run recipe `{}`? ", self.name);
      let mut line = String::new();
      std::io::stdin()
        .read_line(&mut line)
        .map_err(|io_error| Error::GetConfirmation { io_error })?;
      let line = line.trim().to_lowercase();
      Ok(line == "y" || line == "yes")
    } else {
      Ok(true)
    }
  }

  pub(crate) fn public(&self) -> bool {
    !self.private && !self.attributes.contains(&Attribute::Private)
  }

  pub(crate) fn change_directory(&self) -> bool {
    !self.attributes.contains(&Attribute::NoCd)
  }

  pub(crate) fn enabled(&self) -> bool {
    let windows = self.attributes.contains(&Attribute::Windows);
    let linux = self.attributes.contains(&Attribute::Linux);
    let macos = self.attributes.contains(&Attribute::Macos);
Repository: casey/just
File: src/recipe.rs (Rust)

Code before:
      let quiet_command = lines.peek().map_or(false, |line| line.is_quiet());
      let infallible_command = lines.peek().map_or(false, |line| line.is_infallible());

      let comment_line =
        context.settings.ignore_comments && lines.peek().map_or(false, |line| line.is_comment());

      loop {
        if lines.peek().is_none() {
          break;
        }
        let line = lines.next().unwrap();
        line_number += 1;
        if !comment_line {
          evaluated += &evaluator.evaluate_line(line, continued)?;
        }
        if line.is_continuation() && !comment_line {
          continued = true;
          evaluated.pop();
        } else {
          break;
        }
      }

      

      if comment_line {
        continue;
      }

      let mut command = evaluated.as_str();

      if quiet_command {
        command = &command[1..];
      }

      if infallible_command {
        command = &command[1..];
      }

      if command.is_empty() {
        continue;
      }

      if config.dry_run
        || config.verbosity.loquacious()
        || !((quiet_command ^ self.quiet) || config.verbosity.quiet())
      {
        let color = if config.highlight {
          config.color.command(config.command_color)
        } else {
          config.color

Review comment (suggestion):
Extra whitespace:

```suggestion
      if comment_line {
```

Code after:

      let comment_line =
        context.settings.ignore_comments && lines.peek().map_or(false, |line| line.is_comment());

      loop {
        if lines.peek().is_none() {
          break;
        }
        let line = lines.next().unwrap();
        line_number += 1;
        if !comment_line {
          evaluated += &evaluator.evaluate_line(line, continued)?;
        }
        if line.is_continuation() && !comment_line {
          continued = true;
          evaluated.pop();
        } else {
          break;
        }
      }

      if comment_line {
        continue;
      }

      let mut command = evaluated.as_str();

      if quiet_command {
        command = &command[1..];
      }

      if infallible_command {
        command = &command[1..];
      }

      if command.is_empty() {
        continue;
      }

      if config.dry_run
        || config.verbosity.loquacious()
        || !((quiet_command ^ self.quiet) || config.verbosity.quiet())
      {
        let color = if config.highlight {
          config.color.command(config.command_color)
        } else {
          config.color
        };
        eprintln!("{}", color.stderr().paint(command));
      }
Repository: casey/just
File: src/subcommand.rs (Rust)

Code before:
  fn changelog() {
    print!("{}", include_str!("../CHANGELOG.md"));
  }

  fn choose<'src>(
    config: &Config,
    justfile: &Justfile<'src>,
    search: &Search,
    overrides: &BTreeMap<String, String>,
    chooser: Option<&str>,
  ) -> Result<(), Error<'src>> {
    let recipes = justfile
      .public_recipes(config.unsorted)
      .iter()
      .filter(|recipe| recipe.min_arguments() == 0)
      .copied()
      .collect::<Vec<&Recipe<Dependency>>>();

    if recipes.is_empty() {
      return Err(Error::NoChoosableRecipes);
    }

    let chooser = chooser
      .map(OsString::from)
      .or_else(|| env::var_os(config::CHOOSER_ENVIRONMENT_KEY))
      .unwrap_or_else(|| config::chooser_default(justfile));

    let result = justfile
      .settings
      .shell_command(config)
      .arg(&chooser)
      .current_dir(&search.working_directory)
      .stdin(Stdio::piped())
      .stdout(Stdio::piped())
      .spawn();

    let mut child = match result {
      Ok(child) => child,
      Err(io_error) => {
        let (shell_binary, shell_arguments) = justfile.settings.shell(config);
        return Err(Error::ChooserInvoke {
          shell_binary: shell_binary.to_owned(),
          shell_arguments: shell_arguments.join(" "),
          chooser,
          io_error,
        });
      }
    };

    for recipe in recipes {
      if let Err(io_error) = child

Review comment (suggestion):
I think it's more natural to pass in `Search::justfile` here:

```suggestion
      .unwrap_or_else(|| config::chooser_default(&search.justfile));
```

Code after:
  fn changelog() {
    print!("{}", include_str!("../CHANGELOG.md"));
  }

  fn choose<'src>(
    config: &Config,
    justfile: &Justfile<'src>,
    search: &Search,
    overrides: &BTreeMap<String, String>,
    chooser: Option<&str>,
  ) -> Result<(), Error<'src>> {
    let recipes = justfile
      .public_recipes(config.unsorted)
      .iter()
      .filter(|recipe| recipe.min_arguments() == 0)
      .copied()
      .collect::<Vec<&Recipe<Dependency>>>();

    if recipes.is_empty() {
      return Err(Error::NoChoosableRecipes);
    }

    let chooser = chooser
      .map(OsString::from)
      .or_else(|| env::var_os(config::CHOOSER_ENVIRONMENT_KEY))
      .unwrap_or_else(|| config::chooser_default(&search.justfile));

    let result = justfile
      .settings
      .shell_command(config)
      .arg(&chooser)
      .current_dir(&search.working_directory)
      .stdin(Stdio::piped())
      .stdout(Stdio::piped())
      .spawn();

    let mut child = match result {
      Ok(child) => child,
      Err(io_error) => {
        let (shell_binary, shell_arguments) = justfile.settings.shell(config);
        return Err(Error::ChooserInvoke {
          shell_binary: shell_binary.to_owned(),
          shell_arguments: shell_arguments.join(" "),
          chooser,
          io_error,
        });
      }
    };

    for recipe in recipes {
      if let Err(io_error) = child
Repository: casey/just
File: src/function.rs (Rust)

Code before:
    )),
  }
}

fn arch(_context: &FunctionContext) -> Result<String, String> {
  Ok(target::arch().to_owned())
}

fn capitalize(_context: &FunctionContext, s: &str) -> Result<String, String> {
  let mut capitalized = String::new();
  for (i, c) in s.chars().enumerate() {
    if i == 0 {
      capitalized.extend(c.to_uppercase());
    } else {
      capitalized.extend(c.to_lowercase());
    }
  }
  Ok(capitalized)
}

fn clean(_context: &FunctionContext, path: &str) -> Result<String, String> {
  Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned())
}

fn dirs_map(f: fn() -> Option<PathBuf>) -> Result<String, String> {
  Ok(f().map_or(String::new(), |p| p.to_string_lossy().into_owned()))
}

fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
  use std::env::VarError::*;

  if let Some(value) = context.dotenv.get(key) {
    return Ok(value.clone());
  }

  match env::var(key) {
    Err(NotPresent) => Err(format!("environment variable `{key}` not present")),
    Err(NotUnicode(os_string)) => Err(format!(
      "environment variable `{key}` not unicode: {os_string:?}"
    )),
    Ok(value) => Ok(value),
  }
}

fn env_var_or_default(
  context: &FunctionContext,
  key: &str,
  default: &str,
) -> Result<String, String> {
  use std::env::VarError::*;


Review comment (bug):
We should use String::from_utf8, and return an error if conversion fails. `to_string_lossy` will replace invalid UTF-8 sequences with replacement characters, if the user subsequently tries to access the returned path, it will fail.

We should also add a `&'static str` argument to `dirs_map`, which is the name of the directory "cache directory", etc, we're looking up, and include it in the error message.

Code after:
    )),
  }
}

fn arch(_context: &FunctionContext) -> Result<String, String> {
  Ok(target::arch().to_owned())
}

fn capitalize(_context: &FunctionContext, s: &str) -> Result<String, String> {
  let mut capitalized = String::new();
  for (i, c) in s.chars().enumerate() {
    if i == 0 {
      capitalized.extend(c.to_uppercase());
    } else {
      capitalized.extend(c.to_lowercase());
    }
  }
  Ok(capitalized)
}

fn clean(_context: &FunctionContext, path: &str) -> Result<String, String> {
  Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned())
}

fn dir(name: &'static str, f: fn() -> Option<PathBuf>) -> Result<String, String> {
  match f() {
    Some(path) => path
      .as_os_str()
      .to_str()
      .map(str::to_string)
      .ok_or_else(|| {
        format!(
          "unable to convert {name} directory path to string: {}",
          path.display(),
        )
      }),
    None => Err(format!("{name} directory not found")),
  }
}

fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
  use std::env::VarError::*;

  if let Some(value) = context.dotenv.get(key) {
    return Ok(value.clone());
  }

  match env::var(key) {
    Err(NotPresent) => Err(format!("environment variable `{key}` not present")),
    Err(NotUnicode(os_string)) => Err(format!(
      "environment variable `{key}` not unicode: {os_string:?}"
Repository: casey/just
File: tests/test.rs (Rust)

Code before:
use std::process::Child;

use {super::*, pretty_assertions::assert_eq};

macro_rules! test {
  {
    name: $name:ident,
    $(justfile: $justfile:expr,)?
    $(args: ($($arg:tt),*),)?
    $(env: { $($env_key:literal : $env_value:literal,)* },)?
    $(stdin: $stdin:expr,)?
    $(stdout: $stdout:expr,)?
    $(stdout_regex: $stdout_regex:expr,)?
    $(stderr: $stderr:expr,)?
    $(stderr_regex: $stderr_regex:expr,)?
    $(status: $status:expr,)?
    $(shell: $shell:expr,)?
  } => {
    #[test]
    fn $name() {
      let test = crate::test::Test::new();

      $($(let test = test.arg($arg);)*)?
      $($(let test = test.env($env_key, $env_value);)*)?
      $(let test = test.justfile($justfile);)?
      $(let test = test.shell($shell);)?

Review comment (suggestion):
I would put this import in `lib.rs`, since it's common to import `std::process::Child` as just `Child`.

Code after:
use {super::*, pretty_assertions::assert_eq};

macro_rules! test {
  {
    name: $name:ident,
    $(justfile: $justfile:expr,)?
    $(args: ($($arg:tt),*),)?
    $(env: { $($env_key:literal : $env_value:literal,)* },)?
    $(stdin: $stdin:expr,)?
    $(stdout: $stdout:expr,)?
    $(stdout_regex: $stdout_regex:expr,)?
    $(stderr: $stderr:expr,)?
    $(stderr_regex: $stderr_regex:expr,)?
    $(status: $status:expr,)?
    $(shell: $shell:expr,)?
  } => {
    #[test]
    fn $name() {
      let test = crate::test::Test::new();

      $($(let test = test.arg($arg);)*)?
      $($(let test = test.env($env_key, $env_value);)*)?
      $(let test = test.justfile($justfile);)?
      $(let test = test.shell($shell);)?
      $(let test = test.status($status);)?
      $(let test = test.stderr($stderr);)?
Repository: casey/just
File: tests/test.rs (Rust)

Code before:
  pub(crate) fn test_round_trip(mut self, test_round_trip: bool) -> Self {
    self.test_round_trip = test_round_trip;
    self
  }

  pub(crate) fn tree(self, mut tree: Tree) -> Self {
    tree.map(|_name, content| unindent(content));
    tree.instantiate(self.tempdir.path()).unwrap();
    self
  }

  pub(crate) fn unindent_stdout(mut self, unindent_stdout: bool) -> Self {
    self.unindent_stdout = unindent_stdout;
    self
  }

  pub(crate) fn write(self, path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Self {
    let path = self.tempdir.path().join(path);
    fs::create_dir_all(path.parent().unwrap()).unwrap();
    fs::write(path, content).unwrap();
    self
  }
}

impl Test {
  pub(crate) fn run(mut self) -> Output {
    let child = self.spawn();
    self.check_output(child)
  }

  pub(crate) fn spawn(&mut self) -> Child {
    if let Some(justfile) = &self.justfile {
      let justfile = unindent(justfile);
      fs::write(self.justfile_path(), justfile).unwrap();
    }

    let mut dotenv_path = self.tempdir.path().to_path_buf();
    dotenv_path.push(".env");
    fs::write(dotenv_path, "DOTENV_KEY=dotenv-value").unwrap();

    let mut command = Command::new(executable_path("just"));

    if self.shell {
      command.args(["--shell", "bash"]);
    }

    command
      .args(self.args.drain(..))
      .envs(&self.env)
      .current_dir(self.tempdir.path().join(&self.current_dir))
      .stdin(Stdio::piped())

Review comment (suggestion):
Instead of breaking this function up, how about adding the pid to the output struct? That way, callers can all still use run, and if they care about the pid, they can get it from the output.

Code after:
    self.test_round_trip = test_round_trip;
    self
  }

  pub(crate) fn tree(self, mut tree: Tree) -> Self {
    tree.map(|_name, content| unindent(content));
    tree.instantiate(self.tempdir.path()).unwrap();
    self
  }

  pub(crate) fn unindent_stdout(mut self, unindent_stdout: bool) -> Self {
    self.unindent_stdout = unindent_stdout;
    self
  }

  pub(crate) fn write(self, path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Self {
    let path = self.tempdir.path().join(path);
    fs::create_dir_all(path.parent().unwrap()).unwrap();
    fs::write(path, content).unwrap();
    self
  }
}

impl Test {
  pub(crate) fn run(self) -> Output {
    if let Some(justfile) = &self.justfile {
      let justfile = unindent(justfile);
      fs::write(self.justfile_path(), justfile).unwrap();
    }

    let stdout = if self.unindent_stdout {
      unindent(&self.stdout)
    } else {
      self.stdout
    };
    let stderr = unindent(&self.stderr);

    let mut dotenv_path = self.tempdir.path().to_path_buf();
    dotenv_path.push(".env");
    fs::write(dotenv_path, "DOTENV_KEY=dotenv-value").unwrap();

    let mut command = Command::new(executable_path("just"));

    if self.shell {
      command.args(["--shell", "bash"]);
    }

    let mut child = command
      .args(self.args)
      .envs(&self.env)
      .current_dir(self.tempdir.path().join(self.current_dir))
Repository: casey/just
File: tests/confirm.rs (Rust)

Code before:
            echo confirmed
        ",
    )
    .stderr("Run recipe `requires_confirmation`? error: Recipe `requires_confirmation` was not confirmed\n")
    .status(1)
    .run();
}

#[test]
fn confirm_recipe_with_prompt() {
  Test::new()
    .justfile(
      "
        [confirm(\"This is dangerous\")]
        requires_confirmation:
            echo confirmed
        ",
    )
    .stderr("This is dangerous - Run recipe `requires_confirmation`? echo confirmed\n")
    .stdout("confirmed\n")
    .stdin("y")
    .run();
}

#[test]
fn confirm_recipe_arg_with_prompt() {
  Test::new()
    .arg("--yes")
    .justfile(
      "
        [confirm(\"this is dangerous!\")]
        requires_confirmation:
            echo confirmed
        ",
    )
    .stderr("echo confirmed\n")
    .stdout("confirmed\n")
    .run();
}


Review comment (refactor):
I think it's okay to remove this test, since this PR doesn't change the logic of whether or not a recipe with a confirm attribute is run.

Code after:
            echo confirmed
        ",
    )
    .stderr("Run recipe `requires_confirmation`? error: Recipe `requires_confirmation` was not confirmed\n")
    .status(1)
    .run();
}

#[test]
fn confirm_recipe_with_prompt() {
  Test::new()
    .justfile(
      "
        [confirm(\"This is dangerous - are you sure you want to run it?\")]
        requires_confirmation:
            echo confirmed
        ",
    )
    .stderr("This is dangerous - are you sure you want to run it? echo confirmed\n")
    .stdout("confirmed\n")
    .stdin("y")
    .run();
}

#[test]
fn confirm_recipe_with_prompt_too_many_args() {
  Test::new()
    .justfile(
      "
        [confirm(\"This is dangerous - are you sure you want to run it?\",\"this second argument is not supported\")]
        requires_confirmation:
            echo confirmed
        ",
    )
    .stderr("error: Expected ')', but found ','\n ——▶ justfile:1:64\n  │\n1 │ [confirm(\"This is dangerous - are you sure you want to run it?\",\"this second argument is not supported\")]\n  │                                                                ^\n")
    .stdout("")
    .status(1)
    .run();
}

#[test]
fn confirm_attribute_is_formatted_correctly() {
  Test::new()
    .justfile(
      "
        [confirm('prompt')]
        foo:
      ",
    )
    .arg("--dump")
    .stdout("[confirm('prompt')]\nfoo:\n")