summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.md1
-rw-r--r--script/core/diagnostics/unused-function.lua155
-rw-r--r--script/core/diagnostics/unused-vararg.lua5
-rw-r--r--test/diagnostics/common.lua13
4 files changed, 111 insertions, 63 deletions
diff --git a/changelog.md b/changelog.md
index 080bf34e..0b8fd84c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,7 @@
# changelog
## 3.2.2
+* `FIX` diagnostic: `unused-function` cannot handle recursion correctly
* `FIX` [#1093](https://github.com/sumneko/lua-language-server/issues/1093)
* `FIX` runtime errors reported by telemetry, see [#1091](https://github.com/sumneko/lua-language-server/issues/1091)
diff --git a/script/core/diagnostics/unused-function.lua b/script/core/diagnostics/unused-function.lua
index 5b1a1f70..81c7e457 100644
--- a/script/core/diagnostics/unused-function.lua
+++ b/script/core/diagnostics/unused-function.lua
@@ -18,77 +18,106 @@ local function isToBeClosed(source)
return false
end
----@async
-return function (uri, callback)
- local ast = files.getState(uri)
- if not ast then
- return
+---@param source parser.object
+local function isValidFunction(source)
+ if not source then
+ return false
+ end
+ if source.type == 'main' then
+ return false
+ end
+ local parent = source.parent
+ if not parent then
+ return false
+ end
+ if parent.type ~= 'local'
+ and parent.type ~= 'setlocal' then
+ return false
+ end
+ if isToBeClosed(parent) then
+ return false
end
+ return true
+end
- local cache = {}
- ---@async
- local function checkFunction(source)
- if not source then
+local function collect(ast, white, roots, links)
+ guide.eachSourceType(ast, 'function', function (src)
+ if not isValidFunction(src) then
return
end
- if cache[source] ~= nil then
- return cache[source]
- end
- cache[source] = false
- local parent = source.parent
- if not parent then
- return false
- end
- if parent.type ~= 'local'
- and parent.type ~= 'setlocal' then
- return false
- end
- if isToBeClosed(parent) then
- return false
- end
- await.delay()
- if parent.type == 'setlocal' then
- parent = parent.node
+ local loc = src.parent
+ if loc.type == 'setlocal' then
+ loc = loc.node
end
- local refs = parent.ref
- local hasGet
- if refs then
- cache[source] = true
- for _, src in ipairs(refs) do
- if guide.isGet(src) then
- local func = guide.getParentFunction(src)
- if not checkFunction(func) then
- hasGet = true
- break
- end
+ for _, ref in ipairs(loc.ref or {}) do
+ if ref.type == 'getlocal' then
+ local func = guide.getParentFunction(ref)
+ if not isValidFunction(func) or roots[func] then
+ roots[src] = true
+ return
end
+ if not links[func] then
+ links[func] = {}
+ end
+ links[func][#links[func]+1] = src
end
- cache[source] = not hasGet
end
- if not hasGet then
- if client.isVSCode() then
- callback {
- start = source.start,
- finish = source.finish,
- tags = { define.DiagnosticTag.Unnecessary },
- message = lang.script.DIAG_UNUSED_FUNCTION,
- }
- else
- callback {
- start = source.keyword[1],
- finish = source.keyword[2],
- tags = { define.DiagnosticTag.Unnecessary },
- message = lang.script.DIAG_UNUSED_FUNCTION,
- }
- end
- return true
- end
- return false
+ white[src] = true
+ end)
+
+ return white, roots, links
+end
+
+local function turnBlack(source, black, white, links)
+ if black[source] then
+ return
+ end
+ black[source] = true
+ white[source] = nil
+ for _, link in ipairs(links[source] or {}) do
+ turnBlack(link, black, white, links)
end
+end
- -- 只检查局部函数
- ---@async
- guide.eachSourceType(ast.ast, 'function', function (src)
- checkFunction(src)
- end)
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ if vm.isMetaFile(uri) then
+ return
+ end
+
+ local black = {}
+ local white = {}
+ local roots = {}
+ local links = {}
+
+ -- collect
+ collect(state.ast, white, roots, links)
+
+ -- turn black
+ for source in pairs(roots) do
+ turnBlack(source, black, white, links)
+ end
+
+ for source in pairs(white) do
+ if client.isVSCode() then
+ callback {
+ start = source.start,
+ finish = source.finish,
+ tags = { define.DiagnosticTag.Unnecessary },
+ message = lang.script.DIAG_UNUSED_FUNCTION,
+ }
+ else
+ callback {
+ start = source.keyword[1],
+ finish = source.keyword[2],
+ tags = { define.DiagnosticTag.Unnecessary },
+ message = lang.script.DIAG_UNUSED_FUNCTION,
+ }
+ end
+ end
end
diff --git a/script/core/diagnostics/unused-vararg.lua b/script/core/diagnostics/unused-vararg.lua
index 2e07e1ee..ce033cf3 100644
--- a/script/core/diagnostics/unused-vararg.lua
+++ b/script/core/diagnostics/unused-vararg.lua
@@ -2,6 +2,7 @@ local files = require 'files'
local guide = require 'parser.guide'
local define = require 'proto.define'
local lang = require 'language'
+local vm = require 'vm'
return function (uri, callback)
local ast = files.getState(uri)
@@ -9,6 +10,10 @@ return function (uri, callback)
return
end
+ if vm.isMetaFile(uri) then
+ return
+ end
+
guide.eachSourceType(ast.ast, 'function', function (source)
local args = source.args
if not args then
diff --git a/test/diagnostics/common.lua b/test/diagnostics/common.lua
index 53c7b975..5e0d6579 100644
--- a/test/diagnostics/common.lua
+++ b/test/diagnostics/common.lua
@@ -1529,3 +1529,16 @@ local z = x and y
print(z.y)
]]
+
+TEST [[
+local x, y
+function x()
+ y()
+end
+
+function y()
+ x()
+end
+
+x()
+]]