From e1c0a101d4cf3a5f45bc4774508b8a69c0787d54 Mon Sep 17 00:00:00 2001 From: ybai08 <271969568+ybai08@users.noreply.github.com> Date: Sun, 18 Oct 2026 03:36:15 -0500 Subject: [PATCH 1/2] fix: parse `continue` followed by a comment as a continue statement The soft-keyword check for `continue` only looked at the very next token, so a trailing comment (`continue -- note`) made it fall back to a name expression and report exp-in-action. Skip comments when peeking. Fixes #3474 --- changelog.md | 1 + script/parser/compile.lua | 40 +++++++++++++++++++++++++++++++-- test/parser_test/luajit_ext.lua | 11 +++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index daefdb5ae..21ae27119 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +* `FIX` `continue` (LuaJIT extensions or `Lua.runtime.nonstandardSymbol`) followed by a comment on the same line (e.g. `continue -- note`) is now parsed as a `continue` statement instead of reporting `exp-in-action` [#3474](https://github.com/LuaLS/lua-language-server/issues/3474) ## 3.19.2 `2026-10-13` diff --git a/script/parser/compile.lua b/script/parser/compile.lua index e936d52b7..6f10149c8 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -615,6 +615,42 @@ local function skipSpace(isAction) and not skipComment(isAction) end +-- 预读:返回 `Tokens[index + 1]` 起第一个不属于注释的 token,不移动 `Index`。 +-- 短注释会延续到行尾,因此返回其后的换行符(或文件结束时返回 nil)。 +---@param index integer +---@return string? +local function peekTokenSkipComment(index) + while true do + local token = Tokens[index + 1] + local finishMark + if token == '--' + or (token == '//' and State.options.nonstandardSymbol['//']) then + local start = Tokens[index] + local mark = Tokens[index + 2] == start + 2 + and smatch(Lua, '^%[%=*%[', start + 2) + if not mark then + repeat + index = index + 2 + token = Tokens[index + 1] + until not token or NLMap[token] + return token + end + finishMark = sgsub(mark, '%[', ']') + elseif token == '/*' then + finishMark = '*/' + else + return token + end + local _, finishOffset = sfind(Lua, finishMark, Tokens[index] + 2, true) + if not finishOffset then + return nil + end + repeat + index = index + 2 + until not Tokens[index] or Tokens[index] > finishOffset + end +end + local function expectAssign(isAction) local token = Tokens[Index + 1] if token == '=' then @@ -4950,8 +4986,8 @@ function parseAction() if token == 'continue' and (State.options.nonstandardSymbol['continue'] or State.luaJITExtensions) then -- continue 是 soft keyword:仅当其后为换行/`;`/块结束符时才视为 continue 语句, - -- 否则(如 `continue = 2`、`continue()`)作为变量名 - local nextToken = Tokens[Index + 3] + -- 否则(如 `continue = 2`、`continue()`)作为变量名;中间的注释会被跳过 + local nextToken = peekTokenSkipComment(Index + 2) if nextToken == nil or NLMap[nextToken] or nextToken == ';' diff --git a/test/parser_test/luajit_ext.lua b/test/parser_test/luajit_ext.lua index 4367096d1..ef2c559ab 100644 --- a/test/parser_test/luajit_ext.lua +++ b/test/parser_test/luajit_ext.lua @@ -134,6 +134,10 @@ TestLuaJITExt 'while true do continue end' TestLuaJITExt 'for i = 1, 10 do continue end' TestLuaJITExt 'repeat continue until false' TestLuaJITExtDisabled 'while true do continue end' +-- continue 后跟注释(#3474) +TestLuaJITExt 'while true do continue -- comment\nend' +TestLuaJITExt 'while true do continue --[[ comment ]] end' +TestLuaJITExt 'while true do continue --[==[ multi\nline ]==]\nend' print('LuaJIT 扩展基本语法用例通过') @@ -244,6 +248,13 @@ TestIndividual('number_underscore', 'local a = 0x1_2') TestIndividual('number_underscore', 'local a = 1_2.3_4') TestIndividual('number_underscore', 'local a = 1e1_0') TestIndividualDisabled('number_underscore', 'local a = 1_234') +-- continue(soft keyword),后跟注释时仍为 continue 语句(#3474) +TestIndividual('continue', 'for i = 1, 2 do continue end') +TestIndividual('continue', 'for i = 1, 2 do\n continue -- trailing comment\nend') +TestIndividual('continue', 'for i = 1, 2 do continue --[[ comment ]] end') +TestIndividual('continue', 'continue = 1') +TestIndividual('continue', 'continue --[[ comment ]] = 1') +TestIndividualDisabled('continue', 'for i = 1, 2 do continue end') print('LuaJIT 扩展单独启用用例通过') From bb4027ab8a2e0ac57309f41687d1a180771a47c9 Mon Sep 17 00:00:00 2001 From: ybai08 <271969568+ybai08@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:17:01 -0500 Subject: [PATCH 2/2] refactor: look past comments after continue with skipComment and a save point --- script/parser/compile.lua | 65 ++++++++++----------------------- test/parser_test/luajit_ext.lua | 1 + 2 files changed, 20 insertions(+), 46 deletions(-) diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 6f10149c8..7cfca3b85 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -615,42 +615,6 @@ local function skipSpace(isAction) and not skipComment(isAction) end --- 预读:返回 `Tokens[index + 1]` 起第一个不属于注释的 token,不移动 `Index`。 --- 短注释会延续到行尾,因此返回其后的换行符(或文件结束时返回 nil)。 ----@param index integer ----@return string? -local function peekTokenSkipComment(index) - while true do - local token = Tokens[index + 1] - local finishMark - if token == '--' - or (token == '//' and State.options.nonstandardSymbol['//']) then - local start = Tokens[index] - local mark = Tokens[index + 2] == start + 2 - and smatch(Lua, '^%[%=*%[', start + 2) - if not mark then - repeat - index = index + 2 - token = Tokens[index + 1] - until not token or NLMap[token] - return token - end - finishMark = sgsub(mark, '%[', ']') - elseif token == '/*' then - finishMark = '*/' - else - return token - end - local _, finishOffset = sfind(Lua, finishMark, Tokens[index] + 2, true) - if not finishOffset then - return nil - end - repeat - index = index + 2 - until not Tokens[index] or Tokens[index] > finishOffset - end -end - local function expectAssign(isAction) local token = Tokens[Index + 1] if token == '=' then @@ -4986,16 +4950,25 @@ function parseAction() if token == 'continue' and (State.options.nonstandardSymbol['continue'] or State.luaJITExtensions) then -- continue 是 soft keyword:仅当其后为换行/`;`/块结束符时才视为 continue 语句, - -- 否则(如 `continue = 2`、`continue()`)作为变量名;中间的注释会被跳过 - local nextToken = peekTokenSkipComment(Index + 2) - if nextToken == nil - or NLMap[nextToken] - or nextToken == ';' - or nextToken == 'end' - or nextToken == 'else' - or nextToken == 'elseif' - or nextToken == 'until' - or nextToken == ')' then + -- 否则(如 `continue = 2`、`continue()`)作为变量名;中间的注释用 skipComment 跳过后再回退 + local savePoint = getSavePoint() + local commCount = #State.comms + Index = Index + 2 + repeat until not skipComment() + local nextToken = Tokens[Index + 1] + local isStatement = nextToken == nil + or NLMap[nextToken] + or nextToken == ';' + or nextToken == 'end' + or nextToken == 'else' + or nextToken == 'elseif' + or nextToken == 'until' + or nextToken == ')' + savePoint() + for i = commCount + 1, #State.comms do + State.comms[i] = nil + end + if isStatement then return parseBreak() end end diff --git a/test/parser_test/luajit_ext.lua b/test/parser_test/luajit_ext.lua index ef2c559ab..27afe51b6 100644 --- a/test/parser_test/luajit_ext.lua +++ b/test/parser_test/luajit_ext.lua @@ -254,6 +254,7 @@ TestIndividual('continue', 'for i = 1, 2 do\n continue -- trailing comment\ne TestIndividual('continue', 'for i = 1, 2 do continue --[[ comment ]] end') TestIndividual('continue', 'continue = 1') TestIndividual('continue', 'continue --[[ comment ]] = 1') +TestIndividual('continue', 'continue --[[ multi\nline ]] = 1') TestIndividualDisabled('continue', 'for i = 1, 2 do continue end') print('LuaJIT 扩展单独启用用例通过')