scope-resolver-spec.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340
  1. const fs = require('fs');
  2. const path = require('path');
  3. const dedent = require('dedent');
  4. const TextBuffer = require('text-buffer');
  5. const { Point, Range } = TextBuffer;
  6. const CSON = require('season');
  7. const TextEditor = require('../src/text-editor');
  8. const ScopeResolver = require('../src/scope-resolver.js');
  9. const WASMTreeSitterGrammar = require('../src/wasm-tree-sitter-grammar');
  10. const WASMTreeSitterLanguageMode = require('../src/wasm-tree-sitter-language-mode');
  11. const Random = require('random-seed');
  12. const { getRandomBufferRange, buildRandomLines } = require('./helpers/random');
  13. function wait(ms) {
  14. return new Promise((resolve) => {
  15. setTimeout(resolve, ms);
  16. });
  17. }
  18. let PATH = path.resolve( path.join(__dirname, '..', 'packages') );
  19. function resolve(modulePath) {
  20. return require.resolve(`${PATH}/${modulePath}`)
  21. }
  22. const jsGrammarPath = resolve(
  23. 'language-javascript/grammars/modern-tree-sitter-javascript.cson'
  24. );
  25. let jsConfig = CSON.readFileSync(jsGrammarPath);
  26. const jsRegexGrammarPath = resolve(
  27. 'language-javascript/grammars/modern-tree-sitter-regex.cson'
  28. );
  29. let jsRegexConfig = CSON.readFileSync(jsRegexGrammarPath);
  30. async function getAllCapturesWithScopeResolver(grammar, languageMode, scopeResolver, layer = null) {
  31. let query = await grammar.getQuery('highlightsQuery');
  32. layer = layer ?? languageMode.rootLanguageLayer;
  33. let { start, end } = languageMode.buffer.getRange();
  34. let { tree } = layer;
  35. return {
  36. captures: query.captures(tree.rootNode, { startPosition: start, endPosition: end }),
  37. scopeResolver
  38. };
  39. }
  40. function makeScopeResolver(languageMode, layer) {
  41. layer = layer ?? languageMode.rootLanguageLayer;
  42. return new ScopeResolver(
  43. layer,
  44. (name) => languageMode.idForScope(name),
  45. );
  46. }
  47. async function getAllCaptures(grammar, languageMode, layer = null) {
  48. layer = layer ?? languageMode.rootLanguageLayer;
  49. let scopeResolver = makeScopeResolver(languageMode, layer);
  50. return getAllCapturesWithScopeResolver(grammar, languageMode, scopeResolver, layer);
  51. }
  52. async function getAllMatchesWithScopeResolver(...args) {
  53. let { captures, scopeResolver } = await getAllCapturesWithScopeResolver(...args);
  54. let matches = [];
  55. for (let capture of captures) {
  56. let range = scopeResolver.store(capture);
  57. if (range) {
  58. matches.push(capture);
  59. }
  60. }
  61. return matches;
  62. }
  63. async function getAllMatches(...args) {
  64. let { captures, scopeResolver } = await getAllCaptures(...args);
  65. let matches = [];
  66. for (let capture of captures) {
  67. let range = scopeResolver.store(capture);
  68. if (range) {
  69. matches.push(capture);
  70. }
  71. }
  72. return matches;
  73. }
  74. function stringForNodeRange(node) {
  75. return `${node.startIndex}-${node.endIndex}`;
  76. }
  77. function rangeFromDescriptor(rawRange) {
  78. let { startPosition, endPosition } = rawRange;
  79. let start = Point.fromObject(startPosition, true);
  80. let end = Point.fromObject(endPosition, true);
  81. return new Range(start, end);
  82. }
  83. describe('ScopeResolver', () => {
  84. let editor, buffer, grammar;
  85. beforeEach(async () => {
  86. grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig);
  87. editor = await atom.workspace.open('');
  88. buffer = editor.getBuffer();
  89. atom.grammars.addGrammar(grammar);
  90. atom.config.set('core.useTreeSitterParsers', true);
  91. });
  92. afterEach(() => {
  93. ScopeResolver.clearConfigCache();
  94. });
  95. it('resolves all scopes in absence of any tests or adjustments', async () => {
  96. await grammar.setQueryForTest('highlightsQuery', `
  97. (comment) @comment
  98. (string) @string
  99. "=" @operator
  100. `);
  101. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  102. buffer.setLanguageMode(languageMode);
  103. buffer.setText(dedent`
  104. // this is a comment
  105. const foo = "ahaha";
  106. `);
  107. await languageMode.ready;
  108. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  109. for (let capture of captures) {
  110. let { node } = capture;
  111. let range = scopeResolver.store(capture);
  112. expect(stringForNodeRange(range))
  113. .toBe(stringForNodeRange(node));
  114. }
  115. });
  116. it('provides the grammar with the text of leaf nodes only', async () => {
  117. await grammar.setQueryForTest('highlightsQuery', `
  118. (expression_statement) @not_leaf_node
  119. (call_expression) @also_not_leaf_node
  120. (identifier) @leaf_node
  121. (property_identifier) @also_leaf_node
  122. `);
  123. let tokens = [];
  124. const original = grammar.idForScope.bind(grammar);
  125. grammar.idForScope = function (scope, text) {
  126. if (text) {
  127. tokens.push(text);
  128. }
  129. return original(scope, text);
  130. };
  131. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  132. buffer.setLanguageMode(languageMode);
  133. buffer.setText('aa.bb(cc.dd());');
  134. await languageMode.ready;
  135. // If non-leaf nodes are included, this list would included things like
  136. // 'aa.bb()' and `cc.dd()`
  137. expect(tokens).toEqual([
  138. 'aa',
  139. 'bb',
  140. 'cc',
  141. 'dd',
  142. ]);
  143. });
  144. it('interpolates magic tokens in scope names', async () => {
  145. await grammar.setQueryForTest('highlightsQuery', `
  146. (lexical_declaration kind: _ @declaration._TYPE_)
  147. `);
  148. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  149. buffer.setLanguageMode(languageMode);
  150. buffer.setText(dedent`
  151. // this is a comment
  152. const foo = "ahaha";
  153. let bar = 'troz'
  154. `);
  155. await languageMode.ready;
  156. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  157. let names = captures.map(({ name, node }) => {
  158. return ScopeResolver.interpolateName(name, node)
  159. });
  160. names.sort();
  161. expect(names).toEqual([
  162. 'declaration.const',
  163. 'declaration.let'
  164. ]);
  165. });
  166. it('does not apply any scopes when @_IGNORE_ is used', async () => {
  167. await grammar.setQueryForTest('highlightsQuery', `
  168. (lexical_declaration kind: _ @_IGNORE_
  169. (#match? @_IGNORE_ "const"))
  170. (lexical_declaration kind: _ @let
  171. (#match? @let "let"))
  172. `);
  173. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  174. buffer.setLanguageMode(languageMode);
  175. buffer.setText(dedent`
  176. // this is a comment
  177. const foo = "ahaha";
  178. let bar = 'troz'
  179. `);
  180. await languageMode.ready;
  181. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  182. for (let capture of captures) {
  183. let { node, name } = capture;
  184. let result = scopeResolver.store(capture);
  185. if (name === '_IGNORE_') {
  186. expect(!!result).toBe(false);
  187. } else {
  188. expect(!!result).toBe(true);
  189. }
  190. }
  191. });
  192. it('does not apply any scopes when multiple @_IGNORE_s are used', async () => {
  193. await grammar.setQueryForTest('highlightsQuery', `
  194. (variable_declarator
  195. (identifier) @_IGNORE_.identifier
  196. (string) @_IGNORE_.string
  197. )
  198. `);
  199. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  200. buffer.setLanguageMode(languageMode);
  201. buffer.setText(dedent`
  202. // this is a comment
  203. const foo = "ahaha";
  204. let bar = false
  205. `);
  206. await languageMode.ready;
  207. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  208. for (let capture of captures) {
  209. let { node, name } = capture;
  210. let result = scopeResolver.store(capture);
  211. if (name.startsWith('_IGNORE_')) {
  212. expect(!!result).toBe(false);
  213. } else {
  214. expect(!!result).toBe(true);
  215. }
  216. }
  217. });
  218. describe('adjustments', () => {
  219. it('adjusts ranges with (#set! adjust.startAt)', async () => {
  220. await grammar.setQueryForTest('highlightsQuery', `
  221. ((try_statement) @try.plus.brace
  222. (#set! adjust.endAt
  223. firstChild.nextSibling.firstChild.endPosition))
  224. `);
  225. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  226. buffer.setLanguageMode(languageMode);
  227. buffer.setText(dedent`
  228. try { x++ } catch (e) {}
  229. `);
  230. await languageMode.ready;
  231. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  232. let capture = captures[0];
  233. let range = scopeResolver.store(capture);
  234. expect(buffer.getTextInRange(rangeFromDescriptor(range)))
  235. .toBe('try {');
  236. });
  237. it('adjusts ranges with (#set! adjust.endAt)', async () => {
  238. await grammar.setQueryForTest('highlightsQuery', `
  239. ((object) @object.interior
  240. (#set! adjust.startAt firstChild.endPosition)
  241. (#set! adjust.endAt lastChild.startPosition))
  242. `);
  243. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  244. buffer.setLanguageMode(languageMode);
  245. buffer.setText(dedent`
  246. {from: 'x', to: 'y'}
  247. `);
  248. await languageMode.ready;
  249. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  250. let capture = captures[0];
  251. let range = scopeResolver.store(capture);
  252. expect(
  253. buffer.getTextInRange(rangeFromDescriptor(range))
  254. ).toBe(`from: 'x', to: 'y'`);
  255. });
  256. it('adjusts ranges with (#set! adjust.offset(Start|End))', async () => {
  257. // Same result as the previous test, but with a different technique.
  258. await grammar.setQueryForTest('highlightsQuery', `
  259. ((object) @object.interior
  260. (#set! adjust.offsetStart 1)
  261. (#set! adjust.offsetEnd -1))
  262. `);
  263. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  264. buffer.setLanguageMode(languageMode);
  265. buffer.setText(dedent`
  266. {from: 'x', to: 'y'}
  267. `);
  268. await languageMode.ready;
  269. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  270. let capture = captures[0];
  271. let range = scopeResolver.store(capture);
  272. expect(
  273. buffer.getTextInRange(rangeFromDescriptor(range))
  274. ).toBe(`from: 'x', to: 'y'`);
  275. });
  276. it('prevents adjustments outside the original capture', async () => {
  277. await grammar.setQueryForTest('highlightsQuery', `
  278. ((comment) @too-early
  279. (#set! adjust.startAt previousSibling.startPosition))
  280. ((comment) @too-late
  281. (#set! adjust.endAt nextSibling.endPosition))
  282. ((comment) @offset-too-early
  283. (#set! adjust.offsetStart -10))
  284. ((comment) @offset-too-late
  285. (#set! adjust.offsetEnd 10))
  286. `);
  287. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  288. buffer.setLanguageMode(languageMode);
  289. buffer.setText(dedent`
  290. let foo = "this is a line above a comment"
  291. // this is a comment that wants to fly too close to the sun
  292. let bar = "this is a line below a comment"
  293. `);
  294. // Prevent an exception from being thrown before we can even check the
  295. // scopeResolver.
  296. spyOn(languageMode, 'isRowCommented').andReturn(false);
  297. await languageMode.ready;
  298. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  299. for (let capture of captures) {
  300. expect(() => {
  301. scopeResolver.store(capture);
  302. }).toThrow();
  303. }
  304. });
  305. it("adjusts a range around a regex match with `adjust.startAndEndAroundFirstMatchOf`", async () => {
  306. await grammar.setQueryForTest('highlightsQuery', `
  307. ((comment) @todo
  308. (#set! adjust.startAndEndAroundFirstMatchOf "\\\\sTODO(?=:)"))
  309. `);
  310. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  311. buffer.setLanguageMode(languageMode);
  312. buffer.setText(dedent`
  313. // TODO: Do something
  314. // TODO (don't actually do it)
  315. `);
  316. await languageMode.ready;
  317. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  318. let capture = captures[0];
  319. let range = scopeResolver.store(capture);
  320. let matched = [];
  321. for (let capture of captures) {
  322. range = scopeResolver.store(capture);
  323. if (range) { matched.push(range); }
  324. }
  325. expect(matched.length).toBe(1);
  326. expect(
  327. buffer.getTextInRange(rangeFromDescriptor(matched[0]))
  328. ).toBe(` TODO`);
  329. });
  330. });
  331. describe('tests', () => {
  332. it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final)', async () => {
  333. await grammar.setQueryForTest('highlightsQuery', `
  334. (comment) @comment
  335. (string) @string0
  336. ((string) @string1
  337. (#set! capture.final))
  338. (string) @string2
  339. "=" @operator
  340. `);
  341. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  342. buffer.setLanguageMode(languageMode);
  343. buffer.setText(dedent`
  344. // this is a comment
  345. const foo = "ahaha";
  346. `);
  347. await languageMode.ready;
  348. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  349. for (let capture of captures) {
  350. let { name } = capture;
  351. let result = scopeResolver.store(capture);
  352. if (name === 'string0') {
  353. expect(!!result).toBe(true);
  354. }
  355. if (name === 'string1') {
  356. expect(!!result).toBe(true);
  357. }
  358. if (name === 'string2') {
  359. expect(!!result).toBe(false);
  360. }
  361. }
  362. });
  363. it('temporarily supports the deprecated (#set! test.final true)', async () => {
  364. await grammar.setQueryForTest('highlightsQuery', `
  365. (comment) @comment
  366. (string) @string0
  367. ((string) @string1
  368. (#set! test.final true))
  369. (string) @string2
  370. "=" @operator
  371. `);
  372. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  373. buffer.setLanguageMode(languageMode);
  374. buffer.setText(dedent`
  375. // this is a comment
  376. const foo = "ahaha";
  377. `);
  378. await languageMode.ready;
  379. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  380. for (let capture of captures) {
  381. let { node, name } = capture;
  382. let result = scopeResolver.store(capture);
  383. if (name === 'string0') {
  384. expect(!!result).toBe(true);
  385. }
  386. if (name === 'string1') {
  387. expect(!!result).toBe(true);
  388. }
  389. if (name === 'string2') {
  390. expect(!!result).toBe(false);
  391. }
  392. }
  393. });
  394. it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final)', async () => {
  395. await grammar.setQueryForTest('highlightsQuery', `
  396. (comment) @comment
  397. (string) @string0
  398. ((string) @string1
  399. (#set! capture.final))
  400. (string) @string2
  401. "=" @operator
  402. `);
  403. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  404. buffer.setLanguageMode(languageMode);
  405. buffer.setText(dedent`
  406. // this is a comment
  407. const foo = "ahaha";
  408. `);
  409. await languageMode.ready;
  410. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  411. for (let capture of captures) {
  412. let { node, name } = capture;
  413. let result = scopeResolver.store(capture);
  414. if (name === 'string0') {
  415. expect(!!result).toBe(true);
  416. }
  417. if (name === 'string1') {
  418. expect(!!result).toBe(true);
  419. }
  420. if (name === 'string2') {
  421. expect(!!result).toBe(false);
  422. }
  423. }
  424. });
  425. it('rejects scopes for ranges that have already been claimed if set with (#set! capture.shy true)', async () => {
  426. await grammar.setQueryForTest('highlightsQuery', `
  427. (comment) @comment
  428. (string "\\"") @string.double
  429. ((string) @string.other (#set! capture.shy true))
  430. "=" @operator
  431. `);
  432. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  433. buffer.setLanguageMode(languageMode);
  434. buffer.setText(dedent`
  435. // this is a comment
  436. const foo = "ahaha";
  437. const bar = 'troz'
  438. `);
  439. await languageMode.ready;
  440. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  441. let first = true;
  442. for (let capture of captures) {
  443. let { node, name } = capture;
  444. let result = scopeResolver.store(capture);
  445. // First string.other should fail; second should succeed.
  446. if (name === 'string.other') {
  447. let expected = first ? false : true;
  448. first = false;
  449. expect(!!result).toBe(expected);
  450. }
  451. }
  452. });
  453. it('temporarily supports the deprecated (#set! test.shy true)', async () => {
  454. await grammar.setQueryForTest('highlightsQuery', `
  455. (comment) @comment
  456. (string "\\"") @string.double
  457. ((string) @string.other (#set! test.shy true))
  458. "=" @operator
  459. `);
  460. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  461. buffer.setLanguageMode(languageMode);
  462. buffer.setText(dedent`
  463. // this is a comment
  464. const foo = "ahaha";
  465. const bar = 'troz'
  466. `);
  467. await languageMode.ready;
  468. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  469. let first = true;
  470. for (let capture of captures) {
  471. let { node, name } = capture;
  472. let result = scopeResolver.store(capture);
  473. // First string.other should fail; second should succeed.
  474. if (name === 'string.other') {
  475. let expected = first ? false : true;
  476. first = false;
  477. expect(!!result).toBe(expected);
  478. }
  479. }
  480. });
  481. it('rejects scopes for ranges that fail test.first or test.last', async () => {
  482. await grammar.setQueryForTest('highlightsQuery', `
  483. ((string_fragment) @impossible.first
  484. (#is? test.first true))
  485. ((string_fragment) @impossible.last
  486. (#is? test.last true))
  487. ((string) "'" @punctuation.first
  488. (#is? test.first true))
  489. ((string) "'" @punctuation.last
  490. (#is? test.last true))
  491. `);
  492. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  493. buffer.setLanguageMode(languageMode);
  494. buffer.setText(dedent`
  495. // this is a comment
  496. const foo = "ahaha";
  497. const bar = 'troz'
  498. `);
  499. await languageMode.ready;
  500. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  501. for (let capture of captures) {
  502. let { node, name } = capture;
  503. let result = scopeResolver.store(capture);
  504. // Impossible for string_fragment to be the first or last child.
  505. if (name.startsWith('impossible')) {
  506. expect(!!result).toBe(false);
  507. }
  508. if (name === 'punctuation.first') {
  509. expect(node.id).toBe(node.parent.lastChild.id);
  510. } else if (name === 'punctuation.last') {
  511. expect(node.id).toBe(node.parent.firstChild.id);
  512. }
  513. }
  514. });
  515. it('temporarily supports the deprecated (#set! test.onlyIfFirst) and (#set! test.onlyIfLast)', async () => {
  516. await grammar.setQueryForTest('highlightsQuery', `
  517. ((string_fragment) @impossible.first
  518. (#is? test.onlyIfFirst true))
  519. ((string_fragment) @impossible.last
  520. (#is? test.onlyIfLast true))
  521. ((string) "'" @punctuation.first
  522. (#is? test.onlyIfFirst true))
  523. ((string) "'" @punctuation.last
  524. (#is? test.onlyIfLast true))
  525. `);
  526. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  527. buffer.setLanguageMode(languageMode);
  528. buffer.setText(dedent`
  529. // this is a comment
  530. const foo = "ahaha";
  531. const bar = 'troz'
  532. `);
  533. await languageMode.ready;
  534. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  535. for (let capture of captures) {
  536. let { node, name } = capture;
  537. let result = scopeResolver.store(capture);
  538. // Impossible for string_fragment to be the first or last child.
  539. if (name.startsWith('impossible')) {
  540. expect(!!result).toBe(false);
  541. }
  542. if (name === 'punctuation.first') {
  543. expect(node.id).toBe(node.parent.lastChild.id);
  544. } else if (name === 'punctuation.last') {
  545. expect(node.id).toBe(node.parent.firstChild.id);
  546. }
  547. }
  548. });
  549. it('supports test.firstOfType and test.lastOfType', async () => {
  550. await grammar.setQueryForTest('highlightsQuery', `
  551. (formal_parameters (identifier) @first-param
  552. (#is? test.firstOfType identifier))
  553. (formal_parameters (identifier) @last-param
  554. (#is? test.lastOfType identifier))
  555. (formal_parameters "," @first-comma
  556. (#is? test.firstOfType ","))
  557. (formal_parameters "," @last-comma
  558. (#is? test.lastOfType ","))
  559. `);
  560. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  561. buffer.setLanguageMode(languageMode);
  562. buffer.setText(dedent`
  563. function foo (bar, baz, thud, troz) {}
  564. `);
  565. await languageMode.ready;
  566. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  567. let matched = [];
  568. for (let capture of captures) {
  569. let range = scopeResolver.store(capture);
  570. if (range) { matched.push([capture, range]); }
  571. }
  572. expect(matched.length).toBe(4);
  573. expect(matched.map(pair => {
  574. return pair[0].name;
  575. })).toEqual(["first-param", "first-comma", "last-comma", "last-param"]);
  576. });
  577. it('supports test.lastTextOnRow', async () => {
  578. await grammar.setQueryForTest('highlightsQuery', `
  579. ("||" @hanging-logical-operator
  580. (#is? test.lastTextOnRow true))
  581. `);
  582. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  583. buffer.setLanguageMode(languageMode);
  584. buffer.setText(dedent`
  585. let x = foo ||
  586. bar;
  587. let y = foo || bar;
  588. `);
  589. await languageMode.ready;
  590. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  591. let matched = [];
  592. for (let capture of captures) {
  593. let range = scopeResolver.store(capture);
  594. if (range) { matched.push(capture); }
  595. }
  596. expect(matched.length).toBe(1);
  597. expect(matched[0].node.startPosition.row).toBe(0);
  598. expect(matched.map(capture => capture.name)).toEqual(
  599. ["hanging-logical-operator"]);
  600. });
  601. it('supports test.firstTextOnRow', async () => {
  602. await grammar.setQueryForTest('highlightsQuery', `
  603. ("||" @hanging-logical-operator
  604. (#is? test.firstTextOnRow true))
  605. `);
  606. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  607. buffer.setLanguageMode(languageMode);
  608. buffer.setText(dedent`
  609. let x = foo
  610. || bar;
  611. let y = foo || bar;
  612. `);
  613. await languageMode.ready;
  614. let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
  615. let matched = [];
  616. for (let capture of captures) {
  617. let range = scopeResolver.store(capture);
  618. if (range) { matched.push(capture); }
  619. }
  620. expect(matched.length).toBe(1);
  621. expect(matched[0].node.startPosition.row).toBe(1);
  622. expect(matched.map(capture => capture.name)).toEqual(
  623. ["hanging-logical-operator"]);
  624. });
  625. it('supports test.descendantOfType', async () => {
  626. await grammar.setQueryForTest('highlightsQuery', `
  627. ("," @comma-inside-function
  628. (#is? test.descendantOfType function_declaration))
  629. `);
  630. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  631. buffer.setLanguageMode(languageMode);
  632. buffer.setText(dedent`
  633. let foo, bar, baz;
  634. function foo (one, two, three) {}
  635. `);
  636. await languageMode.ready;
  637. let matched = await getAllMatches(grammar, languageMode);
  638. expect(matched.length).toBe(2);
  639. expect(matched.every(cap => {
  640. return cap.node.startPosition.row === 1;
  641. })).toBe(true);
  642. });
  643. it('supports test.descendantOfType (multiple values)', async () => {
  644. await grammar.setQueryForTest('highlightsQuery', `
  645. ("," @comma-inside-function
  646. (#is? test.descendantOfType "function_declaration generator_function_declaration"))
  647. `);
  648. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  649. buffer.setLanguageMode(languageMode);
  650. buffer.setText(dedent`
  651. let foo, bar, baz;
  652. function foo (one, two, three) {}
  653. function* bar(one, two, three) {}
  654. `);
  655. await languageMode.ready;
  656. let matched = await getAllMatches(grammar, languageMode);
  657. expect(matched.length).toBe(4);
  658. expect(matched.every((cap, index) => {
  659. let expectedRow = index >= 2 ? 2 : 1;
  660. return cap.node.startPosition.row === expectedRow;
  661. })).toBe(true);
  662. });
  663. it('supports test.ancestorOfType', async () => {
  664. await grammar.setQueryForTest('highlightsQuery', `
  665. ((function_declaration) @function-with-semicolons
  666. (#is? test.ancestorOfType ";"))
  667. `);
  668. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  669. buffer.setLanguageMode(languageMode);
  670. buffer.setText(dedent`
  671. function foo () {}
  672. function bar () {
  673. console.log(false);
  674. }
  675. `);
  676. await languageMode.ready;
  677. let matched = await getAllMatches(grammar, languageMode);
  678. expect(matched.length).toBe(1);
  679. expect(matched[0].node.text.includes("function bar")).toBe(true);
  680. });
  681. it('supports test.ancestorOfType (multiple values)', async () => {
  682. await grammar.setQueryForTest('highlightsQuery', `
  683. ((function_declaration) @function-with-semicolons-or-booleans
  684. (#is? test.ancestorOfType "; false"))
  685. `);
  686. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  687. buffer.setLanguageMode(languageMode);
  688. buffer.setText(dedent`
  689. function foo () {}
  690. function bar () {
  691. console.log(false);
  692. }
  693. function baz () {
  694. console.log(false)
  695. }
  696. `);
  697. await languageMode.ready;
  698. let matched = await getAllMatches(grammar, languageMode);
  699. expect(matched.length).toBe(2);
  700. expect(matched[0].node.text.includes("function ba")).toBe(true);
  701. expect(matched[1].node.text.includes("function ba")).toBe(true);
  702. });
  703. it('supports test.descendantOfNodeWithData (without value)', async () => {
  704. await grammar.setQueryForTest('highlightsQuery', `
  705. ((function_declaration) @_IGNORE_
  706. (#match? @_IGNORE_ "foo")
  707. (#set! isSpecialFunction true))
  708. ("," @special-comma
  709. (#is? test.descendantOfNodeWithData isSpecialFunction))
  710. `);
  711. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  712. buffer.setLanguageMode(languageMode);
  713. buffer.setText(dedent`
  714. function foo (bar, baz, thud) {}
  715. function bar (lorem, ipsum, dolor) {}
  716. `);
  717. await languageMode.ready;
  718. let matched = await getAllMatches(grammar, languageMode);
  719. expect(matched.length).toBe(2);
  720. expect(matched.every(cap => {
  721. return cap.node.startPosition.row === 0 &&
  722. cap.node.text === ",";
  723. })).toBe(true);
  724. });
  725. it('supports test.descendantOfNodeWithData (with right value)', async () => {
  726. await grammar.setQueryForTest('highlightsQuery', `
  727. ((function_declaration) @_IGNORE_
  728. (#match? @_IGNORE_ "foo" )
  729. (#set! isSpecialFunction "troz"))
  730. ("," @special-comma
  731. (#is? test.descendantOfNodeWithData "isSpecialFunction troz"))
  732. `);
  733. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  734. buffer.setLanguageMode(languageMode);
  735. buffer.setText(dedent`
  736. function foo (bar, baz, thud) {}
  737. function bar (lorem, ipsum, dolor) {}
  738. `);
  739. await languageMode.ready;
  740. let matched = await getAllMatches(grammar, languageMode);
  741. expect(matched.length).toBe(2);
  742. expect(matched.every(cap => {
  743. return cap.node.startPosition.row === 0 &&
  744. cap.node.text === ",";
  745. })).toBe(true);
  746. });
  747. it('supports test.descendantOfNodeWithData (with wrong value)', async () => {
  748. await grammar.setQueryForTest('highlightsQuery', `
  749. ((function_declaration) @_IGNORE_
  750. (#match? @_IGNORE_ "foo")
  751. (#set! isSpecialFunction "troz"))
  752. ("," @special-comma
  753. (#is? test.descendantOfNodeWithData "isSpecialFunction zort"))
  754. `);
  755. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  756. buffer.setLanguageMode(languageMode);
  757. buffer.setText(dedent`
  758. function foo (bar, baz, thud) {}
  759. function bar (lorem, ipsum, dolor) {}
  760. `);
  761. await languageMode.ready;
  762. let matched = await getAllMatches(grammar, languageMode);
  763. // Wrong value, so test shouldn't pass.
  764. expect(matched.length).toBe(0);
  765. });
  766. it('supports test.type', async () => {
  767. await grammar.setQueryForTest('highlightsQuery', `
  768. (formal_parameters _ @function-comma
  769. (#is? test.type ","))
  770. `);
  771. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  772. buffer.setLanguageMode(languageMode);
  773. buffer.setText(dedent`
  774. function foo (bar, baz, thud) {}
  775. `);
  776. await languageMode.ready;
  777. let matched = await getAllMatches(grammar, languageMode);
  778. expect(matched.length).toBe(2);
  779. expect(matched.every(cap => {
  780. return cap.node.text === ",";
  781. })).toBe(true);
  782. });
  783. it('supports test.type with multiple types', async () => {
  784. await grammar.setQueryForTest('highlightsQuery', `
  785. (formal_parameters _ @thing
  786. (#is? test.type ", identifier"))
  787. `);
  788. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  789. buffer.setLanguageMode(languageMode);
  790. buffer.setText(dedent`
  791. function foo (bar, baz, thud) {}
  792. `);
  793. await languageMode.ready;
  794. let matched = await getAllMatches(grammar, languageMode);
  795. expect(matched.length).toBe(5);
  796. });
  797. it('supports test.hasError', async () => {
  798. await grammar.setQueryForTest('highlightsQuery', `
  799. ((statement_block) @messed-up-statement-block
  800. (#is? test.hasError true))
  801. `);
  802. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  803. buffer.setLanguageMode(languageMode);
  804. buffer.setText(dedent`
  805. function foo (bar, baz, thud) {
  806. if !troz zort();
  807. }
  808. `);
  809. await languageMode.ready;
  810. let matched = await getAllMatches(grammar, languageMode);
  811. expect(matched.length).toBe(1);
  812. expect(matched.every(cap => {
  813. return cap.name === 'messed-up-statement-block' && cap.node.hasError;
  814. })).toBe(true);
  815. });
  816. it('supports test.root', async () => {
  817. await grammar.setQueryForTest('highlightsQuery', `
  818. ((_) @is-root
  819. (#is? test.root true))
  820. `);
  821. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  822. buffer.setLanguageMode(languageMode);
  823. buffer.setText(dedent`
  824. function foo (bar, baz, thud) {
  825. if (!troz) { zort(); }
  826. }
  827. `);
  828. await languageMode.ready;
  829. let matched = await getAllMatches(grammar, languageMode);
  830. expect(matched.length).toBe(1);
  831. expect(matched.every(cap => {
  832. return cap.name === 'is-root' && cap.node.type === 'program' &&
  833. !cap.node.parent;
  834. })).toBe(true);
  835. });
  836. it('supports test.lastTextOnRow', async () => {
  837. await grammar.setQueryForTest('highlightsQuery', `
  838. ("||" @orphaned-operator
  839. (#is? test.lastTextOnRow true))
  840. `);
  841. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  842. buffer.setLanguageMode(languageMode);
  843. buffer.setText(dedent`
  844. function foo (bar, baz, thud) {
  845. if (true || false) { console.log('logic!'); }
  846. return true ||
  847. false;
  848. }
  849. `);
  850. await languageMode.ready;
  851. let matched = await getAllMatches(grammar, languageMode);
  852. expect(matched.length).toBe(1);
  853. for (let cap of matched) {
  854. expect(cap.name).toBe('orphaned-operator');
  855. expect(cap.node.type).toBe('||');
  856. expect(cap.node.startPosition.row).toBe(2);
  857. }
  858. });
  859. it('supports test.rangeWithData (without value)', async () => {
  860. await grammar.setQueryForTest('highlightsQuery', `
  861. ((true) @_IGNORE_ (#set! isTrue true))
  862. ([ (true) (false) ] @optimistic-boolean
  863. (#is? test.rangeWithData isTrue))
  864. `);
  865. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  866. buffer.setLanguageMode(languageMode);
  867. buffer.setText(dedent`
  868. function foo (bar, baz, thud) {
  869. if (true || false) { console.log('logic!'); }
  870. return true || false;
  871. }
  872. `);
  873. await languageMode.ready;
  874. let matched = await getAllMatches(grammar, languageMode);
  875. expect(matched.length).toBe(2);
  876. for (let cap of matched) {
  877. expect(cap.name).toBe('optimistic-boolean');
  878. expect(cap.node.text).toBe('true');
  879. }
  880. });
  881. it('supports test.rangeWithData (with right value)', async () => {
  882. await grammar.setQueryForTest('highlightsQuery', `
  883. ((true) @_IGNORE_ (#set! isTrue "exactly"))
  884. ([ (true) (false) ] @optimistic-boolean
  885. (#is? test.rangeWithData "isTrue exactly"))
  886. `);
  887. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  888. buffer.setLanguageMode(languageMode);
  889. buffer.setText(dedent`
  890. function foo (bar, baz, thud) {
  891. if (true || false) { console.log('logic!'); }
  892. return true || false;
  893. }
  894. `);
  895. await languageMode.ready;
  896. let matched = await getAllMatches(grammar, languageMode);
  897. expect(matched.length).toBe(2);
  898. for (let cap of matched) {
  899. expect(cap.name).toBe('optimistic-boolean');
  900. expect(cap.node.text).toBe('true');
  901. }
  902. });
  903. it('supports test.rangeWithData (with wrong value)', async () => {
  904. await grammar.setQueryForTest('highlightsQuery', `
  905. ((true) @_IGNORE_ (#set! isTrue "perhaps"))
  906. ([ (true) (false) ] @optimistic-boolean
  907. (#is? test.rangeWithData "isTrue exactly"))
  908. `);
  909. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  910. buffer.setLanguageMode(languageMode);
  911. buffer.setText(dedent`
  912. function foo (bar, baz, thud) {
  913. if (true || false) { console.log('logic!'); }
  914. return true || false;
  915. }
  916. `);
  917. await languageMode.ready;
  918. let matched = await getAllMatches(grammar, languageMode);
  919. // Values don't match, so the test shouldn't pass.
  920. expect(matched.length).toBe(0);
  921. });
  922. it('supports test.startsOnSameRowAs', async () => {
  923. await grammar.setQueryForTest('highlightsQuery', `
  924. ((false) @non-hanging-false
  925. (#is? test.startsOnSameRowAs parent.startPosition))
  926. `);
  927. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  928. buffer.setLanguageMode(languageMode);
  929. buffer.setText(dedent`
  930. function foo (bar, baz, thud) {
  931. if (true || false) { console.log('logic!'); }
  932. return true ||
  933. false;
  934. }
  935. `);
  936. await languageMode.ready;
  937. let matched = await getAllMatches(grammar, languageMode);
  938. expect(matched.length).toBe(1);
  939. for (let cap of matched) {
  940. expect(cap.name).toBe('non-hanging-false');
  941. expect(cap.node.text).toBe('false');
  942. expect(cap.node.startPosition.row).toBe(1);
  943. }
  944. });
  945. it('supports test.endsOnSameRowAs', async () => {
  946. await grammar.setQueryForTest('highlightsQuery', `
  947. ((true) @non-hanging-true
  948. (#is? test.endsOnSameRowAs parent.endPosition))
  949. `);
  950. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
  951. buffer.setLanguageMode(languageMode);
  952. buffer.setText(dedent`
  953. function foo (bar, baz, thud) {
  954. if (true || false) { console.log('logic!'); }
  955. return true ||
  956. false;
  957. }
  958. `);
  959. await languageMode.ready;
  960. let matched = await getAllMatches(grammar, languageMode);
  961. expect(matched.length).toBe(1);
  962. for (let cap of matched) {
  963. expect(cap.name).toBe('non-hanging-true');
  964. expect(cap.node.text).toBe('true');
  965. expect(cap.node.startPosition.row).toBe(1);
  966. }
  967. });
  968. it('supports test.config (with no arguments)', async () => {
  969. atom.config.set('core.careAboutBooleans', true);
  970. await grammar.setQueryForTest('highlightsQuery', `
  971. ([(true) (false)] @boolean
  972. (#is? test.config core.careAboutBooleans))
  973. `);
  974. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
  975. buffer.setLanguageMode(languageMode);
  976. buffer.setText(dedent`
  977. function foo (bar, baz, thud) {
  978. if (true || false) { console.log('logic!'); }
  979. return true || false;
  980. }
  981. `);
  982. await languageMode.ready;
  983. let matched = await getAllMatches(grammar, languageMode);
  984. expect(matched.length).toBe(4);
  985. atom.config.set('core.careAboutBooleans', false);
  986. matched = await getAllMatches(grammar, languageMode);
  987. expect(matched.length).toBe(0);
  988. });
  989. it('supports test.config (with boolean arguments)', async () => {
  990. atom.config.set('core.careAboutBooleans', true);
  991. await grammar.setQueryForTest('highlightsQuery', `
  992. ([(true) (false)] @boolean
  993. (#is? test.config "core.careAboutBooleans true"))
  994. `);
  995. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
  996. buffer.setLanguageMode(languageMode);
  997. buffer.setText(dedent`
  998. function foo (bar, baz, thud) {
  999. if (true || false) { console.log('logic!'); }
  1000. return true || false;
  1001. }
  1002. `);
  1003. await languageMode.ready;
  1004. let matched = await getAllMatches(grammar, languageMode);
  1005. expect(matched.length).toBe(4);
  1006. atom.config.set('core.careAboutBooleans', false);
  1007. matched = await getAllMatches(grammar, languageMode);
  1008. expect(matched.length).toBe(0);
  1009. });
  1010. it('supports test.config (with number arguments)', async () => {
  1011. atom.config.set('core.careAboutBooleans', 0);
  1012. await grammar.setQueryForTest('highlightsQuery', `
  1013. ([(true) (false)] @boolean
  1014. (#is? test.config "core.careAboutBooleans 0"))
  1015. `);
  1016. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
  1017. buffer.setLanguageMode(languageMode);
  1018. buffer.setText(dedent`
  1019. function foo (bar, baz, thud) {
  1020. if (true || false) { console.log('logic!'); }
  1021. return true || false;
  1022. }
  1023. `);
  1024. await languageMode.ready;
  1025. let matched = await getAllMatches(grammar, languageMode);
  1026. expect(matched.length).toBe(4);
  1027. atom.config.set('core.careAboutBooleans', 1);
  1028. matched = await getAllMatches(grammar, languageMode);
  1029. expect(matched.length).toBe(0);
  1030. });
  1031. it('supports test.config (with string arguments)', async () => {
  1032. atom.config.set('core.careAboutBooleans', "something");
  1033. await grammar.setQueryForTest('highlightsQuery', `
  1034. ([(true) (false)] @boolean
  1035. (#is? test.config "core.careAboutBooleans something"))
  1036. `);
  1037. const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
  1038. buffer.setLanguageMode(languageMode);
  1039. buffer.setText(dedent`
  1040. function foo (bar, baz, thud) {
  1041. if (true || false) { console.log('logic!'); }
  1042. return true || false;
  1043. }
  1044. `);
  1045. await languageMode.ready;
  1046. let scopeResolver = makeScopeResolver(languageMode);
  1047. let matched = await getAllMatchesWithScopeResolver(grammar, languageMode, scopeResolver);
  1048. expect(matched.length).toBe(4);
  1049. atom.config.set('core.careAboutBooleans', "something-else");
  1050. matched = await getAllMatchesWithScopeResolver(grammar, languageMode, scopeResolver);
  1051. expect(matched.length).toBe(0);
  1052. atom.config.set(
  1053. 'core.careAboutBooleans',
  1054. 'something',
  1055. { scope: [grammar.scopeName] }
  1056. );
  1057. matched = await getAllMatchesWithScopeResolver(grammar, languageMode, scopeResolver);
  1058. expect(matched.length).toBe(4);
  1059. atom.config.set('core.careAboutBooleans', "something");
  1060. atom.config.set(
  1061. 'core.careAboutBooleans',
  1062. 'something-else',
  1063. { scope: [grammar.scopeName] }
  1064. );
  1065. matched = await getAllMatchesWithScopeResolver(grammar, languageMode, scopeResolver);
  1066. expect(matched.length).toBe(0);
  1067. });
  1068. it('supports test.injection', async () => {
  1069. jasmine.useRealClock();
  1070. await grammar.setQueryForTest('highlightsQuery', `
  1071. ((escape_sequence) @regex-escape
  1072. (#is? test.injection true))
  1073. `);
  1074. let regexGrammar = new WASMTreeSitterGrammar(atom.grammars, jsRegexGrammarPath, jsRegexConfig);
  1075. await regexGrammar.setQueryForTest('highlightsQuery', `
  1076. ((control_escape) @regex-escape
  1077. (#is? test.injection true))
  1078. `);
  1079. atom.grammars.addGrammar(regexGrammar);
  1080. grammar.addInjectionPoint({
  1081. type: 'regex_pattern',
  1082. language: () => 'js-regex',
  1083. content: (node) => node,
  1084. languageScope: null
  1085. });
  1086. const languageMode = new WASMTreeSitterLanguageMode({
  1087. grammar,
  1088. buffer,
  1089. config: atom.config,
  1090. grammars: atom.grammars
  1091. });
  1092. buffer.setText(String.raw`
  1093. function foo (bar, baz, thud) {
  1094. let newline = "\n";
  1095. let newlineRegex = /lor\nem/;
  1096. }
  1097. `);
  1098. buffer.setLanguageMode(languageMode);
  1099. await languageMode.ready;
  1100. let layers = languageMode.getAllLanguageLayers();
  1101. expect(layers.length).toBe(2);
  1102. let matched = [];
  1103. for (let layer of layers) {
  1104. let results = await getAllMatches(layer.grammar, languageMode, layer);
  1105. matched.push(...results);
  1106. }
  1107. expect(matched.length).toBe(1);
  1108. for (let cap of matched) {
  1109. expect(cap.node.startPosition.row).toBe(3);
  1110. }
  1111. });
  1112. });
  1113. });