From 4ecf99dfb90624dbfb62aca64e0edd1a676c057e Mon Sep 17 00:00:00 2001 From: theMackabu Date: Mon, 20 Apr 2026 16:38:19 -0700 Subject: [PATCH] add fs path coercing --- examples/wasm/demo.sh | 3 ++- src/modules/fs.c | 40 ++++++++++++++++++++++++++----- src/types/modules/fs.d.ts | 12 +++++----- tests/test_fs_readfile_url.mjs | 44 ++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 tests/test_fs_readfile_url.mjs diff --git a/examples/wasm/demo.sh b/examples/wasm/demo.sh index 50a1c46..b95d660 100755 --- a/examples/wasm/demo.sh +++ b/examples/wasm/demo.sh @@ -6,5 +6,6 @@ SRC="${SCRIPT_DIR}/hello.c" BASE="${SRC%.c}" emcc -O3 "$SRC" -o "${BASE}.cjs" -ant "${BASE}.cjs" +./build/ant -pe "\`running ant \${Ant.version}\`" +./build/ant "${BASE}.cjs" rm -f "${BASE}.cjs" "${BASE}.wasm" diff --git a/src/modules/fs.c b/src/modules/fs.c index 266a77d..8b2c758 100644 --- a/src/modules/fs.c +++ b/src/modules/fs.c @@ -32,6 +32,7 @@ #include "modules/events.h" #include "modules/stream.h" #include "modules/symbol.h" +#include "modules/url.h" typedef enum { FS_ENC_NONE = 0, @@ -1097,12 +1098,38 @@ static void fs_request_fail(fs_request_t *req, int uv_code) { req->error_msg = strdup(uv_strerror(uv_code)); } +static ant_value_t fs_coerce_file_url_path(ant_t *js, ant_value_t arg) { + ant_value_t href = js_getprop_fallback(js, arg, "href"); + if (vtype(href) != T_STR) return js_mkundef(); + + const char *href_str = js_getstr(js, href, NULL); + if (!href_str) return js_mkundef(); + + url_state_t parsed = {0}; + if (parse_url_to_state(href_str, NULL, &parsed) != 0) return js_mkundef(); + + ant_value_t path = js_mkundef(); + if (parsed.protocol && strcmp(parsed.protocol, "file:") == 0) { + char *decoded = url_decode_component(parsed.pathname); + if (decoded) { + path = js_mkstr(js, decoded, strlen(decoded)); + free(decoded); + }} + + url_state_clear(&parsed); + return path; +} + static ant_value_t fs_coerce_path(ant_t *js, ant_value_t arg) { if (vtype(arg) == T_STR) return arg; - if (is_object_type(arg)) { - ant_value_t pathname = js_get(js, arg, "pathname"); - if (vtype(pathname) == T_STR) return pathname; - } + if (!is_object_type(arg)) return js_mkundef(); + + ant_value_t path = fs_coerce_file_url_path(js, arg); + if (!is_undefined(path)) return path; + + path = js_get(js, arg, "pathname"); + if (vtype(path) == T_STR) return path; + return js_mkundef(); } @@ -2099,10 +2126,11 @@ static ant_value_t builtin_fs_readBytesSync(ant_t *js, ant_value_t *args, int na static ant_value_t builtin_fs_readFile(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "readFile() requires a path argument"); - if (vtype(args[0]) != T_STR) return js_mkerr(js, "readFile() path must be a string"); + ant_value_t path_val = fs_coerce_path(js, args[0]); + if (vtype(path_val) != T_STR) return js_mkerr(js, "readFile() path must be a string or URL"); size_t path_len; - char *path = js_getstr(js, args[0], &path_len); + char *path = js_getstr(js, path_val, &path_len); if (!path) return js_mkerr(js, "Failed to get path string"); diff --git a/src/types/modules/fs.d.ts b/src/types/modules/fs.d.ts index 98c863e..69370e3 100644 --- a/src/types/modules/fs.d.ts +++ b/src/types/modules/fs.d.ts @@ -66,10 +66,10 @@ declare module 'fs' { }; const promises: typeof import('fs/promises'); - function readFile(path: string, encoding: Encoding): Promise; - function readFile(path: string): Promise; - function readFileSync(path: string, encoding: Encoding | { encoding: Encoding }): string; - function readFileSync(path: string): Uint8Array; + function readFile(path: string | URL, encoding: Encoding): Promise; + function readFile(path: string | URL): Promise; + function readFileSync(path: string | URL, encoding: Encoding | { encoding: Encoding }): string; + function readFileSync(path: string | URL): Uint8Array; function read( fd: number, buffer: ArrayBufferView, @@ -245,8 +245,8 @@ declare module 'fs/promises' { O_APPEND: number; }; - function readFile(path: string, encoding: Encoding): Promise; - function readFile(path: string): Promise; + function readFile(path: string | URL, encoding: Encoding): Promise; + function readFile(path: string | URL): Promise; function open(path: string, flags?: string, mode?: number): Promise; function close(fd: number): Promise; function writeFile(path: string, data: string | ArrayBufferView): Promise; diff --git a/tests/test_fs_readfile_url.mjs b/tests/test_fs_readfile_url.mjs new file mode 100644 index 0000000..aef13a9 --- /dev/null +++ b/tests/test_fs_readfile_url.mjs @@ -0,0 +1,44 @@ +import * as fs from 'node:fs'; +import * as fsp from 'node:fs/promises'; +import path from 'node:path'; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +async function readFileCallback(url, encoding) { + return await new Promise((resolve, reject) => { + fs.readFile(url, encoding, (error, data) => { + if (error) reject(error); + else resolve(data); + }); + }); +} + +async function main() { + const dirPath = path.join(import.meta.dirname, '.fs url tmp'); + const filePath = path.join(dirPath, 'url file.txt'); + const content = 'hello from file URL reads'; + + fs.rmSync(dirPath, { recursive: true, force: true }); + fs.mkdirSync(dirPath, { recursive: true }); + fs.writeFileSync(filePath, content); + const fileUrl = new URL(`file://${encodeURI(filePath)}`); + + try { + const syncContent = fs.readFileSync(fileUrl, 'utf8'); + assert(syncContent === content, `expected sync read to match, got ${JSON.stringify(syncContent)}`); + + const callbackContent = await readFileCallback(fileUrl, 'utf8'); + assert(callbackContent === content, `expected callback read to match, got ${JSON.stringify(callbackContent)}`); + + const promiseContent = await fsp.readFile(fileUrl, 'utf8'); + assert(promiseContent === content, `expected promise read to match, got ${JSON.stringify(promiseContent)}`); + + console.log('fs readFile accepts file URLs'); + } finally { + fs.rmSync(dirPath, { recursive: true, force: true }); + } +} + +await main(); -- 2.51.2