nvim-treesitter: query for jsdoc description within javascript file

1.1k Views Asked by At

I'm experimenting with a testing regime for my neovim plugin regexplainer. The idea is to put a JSDoc docblock containing the expected output on top of each query.

/** 
 * @group CaptureGroups
 * **0-9** (_>= 0x_)
 * capture group 1:
 *   **0-9**
 */
/\d*(\d)/;

My test file would then iterate through the file, getting each regex from the javascript tree, along with it's expected output from the comment. I could then run my plugin on the regexp and check the output against the comment.

I wrote this function to query the document for comments with regexp following.

function M.get_regexes_with_descriptions()
  local log = require'regexplainer.utils'.debug

  local query_js = vim.treesitter.query.parse_query('javascript', [[
    (comment) @comment
    (expression_statement
      (regex
        (regex_pattern) @regex))
  ]])

  local query_jsdoc = vim.treesitter.query.parse_query('jsdoc', [[
    (tag_name) @tag_name
    (description) @description
  ]])

  local parser = vim.treesitter.get_parser(0, 'javascript')

  local tree, other = unpack(parser:parse())

  for id, node, metadata in query_js:iter_captures(tree:root(), 0) do
    local name = query_js.captures[id] -- name of the capture in the query
    if node:type() == 'comment' then
      for cid, cnode, cmetadata in node:iter_children() do
        local cname = query_jsdoc.captures[cid] -- name of the capture in the query
        log(get_info_on_capture(cid, cnode, cname, cmetadata))
      end
    end
  end
end

I expect this to log the tag name and description nodes from the 'jsdoc' grammar, but i actually get nothing

So how do I "query down" into the embedded JSDoc part of the tree? I tried this query, but got a query: invalid node type at position 10 error when parsing the query:

(comment (description) @desc) @comment
(expression_statement
  (regex
    (regex_pattern) @regex))

The TSPlayground output for the file in question looks like this:

comment [0, 0] - [5, 3]
  tag [1, 3] - [4, 12]
    tag_name [1, 3] - [1, 9]
    description [1, 10] - [4, 12]
expression_statement [6, 0] - [6, 10]
  regex [6, 0] - [6, 9]
    pattern: regex_pattern [6, 1] - [6, 8]
      term [6, 1] - [6, 8]
        character_class_escape [6, 1] - [6, 3]
        zero_or_more [6, 3] - [6, 4]
        anonymous_capturing_group [6, 4] - [6, 8]
          pattern [6, 5] - [6, 7]
            term [6, 5] - [6, 7]
              character_class_escape [6, 5] - [6, 7]

EDIT: A Workaround

I developed this workaround but it's a bit unsatisfying, I'd prefer to get the comment's contents along with the regexp in a single query

function M.get_regexes_with_descriptions()
  local log = require'regexplainer.utils'.debug
  local parsers = require "nvim-treesitter.parsers"

  local query_js = vim.treesitter.query.parse_query('javascript', [[
    (expression_statement
      (regex
        (regex_pattern) @regex)) @expr
    ]])

  local query_jsdoc = vim.treesitter.query.parse_query('jsdoc', [[
    (tag
      (tag_name) @tag_name
      ((description) @description
      ;; (#eq? @tag_name "example")
      ))
  ]])

  local parser = parsers.get_parser(0)

  local tree = unpack(parser:parse())

  local caps = {}

  for id, node in query_js:iter_captures(tree:root(), 0) do
    local cap = {}
    local name = query_js.captures[id] -- name of the capture in the query
    log(id, name, ts_utils.get_node_text(node))
    if name == 'expr' then
      cap.regex = node:named_child('pattern')
      cap.comment = node:prev_sibling()
      table.insert(caps, cap)
    end

  end

  local results = {}

  for _, cap in ipairs(caps) do
    local result = {}
    result.text = ts_utils.get_node_text(cap.regex)

    local comment_str = table.concat(ts_utils.get_node_text(cap.comment), '\n')
    local jsdoc_parser = vim.treesitter.get_string_parser(comment_str, 'jsdoc')
    local jsdoc_tree = jsdoc_parser:parse()[1]

    for id, ch, metadata in query_jsdoc:iter_captures(jsdoc_tree:root()) do
      if query_jsdoc.captures[id] == 'description' then
        result.description = table.concat(
          vim.tbl_map(function(line)
            return line:gsub('^%s+%*', '')
          end, ts_utils.get_node_text(ch)), '\n')
      end
    end
    table.insert(results, result)
  end

  return results
end
0

There are 0 best solutions below