vscode 在Lua字符串中使用'function'会导致错误的缩进,

8mmmxcuj  于 5个月前  发布在  Vscode
关注(0)|答案(5)|浏览(47)

当所有扩展都被禁用时,是否会出现这个问题?:是

  • 版本:1.84.2(用户设置)
  • 提交:1a5daa3
  • 日期:2023-11-09T10:51:52.184Z
  • Electron:25.9.2
  • ElectronBuildId:24603566
  • Chromium:114.0.5735.289
  • Node.js:18.15.0
  • V8:11.4.183.29-electron.0
  • OS:Windows_NT x64 10.0.23481

重现步骤:
1.创建一个Lua文件

  • 创建一个包含function的字符串
  • function之前和之后不能有字母
  • 在字符串后添加一个换行符

2cmtqfgy

2cmtqfgy1#

你好,最近我遇到了一个问题,代码片段如下:

function service.addCmd(name, func)
    assert(type(func) == "function", name .. " is not function")
        |<<< undesired indent here

这个问题似乎是由于这里的不完美 increaseIndentPattern 导致的:
vscode/extensions/lua/language-configuration.json
第25行到第26行的 1c7fa5e
| | "indentationRules": { |
| | "increaseIndentPattern": "^((?!(-\-)).)((\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).))|(\{\s*))$", |
我想说,当前的模式已经解决了9x%的正常使用场景👍,但它有一些主要的缺陷:

  1. 当关键字仅在字符串字面量中时,它会导致误报的情况,就像这个问题和我的例子一样。
  2. end|until 在尾随注解中时,它会导致漏报的情况,例如以下情况:
if true then -- not the end
|<<< missing indent
-- this is because the word `end` is matched after `then`
  1. 如果一个 -- (通常意味着一个注解)在字符串字面量中,它也会导致漏报:
if s == "---" then
|<<< missing indent
-- that's because nothing will be matched after `--` is encountered

我一直在尝试改进这个模式,我认为我已经找到了一个更好的解决方案🚀(以及一些测试来证明其正确性)。
我注意到这个问题已经被分配给了 @aiday-mar ,所以我可以直接创建一个PR并链接到这个问题,然后在那里讨论吗?还是我应该在这里发布我的解决方案并先讨论一下再打开一个PR?谢谢😄

a0x5cqrl

a0x5cqrl2#

你好,我最近遇到了这个问题,代码片段如下:

function service.addCmd(name, func)
    assert(type(func) == "function", name .. " is not function")
        |<<< undesired indent here

这似乎是由于这里的不完美 increaseIndentPattern 导致的:
vscode/extensions/lua/language-configuration.json
第25行到第26行的 1c7fa5e
| | "indentationRules": { |
| | "increaseIndentPattern": "^((?!(\-\-)).)((\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).))|(\{\s*))$", |
我认为当前的模式已经解决了9x%的正常使用场景 👍 但是它有一些主要的缺陷:

1. when keywords are just in a **string literal**, it will cause **false positive** cases, just like this issue and my example

2. when the `end|until` is in **trailing comment**, it will cause **false negative** cases, like the following case
if true then -- not the end
|<<< missing indent
-- this is because the word `end` is matched after `then`
3. if a `--` (which normally means a comment) is in a **string literal**, it will cause **false negative** as well:
if s == "---" then
|<<< missing indent
-- that's because nothing will be matched after `--` is encountered

我一直在尝试改进这个模式,我认为我已经找到了一个更好的解决方案 🚀 (以及一些测试来证明其正确性)。我注意到这个问题已经被分配给了 @aiday-mar ,所以我可以直接创建一个PR并链接到这个问题,然后在那里讨论吗?还是我应该在这里发布我的解决方案并先讨论一下,然后再打开一个PR?谢谢 😄
在创建PR之前在这里发布你的解决方案有什么原因吗?

rks48beu

rks48beu3#

这是完全没问题的,我在这里提供我的当前解决方案,并在下面解释它。
(我明天会把我的 test.js 附件上传,因为我现在手头上没有)

"increaseIndentPattern": "^((?!\\-\\-)[^\"']|\"(\\\\\"|(?!\\\\\")[^\"])*\"|'(\\\\'|(?!\\\\')[^'])*')*(\\b(else|then|do|repeat)\\b|\\bfunction\\b([^\\)]+\\)?)?|[\\{\\(])\\s*(\\-\\-.*)?$",
^((?!\-\-)[^"']|"(\\"|(?!\\")[^"])*"|'(\\'|(?!\\')[^'])*')*(\b(else|then|do|repeat)\b|\bfunction\b([^\)]+\)?)?|[\{\(])\s*(\-\-.*)?$

解释

如上所述,现有模式存在 3 主要缺陷,我的解决方案旨在解决所有这些问题

1. 字符串字面量中的关键字

为了正确忽略字符串字面量的关键字,我们的想法是在匹配关键字之前的序列之前消耗引用的字符串。我从这个帖子中借来了这个想法: https://stackoverflow.com/a/23667311

  • 为了简单起见,现在假设字符串只会用双引号括起来 "string literal"

而不是只使用点来匹配前缀序列中的任何字符,我们将其拆分为两部分: .

  • 如果它是 non quote character ,我们只需匹配它: [^"]

否则我们应该消耗整个引用的字符串,即匹配直到关闭引号: "[^"]*"
这给出了类似于以下内容的东西: ([^"]|"[^"]*")*

  • 为了处理转义引号,[^"]* 部分需要分成另外两个情况:
  • 转义引号序列 \\"
  • 除引号之外的任何字符,以及非转义引号序列 (?!\\")[^"]
  • 将它们组合在一起 [^"]* 变成 (\\"|[^"])*

=> 因此这部分最终变成 ([^"]|"(\\"|(?!\\")[^"])*")*

  • 现在 Lua 字符串也可以用单引号括起来 'string literal

它们是一样的,只需将双引号替换为单引号: ([^']|'(\\'|(?!\\')[^'])*')*
在与上面的双引号情况合并后: ([^"']|"(\\"|(?!\\")[^"])*"|'(\\'|(?!\\')[^'])*')*

  • 我们还需要保留跳过注解的能力,所以在开头添加回 (?!\-\-)

=> ((?!\-\-)[^"']|"(\\"|(?!\\")[^"])*"|'(\\'|(?!\\')[^'])*')*
这是解决方案的第一部分。

2. 尾随注解中的关键字

上述技巧保证匹配的关键字不是字符串的一部分。现在我们需要处理一种情况,即如果有 end|until 关键字,下一行不应该缩进。仅仅检查 end|until 是不够的,因为它们可能位于字符串或尾随注解中间。

  • 这检查非常麻烦,所以我采取了完全不同的方法:只允许尾随空格或注解紧跟在匹配的关键字后面。

=> \\s*(\\-\\-.*)?

  • 注意,这对于正则表达式匹配来说是一个更严格的条件,但我认为它大多数情况下都是正确的😕 让我们考虑每个关键字在正则表达式中的情况
  • 我们要么写成 / 或 | (/ 或者 |),或者只是关键字 / 或者 | 以及后面的语句。我不认为会有写成的情况出现。
else a = a + 1
  b = b + 1
end
-- the above style is nonsense
  • else|then|do|repeat 这个关键字要复杂得多,因为一个函数可以被命名为或者匿名的,而且它可能是一个(未完成的)参数列表。我的观察是,在有效的语法中,如果我们期望有一个缩进,那么函数声明最多应该以右括号结尾。右括号是可选的,但是一旦我们匹配到了它,就没有什么(除了尾随空格和注解当然)是可以允许的。
-- should indent
function ()
function a(p1)
function a(p1, p2, ...)
function a(p1,
-- should not indent
function a(a1) a = a + 1 end

-- will not have indent, and it makes no sense to write this
function a(a1) a = a + 1
end

所以无论我们检查不带 ) 是否都行。只要允许尾随空格或注解就可以决定下一行是否缩进。

  • 我们必须实现 [^)]+\)? 关键字可选地跟随任何内容,并且如果存在任何内容,我们应该在 xm37n1x 时停止。
  • xm38n1x :任何东西(除了 xm39n1x 当然)
  • xm39n1x :后面跟着可选的 xm40n1x
  • 这个整个部分应该是可选的。
  • 所有这些应该与 xm42n1x 形成替代匹配,因此它们通过使用 xm43n1x 连接在一起。

=> xm44n1x
这就是解决方案的第二部分。

3. 在字符串中的 xm45n1x 将导致误报负数

这个问题由第一部分的解决方案解决,因为现在字符串被正确忽略了。而且任何匹配为 xm46n1x 的实际上现在是注解前缀了。

bonus:允许在 xm47n1x 之后有尾随注解

在原始模式中,它只是允许在 xm48n1x 之后有尾随空格。

  • 要允许也有尾随空格,将 xm49n1x 更改为 xm50n1x
  • 还要允许尾随注解,我将此部分移动到将上述关键字组连接在一起的地方。

=> xm52n1x

  • 最后,将整个组后面跟着尾随空格和可选的注解。

=> xm53n1x

All together 🎉

xam54b0xa
如前所述,我明天会发布我的 test.js
同时,您可以通过 regex101 进行测试,或者在本地编辑 extensions/lua/language-configuration.json 文件以直接在 vscode 中进行测试。

z4bn682m

z4bn682m4#

我刚刚找到了一个更高效的检查引用字符串的方法👀
"[^"\\]*(?:\\.[^"\\]*)*"
参考: https://stackoverflow.com/a/6525975
我在regex101的调试器中比较了它的性能,它比我提出的"(\\"|(?!\\")[^"])*"快得多(使用更少的步骤)

  • 测试字符串: local s = "this is a test string"
  • 我的解决方案:79步
  • 发现的解决方案:16
  • 测试字符串: local s = "this is a test string
  • 我的解决方案:报告失败匹配需要121步
  • 发现的解决方案:37步报告失败匹配

我一定会把它纳入我提出的解决方案中😄

bgtovc5b

bgtovc5b5#

测试文件

这是我的 test.js ,它包含了我在解决方案开发过程中使用正则表达式的各个阶段。你可以将 const PATTERN = 更改为指向不同的阶段,以查看测试结果是如何演变的。

// original
const stage0 = /^((?!(\-\-)).)*((\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).)*)|(\{\s*))$/;

// remove unnecessary brackets
const stage1 = /^((?!\-\-).)*(\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).)*|\{\s*)$/;

// skip string before keywords
const stage2 = /^((?!\-\-)[^"']|"[^"\\]*(\\.[^"\\]*)*"|'[^'\\]*(\\.[^'\\]*)*')*(\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).)*|\{\s*)$/;

// allow only trailing spaces or comment after keyword
const stage3 = /^((?!\-\-)[^"']|"[^"\\]*(\\.[^"\\]*)*"|'[^'\\]*(\\.[^'\\]*)*')*(\b(else|function|then|do|repeat)\b\s*(\-\-.*)?|\{\s*)$/;

// fix allow param list after function keyword
const stage4 = /^((?!\-\-)[^"']|"[^"\\]*(\\.[^"\\]*)*"|'[^'\\]*(\\.[^'\\]*)*')*(\b(else|function|then|do|repeat)\b((?<=function)[^)]+\)?)?\s*(\-\-.*)?|\{\s*)$/;

// support ending with "("
const stage5 = /^((?!\-\-)[^"']|"[^"\\]*(\\.[^"\\]*)*"|'[^'\\]*(\\.[^'\\]*)*')*(\b(else|function|then|do|repeat)\b((?<=function)[^)]+\)?)?\s*(\-\-.*)?|[\{\(]\s*)$/;

// allow trailing comment after `{` or `(`
const stage6 = /^((?!\-\-)[^"']|"[^"\\]*(\\.[^"\\]*)*"|'[^'\\]*(\\.[^'\\]*)*')*(\b(else|function|then|do|repeat)\b((?<=function)[^)]+\)?)?|[\{\(])\s*(\-\-.*)?$/;

// annotated
const stage7 = /^(?<not_cmt>(?!\-\-)[^"']|(?<dq_str>"[^"\\]*(\\.[^"\\]*)*")|(?<sq_str>'[^'\\]*(\\.[^'\\]*)*'))*(\b(?<kw>else|function|then|do|repeat)\b(?<fparam>(?<=function)[^)]+\)?)?|(?<bracket>[\{\(]))(?<trail>\s*(\-\-.*)?)$/;

// pattern to test
const PATTERN = stage7;
console.log(`PATTERN = ${PATTERN}`);
console.log(`escapsed = ${JSON.stringify(PATTERN.toString().slice(1,-1))}`);

// simple test framework
function test(id, s, expect) {
    if (PATTERN.test(s) !== expect) {
        console.log(`#${id} fail: ${s}`);
    }
}

const positive = String.raw `
if true then
if a == 1 then -- until now
then
else
if true then -- not the end
if s == "--" then
if s == "then" then
if s == "\"then\"" then
if s == "\"--\"" then
if s == '\'--\'' then

function
function a()
local function a()
local a = function ()
function a(p1)
local function a(p1, p2)
local a = function (p1, p2, ...)
table.sort(function (a, b)
function (
function a(
function a(p1, p2,
function a.b(p1, ...)
function -- (
function -- )
function -- ) end

do
while true do
while true do -- end
repeat
repeat -- until

do -- end
{
t = {
local t = {
local t = { -- comment

func(
a = func(
a = func( -- not the end
`

const negative = String.raw `
if
elseif
then end
else end
-- if true then
if s == "then"
if s == "\"then"
if true then end

function () end
function a() end
local function a() end
local a = function() end
local function1 = nil

do end
while true do end
repeat until
repeat until true
do end
do s = "--" end

local a = 1 -- do
local a = -1 -- then
local s = "then"
local s = [[then]]
print("then")
print("function")
assert(type(func) == "function")
`

console.log("===== testing positive cases =====")
positive.split("\n").filter(line => line).forEach((s, i) => test(i+1, s, true));

console.log("===== testing negative cases =====")
negative.split("\n").filter(line => line).forEach((s, i) => test(i+1, s, false));

最新解决方案

与我之前的版本相比,最新解决方案略有不同。我使用了 正向后视 来检查 函数参数 ,并将其放在关键字分支之后: ((?<=function)[^)]+\)?)? 。我认为这会使它更具可读性和可编辑性,因为所有的关键字都分组在一起。 🤔

^((?!\-\-)[^"']|"[^"\\]*(\\.[^"\\]*)*"|'[^'\\]*(\\.[^'\\]*)*')*(\b(else|function|then|do|repeat)\b((?<=function)[^)]+\)?)?|[\{\(])\s*(\-\-.*)?$

带注解的版本

我还制作了一个带注解的版本(使用命名组)以增加详细程度:

^(?<not_cmt>(?!\-\-)[^"']|(?<dq_str>"[^"\\]*(\\.[^"\\]*)*")|(?<sq_str>'[^'\\]*(\\.[^'\\]*)*'))*(\b(?<kw>else|function|then|do|repeat)\b(?<fparam>(?<=function)[^)]+\)?)?|(?<bracket>[\{\(]))(?<trail>\s*(\-\-.*)?)$

再次分解

  • ^(?<not_cmt>(?!\-\-)[^"']|(?<dq_str>"[^"\\]*(\\.[^"\\]*)*")|(?<sq_str>'[^'\\]*(\\.[^'\\]*)*'))* :匹配直到遇到注解,遇到引号时消耗整个引号字符串
  • (?!\-\-)[^"'] :任何不是注解和不是引号的字符,组内互斥
  • (?<dq_str>"[^"\\]*(\\.[^"\\]*)*") :匹配双引号字符串
  • (?<sq_str>'[^'\\]*(\\.[^'\\]*)*') :匹配单引号字符串
  • (\b(?<kw>else|function|then|do|repeat)\b(?<fparam>(?<=function)[^)]+\)?)?|(?<bracket>[\{\(])) :匹配关键字或括号
  • \b(?<kw>else|function|then|do|repeat)\b :匹配边界上的关键字
  • (?<fparam>(?<=function)[^)]+\)?)? :匹配特定于 function 关键字的可选参数列表
  • (?<bracket>[\{\(]) :匹配括号
  • (?<trail>\s*(\-\-.*)?)$ :如果关键字或括号之后有任何内容,必须是尾随空格或注解

相关问题