scope-resolver-spec.js 38 KB

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