fix(lsp): small bugs in snippet-parser #18998

This fixes the following bugs:
`${1:else_text}` -> format with if_text: "else_text"
`${1:-else_text}` -> format with if_text: "else_text"
`${1:}` in `format` (eg. empty else_text) -> error.
`${1:}` (eg. empty placeholder) -> error.

Thanks hrsh7th :)
This commit is contained in:
L3MON4D3
2022-06-29 18:53:49 +02:00
committed by GitHub
parent 1eb9624666
commit 6f6286e4f9
2 changed files with 154 additions and 37 deletions

View File

@@ -156,10 +156,10 @@ P.seq = function(...)
return function(input, pos) return function(input, pos)
local values = {} local values = {}
local new_pos = pos local new_pos = pos
for _, parser in ipairs(parsers) do for i, parser in ipairs(parsers) do
local result = parser(input, new_pos) local result = parser(input, new_pos)
if result.parsed then if result.parsed then
table.insert(values, result.value) values[i] = result.value
new_pos = result.pos new_pos = result.pos
else else
return P.unmatch(pos) return P.unmatch(pos)
@@ -272,22 +272,48 @@ S.format = P.any(
S.open, S.open,
S.int, S.int,
S.colon, S.colon,
P.any( P.seq(S.question, P.opt(P.take_until({ ':' }, { '\\' })), S.colon, P.opt(P.take_until({ '}' }, { '\\' }))),
P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
),
S.close S.close
), ),
function(values) function(values)
return setmetatable({ return setmetatable({
type = Node.Type.FORMAT, type = Node.Type.FORMAT,
capture_index = values[3], capture_index = values[3],
if_text = values[5][2].esc, if_text = values[5][2] and values[5][2].esc or '',
else_text = (values[5][4] or {}).esc, else_text = values[5][4] and values[5][4].esc or '',
}, Node) }, Node)
end end
) ),
P.map(
P.seq(S.dollar, S.open, S.int, S.colon, P.seq(S.plus, P.opt(P.take_until({ '}' }, { '\\' }))), S.close),
function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
if_text = values[5][2] and values[5][2].esc or '',
else_text = '',
}, Node)
end
),
P.map(
P.seq(S.dollar, S.open, S.int, S.colon, S.minus, P.opt(P.take_until({ '}' }, { '\\' })), S.close),
function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
if_text = '',
else_text = values[6] and values[6].esc or '',
}, Node)
end
),
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.opt(P.take_until({ '}' }, { '\\' })), S.close), function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
if_text = '',
else_text = values[5] and values[5].esc or '',
}, Node)
end)
) )
S.transform = P.map( S.transform = P.map(
@@ -333,12 +359,19 @@ S.tabstop = P.any(
S.placeholder = P.any( S.placeholder = P.any(
P.map( P.map(
P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), P.seq(S.dollar, S.open, S.int, S.colon, P.opt(P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' })))), S.close),
function(values) function(values)
return setmetatable({ return setmetatable({
type = Node.Type.PLACEHOLDER, type = Node.Type.PLACEHOLDER,
tabstop = values[3], tabstop = values[3],
children = values[5], -- insert empty text if opt did not match.
children = values[5] or {
setmetatable({
type = Node.Type.TEXT,
raw = '',
esc = '',
}, Node),
},
}, Node) }, Node)
end end
) )

View File

@@ -19,9 +19,9 @@ describe('vim.lsp._snippet', function()
{ {
type = snippet.NodeType.TEXT, type = snippet.NodeType.TEXT,
raw = 'TE\\$\\}XT', raw = 'TE\\$\\}XT',
esc = 'TE$}XT' esc = 'TE$}XT',
} },
} },
}, parse('TE\\$\\}XT')) }, parse('TE\\$\\}XT'))
end) end)
@@ -36,8 +36,8 @@ describe('vim.lsp._snippet', function()
{ {
type = snippet.NodeType.TABSTOP, type = snippet.NodeType.TABSTOP,
tabstop = 2, tabstop = 2,
} },
} },
}, parse('$1${2}')) }, parse('$1${2}'))
end) end)
@@ -56,7 +56,7 @@ describe('vim.lsp._snippet', function()
{ {
type = snippet.NodeType.TEXT, type = snippet.NodeType.TEXT,
raw = 'TE\\$\\}XT', raw = 'TE\\$\\}XT',
esc = 'TE$}XT' esc = 'TE$}XT',
}, },
{ {
type = snippet.NodeType.TABSTOP, type = snippet.NodeType.TABSTOP,
@@ -73,21 +73,21 @@ describe('vim.lsp._snippet', function()
{ {
type = snippet.NodeType.FORMAT, type = snippet.NodeType.FORMAT,
capture_index = 1, capture_index = 1,
modifier = 'upcase' modifier = 'upcase',
} },
} },
}, },
}, },
{ {
type = snippet.NodeType.TEXT, type = snippet.NodeType.TEXT,
raw = 'TE\\$\\}XT', raw = 'TE\\$\\}XT',
esc = 'TE$}XT' esc = 'TE$}XT',
}, },
} },
} },
} },
}, },
} },
}, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}')) }, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}'))
end) end)
@@ -110,8 +110,8 @@ describe('vim.lsp._snippet', function()
{ {
type = snippet.NodeType.TABSTOP, type = snippet.NodeType.TABSTOP,
tabstop = 1, tabstop = 1,
} },
} },
}, },
{ {
type = snippet.NodeType.VARIABLE, type = snippet.NodeType.VARIABLE,
@@ -124,11 +124,11 @@ describe('vim.lsp._snippet', function()
type = snippet.NodeType.FORMAT, type = snippet.NodeType.FORMAT,
capture_index = 1, capture_index = 1,
modifier = 'upcase', modifier = 'upcase',
} },
} },
} },
}, },
} },
}, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}')) }, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}'))
end) end)
@@ -141,12 +141,96 @@ describe('vim.lsp._snippet', function()
tabstop = 1, tabstop = 1,
items = { items = {
',', ',',
'|' '|',
} },
} },
} },
}, parse('${1|\\,,\\||}')) }, parse('${1|\\,,\\||}'))
end) end)
end) it('should parse format', function()
eq({
type = snippet.NodeType.SNIPPET,
children = {
{
type = snippet.NodeType.VARIABLE,
name = 'VAR',
transform = {
type = snippet.NodeType.TRANSFORM,
pattern = 'regex',
format = {
{
type = snippet.NodeType.FORMAT,
capture_index = 1,
modifier = 'upcase',
},
{
type = snippet.NodeType.FORMAT,
capture_index = 1,
if_text = 'if_text',
else_text = '',
},
{
type = snippet.NodeType.FORMAT,
capture_index = 1,
if_text = '',
else_text = 'else_text',
},
{
type = snippet.NodeType.FORMAT,
capture_index = 1,
else_text = 'else_text',
if_text = 'if_text',
},
{
type = snippet.NodeType.FORMAT,
capture_index = 1,
if_text = '',
else_text = 'else_text',
},
},
},
},
},
}, parse('${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}'))
end)
it('should parse empty strings', function()
eq({
children = {
{
children = { {
esc = '',
raw = '',
type = 7,
} },
tabstop = 1,
type = 2,
},
{
esc = ' ',
raw = ' ',
type = 7,
},
{
name = 'VAR',
transform = {
format = {
{
capture_index = 1,
else_text = '',
if_text = '',
type = 6,
},
},
option = 'g',
pattern = 'erg',
type = 5,
},
type = 3,
},
},
type = 0,
}, parse('${1:} ${VAR/erg/${1:?:}/g}'))
end)
end)