Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
8 changes: 4 additions & 4 deletions src/js/node/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,13 +430,13 @@ var access = function access(path: PathLike, mode?: number | Callback, callback?
callback = ensureCallback(type);
type = undefined;
} else if ($isCallable(callback)) {
// Not ensureCallback: node does not validate the 4-argument overload's
// callback, and a non-callable one must stay an ignored `.then` handler.
callback = wrapFsCallback(callback);
} else {
// node does not validate this overload's callback: it skips a non-function one, on failure too
callback = () => {};
}

if ($isCallable(callback)) fs.symlinkCb(err => callback(err === null ? undefined : err), target, path, type);
else fs.symlink(target, path, type).then(callback, callback);
fs.symlinkCb(err => callback(err === null ? undefined : err), target, path, type);
Comment thread
robobun marked this conversation as resolved.
Resolved
},
truncate = function truncate(path, len, callback?) {
if (typeof path === "number") {
Expand Down
48 changes: 41 additions & 7 deletions test/js/node/fs/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7572,13 +7572,47 @@ describe("a throw from a node-style callback is an uncaughtException", () => {
expect(exitCode).toBe(0);
});

it("leaves a non-callable symlink callback as an ignored handler, like node", async () => {
const { stdout, exitCode } = await runScript(`
require("fs").symlink(${file}, ${dirLit} + "/lnc", "file", "notafunc");
setTimeout(() => console.log("quiet"), 50);
`);
expect(stdout).toBe("quiet");
expect(exitCode).toBe(0);
// node does not validate the 4-argument overload's callback: it is stored on
// the request as-is and a non-function one is skipped when the operation
// completes, whether the link was created or the operation failed. The
// pending operation keeps the process alive, so "exited" is printed after
// it completed and anything it reported would precede it.
describe.concurrent("ignores a non-callable 4-argument symlink callback, like node", () => {
function symlinkIgnoringCallback(link: string, callbackLiteral: string) {
return runScript(`
process.on("uncaughtException", e => console.log("UNCAUGHT:" + e.code));
process.on("unhandledRejection", e => console.log("REJECTED:" + e.code));
process.on("exit", () => console.log("exited"));
require("fs").symlink(${file}, ${JSON.stringify(link)}, "file", ${callbackLiteral});
`);
}

it("still creates the link", async () => {
const link = join(dir, "lnc-created");
const { stdout, exitCode } = await symlinkIgnoringCallback(link, `"notafunc"`);
expect(stdout).toBe("exited");
expect(lstatSync(link).isSymbolicLink()).toBe(true);
expect(exitCode).toBe(0);
});

it.each([`"notafunc"`, "null"])("drops the error of a failed operation (callback: %s)", async callbackLiteral => {
const link = join(dir, "missing-dir", "lnc");
const { stdout, exitCode } = await symlinkIgnoringCallback(link, callbackLiteral);
expect(stdout).toBe("exited");
expect(existsSync(link)).toBe(false);
Comment thread
robobun marked this conversation as resolved.
Resolved
expect(exitCode).toBe(0);
});

// An undefined callback selects the 3-argument overload, whose callback
// (here the string "file") node does validate.
it("still throws when the callback is undefined", () => {
const link = join(dir, "lnc-undefined");
expect(() => fs.symlink(join(dir, "file.txt"), link, "file", undefined as any)).toThrowWithCode(
TypeError,
"ERR_INVALID_ARG_TYPE",
);
expect(existsSync(link)).toBe(false);
});
});
});

Expand Down
Loading