fix(lsp): add more robust validation in semantic tokens

- Wrap token generation in pcall to catch any errors
- Add validation for all token parameters (line, startChar, length)
- Validate deltaStart is non-negative
- Return empty tokens if any error occurs
- This should prevent the 'Invalid line: out of range' errors
This commit is contained in:
2026-02-19 12:01:33 +01:00
parent 1808edc1f0
commit d94cb62e0b
+35 -36
View File
@@ -563,7 +563,9 @@ function handlers.textDocument_semanticTokensFull(params)
return { data = {} }
end
-- Protected token collection
local tokens = {}
local ok, err = pcall(function()
local lines = {}
for line in doc.content:gmatch("([^\n]*)\n") do
table.insert(lines, line)
@@ -571,11 +573,21 @@ function handlers.textDocument_semanticTokensFull(params)
local total_lines = #lines
local function add_token(line, startChar, length, tokenType)
-- Validate line number is within bounds
local function add_token(line, startChar, length)
-- Validate all parameters
if not line or not startChar or not length then
return
end
if line < 0 or line >= total_lines then
return
end
if startChar < 0 or length <= 0 then
return
end
if startChar + length > 10000 then
return
end
local deltaLine = line
local deltaStart = startChar
if #tokens >= 4 then
@@ -587,6 +599,10 @@ function handlers.textDocument_semanticTokensFull(params)
else
deltaLine = line - prevLine
end
-- Validate deltaStart is non-negative
if deltaStart < 0 then
return
end
end
table.insert(tokens, deltaLine)
table.insert(tokens, deltaStart)
@@ -601,55 +617,38 @@ function handlers.textDocument_semanticTokensFull(params)
if doc.variables then
for var_name, info in pairs(doc.variables) do
-- Validate variable line number is within bounds
if info.line and info.line >= 0 and info.line < total_lines and info.line == lineNum then
if info and info.line and info.line >= 0 and info.line < total_lines and info.line == lineNum then
local startChar = line:find(var_name, 1, true)
if startChar then
add_token(lineNum, startChar - 1, #var_name, "variable")
add_token(lineNum, startChar - 1, #var_name)
end
end
end
end
local keywords = {
"IF",
"THEN",
"ELSIF",
"ELSE",
"END_IF",
"FOR",
"TO",
"BY",
"DO",
"END_FOR",
"WHILE",
"END_WHILE",
"REPEAT",
"UNTIL",
"END_REPEAT",
"CASE",
"OF",
"END_CASE",
"VAR_INPUT",
"VAR_OUTPUT",
"VAR_IN_OUT",
"VAR_TEMP",
"VAR",
"END_VAR",
"BEGIN",
"EXIT",
"CONTINUE",
"RETURN",
"TRUE",
"FALSE",
"IF", "THEN", "ELSIF", "ELSE", "END_IF",
"FOR", "TO", "BY", "DO", "END_FOR",
"WHILE", "END_WHILE",
"REPEAT", "UNTIL", "END_REPEAT",
"CASE", "OF", "END_CASE",
"VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR", "END_VAR",
"BEGIN", "EXIT", "CONTINUE", "RETURN",
"TRUE", "FALSE",
}
for _, kw in ipairs(keywords) do
local startPos = line:find("%f[%a]" .. kw .. "%f[%A]")
if startPos then
add_token(lineNum, startPos - 1, #kw, "keyword")
add_token(lineNum, startPos - 1, #kw)
end
end
end
end)
-- If any error occurred, return empty tokens
if not ok then
return { data = {} }
end
return { data = tokens }
end