ScopeTests.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. using Jint.Native;
  2. using Jint.Runtime.Debugger;
  3. namespace Jint.Tests.Runtime.Debugger;
  4. public class ScopeTests
  5. {
  6. private static JsValue AssertOnlyScopeContains(DebugScopes scopes, string name, DebugScopeType scopeType)
  7. {
  8. var containingScope = Assert.Single(scopes, s => s.ScopeType == scopeType && s.BindingNames.Contains(name));
  9. Assert.DoesNotContain(scopes, s => s != containingScope && s.BindingNames.Contains(name));
  10. return containingScope.GetBindingValue(name);
  11. }
  12. private static void AssertScope(DebugScope actual, DebugScopeType expectedType, params string[] expectedBindingNames)
  13. {
  14. Assert.Equal(expectedType, actual.ScopeType);
  15. // Global scope will have a number of intrinsic bindings that are outside the scope [no pun] of these tests
  16. if (actual.ScopeType != DebugScopeType.Global)
  17. {
  18. Assert.Equal(expectedBindingNames.Length, actual.BindingNames.Count);
  19. }
  20. foreach (string expectedName in expectedBindingNames)
  21. {
  22. Assert.Contains(expectedName, actual.BindingNames);
  23. }
  24. }
  25. [Fact]
  26. public void AllowsInspectionOfUninitializedGlobalBindings()
  27. {
  28. string script = @"
  29. debugger;
  30. const globalConstant = 'test';
  31. let globalLet = 'test';
  32. ";
  33. TestHelpers.TestAtBreak(script, info =>
  34. {
  35. // Uninitialized global block scoped ("script scoped") bindings return null (and, just as importantly, don't throw):
  36. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalConstant"));
  37. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalLet"));
  38. });
  39. }
  40. [Fact]
  41. public void AllowsInspectionOfUninitializedBlockBindings()
  42. {
  43. string script = @"
  44. function test()
  45. {
  46. debugger;
  47. const globalConstant = 'test';
  48. let globalLet = 'test';
  49. }
  50. test();
  51. ";
  52. TestHelpers.TestAtBreak(script, info =>
  53. {
  54. // Uninitialized block scoped bindings return null (and, just as importantly, don't throw):
  55. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalConstant"));
  56. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalLet"));
  57. });
  58. }
  59. [Fact]
  60. public void ScriptScopeIncludesGlobalConst()
  61. {
  62. string script = @"
  63. const globalConstant = 'test';
  64. debugger;
  65. ";
  66. TestHelpers.TestAtBreak(script, info =>
  67. {
  68. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalConstant", DebugScopeType.Script);
  69. Assert.Equal("test", value.AsString());
  70. });
  71. }
  72. [Fact]
  73. public void ScriptScopeIncludesGlobalLet()
  74. {
  75. string script = @"
  76. let globalLet = 'test';
  77. debugger;";
  78. TestHelpers.TestAtBreak(script, info =>
  79. {
  80. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalLet", DebugScopeType.Script);
  81. Assert.Equal("test", value.AsString());
  82. });
  83. }
  84. [Fact]
  85. public void GlobalScopeIncludesGlobalVar()
  86. {
  87. string script = @"
  88. var globalVar = 'test';
  89. debugger;";
  90. TestHelpers.TestAtBreak(script, info =>
  91. {
  92. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalVar", DebugScopeType.Global);
  93. Assert.Equal("test", value.AsString());
  94. });
  95. }
  96. [Fact]
  97. public void TopLevelBlockScopeIsIdentified()
  98. {
  99. string script = @"
  100. function test()
  101. {
  102. const localConst = 'test';
  103. debugger;
  104. }
  105. test();";
  106. TestHelpers.TestAtBreak(script, info =>
  107. {
  108. Assert.Equal(3, info.CurrentScopeChain.Count);
  109. Assert.Equal(DebugScopeType.Block, info.CurrentScopeChain[0].ScopeType);
  110. Assert.True(info.CurrentScopeChain[0].IsTopLevel);
  111. });
  112. }
  113. [Fact]
  114. public void NonTopLevelBlockScopeIsIdentified()
  115. {
  116. string script = @"
  117. function test()
  118. {
  119. {
  120. const localConst = 'test';
  121. debugger;
  122. }
  123. }
  124. test();";
  125. TestHelpers.TestAtBreak(script, info =>
  126. {
  127. // We only have 3 scopes, because the function top level block scope is empty.
  128. Assert.Equal(3, info.CurrentScopeChain.Count);
  129. Assert.Equal(DebugScopeType.Block, info.CurrentScopeChain[0].ScopeType);
  130. Assert.False(info.CurrentScopeChain[0].IsTopLevel);
  131. });
  132. }
  133. [Fact]
  134. public void BlockScopeIncludesLocalConst()
  135. {
  136. string script = @"
  137. function test()
  138. {
  139. {
  140. const localConst = 'test';
  141. debugger;
  142. }
  143. }
  144. test();";
  145. TestHelpers.TestAtBreak(script, info =>
  146. {
  147. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "localConst", DebugScopeType.Block);
  148. Assert.Equal("test", value.AsString());
  149. });
  150. }
  151. [Fact]
  152. public void BlockScopeIncludesLocalLet()
  153. {
  154. string script = @"
  155. function test()
  156. {
  157. {
  158. let localLet = 'test';
  159. debugger;
  160. }
  161. }
  162. test();";
  163. TestHelpers.TestAtBreak(script, info =>
  164. {
  165. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "localLet", DebugScopeType.Block);
  166. Assert.Equal("test", value.AsString());
  167. });
  168. }
  169. [Fact]
  170. public void LocalScopeIncludesLocalVar()
  171. {
  172. string script = @"
  173. function test()
  174. {
  175. var localVar = 'test';
  176. debugger;
  177. }
  178. test();";
  179. TestHelpers.TestAtBreak(script, info =>
  180. {
  181. AssertOnlyScopeContains(info.CurrentScopeChain, "localVar", DebugScopeType.Local);
  182. });
  183. }
  184. [Fact]
  185. public void LocalScopeIncludesBlockVar()
  186. {
  187. string script = @"
  188. function test()
  189. {
  190. debugger;
  191. {
  192. var localVar = 'test';
  193. }
  194. }
  195. test();";
  196. TestHelpers.TestAtBreak(script, info =>
  197. {
  198. AssertOnlyScopeContains(info.CurrentScopeChain, "localVar", DebugScopeType.Local);
  199. });
  200. }
  201. [Fact]
  202. public void BlockScopedConstIsVisibleInsideBlock()
  203. {
  204. string script = @"
  205. 'dummy statement';
  206. {
  207. const blockConst = 'block';
  208. debugger; // const isn't initialized until declaration
  209. }";
  210. TestHelpers.TestAtBreak(script, info =>
  211. {
  212. AssertOnlyScopeContains(info.CurrentScopeChain, "blockConst", DebugScopeType.Block);
  213. });
  214. }
  215. [Fact]
  216. public void BlockScopedLetIsVisibleInsideBlock()
  217. {
  218. string script = @"
  219. 'dummy statement';
  220. {
  221. let blockLet = 'block';
  222. debugger; // let isn't initialized until declaration
  223. }";
  224. TestHelpers.TestAtBreak(script, info =>
  225. {
  226. AssertOnlyScopeContains(info.CurrentScopeChain, "blockLet", DebugScopeType.Block);
  227. });
  228. }
  229. [Fact]
  230. public void HasCorrectScopeChainForFunction()
  231. {
  232. string script = @"
  233. function add(a, b)
  234. {
  235. debugger;
  236. return a + b;
  237. }
  238. const x = 1;
  239. const y = 2;
  240. const z = add(x, y);";
  241. TestHelpers.TestAtBreak(script, info =>
  242. {
  243. Assert.Collection(info.CurrentScopeChain,
  244. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  245. scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
  246. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  247. });
  248. }
  249. [Fact]
  250. public void HasCorrectScopeChainForNestedFunction()
  251. {
  252. string script = @"
  253. function add(a, b)
  254. {
  255. function power(a)
  256. {
  257. debugger;
  258. return a * a;
  259. }
  260. return power(a) + b;
  261. }
  262. const x = 1;
  263. const y = 2;
  264. const z = add(x, y);";
  265. TestHelpers.TestAtBreak(script, info =>
  266. {
  267. Assert.Collection(info.CurrentScopeChain,
  268. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a"),
  269. // a, arguments shadowed by local - but still exist in this scope
  270. scope => AssertScope(scope, DebugScopeType.Closure, "a", "arguments", "b", "power"),
  271. scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
  272. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  273. });
  274. }
  275. [Fact]
  276. public void HasCorrectScopeChainForBlock()
  277. {
  278. string script = @"
  279. function add(a, b)
  280. {
  281. if (a > 0)
  282. {
  283. const y = b / a;
  284. debugger;
  285. }
  286. return a + b;
  287. }
  288. const x = 1;
  289. const y = 2;
  290. const z = add(x, y);";
  291. TestHelpers.TestAtBreak(script, info =>
  292. {
  293. Assert.Collection(info.CurrentScopeChain,
  294. scope => AssertScope(scope, DebugScopeType.Block, "y"),
  295. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  296. scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // y is shadowed, but still in the scope
  297. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  298. });
  299. }
  300. [Fact]
  301. public void HasCorrectScopeChainForModule()
  302. {
  303. string imported = @"
  304. function add(a, b)
  305. {
  306. debugger;
  307. return a + b;
  308. }
  309. export { add };";
  310. string main = @"
  311. import { add } from 'imported-module';
  312. const x = 1;
  313. const y = 2;
  314. add(x, y);";
  315. TestHelpers.TestAtBreak(engine =>
  316. {
  317. engine.Modules.Add("imported-module", imported);
  318. engine.Modules.Add("main", main);
  319. engine.Modules.Import("main");
  320. },
  321. info =>
  322. {
  323. Assert.Collection(info.CurrentScopeChain,
  324. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  325. scope => AssertScope(scope, DebugScopeType.Module, "add"),
  326. scope => AssertScope(scope, DebugScopeType.Global));
  327. });
  328. }
  329. [Fact]
  330. public void HasCorrectScopeChainForNestedBlock()
  331. {
  332. string script = @"
  333. function add(a, b)
  334. {
  335. if (a > 0)
  336. {
  337. const y = b / a;
  338. if (y > 0)
  339. {
  340. const x = b / y;
  341. debugger;
  342. }
  343. }
  344. return a + b;
  345. }
  346. const x = 1;
  347. const y = 2;
  348. const z = add(x, y);";
  349. TestHelpers.TestAtBreak(script, info =>
  350. {
  351. Assert.Collection(info.CurrentScopeChain,
  352. scope => AssertScope(scope, DebugScopeType.Block, "x"),
  353. scope => AssertScope(scope, DebugScopeType.Block, "y"),
  354. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  355. scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // x, y are shadowed, but still in the scope
  356. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  357. });
  358. }
  359. [Fact]
  360. public void HasCorrectScopeChainForCatch()
  361. {
  362. string script = @"
  363. function func()
  364. {
  365. let a = 1;
  366. try
  367. {
  368. throw new Error('test');
  369. }
  370. catch (error)
  371. {
  372. debugger;
  373. }
  374. }
  375. func();";
  376. TestHelpers.TestAtBreak(script, info =>
  377. {
  378. Assert.Collection(info.CurrentScopeChain,
  379. scope => AssertScope(scope, DebugScopeType.Catch, "error"),
  380. scope => AssertScope(scope, DebugScopeType.Block, "a"),
  381. scope => AssertScope(scope, DebugScopeType.Local, "arguments"),
  382. scope => AssertScope(scope, DebugScopeType.Global, "func"));
  383. });
  384. }
  385. [Fact]
  386. public void HasCorrectScopeChainForWith()
  387. {
  388. string script = @"
  389. const obj = { a: 2, b: 4 };
  390. with (obj)
  391. {
  392. const x = a;
  393. debugger;
  394. };";
  395. TestHelpers.TestAtBreak(script, info =>
  396. {
  397. Assert.Collection(info.CurrentScopeChain,
  398. scope => AssertScope(scope, DebugScopeType.Block, "x"),
  399. scope => AssertScope(scope, DebugScopeType.With, "a", "b"),
  400. scope => AssertScope(scope, DebugScopeType.Script, "obj"),
  401. scope => AssertScope(scope, DebugScopeType.Global));
  402. });
  403. }
  404. [Fact]
  405. public void ScopeChainIncludesNonEmptyScopes()
  406. {
  407. string script = @"
  408. const x = 2;
  409. if (x > 0)
  410. {
  411. const y = x;
  412. if (x > 1)
  413. {
  414. const z = x;
  415. debugger;
  416. }
  417. }";
  418. TestHelpers.TestAtBreak(script, info =>
  419. {
  420. Assert.Collection(info.CurrentScopeChain,
  421. scope => AssertScope(scope, DebugScopeType.Block, "z"),
  422. scope => AssertScope(scope, DebugScopeType.Block, "y"),
  423. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  424. scope => AssertScope(scope, DebugScopeType.Global));
  425. });
  426. }
  427. [Fact]
  428. public void ScopeChainExcludesEmptyScopes()
  429. {
  430. string script = @"
  431. const x = 2;
  432. if (x > 0)
  433. {
  434. if (x > 1)
  435. {
  436. const z = x;
  437. debugger;
  438. }
  439. }";
  440. TestHelpers.TestAtBreak(script, info =>
  441. {
  442. Assert.Collection(info.CurrentScopeChain,
  443. scope => AssertScope(scope, DebugScopeType.Block, "z"),
  444. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  445. scope => AssertScope(scope, DebugScopeType.Global));
  446. });
  447. }
  448. [Fact]
  449. public void ResolvesScopeChainsUpTheCallStack()
  450. {
  451. string script = @"
  452. const x = 1;
  453. function foo(a, c)
  454. {
  455. debugger;
  456. }
  457. function bar(b)
  458. {
  459. foo(b, 2);
  460. }
  461. bar(x);";
  462. TestHelpers.TestAtBreak(script, info =>
  463. {
  464. Assert.Collection(info.CallStack,
  465. frame => Assert.Collection(frame.ScopeChain,
  466. // in foo()
  467. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "c"),
  468. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  469. scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
  470. ),
  471. frame => Assert.Collection(frame.ScopeChain,
  472. // in bar()
  473. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "b"),
  474. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  475. scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
  476. ),
  477. frame => Assert.Collection(frame.ScopeChain,
  478. // in global
  479. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  480. scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
  481. )
  482. );
  483. });
  484. }
  485. [Fact]
  486. public void InspectsModuleScopedBindings()
  487. {
  488. string main = @"const x = 1; debugger;";
  489. TestHelpers.TestAtBreak(engine =>
  490. {
  491. engine.Modules.Add("main", main);
  492. engine.Modules.Import("main");
  493. },
  494. info =>
  495. {
  496. // No need for imports - main module is module scoped too, duh.
  497. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "x", DebugScopeType.Module);
  498. Assert.Equal(1, value.AsInteger());
  499. });
  500. }
  501. }