Skip to content

Using @example JSDoc tags with code blocks produces invalid markdown and breaks syntax highlighting #382

@silasjmatson

Description

@silasjmatson

Disclosure: I used Claude Code to investigate this issue. I have very little experience with lua, neovim scripting, or the language server protocol, so while I believe I've done my due diligence and verified the source of the issue, I could very easily be wrong.

The tsserver_make_tags function in lua/typescript-tools/protocol/utils.luaputs code fences on the same line as the tag, which seems to be invalid markdown (as far as I can tell from the commonmark spec), so it doesn't get highlighted in hover or autocomplete panes.

Example doc with code fence on its own line after the @example tag:

 /**
   *
   * Returns the [Router](#router) object for imperative navigation.
   *
   * @example
   *```tsx
   * import { useRouter } from 'expo-router';
   * import { Text } from 'react-native';
   *
   * export default function Route() {
   *  const router = useRouter();
   *
   *  return (
   *   <Text onPress={() => router.push('/home')}>Go Home</Text>
   *  );
   *}
   * ```
   */
  export declare function useRouter(): Router;

typescript-tools eats the new line and breaks syntax highlighting:

Image

I have a ugly looking autocmd fix in my current neovim config that resolves the issue:

-- Fix typescript-tools.nvim JSDoc tag formatting
-- The original tsserver_make_tags puts code fences on the same line as @example,
-- which produces invalid markdown that breaks syntax highlighting.
-- See: https://github.com/pmizio/typescript-tools.nvim
vim.api.nvim_create_autocmd("User", {
  pattern = "VeryLazy",
  callback = function()
    local ok, utils = pcall(require, "typescript-tools.protocol.utils")
    if not ok then
      return
    end

    local original_docs_to_plain_text = utils.tsserver_docs_to_plain_text

    -- Override tsserver_make_tags to properly format code blocks
    ---@diagnostic disable-next-line: duplicate-set-field
    utils.tsserver_make_tags = function(tags)
      return table.concat(vim.tbl_map(function(it)
        local parts = { "\n_@" }
        table.insert(parts, it.name)
        if it.text then
          local text = original_docs_to_plain_text(it.text, nil, true)
          -- If text starts with a code fence, put it on its own line
          if text:match("^```") then
            table.insert(parts, "_\n\n")
          else
            table.insert(parts, "_ — ")
          end
          table.insert(parts, text)
        end

        return table.concat(parts, "")
      end, tags) or {}, "\n")
    end
  end,
})
Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions