Repository: denoland/deno
Pull request: fix(cli/test): decoding percent-encoding(non-ASCII) file path correctly

# Summary

This PR resolves about the issue.
fixes #10810

And the formerly context is in the PR. 
#22582

Here is an expected behaviour example with this change.

- 🦕.test.ts
```ts
import { assertEquals } from "https://deno.land/std@0.215.0/assert/mod.ts";

Deno.test("example test", () => {
  assertEquals("🍋", "🦕");
});
```

- terminal

```bash
% /path/to/deno test <0001f995>.test.ts
Check file:///path/to/repo/🦕.test.ts
running 1 test from ./🦕.test.ts
example test ... FAILED (3ms)

 ERRORS 

example test => ./🦕.test.ts:3:6
error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   🍋
+   🦕


  throw new AssertionError(message);
        ^
    at assertEquals (https://deno.land/std@0.215.0/assert/assert_equals.ts:52:9)
    at file:///path/to/repo/%F0%9F%A6%95.test.ts:4:3

 FAILURES 

example test => ./🦕.test.ts:3:6

FAILED | 0 passed | 1 failed (29ms)

error: Test failed
```

## related issues

There are test reporter and runtime problems.
#18983

# How we handle runtime behaviour?

The terminal output log not only contains the test reporter but also runtime file path treatment as follows.
`at file:///path/to/repo/%F0%9F%A6%95.test.ts:4:3`

## spec

It seems that there is no specification for `Error.stack` today, and it is implementation-defined behaviour by browser engines.
(Thanks for the [article](https://zenn.dev/pixiv/articles/bb123b2f50cdab), @petamoriken)

- https://github.com/tc39/proposal-error-stacks

## behaviour

Here is an example code to show stack trace, and runs it on modern browsers.

- index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script type="module" src="./🍋.js"></script>
</head>
<body>
</body>
</html>
```

- 🍋.js

```js
throw new Error('🍋')
```

### result

#### Google Chrome(122.0.6261.112)

```log
🍋.js:2 Uncaught Error: 🍋
    at 🍋.js:2:7
(anonymous) @ 🍋.js:2
```

I think that the stack trace implementation is located as follows.

- https://source.chromium.org/chromium/chromium/src/+/refs/tags/122.0.6261.112:v8/src/inspector/v8-stack-trace-impl.cc;l=351-367

#### Firefox(117.0)

```log
Uncaught Error: 🍋
    <anonymous> http://localhost:8080/🍋.js:1
```

#### Safari(17.3.1)

```log
Error: 🍋
module code - 🍋.js:2
```

### non-browser result

#### Node.js(20.12.0)

```log
% node 🍋.mjs         
file:///path/to/%F0%9F%8D%8B.mjs:1
throw new Error('🍋')
      ^

Error: 🍋
    at file:///path/to/%F0%9F%8D%8B.mjs:1:7
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:28:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)
```

## deno implementation context

https://github.com/denoland/deno/blob/92a8ada7194ac013f8c34824f435f09b1f52ca5b/runtime/fmt_errors.rs#L37
https://github.com/denoland/deno_core/blob/5c3d303a6de112bb704620d8ba5956acf4e10a48/core/error.rs#L658

Changed files:
filename: cli/tools/check.rs
patch: @@ -27,6 +27,7 @@ use crate::graph_util::ModuleGraphBuilder;
 use crate::npm::CliNpmResolver;
 use crate::tsc;
 use crate::tsc::Diagnostics;
+use crate::util::path::to_percent_decoded_str;
 use crate::version;
 
 /// Options for performing a check of a module graph. Note that the decision to
@@ -154,7 +155,11 @@ impl TypeChecker {
 
     for root in &graph.roots {
       let root_str = root.as_str();
-      log::info!("{} {}", colors::green("Check"), root_str);
+      log::info!(
+        "{} {}",
+        colors::green("Check"),
+        to_percent_decoded_str(root_str)
+      );
     }
 
     let check_js = ts_config.get_check_js();

filename: cli/tools/test/fmt.rs
patch: @@ -8,6 +8,8 @@ use phf::phf_map;
 use std::borrow::Cow;
 use std::ops::AddAssign;
 
+use crate::util::path::to_percent_decoded_str;
+
 use super::*;
 
 pub fn to_relative_path_or_remote_url(cwd: &Url, path_or_url: &str) -> String {
@@ -19,7 +21,7 @@ pub fn to_relative_path_or_remote_url(cwd: &Url, path_or_url: &str) -> String {
       if !r.starts_with("../") {
         r = format!("./{r}");
       }
-      return r;
+      return to_percent_decoded_str(&r);
     }
   }
   path_or_url.to_string()

filename: tests/integration/test_tests.rs
patch: @@ -63,6 +63,49 @@ itest!(fail {
   output: "test/fail.out",
 });
 
+// GHA CI seems to have a problem with Emoji
+// https://github.com/denoland/deno/pull/23200#issuecomment-2134032695
+#[test]
+fn fail_with_contain_unicode_filename() {
+  let context = TestContextBuilder::new().use_temp_cwd().build();
+  let temp_dir = context.temp_dir();
+  temp_dir.write(
+    "fail_with_contain_unicode_filename🦕.ts",
+    "Deno.test(\"test 0\", () => {
+  throw new Error();
+});
+    ",
+  );
+  let output = context
+    .new_command()
+    .args("test fail_with_contain_unicode_filename🦕.ts")
+    .run();
+  output.skip_output_check();
+  output.assert_exit_code(1);
+  output.assert_matches_text(
+    "Check [WILDCARD]/fail_with_contain_unicode_filename🦕.ts
+running 1 test from ./fail_with_contain_unicode_filename🦕.ts
+test 0 ... FAILED ([WILDCARD])
+
+ ERRORS 
+
+test 0 => ./fail_with_contain_unicode_filename🦕.ts:[WILDCARD]
+error: Error
+  throw new Error();
+        ^
+    at [WILDCARD]/fail_with_contain_unicode_filename%F0%9F%A6%95.ts:[WILDCARD]
+
+ FAILURES 
+
+test 0 => ./fail_with_contain_unicode_filename🦕.ts:[WILDCARD]
+
+FAILED | 0 passed | 1 failed ([WILDCARD])
+
+error: Test failed
+",
+  );
+}
+
 itest!(collect {
   args: "test --ignore=test/collect/ignore test/collect",
   exit_code: 0,