Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
f8e232b267 Fix node:readline Interface not being disposable
Add Symbol.dispose method to readline.Interface classes to support
JavaScript's `using` statement for automatic resource cleanup.

The Symbol.dispose method is an alias for the existing close() method,
following Node.js behavior where rl[Symbol.dispose]() is equivalent
to rl.close().

Changes:
- Add Symbol.dispose method to _Interface class
- Ensure Interface prototype includes Symbol.dispose method
- Add comprehensive tests for both readline and readline/promises
- Test both direct Symbol.dispose calls and using statement behavior

Fixes #22014

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 04:40:28 +00:00
3 changed files with 105 additions and 0 deletions

View File

@@ -1419,6 +1419,14 @@ var _Interface = class Interface extends InterfaceConstructor {
this.emit("close");
}
/**
* Alias for `close()`. Provides Symbol.dispose support for `using` statements.
* @returns {void}
*/
[Symbol.dispose]() {
this.close();
}
/**
* Pauses the `input` stream.
* @returns {void | Interface}
@@ -2533,6 +2541,9 @@ Interface.prototype._getCursorPos = _Interface.prototype.getCursorPos;
Interface.prototype._moveCursor = _Interface.prototype[kMoveCursor];
Interface.prototype._ttyWrite = _Interface.prototype[kTtyWrite];
// Add Symbol.dispose method for disposable support
Interface.prototype[Symbol.dispose] = _Interface.prototype[Symbol.dispose];
function _ttyWriteDumb(s, key) {
key = key || kEmptyObject;

View File

@@ -2021,6 +2021,54 @@ describe("readline.createInterface()", () => {
});
});
it("should support Symbol.dispose for using statements", () => {
const input = new PassThrough();
const output = new PassThrough();
let closed = false;
{
using rl = readline.createInterface({
input: input,
output: output,
});
rl.on("close", () => {
closed = true;
});
// Verify the interface has the Symbol.dispose method
assert.strictEqual(typeof rl[Symbol.dispose], "function");
assert.strictEqual(!closed, true);
}
// After exiting the using block, the interface should be closed
assert.strictEqual(closed, true);
});
it("should support Symbol.dispose as alias for close()", () => {
const input = new PassThrough();
const output = new PassThrough();
let closed = false;
const rl = readline.createInterface({
input: input,
output: output,
});
rl.on("close", () => {
closed = true;
});
// Verify Symbol.dispose exists and works the same as close()
assert.strictEqual(typeof rl[Symbol.dispose], "function");
assert.strictEqual(!closed, true);
rl[Symbol.dispose]();
assert.strictEqual(closed, true);
assert.strictEqual(rl.closed, true);
});
// TODO: Actual pseudo-tty test
// it("should operate correctly when process.env.DUMB is set", () => {
// process.env.TERM = "dumb";

View File

@@ -46,4 +46,50 @@ describe("readline/promises.createInterface()", () => {
done();
});
});
it("should support Symbol.dispose for using statements", () => {
const fi = new FakeInput();
let closed = false;
{
using rl = readlinePromises.createInterface({
input: fi,
output: fi,
});
rl.on("close", () => {
closed = true;
});
// Verify the interface has the Symbol.dispose method
assert.strictEqual(typeof rl[Symbol.dispose], "function");
assert.strictEqual(!closed, true);
}
// After exiting the using block, the interface should be closed
assert.strictEqual(closed, true);
});
it("should support Symbol.dispose as alias for close()", () => {
const fi = new FakeInput();
let closed = false;
const rl = readlinePromises.createInterface({
input: fi,
output: fi,
});
rl.on("close", () => {
closed = true;
});
// Verify Symbol.dispose exists and works the same as close()
assert.strictEqual(typeof rl[Symbol.dispose], "function");
assert.strictEqual(!closed, true);
rl[Symbol.dispose]();
assert.strictEqual(closed, true);
assert.strictEqual(rl.closed, true);
});
});