2022-12-20 22:09:57 +01:00
|
|
|
describe("basic usage", () => {
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("disposes after block exit", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let disposed = false;
|
|
|
|
let inBlock = false;
|
|
|
|
{
|
|
|
|
expect(disposed).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposed = true;
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
inBlock = true;
|
|
|
|
expect(disposed).toBeFalse();
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(inBlock).toBeTrue();
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("disposes in reverse order after block exit", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
const disposed = [];
|
|
|
|
{
|
|
|
|
expect(disposed).toHaveLength(0);
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposed.push("a");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
using b = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposed.push("b");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposed).toHaveLength(0);
|
|
|
|
}
|
|
|
|
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(disposed).toEqual(["b", "a"]);
|
2022-12-20 22:09:57 +01:00
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("disposes in reverse order after block exit even in same declaration", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
const disposed = [];
|
|
|
|
{
|
|
|
|
expect(disposed).toHaveLength(0);
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposed.push("a");
|
|
|
|
},
|
|
|
|
},
|
|
|
|
b = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposed.push("b");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposed).toHaveLength(0);
|
|
|
|
}
|
|
|
|
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(disposed).toEqual(["b", "a"]);
|
2022-12-20 22:09:57 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("behavior with exceptions", () => {
|
2025-10-03 10:24:09 -07:00
|
|
|
function ExpectedError(name) {
|
|
|
|
this.name = name;
|
|
|
|
}
|
2022-12-20 22:09:57 +01:00
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("is run even after throw", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let disposed = false;
|
|
|
|
let inBlock = false;
|
|
|
|
let inCatch = false;
|
|
|
|
try {
|
|
|
|
expect(disposed).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposed = true;
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
inBlock = true;
|
|
|
|
expect(disposed).toBeFalse();
|
|
|
|
throw new ExpectedError();
|
|
|
|
expect().fail();
|
|
|
|
} catch (e) {
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
expect(e).toBeInstanceOf(ExpectedError);
|
|
|
|
inCatch = true;
|
|
|
|
}
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
expect(inBlock).toBeTrue();
|
|
|
|
expect(inCatch).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("throws error if dispose method does", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let disposed = false;
|
|
|
|
let endOfTry = false;
|
|
|
|
let inCatch = false;
|
|
|
|
try {
|
|
|
|
expect(disposed).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
2022-12-20 22:09:57 +01:00
|
|
|
disposed = true;
|
|
|
|
throw new ExpectedError();
|
2025-10-03 10:24:09 -07:00
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposed).toBeFalse();
|
|
|
|
endOfTry = true;
|
|
|
|
} catch (e) {
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
expect(e).toBeInstanceOf(ExpectedError);
|
|
|
|
inCatch = true;
|
|
|
|
}
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
expect(endOfTry).toBeTrue();
|
|
|
|
expect(inCatch).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("if block and using throw get suppressed error", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let disposed = false;
|
|
|
|
let inCatch = false;
|
|
|
|
try {
|
|
|
|
expect(disposed).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
2022-12-20 22:09:57 +01:00
|
|
|
disposed = true;
|
2025-10-03 10:24:09 -07:00
|
|
|
throw new ExpectedError("dispose");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposed).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
throw new ExpectedError("throw");
|
2022-12-20 22:09:57 +01:00
|
|
|
} catch (e) {
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
expect(e).toBeInstanceOf(SuppressedError);
|
|
|
|
expect(e.error).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(e.error.name).toBe("dispose");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(e.suppressed).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(e.suppressed.name).toBe("throw");
|
2022-12-20 22:09:57 +01:00
|
|
|
inCatch = true;
|
|
|
|
}
|
|
|
|
expect(disposed).toBeTrue();
|
|
|
|
expect(inCatch).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("multiple throwing disposes give suppressed error", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let inCatch = false;
|
|
|
|
try {
|
|
|
|
{
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("a");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
using b = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("b");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
expect().fail();
|
|
|
|
} catch (e) {
|
|
|
|
expect(e).toBeInstanceOf(SuppressedError);
|
|
|
|
expect(e.error).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(e.error.name).toBe("a");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(e.suppressed).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(e.suppressed.name).toBe("b");
|
2022-12-20 22:09:57 +01:00
|
|
|
inCatch = true;
|
|
|
|
}
|
|
|
|
expect(inCatch).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("3 throwing disposes give chaining suppressed error", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let inCatch = false;
|
|
|
|
try {
|
|
|
|
{
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("a");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
using b = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("b");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
using c = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("c");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
expect().fail();
|
|
|
|
} catch (e) {
|
|
|
|
expect(e).toBeInstanceOf(SuppressedError);
|
|
|
|
expect(e.error).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(e.error.name).toBe("a");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(e.suppressed).toBeInstanceOf(SuppressedError);
|
|
|
|
|
|
|
|
const inner = e.suppressed;
|
|
|
|
|
|
|
|
expect(inner.error).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(inner.error.name).toBe("b");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(inner.suppressed).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(inner.suppressed.name).toBe("c");
|
2022-12-20 22:09:57 +01:00
|
|
|
inCatch = true;
|
|
|
|
}
|
|
|
|
expect(inCatch).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2025-04-09 17:02:30 +00:00
|
|
|
test.xfail("normal error and multiple disposing errors give chaining suppressed errors", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let inCatch = false;
|
|
|
|
try {
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("a");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
|
2025-10-03 10:24:09 -07:00
|
|
|
using b = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
throw new ExpectedError("b");
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
|
2025-10-03 10:24:09 -07:00
|
|
|
throw new ExpectedError("top");
|
2022-12-20 22:09:57 +01:00
|
|
|
} catch (e) {
|
|
|
|
expect(e).toBeInstanceOf(SuppressedError);
|
|
|
|
expect(e.error).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(e.error.name).toBe("a");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(e.suppressed).toBeInstanceOf(SuppressedError);
|
|
|
|
|
|
|
|
const inner = e.suppressed;
|
|
|
|
|
|
|
|
expect(inner.error).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(inner.error.name).toBe("b");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(inner.suppressed).toBeInstanceOf(ExpectedError);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(inner.suppressed.name).toBe("top");
|
2022-12-20 22:09:57 +01:00
|
|
|
inCatch = true;
|
|
|
|
}
|
|
|
|
expect(inCatch).toBeTrue();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("works in a bunch of scopes", () => {
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("works in block", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let dispose = false;
|
|
|
|
expect(dispose).toBeFalse();
|
|
|
|
{
|
|
|
|
expect(dispose).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
dispose = true;
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(dispose).toBeFalse();
|
|
|
|
}
|
|
|
|
expect(dispose).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("works in static class block", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let dispose = false;
|
|
|
|
expect(dispose).toBeFalse();
|
|
|
|
class A {
|
|
|
|
static {
|
|
|
|
expect(dispose).toBeFalse();
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
dispose = true;
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(dispose).toBeFalse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
expect(dispose).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("works in function", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let dispose = [];
|
|
|
|
function f(val) {
|
|
|
|
const disposeLength = dispose.length;
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
dispose.push(val);
|
|
|
|
},
|
|
|
|
};
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(dispose.length).toBe(disposeLength);
|
|
|
|
}
|
|
|
|
expect(dispose).toEqual([]);
|
|
|
|
f(0);
|
|
|
|
expect(dispose).toEqual([0]);
|
|
|
|
f(1);
|
|
|
|
expect(dispose).toEqual([0, 1]);
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("switch block is treated as full block in function", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
let disposeFull = [];
|
|
|
|
let disposeInner = false;
|
|
|
|
|
|
|
|
function pusher(val) {
|
|
|
|
return {
|
|
|
|
val,
|
2025-10-03 10:24:09 -07:00
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposeFull.push(val);
|
|
|
|
},
|
2022-12-20 22:09:57 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (2) {
|
2025-10-03 10:27:20 -07:00
|
|
|
case 3: {
|
|
|
|
using notDisposed = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
expect().fail("not-disposed 1");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
case 2: {
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposeFull).toEqual([]);
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = pusher("a");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposeFull).toEqual([]);
|
|
|
|
|
2025-10-03 10:24:09 -07:00
|
|
|
using b = pusher("b");
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposeFull).toEqual([]);
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(b.val).toBe("b");
|
2022-12-20 22:09:57 +01:00
|
|
|
|
|
|
|
expect(disposeInner).toBeFalse();
|
|
|
|
}
|
2025-10-03 10:27:20 -07:00
|
|
|
// fallthrough
|
|
|
|
case 1:
|
|
|
|
{
|
|
|
|
expect(disposeFull).toEqual([]);
|
|
|
|
expect(disposeInner).toBeFalse();
|
|
|
|
|
|
|
|
using inner = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
disposeInner = true;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
expect(disposeInner).toBeFalse();
|
|
|
|
}
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(disposeInner).toBeTrue();
|
2025-10-03 10:27:20 -07:00
|
|
|
{
|
|
|
|
using c = pusher("c");
|
|
|
|
expect(c.val).toBe("c");
|
|
|
|
}
|
2022-12-20 22:09:57 +01:00
|
|
|
break;
|
2025-10-03 10:27:20 -07:00
|
|
|
case 0: {
|
|
|
|
using notDisposed2 = {
|
|
|
|
[Symbol.dispose]() {
|
|
|
|
expect().fail("not-disposed 2");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2022-12-20 22:09:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
expect(disposeInner).toBeTrue();
|
2025-10-03 10:24:09 -07:00
|
|
|
expect(disposeFull).toEqual(["c", "b", "a"]);
|
2022-12-20 22:09:57 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("invalid using bindings", () => {
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("nullish values do not throw", () => {
|
2025-10-03 10:24:09 -07:00
|
|
|
using a = null,
|
|
|
|
b = undefined;
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(a).toBeNull();
|
|
|
|
expect(b).toBeUndefined();
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("non-object throws", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
[0, "a", true, NaN, 4n, Symbol.dispose].forEach(value => {
|
|
|
|
expect(() => {
|
|
|
|
using v = value;
|
|
|
|
}).toThrowWithMessage(TypeError, "is not an object");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("object without dispose throws", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(() => {
|
|
|
|
using a = {};
|
|
|
|
}).toThrowWithMessage(TypeError, "does not have dispose method");
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("object with non callable dispose throws", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
[0, "a", true, NaN, 4n, Symbol.dispose, [], {}].forEach(value => {
|
|
|
|
expect(() => {
|
|
|
|
using a = { [Symbol.dispose]: value };
|
|
|
|
}).toThrowWithMessage(TypeError, "is not a function");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("using is still a valid variable name", () => {
|
|
|
|
test("var", () => {
|
|
|
|
"use strict";
|
|
|
|
var using = 1;
|
|
|
|
expect(using).toBe(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("const", () => {
|
|
|
|
"use strict";
|
|
|
|
const using = 1;
|
|
|
|
expect(using).toBe(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("let", () => {
|
|
|
|
"use strict";
|
|
|
|
let using = 1;
|
|
|
|
expect(using).toBe(1);
|
|
|
|
});
|
|
|
|
|
2023-08-09 15:14:05 -04:00
|
|
|
test.xfail("using", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
"use strict";
|
|
|
|
using using = null;
|
|
|
|
expect(using).toBeNull();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("function", () => {
|
|
|
|
"use strict";
|
2025-10-03 10:24:09 -07:00
|
|
|
function using() {
|
|
|
|
return 1;
|
|
|
|
}
|
2022-12-20 22:09:57 +01:00
|
|
|
expect(using()).toBe(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2025-04-09 17:02:30 +00:00
|
|
|
describe("syntax errors / weird artifacts which remain valid", () => {
|
2022-12-20 22:09:57 +01:00
|
|
|
test("no patterns in using", () => {
|
|
|
|
expect("using {a} = {}").not.toEval();
|
|
|
|
expect("using a, {a} = {}").not.toEval();
|
|
|
|
expect("using a = null, [b] = [null]").not.toEval();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("using with array pattern is valid array access", () => {
|
|
|
|
const using = [0, 9999];
|
|
|
|
const a = 1;
|
|
|
|
|
|
|
|
expect(eval("using [a] = 1")).toBe(1);
|
|
|
|
expect(using[1]).toBe(1);
|
|
|
|
|
|
|
|
expect(eval("using [{a: a}, a] = 2")).toBe(2);
|
|
|
|
expect(using[1]).toBe(2);
|
|
|
|
|
|
|
|
expect(eval("using [a, a] = 3")).toBe(3);
|
|
|
|
expect(using[1]).toBe(3);
|
|
|
|
|
|
|
|
expect(eval("using [[a, a], a] = 4")).toBe(4);
|
|
|
|
expect(using[1]).toBe(4);
|
|
|
|
|
|
|
|
expect(eval("using [2, 1, a] = 5")).toBe(5);
|
|
|
|
expect(using[1]).toBe(5);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("declaration without initializer", () => {
|
|
|
|
expect("using a").not.toEval();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("no repeat declarations in single using", () => {
|
|
|
|
expect("using a = null, a = null;").not.toEval();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("cannot have a using declaration named let", () => {
|
|
|
|
expect("using let = null").not.toEval();
|
|
|
|
});
|
|
|
|
});
|