summaryrefslogtreecommitdiff
path: root/script/core/diagnostics
diff options
context:
space:
mode:
authorCppCXY <812125110@qq.com>2022-08-11 19:36:36 +0800
committerCppCXY <812125110@qq.com>2022-08-11 19:36:36 +0800
commitff9103ae4001d8e520171b99cd192997fc689bc9 (patch)
tree04c0b685e81aac48210604dc12d24b91862a36d9 /script/core/diagnostics
parent40f191a85ea21bb64c427f9dab4bc597e2a0ea1b (diff)
parent82bcfef9037c26681993c94b2f92b68d335de3c6 (diff)
downloadlua-language-server-ff9103ae4001d8e520171b99cd192997fc689bc9.zip
Merge branch 'master' of github.com:CppCXY/lua-language-server
Diffstat (limited to 'script/core/diagnostics')
-rw-r--r--script/core/diagnostics/ambiguity-1.lua4
-rw-r--r--script/core/diagnostics/assign-type-mismatch.lua117
-rw-r--r--script/core/diagnostics/cast-local-type.lua50
-rw-r--r--script/core/diagnostics/cast-type-mismatch.lua45
-rw-r--r--script/core/diagnostics/circle-doc-class.lua3
-rw-r--r--script/core/diagnostics/close-non-object.lua9
-rw-r--r--script/core/diagnostics/code-after-break.lua4
-rw-r--r--script/core/diagnostics/codestyle-check.lua2
-rw-r--r--script/core/diagnostics/count-down-loop.lua13
-rw-r--r--script/core/diagnostics/deprecated.lua2
-rw-r--r--script/core/diagnostics/different-requires.lua2
-rw-r--r--script/core/diagnostics/duplicate-doc-alias.lua18
-rw-r--r--script/core/diagnostics/duplicate-doc-field.lua11
-rw-r--r--script/core/diagnostics/duplicate-index.lua5
-rw-r--r--script/core/diagnostics/duplicate-set-field.lua12
-rw-r--r--script/core/diagnostics/empty-block.lua7
-rw-r--r--script/core/diagnostics/global-in-nil-env.lua68
-rw-r--r--script/core/diagnostics/init.lua94
-rw-r--r--script/core/diagnostics/lowercase-global.lua6
-rw-r--r--script/core/diagnostics/missing-parameter.lua57
-rw-r--r--script/core/diagnostics/missing-return-value.lua66
-rw-r--r--script/core/diagnostics/missing-return.lua86
-rw-r--r--script/core/diagnostics/need-check-nil.lua10
-rw-r--r--script/core/diagnostics/newfield-call.lua18
-rw-r--r--script/core/diagnostics/newline-call.lua13
-rw-r--r--script/core/diagnostics/no-unknown.lua29
-rw-r--r--script/core/diagnostics/not-yieldable.lua4
-rw-r--r--script/core/diagnostics/param-type-mismatch.lua72
-rw-r--r--script/core/diagnostics/redefined-local.lua5
-rw-r--r--script/core/diagnostics/redundant-parameter.lua73
-rw-r--r--script/core/diagnostics/redundant-return-value.lua73
-rw-r--r--script/core/diagnostics/return-type-mismatch.lua76
-rw-r--r--script/core/diagnostics/spell-check.lua34
-rw-r--r--script/core/diagnostics/trailing-space.lua20
-rw-r--r--script/core/diagnostics/type-check.lua3
-rw-r--r--script/core/diagnostics/unbalanced-assignments.lua22
-rw-r--r--script/core/diagnostics/undefined-doc-name.lua2
-rw-r--r--script/core/diagnostics/undefined-doc-param.lua42
-rw-r--r--script/core/diagnostics/undefined-env-child.lua32
-rw-r--r--script/core/diagnostics/undefined-field.lua8
-rw-r--r--script/core/diagnostics/undefined-global.lua10
-rw-r--r--script/core/diagnostics/unknown-cast-variable.lua32
-rw-r--r--script/core/diagnostics/unknown-diag-code.lua4
-rw-r--r--script/core/diagnostics/unknown-operator.lua36
-rw-r--r--script/core/diagnostics/unreachable-code.lua84
-rw-r--r--script/core/diagnostics/unused-function.lua5
-rw-r--r--script/core/diagnostics/unused-local.lua33
-rw-r--r--script/core/diagnostics/unused-vararg.lua3
48 files changed, 1120 insertions, 304 deletions
diff --git a/script/core/diagnostics/ambiguity-1.lua b/script/core/diagnostics/ambiguity-1.lua
index f03f4361..830b2f2f 100644
--- a/script/core/diagnostics/ambiguity-1.lua
+++ b/script/core/diagnostics/ambiguity-1.lua
@@ -27,10 +27,10 @@ local literalMap = {
return function (uri, callback)
local state = files.getState(uri)
- if not state then
+ local text = files.getText(uri)
+ if not state or not text then
return
end
- local text = files.getText(uri)
guide.eachSourceType(state.ast, 'binary', function (source)
if source.op.type ~= 'or' then
return
diff --git a/script/core/diagnostics/assign-type-mismatch.lua b/script/core/diagnostics/assign-type-mismatch.lua
new file mode 100644
index 00000000..2d5c3f98
--- /dev/null
+++ b/script/core/diagnostics/assign-type-mismatch.lua
@@ -0,0 +1,117 @@
+local files = require 'files'
+local lang = require 'language'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local await = require 'await'
+
+local checkTypes = {
+ 'local',
+ 'setlocal',
+ 'setglobal',
+ 'setfield',
+ 'setindex',
+ 'setmethod',
+ 'tablefield',
+ 'tableindex'
+}
+
+---@param source parser.object
+---@return boolean
+local function hasMarkType(source)
+ if not source.bindDocs then
+ return false
+ end
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.type'
+ or doc.type == 'doc.class' then
+ return true
+ end
+ end
+ return false
+end
+
+---@param source parser.object
+---@return boolean
+local function hasMarkClass(source)
+ if not source.bindDocs then
+ return false
+ end
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.class' then
+ return true
+ end
+ end
+ return false
+end
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceTypes(state.ast, checkTypes, function (source)
+ local value = source.value
+ if not value then
+ return
+ end
+ await.delay()
+ if source.type == 'setlocal' then
+ local locNode = vm.compileNode(source.node)
+ if not locNode:getData 'hasDefined' then
+ return
+ end
+ end
+ if value.type == 'nil' then
+ --[[
+ ---@class A
+ local mt
+ ---@type X
+ mt._x = nil -- don't warn this
+ ]]
+ if hasMarkType(source) then
+ return
+ end
+ if source.type == 'setfield'
+ or source.type == 'setindex' then
+ return
+ end
+ end
+
+ local valueNode = vm.compileNode(value)
+ if source.type == 'setindex' then
+ -- boolean[1] = nil
+ valueNode = valueNode:copy():removeOptional()
+ end
+
+ if value.type == 'getfield'
+ or value.type == 'getindex' then
+ -- 由于无法对字段进行类型收窄,
+ -- 因此将假值移除再进行检查
+ valueNode = valueNode:copy():setTruthy()
+ end
+
+ local varNode = vm.compileNode(source)
+ if vm.canCastType(uri, varNode, valueNode) then
+ return
+ end
+
+ -- local Cat = setmetatable({}, {__index = Animal}) 允许逆变
+ if hasMarkClass(source) then
+ if vm.canCastType(uri, valueNode:copy():remove 'table', varNode) then
+ return
+ end
+ end
+
+ callback {
+ start = source.start,
+ finish = source.finish,
+ message = lang.script('DIAG_ASSIGN_TYPE_MISMATCH', {
+ def = vm.getInfer(varNode):view(uri),
+ ref = vm.getInfer(valueNode):view(uri),
+ }),
+ }
+ end)
+end
diff --git a/script/core/diagnostics/cast-local-type.lua b/script/core/diagnostics/cast-local-type.lua
new file mode 100644
index 00000000..c3d6e1bb
--- /dev/null
+++ b/script/core/diagnostics/cast-local-type.lua
@@ -0,0 +1,50 @@
+local files = require 'files'
+local lang = require 'language'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local await = require 'await'
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'local', function (loc)
+ if not loc.ref then
+ return
+ end
+ await.delay()
+ local locNode = vm.compileNode(loc)
+ if not locNode:getData 'hasDefined' then
+ return
+ end
+ for _, ref in ipairs(loc.ref) do
+ if ref.type == 'setlocal' and ref.value then
+ await.delay()
+ local refNode = vm.compileNode(ref)
+ local value = ref.value
+
+ if value.type == 'getfield'
+ or value.type == 'getindex' then
+ -- 由于无法对字段进行类型收窄,
+ -- 因此将假值移除再进行检查
+ refNode = refNode:copy():setTruthy()
+ end
+
+ if not vm.canCastType(uri, locNode, refNode) then
+ callback {
+ start = ref.start,
+ finish = ref.finish,
+ message = lang.script('DIAG_CAST_LOCAL_TYPE', {
+ def = vm.getInfer(locNode):view(uri),
+ ref = vm.getInfer(refNode):view(uri),
+ }),
+ }
+ end
+ end
+ end
+ end)
+end
diff --git a/script/core/diagnostics/cast-type-mismatch.lua b/script/core/diagnostics/cast-type-mismatch.lua
new file mode 100644
index 00000000..a48e6cca
--- /dev/null
+++ b/script/core/diagnostics/cast-type-mismatch.lua
@@ -0,0 +1,45 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local lang = require 'language'
+local vm = require 'vm'
+local await = require 'await'
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ if not state.ast.docs then
+ return
+ end
+
+ for _, doc in ipairs(state.ast.docs) do
+ if doc.type == 'doc.cast' and doc.loc then
+ await.delay()
+ local defs = vm.getDefs(doc.loc)
+ local loc = defs[1]
+ if loc then
+ local defNode = vm.compileNode(loc)
+ if defNode:getData 'hasDefined' then
+ for _, cast in ipairs(doc.casts) do
+ if not cast.mode and cast.extends then
+ local refNode = vm.compileNode(cast.extends)
+ if not vm.canCastType(uri, defNode, refNode) then
+ callback {
+ start = cast.extends.start,
+ finish = cast.extends.finish,
+ message = lang.script('DIAG_CAST_TYPE_MISMATCH', {
+ def = vm.getInfer(defNode):view(uri),
+ ref = vm.getInfer(refNode):view(uri),
+ })
+ }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/script/core/diagnostics/circle-doc-class.lua b/script/core/diagnostics/circle-doc-class.lua
index 40d4afeb..fcd2021d 100644
--- a/script/core/diagnostics/circle-doc-class.lua
+++ b/script/core/diagnostics/circle-doc-class.lua
@@ -2,7 +2,9 @@ local files = require 'files'
local lang = require 'language'
local vm = require 'vm'
local guide = require 'parser.guide'
+local await = require 'await'
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
@@ -18,6 +20,7 @@ return function (uri, callback)
if not doc.extends then
goto CONTINUE
end
+ await.delay()
local myName = guide.getKeyName(doc)
local list = { doc }
local mark = {}
diff --git a/script/core/diagnostics/close-non-object.lua b/script/core/diagnostics/close-non-object.lua
index c97014fa..1a42b800 100644
--- a/script/core/diagnostics/close-non-object.lua
+++ b/script/core/diagnostics/close-non-object.lua
@@ -25,10 +25,11 @@ return function (uri, callback)
return
end
local infer = vm.getInfer(source.value)
- if not infer:hasClass()
- and not infer:hasType 'nil'
- and not infer:hasType 'table'
- and infer:view('any', uri) ~= 'any' then
+ if not infer:hasClass(uri)
+ and not infer:hasType(uri, 'nil')
+ and not infer:hasType(uri, 'table')
+ and not infer:hasUnknown(uri)
+ and not infer:hasAny(uri) then
callback {
start = source.value.start,
finish = source.value.finish,
diff --git a/script/core/diagnostics/code-after-break.lua b/script/core/diagnostics/code-after-break.lua
index 21f7e83a..963fd9ed 100644
--- a/script/core/diagnostics/code-after-break.lua
+++ b/script/core/diagnostics/code-after-break.lua
@@ -2,7 +2,9 @@ local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
local define = require 'proto.define'
+local await = require 'await'
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
@@ -10,12 +12,14 @@ return function (uri, callback)
end
local mark = {}
+ ---@async
guide.eachSourceType(state.ast, 'break', function (source)
local list = source.parent
if mark[list] then
return
end
mark[list] = true
+ await.delay()
for i = #list, 1, -1 do
local src = list[i]
if src == source then
diff --git a/script/core/diagnostics/codestyle-check.lua b/script/core/diagnostics/codestyle-check.lua
index 34d55ee2..25603b4b 100644
--- a/script/core/diagnostics/codestyle-check.lua
+++ b/script/core/diagnostics/codestyle-check.lua
@@ -7,7 +7,7 @@ local pformatting = require 'provider.formatting'
---@async
return function(uri, callback)
- local text = files.getText(uri)
+ local text = files.getOriginText(uri)
if not text then
return
end
diff --git a/script/core/diagnostics/count-down-loop.lua b/script/core/diagnostics/count-down-loop.lua
index 9bc4b273..bd6e5ee3 100644
--- a/script/core/diagnostics/count-down-loop.lua
+++ b/script/core/diagnostics/count-down-loop.lua
@@ -10,12 +10,15 @@ return function (uri, callback)
end
guide.eachSourceType(state.ast, 'loop', function (source)
- local maxNumer = source.max and tonumber(source.max[1])
- if maxNumer ~= 1 then
+ local maxNumber = source.max and tonumber(source.max[1])
+ if not maxNumber then
return
end
local minNumber = source.init and tonumber(source.init[1])
- if minNumber and minNumber <= 1 then
+ if minNumber and maxNumber and minNumber <= maxNumber then
+ return
+ end
+ if not minNumber and maxNumber ~= 1 then
return
end
if not source.step then
@@ -24,7 +27,7 @@ return function (uri, callback)
finish = source.max.finish,
message = lang.script('DIAG_COUNT_DOWN_LOOP'
, ('%s, %s'):format(text:sub(
- guide.positionToOffset(state, source.init.start),
+ guide.positionToOffset(state, source.init.start + 1),
guide.positionToOffset(state, source.max.finish)
), '-1')
)
@@ -37,7 +40,7 @@ return function (uri, callback)
finish = source.step.finish,
message = lang.script('DIAG_COUNT_DOWN_LOOP'
, ('%s, -%s'):format(text:sub(
- guide.positionToOffset(state, source.init.start),
+ guide.positionToOffset(state, source.init.start + 1),
guide.positionToOffset(state, source.max.finish)
), source.step[1])
)
diff --git a/script/core/diagnostics/deprecated.lua b/script/core/diagnostics/deprecated.lua
index 27920c43..85ae2d19 100644
--- a/script/core/diagnostics/deprecated.lua
+++ b/script/core/diagnostics/deprecated.lua
@@ -15,7 +15,7 @@ return function (uri, callback)
return
end
- local dglobals = config.get(uri, 'Lua.diagnostics.globals')
+ local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
local rspecial = config.get(uri, 'Lua.runtime.special')
guide.eachSourceTypes(ast.ast, types, function (src) ---@async
diff --git a/script/core/diagnostics/different-requires.lua b/script/core/diagnostics/different-requires.lua
index de063c9f..22e3e681 100644
--- a/script/core/diagnostics/different-requires.lua
+++ b/script/core/diagnostics/different-requires.lua
@@ -21,7 +21,7 @@ return function (uri, callback)
return
end
local literal = arg1[1]
- local results = rpath.findUrisByRequirePath(uri, literal)
+ local results = rpath.findUrisByRequireName(uri, literal)
if not results or #results ~= 1 then
return
end
diff --git a/script/core/diagnostics/duplicate-doc-alias.lua b/script/core/diagnostics/duplicate-doc-alias.lua
index 3df6f972..360358e4 100644
--- a/script/core/diagnostics/duplicate-doc-alias.lua
+++ b/script/core/diagnostics/duplicate-doc-alias.lua
@@ -2,7 +2,9 @@ local files = require 'files'
local lang = require 'language'
local vm = require 'vm'
local guide = require 'parser.guide'
+local await = require 'await'
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
@@ -15,14 +17,20 @@ return function (uri, callback)
local cache = {}
for _, doc in ipairs(state.ast.docs) do
- if doc.type == 'doc.alias' then
+ if doc.type == 'doc.alias'
+ or doc.type == 'doc.enum' then
local name = guide.getKeyName(doc)
+ if not name then
+ return
+ end
+ await.delay()
if not cache[name] then
local docs = vm.getDocSets(uri, name)
cache[name] = {}
for _, otherDoc in ipairs(docs) do
if otherDoc.type == 'doc.alias'
- or otherDoc.type == 'doc.class' then
+ or otherDoc.type == 'doc.class'
+ or otherDoc.type == 'doc.enum' then
cache[name][#cache[name]+1] = {
start = otherDoc.start,
finish = otherDoc.finish,
@@ -33,10 +41,10 @@ return function (uri, callback)
end
if #cache[name] > 1 then
callback {
- start = doc.alias.start,
- finish = doc.alias.finish,
+ start = (doc.alias or doc.enum).start,
+ finish = (doc.alias or doc.enum).finish,
related = cache,
- message = lang.script('DIAG_DUPLICATE_DOC_CLASS', name)
+ message = lang.script('DIAG_DUPLICATE_DOC_ALIAS', name)
}
end
end
diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua
index d4116b9b..a30dfa88 100644
--- a/script/core/diagnostics/duplicate-doc-field.lua
+++ b/script/core/diagnostics/duplicate-doc-field.lua
@@ -1,5 +1,7 @@
local files = require 'files'
local lang = require 'language'
+local vm = require 'vm.vm'
+local await = require 'await'
local function getFieldEventName(doc)
if not doc.extends then
@@ -28,6 +30,7 @@ local function getFieldEventName(doc)
return nil
end
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
@@ -45,7 +48,13 @@ return function (uri, callback)
mark = {}
elseif doc.type == 'doc.field' then
if mark then
- local name = ('%q'):format(doc.field[1])
+ await.delay()
+ local name
+ if doc.field.type == 'doc.type' then
+ name = ('[%s]'):format(vm.getInfer(doc.field):view(uri))
+ else
+ name = ('%q'):format(doc.field[1])
+ end
local eventName = getFieldEventName(doc)
if eventName then
name = name .. '|' .. eventName
diff --git a/script/core/diagnostics/duplicate-index.lua b/script/core/diagnostics/duplicate-index.lua
index 5097ab3a..dfd9bd4b 100644
--- a/script/core/diagnostics/duplicate-index.lua
+++ b/script/core/diagnostics/duplicate-index.lua
@@ -2,14 +2,17 @@ local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
local define = require 'proto.define'
+local await = require 'await'
+---@async
return function (uri, callback)
local ast = files.getState(uri)
if not ast then
return
end
-
+ ---@async
guide.eachSourceType(ast.ast, 'table', function (source)
+ await.delay()
local mark = {}
for _, obj in ipairs(source) do
if obj.type == 'tablefield'
diff --git a/script/core/diagnostics/duplicate-set-field.lua b/script/core/diagnostics/duplicate-set-field.lua
index 8052c420..ce67ab46 100644
--- a/script/core/diagnostics/duplicate-set-field.lua
+++ b/script/core/diagnostics/duplicate-set-field.lua
@@ -3,17 +3,21 @@ local lang = require 'language'
local define = require 'proto.define'
local guide = require 'parser.guide'
local vm = require 'vm'
+local await = require 'await'
+---@async
return function (uri, callback)
local ast = files.getState(uri)
if not ast then
return
end
+ ---@async
guide.eachSourceType(ast.ast, 'local', function (source)
if not source.ref then
return
end
+ await.delay()
local sets = {}
for _, ref in ipairs(source.ref) do
if ref.type ~= 'getlocal' then
@@ -48,10 +52,12 @@ return function (uri, callback)
local blocks = {}
for _, value in ipairs(values) do
local block = guide.getBlock(value)
- if not blocks[block] then
- blocks[block] = {}
+ if block then
+ if not blocks[block] then
+ blocks[block] = {}
+ end
+ blocks[block][#blocks[block]+1] = value
end
- blocks[block][#blocks[block]+1] = value
end
for _, defs in pairs(blocks) do
if #defs <= 1 then
diff --git a/script/core/diagnostics/empty-block.lua b/script/core/diagnostics/empty-block.lua
index fc205d7e..e05b6aef 100644
--- a/script/core/diagnostics/empty-block.lua
+++ b/script/core/diagnostics/empty-block.lua
@@ -2,15 +2,18 @@ local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
local define = require 'proto.define'
+local await = require 'await'
--- 检查空代码块
+-- 检查空代码块
-- 但是排除忙等待(repeat/while)
+---@async
return function (uri, callback)
local ast = files.getState(uri)
if not ast then
return
end
+ await.delay()
guide.eachSourceType(ast.ast, 'if', function (source)
for _, block in ipairs(source) do
if #block > 0 then
@@ -24,6 +27,7 @@ return function (uri, callback)
message = lang.script.DIAG_EMPTY_BLOCK,
}
end)
+ await.delay()
guide.eachSourceType(ast.ast, 'loop', function (source)
if #source > 0 then
return
@@ -35,6 +39,7 @@ return function (uri, callback)
message = lang.script.DIAG_EMPTY_BLOCK,
}
end)
+ await.delay()
guide.eachSourceType(ast.ast, 'in', function (source)
if #source > 0 then
return
diff --git a/script/core/diagnostics/global-in-nil-env.lua b/script/core/diagnostics/global-in-nil-env.lua
index 334fd81a..e154080c 100644
--- a/script/core/diagnostics/global-in-nil-env.lua
+++ b/script/core/diagnostics/global-in-nil-env.lua
@@ -2,65 +2,35 @@ local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
--- TODO: 检查路径是否可达
-local function mayRun(path)
- return true
-end
-
return function (uri, callback)
- local ast = files.getState(uri)
- if not ast then
- return
- end
- local root = guide.getRoot(ast.ast)
- local env = guide.getENV(root)
-
- local nilDefs = {}
- if not env or not env.ref then
- return
- end
- for _, ref in ipairs(env.ref) do
- if ref.type == 'setlocal' then
- if ref.value and ref.value.type == 'nil' then
- nilDefs[#nilDefs+1] = ref
- end
- end
- end
-
- if #nilDefs == 0 then
+ local state = files.getState(uri)
+ if not state then
return
end
local function check(source)
local node = source.node
if node.tag == '_ENV' then
- local ok
- for _, nilDef in ipairs(nilDefs) do
- local mode, pathA = guide.getPath(nilDef, source)
- if mode == 'before'
- and mayRun(pathA) then
- ok = nilDef
- break
- end
- end
- if ok then
- callback {
- start = source.start,
- finish = source.finish,
- uri = uri,
- message = lang.script.DIAG_GLOBAL_IN_NIL_ENV,
- related = {
- {
- start = ok.start,
- finish = ok.finish,
- uri = uri,
- }
+ return
+ end
+
+ if not node.value or node.value.type == 'nil' then
+ callback {
+ start = source.start,
+ finish = source.finish,
+ uri = uri,
+ message = lang.script.DIAG_GLOBAL_IN_NIL_ENV,
+ related = {
+ {
+ start = node.start,
+ finish = node.finish,
+ uri = uri,
}
}
- end
+ }
end
end
- guide.eachSourceType(ast.ast, 'getglobal', check)
- guide.eachSourceType(ast.ast, 'setglobal', check)
+ guide.eachSourceType(state.ast, 'getglobal', check)
+ guide.eachSourceType(state.ast, 'setglobal', check)
end
diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua
index b4ae3715..c33de6ce 100644
--- a/script/core/diagnostics/init.lua
+++ b/script/core/diagnostics/init.lua
@@ -3,14 +3,22 @@ local define = require 'proto.define'
local config = require 'config'
local await = require 'await'
local vm = require "vm.vm"
+local util = require 'utility'
+local diagd = require 'proto.diagnostic'
-- 把耗时最长的诊断放到最后面
local diagSort = {
- ['redundant-value'] = 96,
- ['not-yieldable'] = 97,
- ['deprecated'] = 98,
- ['undefined-field'] = 99,
- ['redundant-parameter'] = 100,
+ ['redundant-value'] = 100,
+ ['not-yieldable'] = 100,
+ ['deprecated'] = 100,
+ ['undefined-field'] = 110,
+ ['redundant-parameter'] = 110,
+ ['cast-local-type'] = 120,
+ ['assign-type-mismatch'] = 120,
+ ['param-type-mismatch'] = 120,
+ ['missing-return'] = 120,
+ ['missing-return-value'] = 120,
+ ['redundant-return-value'] = 120,
}
local diagList = {}
@@ -46,30 +54,86 @@ local function checkSleep(uri, passed)
sleepRest = sleepRest - sleeped
end
+---@param uri uri
+---@param name string
+---@return string
+local function getSeverity(uri, name)
+ local severity = config.get(uri, 'Lua.diagnostics.severity')[name]
+ or define.DiagnosticDefaultSeverity[name]
+ if severity:sub(-1) == '!' then
+ return severity:sub(1, -2)
+ end
+ local groupSeverity = config.get(uri, 'Lua.diagnostics.groupSeverity')
+ local groups = diagd.getGroups(name)
+ local groupLevel = 999
+ for _, groupName in ipairs(groups) do
+ local gseverity = groupSeverity[groupName]
+ if gseverity and gseverity ~= 'Fallback' then
+ groupLevel = math.min(groupLevel, define.DiagnosticSeverity[gseverity])
+ end
+ end
+ if groupLevel == 999 then
+ return severity
+ end
+ for severityName, level in pairs(define.DiagnosticSeverity) do
+ if level == groupLevel then
+ return severityName
+ end
+ end
+ return severity
+end
+
+---@param uri uri
+---@param name string
+---@return string
+local function getStatus(uri, name)
+ local status = config.get(uri, 'Lua.diagnostics.neededFileStatus')[name]
+ or define.DiagnosticDefaultNeededFileStatus[name]
+ if status:sub(-1) == '!' then
+ return status:sub(1, -2)
+ end
+ local groupStatus = config.get(uri, 'Lua.diagnostics.groupFileStatus')
+ local groups = diagd.getGroups(name)
+ local groupLevel = 0
+ for _, groupName in ipairs(groups) do
+ local gstatus = groupStatus[groupName]
+ if gstatus and gstatus ~= 'Fallback' then
+ groupLevel = math.max(groupLevel, define.DiagnosticFileStatus[gstatus])
+ end
+ end
+ if groupLevel == 0 then
+ return status
+ end
+ for statusName, level in pairs(define.DiagnosticFileStatus) do
+ if level == groupLevel then
+ return statusName
+ end
+ end
+ return status
+end
+
---@async
---@param uri uri
---@param name string
---@param isScopeDiag boolean
---@param response async fun(result: any)
local function check(uri, name, isScopeDiag, response)
- if config.get(uri, 'Lua.diagnostics.disable')[name] then
+ local disables = config.get(uri, 'Lua.diagnostics.disable')
+ if util.arrayHas(disables, name) then
return
end
- local level = config.get(uri, 'Lua.diagnostics.severity')[name]
- or define.DiagnosticDefaultSeverity[name]
-
- local neededFileStatus = config.get(uri, 'Lua.diagnostics.neededFileStatus')[name]
- or define.DiagnosticDefaultNeededFileStatus[name]
+ local severity = getSeverity(uri, name)
+ local status = getStatus(uri, name)
- if neededFileStatus == 'None' then
+ if status == 'None' then
return
end
- if neededFileStatus == 'Opened' and not files.isOpen(uri) then
+ if status == 'Opened' and not files.isOpen(uri) then
return
end
- local severity = define.DiagnosticSeverity[level]
+ local level = define.DiagnosticSeverity[severity]
local clock = os.clock()
local mark = {}
---@async
@@ -85,7 +149,7 @@ local function check(uri, name, isScopeDiag, response)
end
mark[result.start] = true
- result.level = severity or result.level
+ result.level = level or result.level
result.code = name
response(result)
end, name)
diff --git a/script/core/diagnostics/lowercase-global.lua b/script/core/diagnostics/lowercase-global.lua
index d03e8c70..68bec234 100644
--- a/script/core/diagnostics/lowercase-global.lua
+++ b/script/core/diagnostics/lowercase-global.lua
@@ -3,6 +3,7 @@ local guide = require 'parser.guide'
local lang = require 'language'
local config = require 'config'
local vm = require 'vm'
+local util = require 'utility'
local function isDocClass(source)
if not source.bindDocs then
@@ -23,10 +24,7 @@ return function (uri, callback)
return
end
- local definedGlobal = {}
- for name in pairs(config.get(uri, 'Lua.diagnostics.globals')) do
- definedGlobal[name] = true
- end
+ local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
guide.eachSourceType(ast.ast, 'setglobal', function (source)
local name = guide.getKeyName(source)
diff --git a/script/core/diagnostics/missing-parameter.lua b/script/core/diagnostics/missing-parameter.lua
index 698680ca..78b94a09 100644
--- a/script/core/diagnostics/missing-parameter.lua
+++ b/script/core/diagnostics/missing-parameter.lua
@@ -2,68 +2,27 @@ local files = require 'files'
local guide = require 'parser.guide'
local vm = require 'vm'
local lang = require 'language'
+local await = require 'await'
-local function countCallArgs(source)
- local result = 0
- if not source.args then
- return 0
- end
- result = result + #source.args
- return result
-end
-
----@return integer
-local function countFuncArgs(source)
- if not source.args or #source.args == 0 then
- return 0
- end
- local count = 0
- for i = #source.args, 1, -1 do
- local arg = source.args[i]
- if arg.type ~= '...'
- and not (arg.name and arg.name[1] =='...')
- and not vm.compileNode(arg):isNullable() then
- return i
- end
- end
- return count
-end
-
-local function getFuncArgs(func)
- local funcArgs
- local defs = vm.getDefs(func)
- for _, def in ipairs(defs) do
- if def.type == 'function'
- or def.type == 'doc.type.function' then
- local args = countFuncArgs(def)
- if not funcArgs or args < funcArgs then
- funcArgs = args
- end
- end
- end
- return funcArgs
-end
-
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
return
end
+ ---@async
guide.eachSourceType(state.ast, 'call', function (source)
- local callArgs = countCallArgs(source)
+ await.delay()
+ local _, callArgs = vm.countList(source.args)
- local func = source.node
- local funcArgs = getFuncArgs(func)
+ local funcNode = vm.compileNode(source.node)
+ local funcArgs = vm.countParamsOfNode(funcNode)
- if not funcArgs then
+ if callArgs >= funcArgs then
return
end
- local delta = callArgs - funcArgs
- if delta >= 0 then
- return
- end
callback {
start = source.start,
finish = source.finish,
diff --git a/script/core/diagnostics/missing-return-value.lua b/script/core/diagnostics/missing-return-value.lua
new file mode 100644
index 00000000..2156d66c
--- /dev/null
+++ b/script/core/diagnostics/missing-return-value.lua
@@ -0,0 +1,66 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local lang = require 'language'
+local await = require 'await'
+
+local function hasDocReturn(func)
+ if not func.bindDocs then
+ return false
+ end
+ for _, doc in ipairs(func.bindDocs) do
+ if doc.type == 'doc.return' then
+ return true
+ end
+ end
+ return false
+end
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'function', function (source)
+ await.delay()
+ if not hasDocReturn(source) then
+ return
+ end
+ local min = vm.countReturnsOfFunction(source)
+ if min == 0 then
+ return
+ end
+ local returns = source.returns
+ if not returns then
+ return
+ end
+ for _, ret in ipairs(returns) do
+ local rmin, rmax = vm.countList(ret)
+ if rmax < min then
+ if rmin == rmax then
+ callback {
+ start = ret.start,
+ finish = ret.start + #'return',
+ message = lang.script('DIAG_MISSING_RETURN_VALUE', {
+ min = min,
+ rmax = rmax,
+ }),
+ }
+ else
+ callback {
+ start = ret.start,
+ finish = ret.start + #'return',
+ message = lang.script('DIAG_MISSING_RETURN_VALUE_RANGE', {
+ min = min,
+ rmin = rmin,
+ rmax = rmax,
+ }),
+ }
+ end
+ end
+ end
+ end)
+end
diff --git a/script/core/diagnostics/missing-return.lua b/script/core/diagnostics/missing-return.lua
new file mode 100644
index 00000000..e3539ac0
--- /dev/null
+++ b/script/core/diagnostics/missing-return.lua
@@ -0,0 +1,86 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local lang = require 'language'
+local await = require 'await'
+
+---@param block parser.object
+---@return boolean
+local function hasReturn(block)
+ if block.hasReturn or block.hasError then
+ return true
+ end
+ if block.type == 'if' then
+ local hasElse
+ for _, subBlock in ipairs(block) do
+ if not hasReturn(subBlock) then
+ return false
+ end
+ if subBlock.type == 'elseblock' then
+ hasElse = true
+ end
+ end
+ return hasElse == true
+ else
+ if block.type == 'while' then
+ if vm.testCondition(block.filter) then
+ return true
+ end
+ end
+ for _, action in ipairs(block) do
+ if guide.isBlockType(action) then
+ if hasReturn(action) then
+ return true
+ end
+ end
+ end
+ end
+ return false
+end
+
+---@param func parser.object
+---@return boolean
+local function isEmptyFunction(func)
+ if #func > 0 then
+ return false
+ end
+ local startRow = guide.rowColOf(func.start)
+ local finishRow = guide.rowColOf(func.finish)
+ return finishRow - startRow <= 1
+end
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'function', function (source)
+ -- check declare only
+ if isEmptyFunction(source) then
+ return
+ end
+ await.delay()
+ if vm.countReturnsOfFunction(source, true) == 0 then
+ return
+ end
+ if hasReturn(source) then
+ return
+ end
+ local lastAction = source[#source]
+ local pos
+ if lastAction then
+ pos = lastAction.range or lastAction.finish
+ else
+ local row = guide.rowColOf(source.finish)
+ pos = guide.positionOf(row - 1, 0)
+ end
+ callback {
+ start = pos,
+ finish = pos,
+ message = lang.script('DIAG_MISSING_RETURN'),
+ }
+ end)
+end
diff --git a/script/core/diagnostics/need-check-nil.lua b/script/core/diagnostics/need-check-nil.lua
index 98fdfd08..9c86939a 100644
--- a/script/core/diagnostics/need-check-nil.lua
+++ b/script/core/diagnostics/need-check-nil.lua
@@ -2,14 +2,18 @@ local files = require 'files'
local guide = require 'parser.guide'
local vm = require 'vm'
local lang = require 'language'
+local await = require 'await'
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
return
end
+ ---@async
guide.eachSourceType(state.ast, 'getlocal', function (src)
+ await.delay()
local checkNil
local nxt = src.next
if nxt then
@@ -24,11 +28,15 @@ return function (uri, callback)
if call and call.type == 'call' and call.node == src then
checkNil = true
end
+ local setIndex = src.parent
+ if setIndex and setIndex.type == 'setindex' and setIndex.index == src then
+ checkNil = true
+ end
if not checkNil then
return
end
local node = vm.compileNode(src)
- if node:hasFalsy() then
+ if node:hasFalsy() and not vm.getInfer(src):hasType(uri, 'any') then
callback {
start = src.start,
finish = src.finish,
diff --git a/script/core/diagnostics/newfield-call.lua b/script/core/diagnostics/newfield-call.lua
index 669ed2bb..bd114959 100644
--- a/script/core/diagnostics/newfield-call.lua
+++ b/script/core/diagnostics/newfield-call.lua
@@ -1,16 +1,20 @@
local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
+local await = require 'await'
+local sub = require 'core.substring'
+---@async
return function (uri, callback)
- local ast = files.getState(uri)
- if not ast then
+ local state = files.getState(uri)
+ local text = files.getText(uri)
+ if not state or not text then
return
end
- local text = files.getText(uri)
-
- guide.eachSourceType(ast.ast, 'table', function (source)
+ ---@async
+ guide.eachSourceType(state.ast, 'table', function (source)
+ await.delay()
for i = 1, #source do
local field = source[i]
if field.type ~= 'tableexp' then
@@ -33,8 +37,8 @@ return function (uri, callback)
start = call.start,
finish = call.finish,
message = lang.script('DIAG_PREFIELD_CALL'
- , text:sub(func.start, func.finish)
- , text:sub(args.start, args.finish)
+ , sub(state)(func.start + 1, func.finish)
+ , sub(state)(args.start + 1, args.finish)
)
}
end
diff --git a/script/core/diagnostics/newline-call.lua b/script/core/diagnostics/newline-call.lua
index 3f2d5ca5..2ba2ce03 100644
--- a/script/core/diagnostics/newline-call.lua
+++ b/script/core/diagnostics/newline-call.lua
@@ -1,14 +1,18 @@
local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
+local await = require 'await'
+local sub = require 'core.substring'
+---@async
return function (uri, callback)
local state = files.getState(uri)
local text = files.getText(uri)
- if not state then
+ if not state or not text then
return
end
+ ---@async
guide.eachSourceType(state.ast, 'call', function (source)
local node = source.node
local args = source.args
@@ -20,6 +24,9 @@ return function (uri, callback)
if not source.next then
return
end
+
+ await.delay()
+
local startOffset = guide.positionToOffset(state, args.start) + 1
local finishOffset = guide.positionToOffset(state, args.finish)
if text:sub(startOffset, startOffset) ~= '('
@@ -38,8 +45,8 @@ return function (uri, callback)
start = node.start,
finish = args.finish,
message = lang.script('DIAG_PREVIOUS_CALL'
- , text:sub(node.start, node.finish)
- , text:sub(args.start, args.finish)
+ , sub(state)(node.start + 1, node.finish)
+ , sub(state)(args.start + 1, args.finish)
),
}
end
diff --git a/script/core/diagnostics/no-unknown.lua b/script/core/diagnostics/no-unknown.lua
index 48aab5da..e706931a 100644
--- a/script/core/diagnostics/no-unknown.lua
+++ b/script/core/diagnostics/no-unknown.lua
@@ -2,25 +2,30 @@ local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
local vm = require 'vm'
+local await = require 'await'
+local types = {
+ 'local',
+ 'setlocal',
+ 'setglobal',
+ 'getglobal',
+ 'setfield',
+ 'setindex',
+ 'tablefield',
+ 'tableindex',
+}
+
+---@async
return function (uri, callback)
local ast = files.getState(uri)
if not ast then
return
end
- guide.eachSource(ast.ast, function (source)
- if source.type ~= 'local'
- and source.type ~= 'setlocal'
- and source.type ~= 'setglobal'
- and source.type ~= 'getglobal'
- and source.type ~= 'setfield'
- and source.type ~= 'setindex'
- and source.type ~= 'tablefield'
- and source.type ~= 'tableindex' then
- return
- end
- if vm.getInfer(source):view() == 'unknown' then
+ ---@async
+ guide.eachSourceTypes(ast.ast, types, function (source)
+ await.delay()
+ if vm.getInfer(source):view(uri) == 'unknown' then
callback {
start = source.start,
finish = source.finish,
diff --git a/script/core/diagnostics/not-yieldable.lua b/script/core/diagnostics/not-yieldable.lua
index a1c84276..055025d4 100644
--- a/script/core/diagnostics/not-yieldable.lua
+++ b/script/core/diagnostics/not-yieldable.lua
@@ -11,7 +11,7 @@ local function isYieldAble(defs, i)
local arg = def.args and def.args[i]
if arg then
hasFuncDef = true
- if vm.getInfer(arg):hasType 'any'
+ if vm.getInfer(arg):hasType(guide.getUri(def), 'any')
or vm.isAsync(arg, true)
or arg.type == '...' then
return true
@@ -22,7 +22,7 @@ local function isYieldAble(defs, i)
local arg = def.args and def.args[i]
if arg then
hasFuncDef = true
- if vm.getInfer(arg.extends):hasType 'any'
+ if vm.getInfer(arg.extends):hasType(guide.getUri(def), 'any')
or vm.isAsync(arg.extends, true) then
return true
end
diff --git a/script/core/diagnostics/param-type-mismatch.lua b/script/core/diagnostics/param-type-mismatch.lua
new file mode 100644
index 00000000..6f34f579
--- /dev/null
+++ b/script/core/diagnostics/param-type-mismatch.lua
@@ -0,0 +1,72 @@
+local files = require 'files'
+local lang = require 'language'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local await = require 'await'
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@param funcNode vm.node
+ ---@param i integer
+ ---@return vm.node?
+ local function getDefNode(funcNode, i)
+ local defNode = vm.createNode()
+ for f in funcNode:eachObject() do
+ if f.type == 'function'
+ or f.type == 'doc.type.function' then
+ local param = f.args and f.args[i]
+ if param then
+ defNode:merge(vm.compileNode(param))
+ if param[1] == '...' then
+ defNode:addOptional()
+ end
+ end
+ end
+ end
+ if defNode:isEmpty() then
+ return nil
+ end
+ return defNode
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'call', function (source)
+ if not source.args then
+ return
+ end
+ await.delay()
+ local funcNode = vm.compileNode(source.node)
+ for i, arg in ipairs(source.args) do
+ if i == 1 and source.node.type == 'getmethod' then
+ goto CONTINUE
+ end
+ local refNode = vm.compileNode(arg)
+ local defNode = getDefNode(funcNode, i)
+ if not defNode then
+ goto CONTINUE
+ end
+ if arg.type == 'getfield'
+ or arg.type == 'getindex' then
+ -- 由于无法对字段进行类型收窄,
+ -- 因此将假值移除再进行检查
+ refNode = refNode:copy():setTruthy()
+ end
+ if not vm.canCastType(uri, defNode, refNode) then
+ callback {
+ start = arg.start,
+ finish = arg.finish,
+ message = lang.script('DIAG_PARAM_TYPE_MISMATCH', {
+ def = vm.getInfer(defNode):view(uri),
+ ref = vm.getInfer(refNode):view(uri),
+ })
+ }
+ end
+ ::CONTINUE::
+ end
+ end)
+end
diff --git a/script/core/diagnostics/redefined-local.lua b/script/core/diagnostics/redefined-local.lua
index 2157ae71..1fb3ca6b 100644
--- a/script/core/diagnostics/redefined-local.lua
+++ b/script/core/diagnostics/redefined-local.lua
@@ -1,18 +1,23 @@
local files = require 'files'
local guide = require 'parser.guide'
local lang = require 'language'
+local await = require 'await'
+---@async
return function (uri, callback)
local ast = files.getState(uri)
if not ast then
return
end
+
+ ---@async
guide.eachSourceType(ast.ast, 'local', function (source)
local name = source[1]
if name == '_'
or name == ast.ENVMode then
return
end
+ await.delay()
local exist = guide.getLocal(source, name, source.start-1)
if exist then
callback {
diff --git a/script/core/diagnostics/redundant-parameter.lua b/script/core/diagnostics/redundant-parameter.lua
index 41781df8..9898d9bd 100644
--- a/script/core/diagnostics/redundant-parameter.lua
+++ b/script/core/diagnostics/redundant-parameter.lua
@@ -2,73 +2,48 @@ local files = require 'files'
local guide = require 'parser.guide'
local vm = require 'vm'
local lang = require 'language'
+local await = require 'await'
-local function countCallArgs(source)
- local result = 0
- if not source.args then
- return 0
- end
- result = result + #source.args
- return result
-end
-
-local function countFuncArgs(source)
- if not source.args or #source.args == 0 then
- return 0
- end
- local lastArg = source.args[#source.args]
- if lastArg.type == '...'
- or (lastArg.name and lastArg.name[1] == '...') then
- return math.maxinteger
- else
- return #source.args
- end
-end
-
-local function getFuncArgs(func)
- local funcArgs
- local defs = vm.getDefs(func)
- for _, def in ipairs(defs) do
- if def.type == 'function'
- or def.type == 'doc.type.function' then
- local args = countFuncArgs(def)
- if not funcArgs or args > funcArgs then
- funcArgs = args
- end
- end
- end
- return funcArgs
-end
-
+---@async
return function (uri, callback)
local state = files.getState(uri)
if not state then
return
end
+ ---@async
guide.eachSourceType(state.ast, 'call', function (source)
- local callArgs = countCallArgs(source)
+ await.delay()
+ local callArgs = vm.countList(source.args)
if callArgs == 0 then
return
end
- local func = source.node
- local funcArgs = getFuncArgs(func)
+ local funcNode = vm.compileNode(source.node)
+ local _, funcArgs = vm.countParamsOfNode(funcNode)
- if not funcArgs then
- return
- end
-
- local delta = callArgs - funcArgs
- if delta <= 0 then
+ if callArgs <= funcArgs then
return
end
if callArgs == 1 and source.node.type == 'getmethod' then
return
end
- for i = #source.args - delta + 1, #source.args do
- local arg = source.args[i]
- if arg then
+ if funcArgs + 1 > #source.args then
+ local lastArg = source.args[#source.args]
+ if lastArg.type == 'call' and funcArgs > 0 then
+ -- 如果函数接收至少一个参数,那么调用方最后一个参数是函数调用
+ -- 导致的参数数量太多可以忽略。
+ -- 如果函数不接收任何参数,那么任何参数都是错误的。
+ return
+ end
+ callback {
+ start = lastArg.start,
+ finish = lastArg.finish,
+ message = lang.script('DIAG_OVER_MAX_ARGS', funcArgs, callArgs)
+ }
+ else
+ for i = funcArgs + 1, #source.args do
+ local arg = source.args[i]
callback {
start = arg.start,
finish = arg.finish,
diff --git a/script/core/diagnostics/redundant-return-value.lua b/script/core/diagnostics/redundant-return-value.lua
new file mode 100644
index 00000000..36432f98
--- /dev/null
+++ b/script/core/diagnostics/redundant-return-value.lua
@@ -0,0 +1,73 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local lang = require 'language'
+local await = require 'await'
+
+local function hasDocReturn(func)
+ if not func.bindDocs then
+ return false
+ end
+ for _, doc in ipairs(func.bindDocs) do
+ if doc.type == 'doc.return' then
+ return true
+ end
+ end
+ return false
+end
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'function', function (source)
+ await.delay()
+ if not hasDocReturn(source) then
+ return
+ end
+ local _, max = vm.countReturnsOfFunction(source)
+ local returns = source.returns
+ if not returns then
+ return
+ end
+ for _, ret in ipairs(returns) do
+ local rmin, rmax = vm.countList(ret)
+ if rmin > max then
+ for i = max + 1, #ret - 1 do
+ callback {
+ start = ret[i].start,
+ finish = ret[i].finish,
+ message = lang.script('DIAG_REDUNDANT_RETURN_VALUE', {
+ max = max,
+ rmax = i,
+ }),
+ }
+ end
+ if #ret == rmax then
+ callback {
+ start = ret[#ret].start,
+ finish = ret[#ret].finish,
+ message = lang.script('DIAG_REDUNDANT_RETURN_VALUE', {
+ max = max,
+ rmax = rmax,
+ }),
+ }
+ else
+ callback {
+ start = ret[#ret].start,
+ finish = ret[#ret].finish,
+ message = lang.script('DIAG_REDUNDANT_RETURN_VALUE_RANGE', {
+ max = max,
+ rmin = #ret,
+ rmax = rmax,
+ }),
+ }
+ end
+ end
+ end
+ end)
+end
diff --git a/script/core/diagnostics/return-type-mismatch.lua b/script/core/diagnostics/return-type-mismatch.lua
new file mode 100644
index 00000000..cce4aad8
--- /dev/null
+++ b/script/core/diagnostics/return-type-mismatch.lua
@@ -0,0 +1,76 @@
+local files = require 'files'
+local lang = require 'language'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local await = require 'await'
+
+---@param func parser.object
+---@return vm.node[]?
+local function getDocReturns(func)
+ if not func.bindDocs then
+ return nil
+ end
+ local returns = {}
+ for _, doc in ipairs(func.bindDocs) do
+ if doc.type == 'doc.return' then
+ for _, ret in ipairs(doc.returns) do
+ returns[ret.returnIndex] = vm.compileNode(ret)
+ end
+ end
+ end
+ if #returns == 0 then
+ return nil
+ end
+ return returns
+end
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@param docReturns vm.node[]
+ ---@param rets parser.object
+ local function checkReturn(docReturns, rets)
+ for i, docRet in ipairs(docReturns) do
+ local retNode, exp = vm.selectNode(rets, i)
+ if not exp then
+ break
+ end
+ if retNode:hasName 'nil' then
+ if exp.type == 'getfield'
+ or exp.type == 'getindex' then
+ retNode = retNode:copy():removeOptional()
+ end
+ end
+ if not vm.canCastType(uri, docRet, retNode) then
+ callback {
+ start = exp.start,
+ finish = exp.finish,
+ message = lang.script('DIAG_RETURN_TYPE_MISMATCH', {
+ def = vm.getInfer(docRet):view(uri),
+ ref = vm.getInfer(retNode):view(uri),
+ index = i,
+ }),
+ }
+ end
+ end
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'function', function (source)
+ if not source.returns then
+ return
+ end
+ await.delay()
+ local docReturns = getDocReturns(source)
+ if not docReturns then
+ return
+ end
+ for _, ret in ipairs(source.returns) do
+ checkReturn(docReturns, ret)
+ await.delay()
+ end
+ end)
+end
diff --git a/script/core/diagnostics/spell-check.lua b/script/core/diagnostics/spell-check.lua
new file mode 100644
index 00000000..7369a235
--- /dev/null
+++ b/script/core/diagnostics/spell-check.lua
@@ -0,0 +1,34 @@
+local files = require 'files'
+local converter = require 'proto.converter'
+local log = require 'log'
+local spell = require 'provider.spell'
+
+
+---@async
+return function(uri, callback)
+ local text = files.getOriginText(uri)
+ if not text then
+ return
+ end
+
+ local status, diagnosticInfos = spell.spellCheck(uri, text)
+
+ if not status then
+ if diagnosticInfos ~= nil then
+ log.error(diagnosticInfos)
+ end
+
+ return
+ end
+
+ if diagnosticInfos then
+ for _, diagnosticInfo in ipairs(diagnosticInfos) do
+ callback {
+ start = converter.unpackPosition(uri, diagnosticInfo.range.start),
+ finish = converter.unpackPosition(uri, diagnosticInfo.range["end"]),
+ message = diagnosticInfo.message,
+ data = diagnosticInfo.data
+ }
+ end
+ end
+end
diff --git a/script/core/diagnostics/trailing-space.lua b/script/core/diagnostics/trailing-space.lua
index cc51cf77..2e0398b2 100644
--- a/script/core/diagnostics/trailing-space.lua
+++ b/script/core/diagnostics/trailing-space.lua
@@ -1,25 +1,18 @@
local files = require 'files'
local lang = require 'language'
local guide = require 'parser.guide'
+local await = require 'await'
-local function isInString(ast, offset)
- local result = false
- guide.eachSourceType(ast, 'string', function (source)
- if offset >= source.start and offset <= source.finish then
- result = true
- end
- end)
- return result
-end
-
+---@async
return function (uri, callback)
local state = files.getState(uri)
- if not state then
+ local text = files.getText(uri)
+ if not state or not text then
return
end
- local text = files.getText(uri)
local lines = state.lines
for i = 0, #lines do
+ await.delay()
local startOffset = lines[i]
local finishOffset = text:find('[\r\n]', startOffset) or (#text + 1)
local lastOffset = finishOffset - 1
@@ -28,7 +21,8 @@ return function (uri, callback)
goto NEXT_LINE
end
local lastPos = guide.offsetToPosition(state, lastOffset)
- if isInString(state.ast, lastPos) then
+ if guide.isInString(state.ast, lastPos)
+ or guide.isInComment(state.ast, lastPos) then
goto NEXT_LINE
end
local firstOffset = startOffset
diff --git a/script/core/diagnostics/type-check.lua b/script/core/diagnostics/type-check.lua
deleted file mode 100644
index cc2b3228..00000000
--- a/script/core/diagnostics/type-check.lua
+++ /dev/null
@@ -1,3 +0,0 @@
----@async
-return function(uri, callback)
-end
diff --git a/script/core/diagnostics/unbalanced-assignments.lua b/script/core/diagnostics/unbalanced-assignments.lua
index df71f0c9..c21ca993 100644
--- a/script/core/diagnostics/unbalanced-assignments.lua
+++ b/script/core/diagnostics/unbalanced-assignments.lua
@@ -2,7 +2,17 @@ local files = require 'files'
local define = require 'proto.define'
local lang = require 'language'
local guide = require 'parser.guide'
+local await = require 'await'
+local types = {
+ 'local',
+ 'setlocal',
+ 'setglobal',
+ 'setfield',
+ 'setindex' ,
+}
+
+---@async
return function (uri, callback, code)
local ast = files.getState(uri)
if not ast then
@@ -31,13 +41,9 @@ return function (uri, callback, code)
end
end
- guide.eachSource(ast.ast, function (source)
- if source.type == 'local'
- or source.type == 'setlocal'
- or source.type == 'setglobal'
- or source.type == 'setfield'
- or source.type == 'setindex' then
- checkSet(source)
- end
+ ---@async
+ guide.eachSourceTypes(ast.ast, types, function (source)
+ await.delay()
+ checkSet(source)
end)
end
diff --git a/script/core/diagnostics/undefined-doc-name.lua b/script/core/diagnostics/undefined-doc-name.lua
index 69edb380..bacd4288 100644
--- a/script/core/diagnostics/undefined-doc-name.lua
+++ b/script/core/diagnostics/undefined-doc-name.lua
@@ -32,7 +32,7 @@ return function (uri, callback)
return
end
local name = source[1]
- if name == '...' then
+ if name == '...' or name == '_' then
return
end
if #vm.getDocSets(uri, name) > 0
diff --git a/script/core/diagnostics/undefined-doc-param.lua b/script/core/diagnostics/undefined-doc-param.lua
index 98919284..7a60a74f 100644
--- a/script/core/diagnostics/undefined-doc-param.lua
+++ b/script/core/diagnostics/undefined-doc-param.lua
@@ -1,21 +1,6 @@
local files = require 'files'
local lang = require 'language'
-local function hasParamName(func, name)
- if not func.args then
- return false
- end
- for _, arg in ipairs(func.args) do
- if arg[1] == name then
- return true
- end
- if arg.type == '...' and name == '...' then
- return true
- end
- end
- return false
-end
-
return function (uri, callback)
local state = files.getState(uri)
if not state then
@@ -27,26 +12,13 @@ return function (uri, callback)
end
for _, doc in ipairs(state.ast.docs) do
- if doc.type ~= 'doc.param' then
- goto CONTINUE
- end
- local binds = doc.bindSources
- if not binds then
- goto CONTINUE
- end
- local param = doc.param
- local name = param[1]
- for _, source in ipairs(binds) do
- if source.type == 'function' then
- if not hasParamName(source, name) then
- callback {
- start = param.start,
- finish = param.finish,
- message = lang.script('DIAG_UNDEFINED_DOC_PARAM', name)
- }
- end
- end
+ if doc.type == 'doc.param'
+ and not doc.bindSource then
+ callback {
+ start = doc.param.start,
+ finish = doc.param.finish,
+ message = lang.script('DIAG_UNDEFINED_DOC_PARAM', doc.param[1])
+ }
end
- ::CONTINUE::
end
end
diff --git a/script/core/diagnostics/undefined-env-child.lua b/script/core/diagnostics/undefined-env-child.lua
index 2f559697..1dff575b 100644
--- a/script/core/diagnostics/undefined-env-child.lua
+++ b/script/core/diagnostics/undefined-env-child.lua
@@ -3,20 +3,40 @@ local guide = require 'parser.guide'
local lang = require 'language'
local vm = require "vm.vm"
+---@param source parser.object
+---@return boolean
+local function isBindDoc(source)
+ if not source.bindDocs then
+ return false
+ end
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.type'
+ or doc.type == 'doc.class' then
+ return true
+ end
+ end
+ return false
+end
+
return function (uri, callback)
- local ast = files.getState(uri)
- if not ast then
+ local state = files.getState(uri)
+ if not state then
return
end
- guide.eachSourceType(ast.ast, 'getglobal', function (source)
- -- 单独验证自己是否在重载过的 _ENV 中有定义
+
+ guide.eachSourceType(state.ast, 'getglobal', function (source)
if source.node.tag == '_ENV' then
return
end
- local defs = vm.getDefs(source)
- if #defs > 0 then
+
+ if not isBindDoc(source.node) then
return
end
+
+ if #vm.getDefs(source) > 0 then
+ return
+ end
+
local key = source[1]
callback {
start = source.start,
diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua
index 41fcda48..a83241f5 100644
--- a/script/core/diagnostics/undefined-field.lua
+++ b/script/core/diagnostics/undefined-field.lua
@@ -34,11 +34,11 @@ return function (uri, callback)
local node = src.node
if node then
local ok
- for view in vm.getInfer(node):eachView() do
- if not skipCheckClass[view] then
- ok = true
- break
+ for view in vm.getInfer(node):eachView(uri) do
+ if skipCheckClass[view] then
+ return
end
+ ok = true
end
if not ok then
return
diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua
index bd0aae69..179c9204 100644
--- a/script/core/diagnostics/undefined-global.lua
+++ b/script/core/diagnostics/undefined-global.lua
@@ -4,6 +4,7 @@ local lang = require 'language'
local config = require 'config'
local guide = require 'parser.guide'
local await = require 'await'
+local util = require 'utility'
local requireLike = {
['include'] = true,
@@ -14,17 +15,17 @@ local requireLike = {
---@async
return function (uri, callback)
- local ast = files.getState(uri)
- if not ast then
+ local state = files.getState(uri)
+ if not state then
return
end
- local dglobals = config.get(uri, 'Lua.diagnostics.globals')
+ local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
local rspecial = config.get(uri, 'Lua.runtime.special')
local cache = {}
-- 遍历全局变量,检查所有没有 set 模式的全局变量
- guide.eachSourceType(ast.ast, 'getglobal', function (src) ---@async
+ guide.eachSourceType(state.ast, 'getglobal', function (src) ---@async
local key = src[1]
if not key then
return
@@ -40,6 +41,7 @@ return function (uri, callback)
return
end
if cache[key] == nil then
+ await.delay()
cache[key] = vm.hasGlobalSets(uri, 'variable', key)
end
if cache[key] then
diff --git a/script/core/diagnostics/unknown-cast-variable.lua b/script/core/diagnostics/unknown-cast-variable.lua
new file mode 100644
index 00000000..3f082a50
--- /dev/null
+++ b/script/core/diagnostics/unknown-cast-variable.lua
@@ -0,0 +1,32 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local lang = require 'language'
+local vm = require 'vm'
+local await = require 'await'
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ if not state.ast.docs then
+ return
+ end
+
+ for _, doc in ipairs(state.ast.docs) do
+ if doc.type == 'doc.cast' and doc.loc then
+ await.delay()
+ local defs = vm.getDefs(doc.loc)
+ local loc = defs[1]
+ if not loc then
+ callback {
+ start = doc.loc.start,
+ finish = doc.loc.finish,
+ message = lang.script('DIAG_UNKNOWN_CAST_VARIABLE', doc.loc[1])
+ }
+ end
+ end
+ end
+end
diff --git a/script/core/diagnostics/unknown-diag-code.lua b/script/core/diagnostics/unknown-diag-code.lua
index 9e492a29..07128a27 100644
--- a/script/core/diagnostics/unknown-diag-code.lua
+++ b/script/core/diagnostics/unknown-diag-code.lua
@@ -1,6 +1,6 @@
local files = require 'files'
local lang = require 'language'
-local define = require 'proto.define'
+local diag = require 'proto.diagnostic'
return function (uri, callback)
local state = files.getState(uri)
@@ -17,7 +17,7 @@ return function (uri, callback)
if doc.names then
for _, nameUnit in ipairs(doc.names) do
local code = nameUnit[1]
- if not define.DiagnosticDefaultSeverity[code] then
+ if not diag.getDiagAndErrNameMap()[code] then
callback {
start = nameUnit.start,
finish = nameUnit.finish,
diff --git a/script/core/diagnostics/unknown-operator.lua b/script/core/diagnostics/unknown-operator.lua
new file mode 100644
index 00000000..7404b5ef
--- /dev/null
+++ b/script/core/diagnostics/unknown-operator.lua
@@ -0,0 +1,36 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local lang = require 'language'
+local vm = require 'vm'
+local await = require 'await'
+local util = require 'utility'
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ if not state.ast.docs then
+ return
+ end
+
+ for _, doc in ipairs(state.ast.docs) do
+ if doc.type == 'doc.operator' then
+ local op = doc.op
+ if op then
+ local opName = op[1]
+ if not vm.OP_BINARY_MAP[opName]
+ and not vm.OP_UNARY_MAP[opName]
+ and not vm.OP_OTHER_MAP[opName] then
+ callback {
+ start = doc.op.start,
+ finish = doc.op.finish,
+ message = lang.script('DIAG_UNKNOWN_OPERATOR', opName)
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/script/core/diagnostics/unreachable-code.lua b/script/core/diagnostics/unreachable-code.lua
new file mode 100644
index 00000000..4f0a38b7
--- /dev/null
+++ b/script/core/diagnostics/unreachable-code.lua
@@ -0,0 +1,84 @@
+local files = require 'files'
+local guide = require 'parser.guide'
+local vm = require 'vm'
+local lang = require 'language'
+local await = require 'await'
+local define = require 'proto.define'
+
+---@param source parser.object
+---@return boolean
+local function allLiteral(source)
+ local result = true
+ guide.eachSource(source, function (src)
+ if src.type ~= 'unary'
+ and src.type ~= 'binary'
+ and not guide.isLiteral(src) then
+ result = false
+ return false
+ end
+ end)
+ return result
+end
+
+---@param block parser.object
+---@return boolean
+local function hasReturn(block)
+ if block.hasReturn or block.hasError then
+ return true
+ end
+ if block.type == 'if' then
+ local hasElse
+ for _, subBlock in ipairs(block) do
+ if not hasReturn(subBlock) then
+ return false
+ end
+ if subBlock.type == 'elseblock' then
+ hasElse = true
+ end
+ end
+ return hasElse == true
+ else
+ if block.type == 'while' then
+ if vm.testCondition(block.filter)
+ and not block.breaks
+ and allLiteral(block.filter) then
+ return true
+ end
+ end
+ for _, action in ipairs(block) do
+ if guide.isBlockType(action) then
+ if hasReturn(action) then
+ return true
+ end
+ end
+ end
+ end
+ return false
+end
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceTypes(state.ast, {'main', 'function'}, function (source)
+ await.delay()
+ for i, action in ipairs(source) do
+ if guide.isBlockType(action)
+ and hasReturn(action) then
+ if i < #source then
+ callback {
+ start = source[i+1].start,
+ finish = source[#source].finish,
+ tags = { define.DiagnosticTag.Unnecessary },
+ message = lang.script('DIAG_UNREACHABLE_CODE'),
+ }
+ end
+ return
+ end
+ end
+ end)
+end
diff --git a/script/core/diagnostics/unused-function.lua b/script/core/diagnostics/unused-function.lua
index 813ac804..a873375f 100644
--- a/script/core/diagnostics/unused-function.lua
+++ b/script/core/diagnostics/unused-function.lua
@@ -18,7 +18,8 @@ local function isToBeClosed(source)
return false
end
----@param source parser.object
+---@param source parser.object?
+---@return boolean
local function isValidFunction(source)
if not source then
return false
@@ -55,7 +56,7 @@ local function collect(ast, white, roots, links)
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
+ if not func or not isValidFunction(func) or roots[func] then
roots[src] = true
return
end
diff --git a/script/core/diagnostics/unused-local.lua b/script/core/diagnostics/unused-local.lua
index d12ceb2b..8f2ee217 100644
--- a/script/core/diagnostics/unused-local.lua
+++ b/script/core/diagnostics/unused-local.lua
@@ -3,6 +3,8 @@ local guide = require 'parser.guide'
local define = require 'proto.define'
local lang = require 'language'
local vm = require 'vm.vm'
+local config = require 'config.config'
+local glob = require 'glob'
local function hasGet(loc)
if not loc.ref then
@@ -63,18 +65,24 @@ local function isDocClass(source)
return false
end
-local function isDocParam(source)
- if not source.bindDocs then
+---@param func parser.object
+---@return boolean
+local function isEmptyFunction(func)
+ if #func > 0 then
return false
end
- for _, doc in ipairs(source.bindDocs) do
- if doc.type == 'doc.param' then
- if doc.param[1] == source[1] then
- return true
- end
- end
+ local startRow = guide.rowColOf(func.start)
+ local finishRow = guide.rowColOf(func.finish)
+ return finishRow - startRow <= 1
+end
+
+---@param source parser.object
+local function isDeclareFunctionParam(source)
+ if source.parent.type ~= 'funcargs' then
+ return false
end
- return false
+ local func = source.parent.parent
+ return isEmptyFunction(func)
end
return function (uri, callback)
@@ -82,19 +90,24 @@ return function (uri, callback)
if not ast then
return
end
+ local ignorePatterns = config.get(uri, 'Lua.diagnostics.unusedLocalExclude')
+ local ignore = glob.glob(ignorePatterns)
guide.eachSourceType(ast.ast, 'local', function (source)
local name = source[1]
if name == '_'
or name == ast.ENVMode then
return
end
+ if ignore(name) then
+ return
+ end
if isToBeClosed(source) then
return
end
if isDocClass(source) then
return
end
- if vm.isMetaFile(uri) and isDocParam(source) then
+ if isDeclareFunctionParam(source) then
return
end
local data = hasGet(source)
diff --git a/script/core/diagnostics/unused-vararg.lua b/script/core/diagnostics/unused-vararg.lua
index ce033cf3..08f12c4d 100644
--- a/script/core/diagnostics/unused-vararg.lua
+++ b/script/core/diagnostics/unused-vararg.lua
@@ -15,6 +15,9 @@ return function (uri, callback)
end
guide.eachSourceType(ast.ast, 'function', function (source)
+ if #source == 0 then
+ return
+ end
local args = source.args
if not args then
return