using-declaration.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. describe("basic usage", () => {
  2. test.xfailIf(isBytecodeInterpreterEnabled(), "disposes after block exit", () => {
  3. let disposed = false;
  4. let inBlock = false;
  5. {
  6. expect(disposed).toBeFalse();
  7. using a = { [Symbol.dispose]() { disposed = true; } };
  8. inBlock = true;
  9. expect(disposed).toBeFalse();
  10. }
  11. expect(inBlock).toBeTrue();
  12. expect(disposed).toBeTrue();
  13. });
  14. test.xfailIf(isBytecodeInterpreterEnabled(), "disposes in reverse order after block exit", () => {
  15. const disposed = [];
  16. {
  17. expect(disposed).toHaveLength(0);
  18. using a = { [Symbol.dispose]() { disposed.push('a'); } };
  19. using b = { [Symbol.dispose]() { disposed.push('b'); } };
  20. expect(disposed).toHaveLength(0);
  21. }
  22. expect(disposed).toEqual(['b', 'a']);
  23. });
  24. test.xfailIf(isBytecodeInterpreterEnabled(), "disposes in reverse order after block exit even in same declaration", () => {
  25. const disposed = [];
  26. {
  27. expect(disposed).toHaveLength(0);
  28. using a = { [Symbol.dispose]() { disposed.push('a'); } },
  29. b = { [Symbol.dispose]() { disposed.push('b'); } };
  30. expect(disposed).toHaveLength(0);
  31. }
  32. expect(disposed).toEqual(['b', 'a']);
  33. });
  34. });
  35. describe("behavior with exceptions", () => {
  36. function ExpectedError(name) { this.name = name; }
  37. test.xfailIf(isBytecodeInterpreterEnabled(), "is run even after throw", () => {
  38. let disposed = false;
  39. let inBlock = false;
  40. let inCatch = false;
  41. try {
  42. expect(disposed).toBeFalse();
  43. using a = { [Symbol.dispose]() { disposed = true; } };
  44. inBlock = true;
  45. expect(disposed).toBeFalse();
  46. throw new ExpectedError();
  47. expect().fail();
  48. } catch (e) {
  49. expect(disposed).toBeTrue();
  50. expect(e).toBeInstanceOf(ExpectedError);
  51. inCatch = true;
  52. }
  53. expect(disposed).toBeTrue();
  54. expect(inBlock).toBeTrue();
  55. expect(inCatch).toBeTrue();
  56. });
  57. test.xfailIf(isBytecodeInterpreterEnabled(), "throws error if dispose method does", () => {
  58. let disposed = false;
  59. let endOfTry = false;
  60. let inCatch = false;
  61. try {
  62. expect(disposed).toBeFalse();
  63. using a = { [Symbol.dispose]() {
  64. disposed = true;
  65. throw new ExpectedError();
  66. } };
  67. expect(disposed).toBeFalse();
  68. endOfTry = true;
  69. } catch (e) {
  70. expect(disposed).toBeTrue();
  71. expect(e).toBeInstanceOf(ExpectedError);
  72. inCatch = true;
  73. }
  74. expect(disposed).toBeTrue();
  75. expect(endOfTry).toBeTrue();
  76. expect(inCatch).toBeTrue();
  77. });
  78. test.xfailIf(isBytecodeInterpreterEnabled(), "if block and using throw get suppressed error", () => {
  79. let disposed = false;
  80. let inCatch = false;
  81. try {
  82. expect(disposed).toBeFalse();
  83. using a = { [Symbol.dispose]() {
  84. disposed = true;
  85. throw new ExpectedError('dispose');
  86. } };
  87. expect(disposed).toBeFalse();
  88. throw new ExpectedError('throw');
  89. } catch (e) {
  90. expect(disposed).toBeTrue();
  91. expect(e).toBeInstanceOf(SuppressedError);
  92. expect(e.error).toBeInstanceOf(ExpectedError);
  93. expect(e.error.name).toBe('dispose');
  94. expect(e.suppressed).toBeInstanceOf(ExpectedError);
  95. expect(e.suppressed.name).toBe('throw');
  96. inCatch = true;
  97. }
  98. expect(disposed).toBeTrue();
  99. expect(inCatch).toBeTrue();
  100. });
  101. test.xfailIf(isBytecodeInterpreterEnabled(), "multiple throwing disposes give suppressed error", () => {
  102. let inCatch = false;
  103. try {
  104. {
  105. using a = { [Symbol.dispose]() {
  106. throw new ExpectedError('a');
  107. } };
  108. using b = { [Symbol.dispose]() {
  109. throw new ExpectedError('b');
  110. } };
  111. }
  112. expect().fail();
  113. } catch (e) {
  114. expect(e).toBeInstanceOf(SuppressedError);
  115. expect(e.error).toBeInstanceOf(ExpectedError);
  116. expect(e.error.name).toBe('a');
  117. expect(e.suppressed).toBeInstanceOf(ExpectedError);
  118. expect(e.suppressed.name).toBe('b');
  119. inCatch = true;
  120. }
  121. expect(inCatch).toBeTrue();
  122. });
  123. test.xfailIf(isBytecodeInterpreterEnabled(), "3 throwing disposes give chaining suppressed error", () => {
  124. let inCatch = false;
  125. try {
  126. {
  127. using a = { [Symbol.dispose]() {
  128. throw new ExpectedError('a');
  129. } };
  130. using b = { [Symbol.dispose]() {
  131. throw new ExpectedError('b');
  132. } };
  133. using c = { [Symbol.dispose]() {
  134. throw new ExpectedError('c');
  135. } };
  136. }
  137. expect().fail();
  138. } catch (e) {
  139. expect(e).toBeInstanceOf(SuppressedError);
  140. expect(e.error).toBeInstanceOf(ExpectedError);
  141. expect(e.error.name).toBe('a');
  142. expect(e.suppressed).toBeInstanceOf(SuppressedError);
  143. const inner = e.suppressed;
  144. expect(inner.error).toBeInstanceOf(ExpectedError);
  145. expect(inner.error.name).toBe('b');
  146. expect(inner.suppressed).toBeInstanceOf(ExpectedError);
  147. expect(inner.suppressed.name).toBe('c');
  148. inCatch = true;
  149. }
  150. expect(inCatch).toBeTrue();
  151. });
  152. test.xfailIf(isBytecodeInterpreterEnabled(), "normal error and multiple disposing erorrs give chaining suppressed errors", () => {
  153. let inCatch = false;
  154. try {
  155. using a = { [Symbol.dispose]() {
  156. throw new ExpectedError('a');
  157. } };
  158. using b = { [Symbol.dispose]() {
  159. throw new ExpectedError('b');
  160. } };
  161. throw new ExpectedError('top');
  162. } catch (e) {
  163. expect(e).toBeInstanceOf(SuppressedError);
  164. expect(e.error).toBeInstanceOf(ExpectedError);
  165. expect(e.error.name).toBe('a');
  166. expect(e.suppressed).toBeInstanceOf(SuppressedError);
  167. const inner = e.suppressed;
  168. expect(inner.error).toBeInstanceOf(ExpectedError);
  169. expect(inner.error.name).toBe('b');
  170. expect(inner.suppressed).toBeInstanceOf(ExpectedError);
  171. expect(inner.suppressed.name).toBe('top');
  172. inCatch = true;
  173. }
  174. expect(inCatch).toBeTrue();
  175. });
  176. });
  177. describe("works in a bunch of scopes", () => {
  178. test.xfailIf(isBytecodeInterpreterEnabled(), "works in block", () => {
  179. let dispose = false;
  180. expect(dispose).toBeFalse();
  181. {
  182. expect(dispose).toBeFalse();
  183. using a = { [Symbol.dispose]() { dispose = true; } }
  184. expect(dispose).toBeFalse();
  185. }
  186. expect(dispose).toBeTrue();
  187. });
  188. test.xfailIf(isBytecodeInterpreterEnabled(), "works in static class block", () => {
  189. let dispose = false;
  190. expect(dispose).toBeFalse();
  191. class A {
  192. static {
  193. expect(dispose).toBeFalse();
  194. using a = { [Symbol.dispose]() { dispose = true; } }
  195. expect(dispose).toBeFalse();
  196. }
  197. }
  198. expect(dispose).toBeTrue();
  199. });
  200. test.xfailIf(isBytecodeInterpreterEnabled(), "works in function", () => {
  201. let dispose = [];
  202. function f(val) {
  203. const disposeLength = dispose.length;
  204. using a = { [Symbol.dispose]() { dispose.push(val); } }
  205. expect(dispose.length).toBe(disposeLength);
  206. }
  207. expect(dispose).toEqual([]);
  208. f(0);
  209. expect(dispose).toEqual([0]);
  210. f(1);
  211. expect(dispose).toEqual([0, 1]);
  212. });
  213. test.xfailIf(isBytecodeInterpreterEnabled(), "switch block is treated as full block in function", () => {
  214. let disposeFull = [];
  215. let disposeInner = false;
  216. function pusher(val) {
  217. return {
  218. val,
  219. [Symbol.dispose]() { disposeFull.push(val); }
  220. };
  221. }
  222. switch (2) {
  223. case 3:
  224. using notDisposed = { [Symbol.dispose]() { expect().fail("not-disposed 1"); } };
  225. case 2:
  226. expect(disposeFull).toEqual([]);
  227. using a = pusher('a');
  228. expect(disposeFull).toEqual([]);
  229. using b = pusher('b');
  230. expect(disposeFull).toEqual([]);
  231. expect(b.val).toBe('b');
  232. expect(disposeInner).toBeFalse();
  233. // fallthrough
  234. case 1: {
  235. expect(disposeFull).toEqual([]);
  236. expect(disposeInner).toBeFalse();
  237. using inner = { [Symbol.dispose]() { disposeInner = true; } }
  238. expect(disposeInner).toBeFalse();
  239. }
  240. expect(disposeInner).toBeTrue();
  241. using c = pusher('c');
  242. expect(c.val).toBe('c');
  243. break;
  244. case 0:
  245. using notDisposed2 = { [Symbol.dispose]() { expect().fail("not-disposed 2"); } };
  246. }
  247. expect(disposeInner).toBeTrue();
  248. expect(disposeFull).toEqual(['c', 'b', 'a']);
  249. });
  250. });
  251. describe("invalid using bindings", () => {
  252. test.xfailIf(isBytecodeInterpreterEnabled(), "nullish values do not throw", () => {
  253. using a = null, b = undefined;
  254. expect(a).toBeNull();
  255. expect(b).toBeUndefined();
  256. });
  257. test.xfailIf(isBytecodeInterpreterEnabled(), "non-object throws", () => {
  258. [0, "a", true, NaN, 4n, Symbol.dispose].forEach(value => {
  259. expect(() => {
  260. using v = value;
  261. }).toThrowWithMessage(TypeError, "is not an object");
  262. });
  263. });
  264. test.xfailIf(isBytecodeInterpreterEnabled(), "object without dispose throws", () => {
  265. expect(() => {
  266. using a = {};
  267. }).toThrowWithMessage(TypeError, "does not have dispose method");
  268. });
  269. test.xfailIf(isBytecodeInterpreterEnabled(), "object with non callable dispose throws", () => {
  270. [0, "a", true, NaN, 4n, Symbol.dispose, [], {}].forEach(value => {
  271. expect(() => {
  272. using a = { [Symbol.dispose]: value };
  273. }).toThrowWithMessage(TypeError, "is not a function");
  274. });
  275. });
  276. });
  277. describe("using is still a valid variable name", () => {
  278. test("var", () => {
  279. "use strict";
  280. var using = 1;
  281. expect(using).toBe(1);
  282. });
  283. test("const", () => {
  284. "use strict";
  285. const using = 1;
  286. expect(using).toBe(1);
  287. });
  288. test("let", () => {
  289. "use strict";
  290. let using = 1;
  291. expect(using).toBe(1);
  292. });
  293. test.xfailIf(isBytecodeInterpreterEnabled(), "using", () => {
  294. "use strict";
  295. using using = null;
  296. expect(using).toBeNull();
  297. });
  298. test("function", () => {
  299. "use strict";
  300. function using() { return 1; }
  301. expect(using()).toBe(1);
  302. });
  303. });
  304. describe("syntax errors / werid artifacts which remain valid", () => {
  305. test("no patterns in using", () => {
  306. expect("using {a} = {}").not.toEval();
  307. expect("using a, {a} = {}").not.toEval();
  308. expect("using a = null, [b] = [null]").not.toEval();
  309. });
  310. test("using with array pattern is valid array access", () => {
  311. const using = [0, 9999];
  312. const a = 1;
  313. expect(eval("using [a] = 1")).toBe(1);
  314. expect(using[1]).toBe(1);
  315. expect(eval("using [{a: a}, a] = 2")).toBe(2);
  316. expect(using[1]).toBe(2);
  317. expect(eval("using [a, a] = 3")).toBe(3);
  318. expect(using[1]).toBe(3);
  319. expect(eval("using [[a, a], a] = 4")).toBe(4);
  320. expect(using[1]).toBe(4);
  321. expect(eval("using [2, 1, a] = 5")).toBe(5);
  322. expect(using[1]).toBe(5);
  323. });
  324. test("declaration without initializer", () => {
  325. expect("using a").not.toEval();
  326. });
  327. test("no repeat declarations in single using", () => {
  328. expect("using a = null, a = null;").not.toEval();
  329. });
  330. test("cannot have a using declaration named let", () => {
  331. expect("using let = null").not.toEval();
  332. });
  333. });