From f4528df26eb6528fb9af6dcb1b7e36632c25c24a Mon Sep 17 00:00:00 2001 From: "roy.crippen4" Date: Sun, 14 Jul 2024 13:24:34 -0400 Subject: [PATCH 1/5] fix(#199): Auto-tag react fragments Detects if `<>` was typed in a react file (`js`, `jsx`, or `tsx`) and autocloses the fragment tag. --- lua/nvim-ts-autotag/internal.lua | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lua/nvim-ts-autotag/internal.lua b/lua/nvim-ts-autotag/internal.lua index 61d0397..123fb04 100644 --- a/lua/nvim-ts-autotag/internal.lua +++ b/lua/nvim-ts-autotag/internal.lua @@ -242,6 +242,42 @@ local function check_close_tag(close_slash_tag) return false end +local function is_react_file() + local ft = vim.bo.ft + -- check filetypes first. + if ft == 'javascriptreact' or 'typescriptreact' then + return true + elseif ft ~= 'javascript' then + return false + end + + local ok, buf_parser = pcall(vim.treesitter.get_parser) + if not ok then + return false + end + + local tree = buf_parser:parse(true) + if not tree then + return false + end + + local root = tree[1]:root() + -- parse the tree for jsx nodes + local query = vim.treesitter.query.parse('javascript', [[ + (jsx_element) + (jsx_self_closing_element) + ]]) + + -- iterate over nodes to find jsx nodes + for _, node in query:iter_captures(root, 0, 0, -1) do + if node then + return true + end + end + return false +end + + M.close_tag = function() local ok, buf_parser = pcall(vim.treesitter.get_parser) if not ok then @@ -252,6 +288,9 @@ M.close_tag = function() if result == true and tag_name ~= nil then vim.api.nvim_put({ string.format("", tag_name) }, "", true, false) vim.cmd([[normal! F>]]) + elseif is_react_file() then + vim.api.nvim_put({ "" }, "", true, false) + vim.cmd([[normal! F>]]) end end From 4ab5a1fd9fac6a6ed0d381a7a7d02d3cb3f561e3 Mon Sep 17 00:00:00 2001 From: "roy.crippen4" Date: Sun, 14 Jul 2024 16:48:35 -0400 Subject: [PATCH 2/5] test(react): Testing for auto-close react fragments in `js` and `tsx` files --- sample/index.js | 23 +++++++++++++++++++++++ tests/specs/closetag_spec.lua | 33 +++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 sample/index.js diff --git a/sample/index.js b/sample/index.js new file mode 100644 index 0000000..072cb5d --- /dev/null +++ b/sample/index.js @@ -0,0 +1,23 @@ + +import React, { useCallback, useEffect } from 'react' + +const SamplePage = () => { + const [state, setstate] = useState(initialState) + + + return ( +
+ + + + + + + + + +
+ ) +} + +export default SamplePage diff --git a/tests/specs/closetag_spec.lua b/tests/specs/closetag_spec.lua index fc9f127..c2bda45 100644 --- a/tests/specs/closetag_spec.lua +++ b/tests/specs/closetag_spec.lua @@ -144,11 +144,20 @@ local data = { filetype = "typescriptreact", linenr = 12, key = [[>]], - before = [[<]], - after = [[<>|]] + before = [[<|
]], + after = [[<>|
]] }, { - name = "17 vue auto close tag", + name = "17 javascript autoclose fragment", + filepath = "./sample/index.js", + filetype = "javascript", + linenr = 12, + key = [[>]], + before = [[<|
]], + after = [[<>|
]] + }, + { + name = "18 vue auto close tag", filepath = "./sample/index.vue", filetype = "vue", linenr = 4, @@ -157,7 +166,7 @@ local data = { after = [[|]], }, { - name = "18 vue not close on script", + name = "19 vue not close on script", filepath = "./sample/index.vue", filetype = "vue", linenr = 12, @@ -166,7 +175,7 @@ local data = { after = [[const data:Array| ]], }, { - name = "19 php div ", + name = "20 php div ", filepath = "./sample/index.php", filetype = "php", linenr = 25, @@ -184,7 +193,7 @@ local data = { -- after = [[
|
]], -- }, { - name = "20 lit template div", + name = "21 lit template div", filepath = "./sample/index.ts", filetype = "typescript", linenr = 3, @@ -193,7 +202,7 @@ local data = { after = [[
|
]], }, { - name = "21 eruby template div", + name = "22 eruby template div", filepath = "./sample/index.html.erb", filetype = "eruby", linenr = 10, @@ -202,7 +211,7 @@ local data = { after = [[
|
]], }, { - name = "22 eruby template ruby string", + name = "23 eruby template ruby string", filepath = "./sample/index.html.erb", filetype = "eruby", linenr = 10, @@ -211,7 +220,7 @@ local data = { after = [[<%=
| %> ]], }, { - name = "23 templ close tag", + name = "24 templ close tag", filepath = "./sample/index.templ", filetype = "templ", linenr = 10, @@ -220,7 +229,7 @@ local data = { after = [[
|
]], }, { - name = "24 templ close tag", + name = "25 templ close tag", filepath = "./sample/index.templ", filetype = "templ", linenr = 10, @@ -229,7 +238,7 @@ local data = { after = [[
|
]], }, { - name = "25 templ not close tag on close tag", + name = "26 templ not close tag on close tag", filepath = "./sample/index.templ", filetype = "templ", linenr = 10, @@ -238,7 +247,7 @@ local data = { after = [[
aa
|]], }, { - name = "26 templ not close on input tag", + name = "27 templ not close on input tag", filepath = "./sample/index.templ", filetype = "templ", linenr = 10, From cbe184f12d7de8139c957e31d1b6271a9892a3dd Mon Sep 17 00:00:00 2001 From: "roy.crippen4" Date: Sun, 14 Jul 2024 16:49:42 -0400 Subject: [PATCH 3/5] fix/refactor: Auto-close react fragments Refactor: Moved detection functions into `utils.lua` Fix: Improved react detection logic in `.js` files --- lua/nvim-ts-autotag/internal.lua | 38 +------------------- lua/nvim-ts-autotag/utils.lua | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/lua/nvim-ts-autotag/internal.lua b/lua/nvim-ts-autotag/internal.lua index 0eced70..98ce103 100644 --- a/lua/nvim-ts-autotag/internal.lua +++ b/lua/nvim-ts-autotag/internal.lua @@ -242,42 +242,6 @@ local function check_close_tag(close_slash_tag) return false end -local function is_react_file() - local ft = vim.bo.ft - -- check filetypes first. - if ft == "javascriptreact" or "typescriptreact" then - return true - elseif ft ~= "javascript" then - return false - end - - local ok, buf_parser = pcall(vim.treesitter.get_parser) - if not ok then - return false - end - - local tree = buf_parser:parse(true) - if not tree then - return false - end - - local root = tree[1]:root() - -- parse the tree for jsx nodes - local query = vim.treesitter.query.parse("javascript", [[ - (jsx_element) - (jsx_self_closing_element) - ]]) - - -- iterate over nodes to find jsx nodes - for _, node in query:iter_captures(root, 0, 0, -1) do - if node then - return true - end - end - return false -end - - M.close_tag = function() local ok, buf_parser = pcall(vim.treesitter.get_parser) if not ok then @@ -288,7 +252,7 @@ M.close_tag = function() if result == true and tag_name ~= nil then vim.api.nvim_put({ string.format("", tag_name) }, "", true, false) vim.cmd([[normal! F>]]) - elseif is_react_file() then + elseif utils.is_react_file() and utils.is_react_fragment() then vim.api.nvim_put({ "" }, "", true, false) vim.cmd([[normal! F>]]) end diff --git a/lua/nvim-ts-autotag/utils.lua b/lua/nvim-ts-autotag/utils.lua index ab5e276..b2c03f8 100644 --- a/lua/nvim-ts-autotag/utils.lua +++ b/lua/nvim-ts-autotag/utils.lua @@ -2,6 +2,68 @@ local log = require("nvim-ts-autotag._log") local get_node_text = vim.treesitter.get_node_text local M = {} +---@return boolean +function M.is_react_file() + local ft = vim.bo.ft + -- check filetypes first. + if ft == "javascriptreact" or ft == "typescriptreact" then + return true + elseif ft ~= "javascript" then + return false + end + + local ok, buf_parser = pcall(vim.treesitter.get_parser) + if not ok then + return false + end + + local tree = buf_parser:parse(true) + if not tree then + return false + end + + local root = tree[1]:root() + local queries = { 'jsx_element', 'jsx_self_closing_element' } + + for _, query in ipairs(queries) do + if M.node_exists(root, query) then + return true + end + end + + return false +end + + + +---@return boolean +function M.is_react_fragment() + local line = vim.fn.getline(".") + local col = vim.fn.col(".") - 2 + local strpart = vim.fn.strpart(line, col) + local char_at_cursor = vim.fn.strcharpart(strpart, 0, 1) ---@type string + return char_at_cursor == "<" +end + +---@param node TSNode +---@param query string +---@return boolean +function M.node_exists(node, query) + if node:type() == query then + return true + end + + for child in node:iter_children() do + if M.node_exists(child, query) then + return true + end + end + + return false +end + + + M.get_node_text = function(node) local _, txt = pcall(get_node_text, node, vim.api.nvim_get_current_buf()) return vim.split(txt, "\n") or {} From 2553ac552069736910dfd2a87ebb4ff2150c6549 Mon Sep 17 00:00:00 2001 From: roycrippen4 <54562558+roycrippen4@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:35:03 -0400 Subject: [PATCH 4/5] Update lua/nvim-ts-autotag/utils.lua Co-authored-by: Price Hiller --- lua/nvim-ts-autotag/utils.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/nvim-ts-autotag/utils.lua b/lua/nvim-ts-autotag/utils.lua index b2c03f8..0fdd0b7 100644 --- a/lua/nvim-ts-autotag/utils.lua +++ b/lua/nvim-ts-autotag/utils.lua @@ -11,7 +11,8 @@ function M.is_react_file() elseif ft ~= "javascript" then return false end - +-- If we are in a `javascript` file, then check the content to see if the +-- current file counts as a react file local ok, buf_parser = pcall(vim.treesitter.get_parser) if not ok then return false From 33a38a53b160e382a7b61e096e7835bc833e9e6d Mon Sep 17 00:00:00 2001 From: "roy.crippen4" Date: Sun, 14 Jul 2024 17:36:32 -0400 Subject: [PATCH 5/5] chore: formatting and clarification comment --- lua/nvim-ts-autotag/utils.lua | 10 +++------- tests/specs/closetag_spec.lua | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lua/nvim-ts-autotag/utils.lua b/lua/nvim-ts-autotag/utils.lua index 0fdd0b7..6b5fbdc 100644 --- a/lua/nvim-ts-autotag/utils.lua +++ b/lua/nvim-ts-autotag/utils.lua @@ -11,8 +11,8 @@ function M.is_react_file() elseif ft ~= "javascript" then return false end --- If we are in a `javascript` file, then check the content to see if the --- current file counts as a react file + -- If we are in a `javascript` file, then check the content to see if the + -- current file counts as a react file local ok, buf_parser = pcall(vim.treesitter.get_parser) if not ok then return false @@ -24,7 +24,7 @@ function M.is_react_file() end local root = tree[1]:root() - local queries = { 'jsx_element', 'jsx_self_closing_element' } + local queries = { "jsx_element", "jsx_self_closing_element" } for _, query in ipairs(queries) do if M.node_exists(root, query) then @@ -35,8 +35,6 @@ function M.is_react_file() return false end - - ---@return boolean function M.is_react_fragment() local line = vim.fn.getline(".") @@ -63,8 +61,6 @@ function M.node_exists(node, query) return false end - - M.get_node_text = function(node) local _, txt = pcall(get_node_text, node, vim.api.nvim_get_current_buf()) return vim.split(txt, "\n") or {} diff --git a/tests/specs/closetag_spec.lua b/tests/specs/closetag_spec.lua index c2bda45..4e2284d 100644 --- a/tests/specs/closetag_spec.lua +++ b/tests/specs/closetag_spec.lua @@ -145,7 +145,7 @@ local data = { linenr = 12, key = [[>]], before = [[<|
]], - after = [[<>|
]] + after = [[<>|
]], }, { name = "17 javascript autoclose fragment", @@ -154,7 +154,7 @@ local data = { linenr = 12, key = [[>]], before = [[<|
]], - after = [[<>|
]] + after = [[<>|
]], }, { name = "18 vue auto close tag",