123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- describe("basic usage", () => {
- test.xfailIf(isBytecodeInterpreterEnabled(), "disposes after block exit", () => {
- let disposed = false;
- let inBlock = false;
- {
- expect(disposed).toBeFalse();
- using a = { [Symbol.dispose]() { disposed = true; } };
- inBlock = true;
- expect(disposed).toBeFalse();
- }
- expect(inBlock).toBeTrue();
- expect(disposed).toBeTrue();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "disposes in reverse order after block exit", () => {
- const disposed = [];
- {
- expect(disposed).toHaveLength(0);
- using a = { [Symbol.dispose]() { disposed.push('a'); } };
- using b = { [Symbol.dispose]() { disposed.push('b'); } };
- expect(disposed).toHaveLength(0);
- }
- expect(disposed).toEqual(['b', 'a']);
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "disposes in reverse order after block exit even in same declaration", () => {
- const disposed = [];
- {
- expect(disposed).toHaveLength(0);
- using a = { [Symbol.dispose]() { disposed.push('a'); } },
- b = { [Symbol.dispose]() { disposed.push('b'); } };
- expect(disposed).toHaveLength(0);
- }
- expect(disposed).toEqual(['b', 'a']);
- });
- });
- describe("behavior with exceptions", () => {
- function ExpectedError(name) { this.name = name; }
- test.xfailIf(isBytecodeInterpreterEnabled(), "is run even after throw", () => {
- let disposed = false;
- let inBlock = false;
- let inCatch = false;
- try {
- expect(disposed).toBeFalse();
- using a = { [Symbol.dispose]() { disposed = true; } };
- 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();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "throws error if dispose method does", () => {
- let disposed = false;
- let endOfTry = false;
- let inCatch = false;
- try {
- expect(disposed).toBeFalse();
- using a = { [Symbol.dispose]() {
- disposed = true;
- throw new ExpectedError();
- } };
- 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();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "if block and using throw get suppressed error", () => {
- let disposed = false;
- let inCatch = false;
- try {
- expect(disposed).toBeFalse();
- using a = { [Symbol.dispose]() {
- disposed = true;
- throw new ExpectedError('dispose');
- } };
- expect(disposed).toBeFalse();
- throw new ExpectedError('throw');
- } catch (e) {
- expect(disposed).toBeTrue();
- expect(e).toBeInstanceOf(SuppressedError);
- expect(e.error).toBeInstanceOf(ExpectedError);
- expect(e.error.name).toBe('dispose');
- expect(e.suppressed).toBeInstanceOf(ExpectedError);
- expect(e.suppressed.name).toBe('throw');
- inCatch = true;
- }
- expect(disposed).toBeTrue();
- expect(inCatch).toBeTrue();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "multiple throwing disposes give suppressed error", () => {
- let inCatch = false;
- try {
- {
- using a = { [Symbol.dispose]() {
- throw new ExpectedError('a');
- } };
- using b = { [Symbol.dispose]() {
- throw new ExpectedError('b');
- } };
- }
- expect().fail();
- } catch (e) {
- expect(e).toBeInstanceOf(SuppressedError);
- expect(e.error).toBeInstanceOf(ExpectedError);
- expect(e.error.name).toBe('a');
- expect(e.suppressed).toBeInstanceOf(ExpectedError);
- expect(e.suppressed.name).toBe('b');
- inCatch = true;
- }
- expect(inCatch).toBeTrue();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "3 throwing disposes give chaining suppressed error", () => {
- let inCatch = false;
- try {
- {
- using a = { [Symbol.dispose]() {
- throw new ExpectedError('a');
- } };
- using b = { [Symbol.dispose]() {
- throw new ExpectedError('b');
- } };
- using c = { [Symbol.dispose]() {
- throw new ExpectedError('c');
- } };
- }
- expect().fail();
- } catch (e) {
- expect(e).toBeInstanceOf(SuppressedError);
- expect(e.error).toBeInstanceOf(ExpectedError);
- expect(e.error.name).toBe('a');
- expect(e.suppressed).toBeInstanceOf(SuppressedError);
- const inner = e.suppressed;
- expect(inner.error).toBeInstanceOf(ExpectedError);
- expect(inner.error.name).toBe('b');
- expect(inner.suppressed).toBeInstanceOf(ExpectedError);
- expect(inner.suppressed.name).toBe('c');
- inCatch = true;
- }
- expect(inCatch).toBeTrue();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "normal error and multiple disposing erorrs give chaining suppressed errors", () => {
- let inCatch = false;
- try {
- using a = { [Symbol.dispose]() {
- throw new ExpectedError('a');
- } };
- using b = { [Symbol.dispose]() {
- throw new ExpectedError('b');
- } };
- throw new ExpectedError('top');
- } catch (e) {
- expect(e).toBeInstanceOf(SuppressedError);
- expect(e.error).toBeInstanceOf(ExpectedError);
- expect(e.error.name).toBe('a');
- expect(e.suppressed).toBeInstanceOf(SuppressedError);
- const inner = e.suppressed;
- expect(inner.error).toBeInstanceOf(ExpectedError);
- expect(inner.error.name).toBe('b');
- expect(inner.suppressed).toBeInstanceOf(ExpectedError);
- expect(inner.suppressed.name).toBe('top');
- inCatch = true;
- }
- expect(inCatch).toBeTrue();
- });
- });
- describe("works in a bunch of scopes", () => {
- test.xfailIf(isBytecodeInterpreterEnabled(), "works in block", () => {
- let dispose = false;
- expect(dispose).toBeFalse();
- {
- expect(dispose).toBeFalse();
- using a = { [Symbol.dispose]() { dispose = true; } }
- expect(dispose).toBeFalse();
- }
- expect(dispose).toBeTrue();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "works in static class block", () => {
- let dispose = false;
- expect(dispose).toBeFalse();
- class A {
- static {
- expect(dispose).toBeFalse();
- using a = { [Symbol.dispose]() { dispose = true; } }
- expect(dispose).toBeFalse();
- }
- }
- expect(dispose).toBeTrue();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "works in function", () => {
- let dispose = [];
- function f(val) {
- const disposeLength = dispose.length;
- using a = { [Symbol.dispose]() { dispose.push(val); } }
- expect(dispose.length).toBe(disposeLength);
- }
- expect(dispose).toEqual([]);
- f(0);
- expect(dispose).toEqual([0]);
- f(1);
- expect(dispose).toEqual([0, 1]);
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "switch block is treated as full block in function", () => {
- let disposeFull = [];
- let disposeInner = false;
- function pusher(val) {
- return {
- val,
- [Symbol.dispose]() { disposeFull.push(val); }
- };
- }
- switch (2) {
- case 3:
- using notDisposed = { [Symbol.dispose]() { expect().fail("not-disposed 1"); } };
- case 2:
- expect(disposeFull).toEqual([]);
- using a = pusher('a');
- expect(disposeFull).toEqual([]);
- using b = pusher('b');
- expect(disposeFull).toEqual([]);
- expect(b.val).toBe('b');
- expect(disposeInner).toBeFalse();
- // fallthrough
- case 1: {
- expect(disposeFull).toEqual([]);
- expect(disposeInner).toBeFalse();
- using inner = { [Symbol.dispose]() { disposeInner = true; } }
- expect(disposeInner).toBeFalse();
- }
- expect(disposeInner).toBeTrue();
- using c = pusher('c');
- expect(c.val).toBe('c');
- break;
- case 0:
- using notDisposed2 = { [Symbol.dispose]() { expect().fail("not-disposed 2"); } };
- }
- expect(disposeInner).toBeTrue();
- expect(disposeFull).toEqual(['c', 'b', 'a']);
- });
- });
- describe("invalid using bindings", () => {
- test.xfailIf(isBytecodeInterpreterEnabled(), "nullish values do not throw", () => {
- using a = null, b = undefined;
- expect(a).toBeNull();
- expect(b).toBeUndefined();
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "non-object throws", () => {
- [0, "a", true, NaN, 4n, Symbol.dispose].forEach(value => {
- expect(() => {
- using v = value;
- }).toThrowWithMessage(TypeError, "is not an object");
- });
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "object without dispose throws", () => {
- expect(() => {
- using a = {};
- }).toThrowWithMessage(TypeError, "does not have dispose method");
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "object with non callable dispose throws", () => {
- [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);
- });
- test.xfailIf(isBytecodeInterpreterEnabled(), "using", () => {
- "use strict";
- using using = null;
- expect(using).toBeNull();
- });
- test("function", () => {
- "use strict";
- function using() { return 1; }
- expect(using()).toBe(1);
- });
- });
- describe("syntax errors / werid artifacts which remain valid", () => {
- 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();
- });
- });
|