diff options
author | CppCXY <812125110@qq.com> | 2022-08-11 19:36:36 +0800 |
---|---|---|
committer | CppCXY <812125110@qq.com> | 2022-08-11 19:36:36 +0800 |
commit | ff9103ae4001d8e520171b99cd192997fc689bc9 (patch) | |
tree | 04c0b685e81aac48210604dc12d24b91862a36d9 /script/core/diagnostics | |
parent | 40f191a85ea21bb64c427f9dab4bc597e2a0ea1b (diff) | |
parent | 82bcfef9037c26681993c94b2f92b68d335de3c6 (diff) | |
download | lua-language-server-ff9103ae4001d8e520171b99cd192997fc689bc9.zip |
Merge branch 'master' of github.com:CppCXY/lua-language-server
Diffstat (limited to 'script/core/diagnostics')
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 |