neovim configuration using rocks.nvim plugin manager
1require("luasnip.session.snippet_collection").clear_snippets("typescriptreact")
2
3local ls = require("luasnip")
4local s = ls.snippet
5local i = ls.insert_node
6local t = ls.text_node
7local c = ls.choice_node
8local fn = ls.function_node
9local dn = ls.dynamic_node
10local sn = ls.snippet_node
11local rep = require("luasnip.extras").rep
12local fmt = require("luasnip.extras.fmt").fmt
13local fmta = require("luasnip.extras.fmt").fmta
14
15-- Get a list of the property names given an `interface_declaration`
16-- treesitter *tsx* node.
17-- Ie, if the treesitter node represents:
18-- interface {
19-- prop1: string;
20-- prop2: number;
21-- }
22-- Then this function would return `{"prop1", "prop2"}
23---@param node TSNode interface declaration node
24---@return string[]
25local function get_prop_names(node)
26 local interface_body = node:field("body")[1]
27 if not interface_body then
28 return {}
29 end
30
31 local prop_names = {}
32
33 for prop_signature in interface_body:iter_children() do
34 if prop_signature:type() == "property_signature" then
35 local prop_iden = prop_signature:child(0)
36 local prop_name = vim.treesitter.get_node_text(prop_iden, 0)
37 prop_names[#prop_names + 1] = prop_name
38 end
39 end
40
41 return prop_names
42end
43
44-- original: https://gist.github.com/davidatsurge/9873d9cb1781f1a37c0f25d24cb1b3ab
45-- https://www.reddit.com/r/neovim/comments/uuhk1t/feedback_on_luasniptreesitter_snippet_that_fills/
46local componentWithProps = fmta(
47 [[
48 <>interface <>Props {
49 <><>
50 }
51
52 export function <>({ <> }: <>Props) {
53 <>return <>;
54 };
55 ]],
56 {
57 c(1, { t "export ", t "" }),
58 -- Initialize component name to file name
59 rep(3),
60 t "\t",
61 i(2, "// Props"),
62 dn(3, function(_, snip)
63 local filename = vim.fn.expand("%:t:r")
64 local comp_name
65 -- TODO: rewrite this with `vim.fs` lua api
66 if filename == "index" then
67 comp_name = vim.fn.expand("%"):match("([^/]+)/[^/]+$")
68 else
69 comp_name = vim.fn.substitute(snip.env.TM_FILENAME, "\\..*$", "", "g")
70 end
71 return sn(nil, { i(1, comp_name) })
72 end, { 1 }),
73 fn(function(_, snip, _)
74 local pos_begin = snip.nodes[4].mark:pos_begin()
75 local pos_end = snip.nodes[4].mark:pos_end()
76 local parser = vim.treesitter.get_parser(0, "tsx")
77 local tstree = parser:parse()
78
79 local node = tstree[1]:root():named_descendant_for_range(pos_begin[1], pos_begin[2], pos_end[1], pos_end[2])
80
81 while node ~= nil and node:type() ~= "interface_declaration" do
82 node = node:parent()
83 end
84
85 if node == nil then
86 return ""
87 end
88
89 -- `node` is now surely of type "interface_declaration"
90 local prop_names = get_prop_names(node)
91
92 return vim.fn.join(prop_names, ", ")
93 end, { 2 }),
94 rep(3),
95 t "\t",
96 i(4, "null"),
97 }
98)
99
100-- stylua: ignore
101ls.add_snippets("typescriptreact", {
102 s("cp", componentWithProps),
103})