diff options
Diffstat (limited to 'script/core')
22 files changed, 864 insertions, 215 deletions
diff --git a/script/core/code-action.lua b/script/core/code-action.lua index e226e03b..720cd4c4 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -4,6 +4,11 @@ local util = require 'utility' local sp = require 'bee.subprocess' local guide = require "parser.guide" local converter = require 'proto.converter' +local autoreq = require 'core.completion.auto-require' +local rpath = require 'workspace.require-path' +local furi = require 'file-uri' +local undefined = require 'core.diagnostics.undefined-global' +local vm = require 'vm' ---@param uri uri ---@param row integer @@ -676,6 +681,54 @@ local function checkJsonToLua(results, uri, start, finish) } end +local function findRequireTargets(visiblePaths) + local targets = {} + for _, visible in ipairs(visiblePaths) do + targets[#targets+1] = visible.name + end + return targets +end + +local function checkMissingRequire(results, uri, start, finish) + local state = files.getState(uri) + local text = files.getText(uri) + if not state or not text then + return + end + + local function addRequires(global, endpos) + autoreq.check(state, global, endpos, function(moduleFile, stemname, targetSource) + local visiblePaths = rpath.getVisiblePath(uri, furi.decode(moduleFile)) + if not visiblePaths or #visiblePaths == 0 then return end + + for _, target in ipairs(findRequireTargets(visiblePaths)) do + results[#results+1] = { + title = lang.script('ACTION_AUTOREQUIRE', target, global), + kind = 'refactor.rewrite', + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = guide.getUri(state.ast), + target = moduleFile, + name = global, + requireName = target + }, + }, + } + } + end + end) + end + + guide.eachSourceBetween(state.ast, start, finish, function (source) + if vm.isUndefinedGlobal(source) then + addRequires(source[1], source.finish) + end + end) +end + return function (uri, start, finish, diagnostics) local ast = files.getState(uri) if not ast then @@ -688,6 +741,7 @@ return function (uri, start, finish, diagnostics) checkSwapParams(results, uri, start, finish) --checkExtractAsFunction(results, uri, start, finish) checkJsonToLua(results, uri, start, finish) + checkMissingRequire(results, uri, start, finish) return results end diff --git a/script/core/code-lens.lua b/script/core/code-lens.lua index ed95ea6a..bc39ec86 100644 --- a/script/core/code-lens.lua +++ b/script/core/code-lens.lua @@ -6,7 +6,7 @@ local getRef = require 'core.reference' local lang = require 'language' ---@class parser.state ----@field package _codeLens codeLens +---@field package _codeLens? codeLens ---@class codeLens.resolving ---@field mode 'reference' @@ -14,7 +14,6 @@ local lang = require 'language' ---@class codeLens.result ---@field position integer ----@field uri uri ---@field id integer ---@class codeLens diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index 32911d92..a96cc918 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -135,6 +135,7 @@ return function (data) local uri = data.uri local target = data.target local name = data.name + local requireName = data.requireName local state = files.getState(uri) if not state then return @@ -149,11 +150,13 @@ return function (data) return #a.name < #b.name end) - local result = askAutoRequire(uri, visiblePaths) - if not result then - return + if not requireName then + requireName = askAutoRequire(uri, visiblePaths) + if not requireName then + return + end end local offset, fmt = findInsertRow(uri) - applyAutoRequire(uri, offset, name, result, fmt) + applyAutoRequire(uri, offset, name, requireName, fmt) end diff --git a/script/core/command/reloadFFIMeta.lua b/script/core/command/reloadFFIMeta.lua new file mode 100644 index 00000000..d00929ad --- /dev/null +++ b/script/core/command/reloadFFIMeta.lua @@ -0,0 +1,56 @@ +local config = require 'config' +local ws = require 'workspace' +local fs = require 'bee.filesystem' +local scope = require 'workspace.scope' +local SDBMHash = require 'SDBMHash' +local searchCode = require 'plugins.ffi.searchCode' +local cdefRerence = require 'plugins.ffi.cdefRerence' +local ffi = require 'plugins.ffi' + +local function createDir(uri) + local dir = scope.getScope(uri).uri or 'default' + local fileDir = fs.path(METAPATH) / ('%08x'):format(SDBMHash():hash(dir)) + if fs.exists(fileDir) then + return fileDir, true + end + fs.create_directories(fileDir) + return fileDir +end + +---@async +return function (uri) + if config.get(uri, 'Lua.runtime.version') ~= 'LuaJIT' then + return + end + + ws.awaitReady(uri) + + local fileDir, exists = createDir(uri) + + local refs = cdefRerence() + if not refs or #refs == 0 then + return + end + + for i, v in ipairs(refs) do + local target_uri = v.uri + local codes = searchCode(refs, target_uri) + if not codes then + return + end + + ffi.build_single(codes, fileDir, target_uri) + end + + if not exists then + local client = require 'client' + client.setConfig { + { + key = 'Lua.workspace.library', + action = 'add', + value = tostring(fileDir), + uri = uri, + } + } + end +end diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 0ec503de..acb3adbe 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -504,7 +504,8 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent local kind = define.CompletionItemKind.Field if (value.type == 'function' and not vm.isVarargFunctionWithOverloads(value)) or value.type == 'doc.type.function' then - if oop then + local isMethod = value.parent.type == 'setmethod' + if isMethod then kind = define.CompletionItemKind.Method else kind = define.CompletionItemKind.Function @@ -512,6 +513,7 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent buildFunction(results, src, value, oop, { label = name, kind = kind, + isMethod = isMethod, match = name:match '^[^(]+', insertText = name:match '^[^(]+', deprecated = vm.getDeprecated(src) and true or nil, @@ -626,12 +628,43 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o end ::CONTINUE:: end + + local fieldResults = {} for name, src in util.sortPairs(fields) do if src then - checkFieldThen(state, name, src, word, startPos, position, parent, oop, results) + checkFieldThen(state, name, src, word, startPos, position, parent, oop, fieldResults) await.delay() end end + + local scoreMap = {} + for i, res in ipairs(fieldResults) do + scoreMap[res] = i + end + table.sort(fieldResults, function (a, b) + local score1 = scoreMap[a] + local score2 = scoreMap[b] + if oop then + if not a.isMethod then + score1 = score1 + 10000 + end + if not b.isMethod then + score2 = score2 + 10000 + end + else + if a.isMethod then + score1 = score1 + 10000 + end + if b.isMethod then + score2 = score2 + 10000 + end + end + return score1 < score2 + end) + + for _, res in ipairs(fieldResults) do + results[#results+1] = res + end end ---@async @@ -1218,6 +1251,46 @@ local function insertDocEnum(state, pos, doc, enums) return enums end +---@param state parser.state +---@param pos integer +---@param doc vm.node.object +---@param enums table[] +---@return table[]? +local function insertDocEnumKey(state, pos, doc, enums) + local tbl = doc.bindSource + if not tbl then + return nil + end + local keyEnums = {} + for _, field in ipairs(tbl) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if not field.value then + goto CONTINUE + end + local key = guide.getKeyName(field) + if not key then + goto CONTINUE + end + enums[#enums+1] = { + label = ('%q'):format(key), + kind = define.CompletionItemKind.EnumMember, + id = stack(field, function (newField) ---@async + return { + detail = buildDetail(newField), + description = buildDesc(newField), + } + end), + } + ::CONTINUE:: + end + end + for _, enum in ipairs(keyEnums) do + enums[#enums+1] = enum + end + return enums +end + local function buildInsertDocFunction(doc) local args = {} for i, arg in ipairs(doc.args) do @@ -1283,7 +1356,11 @@ local function insertEnum(state, pos, src, enums, isInArray, mark) elseif src.type == 'global' and src.cate == 'type' then for _, set in ipairs(src:getSets(state.uri)) do if set.type == 'doc.enum' then - insertDocEnum(state, pos, set, enums) + if vm.docHasAttr(set, 'key') then + insertDocEnumKey(state, pos, set, enums) + else + insertDocEnum(state, pos, set, enums) + end end end end @@ -1539,14 +1616,13 @@ local function checkTableLiteralField(state, position, tbl, fields, results) end end if left then - local hasResult = false + local fieldResults = {} for _, field in ipairs(fields) do local name = guide.getKeyName(field) if name and not mark[name] and matchKey(left, tostring(name)) then - hasResult = true - results[#results+1] = { + local res = { label = guide.getKeyName(field), kind = define.CompletionItemKind.Property, id = stack(field, function (newField) ---@async @@ -1556,9 +1632,20 @@ local function checkTableLiteralField(state, position, tbl, fields, results) } end), } + if field.optional + or vm.compileNode(field):isNullable() then + res.insertText = res.label + res.label = res.label.. '?' + end + fieldResults[#fieldResults+1] = res end end - return hasResult + util.sortByScore(fieldResults, { + function (r) return r.insertText and 0 or 1 end, + util.sortCallbackOfIndex(fieldResults), + }) + util.arrayMerge(results, fieldResults) + return #fieldResults > 0 end end @@ -1571,7 +1658,8 @@ local function tryCallArg(state, position, results) if arg and arg.type == 'function' then return end - local node = vm.compileCallArg({ type = 'dummyarg' }, call, argIndex) + ---@diagnostic disable-next-line: missing-fields + local node = vm.compileCallArg({ type = 'dummyarg', uri = state.uri }, call, argIndex) if not node then return end @@ -2070,7 +2158,7 @@ local function tryluaDocByErr(state, position, err, docState, results) end end -local function buildluaDocOfFunction(func) +local function buildluaDocOfFunction(func, pad) local index = 1 local buf = {} buf[#buf+1] = '${1:comment}' @@ -2094,7 +2182,8 @@ local function buildluaDocOfFunction(func) local funcArg = func.args[n] if funcArg[1] and funcArg.type ~= 'self' then index = index + 1 - buf[#buf+1] = ('---@param %s ${%d:%s}'):format( + buf[#buf+1] = ('---%s@param %s ${%d:%s}'):format( + pad and ' ' or '', funcArg[1], index, arg @@ -2103,7 +2192,8 @@ local function buildluaDocOfFunction(func) end for _, rtn in ipairs(returns) do index = index + 1 - buf[#buf+1] = ('---@return ${%d:%s}'):format( + buf[#buf+1] = ('---%s@return ${%d:%s}'):format( + pad and ' ' or '', index, rtn ) @@ -2112,7 +2202,7 @@ local function buildluaDocOfFunction(func) return insertText end -local function tryluaDocOfFunction(doc, results) +local function tryluaDocOfFunction(doc, results, pad) if not doc.bindSource then return end @@ -2134,7 +2224,7 @@ local function tryluaDocOfFunction(doc, results) end end end - local insertText = buildluaDocOfFunction(func) + local insertText = buildluaDocOfFunction(func, pad) results[#results+1] = { label = '@param;@return', kind = define.CompletionItemKind.Snippet, @@ -2152,9 +2242,9 @@ local function tryLuaDoc(state, position, results) end if doc.type == 'doc.comment' then local line = doc.originalComment.text - -- 尝试 ---$ - if line == '-' then - tryluaDocOfFunction(doc, results) + -- 尝试 '---$' or '--- $' + if line == '-' or line == '- ' then + tryluaDocOfFunction(doc, results, line == '- ') return end -- 尝试 ---@$ diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua index e6f50242..aa0e2148 100644 --- a/script/core/completion/keyword.lua +++ b/script/core/completion/keyword.lua @@ -3,6 +3,7 @@ local files = require 'files' local guide = require 'parser.guide' local config = require 'config' local util = require 'utility' +local lookback = require 'core.look-backward' local keyWordMap = { { 'do', function(info, results) @@ -372,17 +373,35 @@ end" else newText = '::continue::' end + local additional = {} + + local word = lookback.findWord(info.state.lua, guide.positionToOffset(info.state, info.start) - 1) + if word ~= 'goto' then + additional[#additional+1] = { + start = info.start, + finish = info.start, + newText = 'goto ', + } + end + + local hasContinue = guide.eachSourceType(mostInsideBlock, 'label', function (src) + if src[1] == 'continue' then + return true + end + end) + + if not hasContinue then + additional[#additional+1] = { + start = endPos, + finish = endPos, + newText = newText, + } + end results[#results+1] = { label = 'goto continue ..', kind = define.CompletionItemKind.Snippet, - insertText = "goto continue", - additionalTextEdits = { - { - start = endPos, - finish = endPos, - newText = newText, - } - } + insertText = "continue", + additionalTextEdits = additional, } return true end } diff --git a/script/core/diagnostics/cast-local-type.lua b/script/core/diagnostics/cast-local-type.lua index 1998b915..26445374 100644 --- a/script/core/diagnostics/cast-local-type.lua +++ b/script/core/diagnostics/cast-local-type.lua @@ -16,6 +16,9 @@ return function (uri, callback) if not loc.ref then return end + if loc[1] == '_' then + return + end await.delay() local locNode = vm.compileNode(loc) if not locNode.hasDefined then diff --git a/script/core/diagnostics/helper/missing-doc-helper.lua b/script/core/diagnostics/helper/missing-doc-helper.lua new file mode 100644 index 00000000..116173f2 --- /dev/null +++ b/script/core/diagnostics/helper/missing-doc-helper.lua @@ -0,0 +1,83 @@ +local lang = require 'language' + +local m = {} + +local function findParam(docs, param) + if not docs then + return false + end + + for _, doc in ipairs(docs) do + if doc.type == 'doc.param' then + if doc.param[1] == param then + return true + end + end + end + + return false +end + +local function findReturn(docs, index) + if not docs then + return false + end + + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' then + for _, ret in ipairs(doc.returns) do + if ret.returnIndex == index then + return true + end + end + end + end + + return false +end + +local function checkFunction(source, callback, commentId, paramId, returnId) + local functionName = source.parent[1] + local argCount = source.args and #source.args or 0 + + if argCount == 0 and not source.returns and not source.bindDocs then + callback { + start = source.start, + finish = source.finish, + message = lang.script(commentId, functionName), + } + end + + if argCount > 0 then + for _, arg in ipairs(source.args) do + local argName = arg[1] + if argName ~= 'self' + and argName ~= '_' then + if not findParam(source.bindDocs, argName) then + callback { + start = arg.start, + finish = arg.finish, + message = lang.script(paramId, argName, functionName), + } + end + end + end + end + + if source.returns then + for _, ret in ipairs(source.returns) do + for index, expr in ipairs(ret) do + if not findReturn(source.bindDocs, index) then + callback { + start = expr.start, + finish = expr.finish, + message = lang.script(returnId, index, functionName), + } + end + end + end + end +end + +m.CheckFunction = checkFunction +return m diff --git a/script/core/diagnostics/incomplete-signature-doc.lua b/script/core/diagnostics/incomplete-signature-doc.lua index 91f2db74..1ffbb77a 100644 --- a/script/core/diagnostics/incomplete-signature-doc.lua +++ b/script/core/diagnostics/incomplete-signature-doc.lua @@ -38,6 +38,19 @@ local function findReturn(docs, index) return false end +--- check if there's any signature doc (@param or @return), or just comments, @async, ... +local function findSignatureDoc(docs) + if not docs then + return false + end + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' or doc.type == 'doc.param' then + return true + end + end + return false +end + ---@async return function (uri, callback) local state = files.getState(uri) @@ -57,17 +70,22 @@ return function (uri, callback) return end - local functionName = source.parent[1] + --- don't apply rule if there is no @param or @return annotation yet + --- so comments and @async can be applied without the need for a full documentation + if(not findSignatureDoc(source.bindDocs)) then + return + end - if #source.args > 0 then + if source.args and #source.args > 0 then for _, arg in ipairs(source.args) do local argName = arg[1] - if argName ~= 'self' then + if argName ~= 'self' + and argName ~= '_' then if not findParam(source.bindDocs, argName) then callback { start = arg.start, finish = arg.finish, - message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM', argName, functionName), + message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM', argName), } end end @@ -81,7 +99,7 @@ return function (uri, callback) callback { start = expr.start, finish = expr.finish, - message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN', index, functionName), + message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN', index), } end end diff --git a/script/core/diagnostics/inject-field.lua b/script/core/diagnostics/inject-field.lua new file mode 100644 index 00000000..2866eef8 --- /dev/null +++ b/script/core/diagnostics/inject-field.lua @@ -0,0 +1,147 @@ +local files = require 'files' +local vm = require 'vm' +local lang = require 'language' +local guide = require 'parser.guide' +local await = require 'await' +local hname = require 'core.hover.name' + +local skipCheckClass = { + ['unknown'] = true, + ['any'] = true, + ['table'] = true, +} + +---@async +return function (uri, callback) + local ast = files.getState(uri) + if not ast then + return + end + + ---@async + local function checkInjectField(src) + await.delay() + + local node = src.node + if not node then + return + end + local ok + for view in vm.getInfer(node):eachView(uri) do + if skipCheckClass[view] then + return + end + ok = true + end + if not ok then + return + end + + local isExact + local class = vm.getDefinedClass(uri, node) + if class then + for _, doc in ipairs(class:getSets(uri)) do + if vm.docHasAttr(doc, 'exact') then + isExact = true + break + end + end + if not isExact then + return + end + if src.type == 'setmethod' + and not guide.getSelfNode(node) then + return + end + end + + for _, def in ipairs(vm.getDefs(src)) do + local dnode = def.node + if dnode + and not isExact + and vm.getDefinedClass(uri, dnode) then + return + end + if def.type == 'doc.type.field' then + return + end + if def.type == 'doc.field' then + return + end + end + + local howToFix = '' + if not isExact then + howToFix = lang.script('DIAG_INJECT_FIELD_FIX_CLASS', { + node = hname(node), + fix = '---@class', + }) + for _, ndef in ipairs(vm.getDefs(node)) do + if ndef.type == 'doc.type.table' then + howToFix = lang.script('DIAG_INJECT_FIELD_FIX_TABLE', { + fix = '[any]: any', + }) + break + end + end + end + + local message = lang.script('DIAG_INJECT_FIELD', { + class = vm.getInfer(node):view(uri), + field = guide.getKeyName(src), + fix = howToFix, + }) + if src.type == 'setfield' and src.field then + callback { + start = src.field.start, + finish = src.field.finish, + message = message, + } + elseif src.type == 'setmethod' and src.method then + callback { + start = src.method.start, + finish = src.method.finish, + message = message, + } + end + end + guide.eachSourceType(ast.ast, 'setfield', checkInjectField) + guide.eachSourceType(ast.ast, 'setmethod', checkInjectField) + + ---@async + local function checkExtraTableField(src) + await.delay() + + if not src.bindSource then + return + end + if not vm.docHasAttr(src, 'exact') then + return + end + local value = src.bindSource.value + if not value or value.type ~= 'table' then + return + end + for _, field in ipairs(value) do + local defs = vm.getDefs(field) + for _, def in ipairs(defs) do + if def.type == 'doc.field' then + goto nextField + end + end + local message = lang.script('DIAG_INJECT_FIELD', { + class = vm.getInfer(src):view(uri), + field = guide.getKeyName(src), + fix = '', + }) + callback { + start = field.start, + finish = field.finish, + message = message, + } + ::nextField:: + end + end + + guide.eachSourceType(ast.ast, 'doc.class', checkExtraTableField) +end diff --git a/script/core/diagnostics/missing-fields.lua b/script/core/diagnostics/missing-fields.lua new file mode 100644 index 00000000..210920fd --- /dev/null +++ b/script/core/diagnostics/missing-fields.lua @@ -0,0 +1,84 @@ +local vm = require 'vm' +local files = require 'files' +local guide = require 'parser.guide' +local await = require 'await' +local lang = require 'language' + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + ---@async + guide.eachSourceType(state.ast, 'table', function (src) + await.delay() + + local defs = vm.getDefs(src) + for _, def in ipairs(defs) do + if def.type == 'doc.class' and def.bindSource then + if guide.isInRange(def.bindSource, src.start) then + return + end + end + if def.type == 'doc.type.array' + or def.type == 'doc.type.table' then + return + end + end + local warnings = {} + for _, def in ipairs(defs) do + if def.type == 'doc.class' then + if not def.fields then + return + end + + local requiresKeys = {} + for _, field in ipairs(def.fields) do + if not field.optional + and not vm.compileNode(field):isNullable() then + local key = vm.getKeyName(field) + if key and not requiresKeys[key] then + requiresKeys[key] = true + requiresKeys[#requiresKeys+1] = key + end + end + end + + if #requiresKeys == 0 then + return + end + local myKeys = {} + for _, field in ipairs(src) do + local key = vm.getKeyName(field) + if key then + myKeys[key] = true + end + end + + local missedKeys = {} + for _, key in ipairs(requiresKeys) do + if not myKeys[key] then + missedKeys[#missedKeys+1] = ('`%s`'):format(key) + end + end + + if #missedKeys == 0 then + return + end + + warnings[#warnings+1] = lang.script('DIAG_MISSING_FIELDS', def.class[1], table.concat(missedKeys, ', ')) + end + end + + if #warnings == 0 then + return + end + callback { + start = src.start, + finish = src.finish, + message = table.concat(warnings, '\n') + } + end) +end diff --git a/script/core/diagnostics/missing-global-doc.lua b/script/core/diagnostics/missing-global-doc.lua index adcdf404..e104d232 100644 --- a/script/core/diagnostics/missing-global-doc.lua +++ b/script/core/diagnostics/missing-global-doc.lua @@ -1,41 +1,7 @@ local files = require 'files' -local lang = require 'language' local guide = require "parser.guide" local await = require 'await' - -local function findParam(docs, param) - if not docs then - return false - end - - for _, doc in ipairs(docs) do - if doc.type == 'doc.param' then - if doc.param[1] == param then - return true - end - end - end - - return false -end - -local function findReturn(docs, index) - if not docs then - return false - end - - for _, doc in ipairs(docs) do - if doc.type == 'doc.return' then - for _, ret in ipairs(doc.returns) do - if ret.returnIndex == index then - return true - end - end - end - end - - return false -end +local helper = require 'core.diagnostics.helper.missing-doc-helper' ---@async return function (uri, callback) @@ -56,43 +22,6 @@ return function (uri, callback) return end - local functionName = source.parent[1] - - if #source.args == 0 and not source.returns and not source.bindDocs then - callback { - start = source.start, - finish = source.finish, - message = lang.script('DIAG_MISSING_GLOBAL_DOC_COMMENT', functionName), - } - end - - if #source.args > 0 then - for _, arg in ipairs(source.args) do - local argName = arg[1] - if argName ~= 'self' then - if not findParam(source.bindDocs, argName) then - callback { - start = arg.start, - finish = arg.finish, - message = lang.script('DIAG_MISSING_GLOBAL_DOC_PARAM', argName, functionName), - } - end - end - end - end - - if source.returns then - for _, ret in ipairs(source.returns) do - for index, expr in ipairs(ret) do - if not findReturn(source.bindDocs, index) then - callback { - start = expr.start, - finish = expr.finish, - message = lang.script('DIAG_MISSING_GLOBAL_DOC_RETURN', index, functionName), - } - end - end - end - end + helper.CheckFunction(source, callback, 'DIAG_MISSING_GLOBAL_DOC_COMMENT', 'DIAG_MISSING_GLOBAL_DOC_PARAM', 'DIAG_MISSING_GLOBAL_DOC_RETURN') end) end diff --git a/script/core/diagnostics/missing-local-export-doc.lua b/script/core/diagnostics/missing-local-export-doc.lua new file mode 100644 index 00000000..da413961 --- /dev/null +++ b/script/core/diagnostics/missing-local-export-doc.lua @@ -0,0 +1,52 @@ +local files = require 'files' +local guide = require "parser.guide" +local await = require 'await' +local helper = require 'core.diagnostics.helper.missing-doc-helper' + +---@async +local function findSetField(ast, name, callback) + ---@async + guide.eachSourceType(ast, 'setfield', function (source) + await.delay() + if source.node[1] == name then + local funcPtr = source.value.node + if not funcPtr then + return + end + local func = funcPtr.value + if not func then + return + end + if funcPtr.type == 'local' and func.type == 'function' then + helper.CheckFunction(func, callback, 'DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT', 'DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM', 'DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN') + end + end + end) +end + +---@async +return function (uri, callback) + local state = files.getState(uri) + + if not state then + return + end + + if not state.ast then + return + end + + ---@async + guide.eachSourceType(state.ast, 'return', function (source) + await.delay() + --table + + for i, ret in ipairs(source) do + if ret.type == 'getlocal' then + if ret.node.value and ret.node.value.type == 'table' then + findSetField(state.ast, ret[1], callback) + end + end + end + end) +end diff --git a/script/core/diagnostics/name-style-check.lua b/script/core/diagnostics/name-style-check.lua new file mode 100644 index 00000000..4bdb068f --- /dev/null +++ b/script/core/diagnostics/name-style-check.lua @@ -0,0 +1,35 @@ +local files = require 'files' +local converter = require 'proto.converter' +local log = require 'log' +local nameStyle = require 'provider.name-style' + + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + local text = state.originText + + local status, diagnosticInfos = nameStyle.nameStyleCheck(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(state, diagnosticInfo.range.start), + finish = converter.unpackPosition(state, diagnosticInfo.range["end"]), + message = diagnosticInfo.message, + data = diagnosticInfo.data + } + end + end +end diff --git a/script/core/diagnostics/param-type-mismatch.lua b/script/core/diagnostics/param-type-mismatch.lua index da39c5e1..acbf9c8c 100644 --- a/script/core/diagnostics/param-type-mismatch.lua +++ b/script/core/diagnostics/param-type-mismatch.lua @@ -32,26 +32,28 @@ end ---@param funcNode vm.node ---@param i integer +---@param uri uri ---@return vm.node? -local function getDefNode(funcNode, i) +local function getDefNode(funcNode, i, uri) 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] + for src in funcNode:eachObject() do + if src.type == 'function' + or src.type == 'doc.type.function' then + local param = src.args and src.args[i] if param then defNode:merge(vm.compileNode(param)) if param[1] == '...' then defNode:addOptional() end - - expandGenerics(defNode) end end end if defNode:isEmpty() then return nil end + + expandGenerics(defNode) + return defNode end @@ -91,7 +93,7 @@ return function (uri, callback) if not refNode then goto CONTINUE end - local defNode = getDefNode(funcNode, i) + local defNode = getDefNode(funcNode, i, uri) if not defNode then goto CONTINUE end diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index a83241f5..4fd55966 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -8,13 +8,6 @@ local skipCheckClass = { ['unknown'] = true, ['any'] = true, ['table'] = true, - ['nil'] = true, - ['number'] = true, - ['integer'] = true, - ['boolean'] = true, - ['function'] = true, - ['userdata'] = true, - ['lightuserdata'] = true, } ---@async @@ -61,5 +54,4 @@ return function (uri, callback) end guide.eachSourceType(ast.ast, 'getfield', checkUndefinedField) guide.eachSourceType(ast.ast, 'getmethod', checkUndefinedField) - guide.eachSourceType(ast.ast, 'getindex', checkUndefinedField) end diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index 179c9204..d9d94959 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -20,41 +20,21 @@ return function (uri, callback) return end - local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) - local rspecial = config.get(uri, 'Lua.runtime.special') - local cache = {} - -- 遍历全局变量,检查所有没有 set 模式的全局变量 guide.eachSourceType(state.ast, 'getglobal', function (src) ---@async - local key = src[1] - if not key then - return - end - if dglobals[key] then - return - end - if rspecial[key] then - return - end - local node = src.node - if node.tag ~= '_ENV' then - return - end - if cache[key] == nil then - await.delay() - cache[key] = vm.hasGlobalSets(uri, 'variable', key) - end - if cache[key] then - return - end - local message = lang.script('DIAG_UNDEF_GLOBAL', key) - if requireLike[key:lower()] then - message = ('%s(%s)'):format(message, lang.script('DIAG_REQUIRE_LIKE', key)) + if vm.isUndefinedGlobal(src) then + local key = src[1] + local message = lang.script('DIAG_UNDEF_GLOBAL', key) + if requireLike[key:lower()] then + message = ('%s(%s)'):format(message, lang.script('DIAG_REQUIRE_LIKE', key)) + end + + callback { + start = src.start, + finish = src.finish, + message = message, + undefinedGlobal = src[1] + } end - callback { - start = src.start, - finish = src.finish, - message = message, - } end) end diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index f5890b21..75189b06 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -336,7 +336,7 @@ local function tryDocFieldComment(source) end end -local function getFunctionComment(source) +local function getFunctionComment(source, raw) local docGroup = source.bindDocs if not docGroup then return @@ -356,14 +356,14 @@ local function getFunctionComment(source) if doc.type == 'doc.comment' then local comment = normalizeComment(doc.comment.text, uri) md:add('md', comment) - elseif doc.type == 'doc.param' then + elseif doc.type == 'doc.param' and not raw then if doc.comment then md:add('md', ('@*param* `%s` — %s'):format( doc.param[1], doc.comment.text )) end - elseif doc.type == 'doc.return' then + elseif doc.type == 'doc.return' and not raw then if hasReturnComment then local name = {} for _, rtn in ipairs(doc.returns) do @@ -401,13 +401,13 @@ local function getFunctionComment(source) end ---@async -local function tryDocComment(source) +local function tryDocComment(source, raw) local md = markdown() if source.value and source.value.type == 'function' then source = source.value end if source.type == 'function' then - local comment = getFunctionComment(source) + local comment = getFunctionComment(source, raw) md:add('md', comment) source = source.parent end @@ -429,7 +429,7 @@ local function tryDocComment(source) end ---@async -local function tryDocOverloadToComment(source) +local function tryDocOverloadToComment(source, raw) if source.type ~= 'doc.type.function' then return end @@ -438,7 +438,7 @@ local function tryDocOverloadToComment(source) or not doc.bindSource then return end - local md = tryDocComment(doc.bindSource) + local md = tryDocComment(doc.bindSource, raw) if md then return md end @@ -477,38 +477,59 @@ local function tryDocEnum(source) if not tbl then return end - local md = markdown() - md:add('lua', '{') - for _, field in ipairs(tbl) do - if field.type == 'tablefield' - or field.type == 'tableindex' then - if not field.value then - goto CONTINUE - end - local key = guide.getKeyName(field) - if not key then - goto CONTINUE - end - if field.value.type == 'integer' - or field.value.type == 'string' then - md:add('lua', (' %s: %s = %s,'):format(key, field.value.type, field.value[1])) + if vm.docHasAttr(source, 'key') then + local md = markdown() + local keys = {} + for _, field in ipairs(tbl) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if not field.value then + goto CONTINUE + end + local key = guide.getKeyName(field) + if not key then + goto CONTINUE + end + keys[#keys+1] = ('%q'):format(key) + ::CONTINUE:: end - if field.value.type == 'binary' - or field.value.type == 'unary' then - local number = vm.getNumber(field.value) - if number then - md:add('lua', (' %s: %s = %s,'):format(key, math.tointeger(number) and 'integer' or 'number', number)) + end + md:add('lua', table.concat(keys, ' | ')) + return md:string() + else + local md = markdown() + md:add('lua', '{') + for _, field in ipairs(tbl) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if not field.value then + goto CONTINUE + end + local key = guide.getKeyName(field) + if not key then + goto CONTINUE + end + if field.value.type == 'integer' + or field.value.type == 'string' then + md:add('lua', (' %s: %s = %s,'):format(key, field.value.type, field.value[1])) + end + if field.value.type == 'binary' + or field.value.type == 'unary' then + local number = vm.getNumber(field.value) + if number then + md:add('lua', (' %s: %s = %s,'):format(key, math.tointeger(number) and 'integer' or 'number', number)) + end end + ::CONTINUE:: end - ::CONTINUE:: end + md:add('lua', '}') + return md:string() end - md:add('lua', '}') - return md:string() end ---@async -return function (source) +return function (source, raw) if source.type == 'string' then return asString(source) end @@ -518,10 +539,10 @@ return function (source) if source.type == 'field' then source = source.parent end - return tryDocOverloadToComment(source) + return tryDocOverloadToComment(source, raw) or tryDocFieldComment(source) or tyrDocParamComment(source) - or tryDocComment(source) + or tryDocComment(source, raw) or tryDocClassComment(source) or tryDocModule(source) or tryDocEnum(source) diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua index 6ce4dde9..62e51927 100644 --- a/script/core/hover/label.lua +++ b/script/core/hover/label.lua @@ -134,7 +134,7 @@ local function asField(source) end local function asDocFieldName(source) - local name = source.field[1] + local name = vm.viewKey(source, guide.getUri(source)) or '?' local class for _, doc in ipairs(source.bindGroup) do if doc.type == 'doc.class' then @@ -143,10 +143,12 @@ local function asDocFieldName(source) end end local view = vm.getInfer(source.extends):view(guide.getUri(source)) - if not class then - return ('(field) ?.%s: %s'):format(name, view) + local className = class and class.class[1] or '?' + if name:match(guide.namePatternFull) then + return ('(field) %s.%s: %s'):format(className, name, view) + else + return ('(field) %s%s: %s'):format(className, name, view) end - return ('(field) %s.%s: %s'):format(class.class[1], name, view) end local function asString(source) diff --git a/script/core/rename.lua b/script/core/rename.lua index 507def20..534a972a 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -3,42 +3,59 @@ local vm = require 'vm' local util = require 'utility' local findSource = require 'core.find-source' local guide = require 'parser.guide' +local config = require 'config' local Forcing +---@param str string +---@return string local function trim(str) return str:match '^%s*(%S+)%s*$' end -local function isValidName(str) +---@param uri uri +---@param str string +---@return boolean +local function isValidName(uri, str) if not str then return false end - return str:match '^[%a_][%w_]*$' + local allowUnicode = config.get(uri, 'Lua.runtime.unicodeName') + if allowUnicode then + return str:match '^[%a_\x80-\xff][%w_\x80-\xff]*$' + else + return str:match '^[%a_][%w_]*$' + end end -local function isValidGlobal(str) +---@param uri uri +---@param str string +---@return boolean +local function isValidGlobal(uri, str) if not str then return false end for s in str:gmatch '[^%.]*' do - if not isValidName(trim(s)) then + if not isValidName(uri, trim(s)) then return false end end return true end -local function isValidFunctionName(str) - if isValidGlobal(str) then +---@param uri uri +---@param str string +---@return boolean +local function isValidFunctionName(uri, str) + if isValidGlobal(uri, str) then return true end local offset = str:find(':', 1, true) if not offset then return false end - return isValidGlobal(trim(str:sub(1, offset-1))) - and isValidName(trim(str:sub(offset+1))) + return isValidGlobal(uri, trim(str:sub(1, offset-1))) + and isValidName(uri, trim(str:sub(offset+1))) end local function isFunctionGlobalName(source) @@ -54,7 +71,7 @@ local function isFunctionGlobalName(source) end local function renameLocal(source, newname, callback) - if isValidName(newname) then + if isValidName(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return end @@ -62,7 +79,7 @@ local function renameLocal(source, newname, callback) end local function renameField(source, newname, callback) - if isValidName(newname) then + if isValidName(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return true end @@ -108,11 +125,11 @@ local function renameField(source, newname, callback) end local function renameGlobal(source, newname, callback) - if isValidGlobal(newname) then + if isValidGlobal(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return true end - if isValidFunctionName(newname) then + if isValidFunctionName(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return true end @@ -397,7 +414,11 @@ function m.rename(uri, pos, newname) return end mark[uid] = true - if files.isLibrary(turi, true) then + if vm.isMetaFile(turi) then + return + end + if files.isLibrary(turi, true) + and not files.isLibrary(uri, true) then return end results[#results+1] = { diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index 4d191b69..e908ef7b 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -138,12 +138,20 @@ local Care = util.switch() local uri = guide.getUri(loc) -- 1. 值为函数的局部变量 | Local variable whose value is a function if vm.getInfer(source):hasFunction(uri) then - results[#results+1] = { - start = source.start, - finish = source.finish, - type = define.TokenTypes['function'], - modifieres = define.TokenModifiers.declaration, - } + if source.type == 'local' then + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes['function'], + modifieres = define.TokenModifiers.declaration, + } + else + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes['function'], + } + end return end -- 3. 特殊变量 | Special variableif source[1] == '_ENV' then @@ -703,6 +711,14 @@ local Care = util.switch() type = define.TokenTypes.namespace, } end) + : case 'doc.attr' + : call(function (source, options, results) + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.decorator, + } + end) ---@param state table ---@param results table @@ -866,6 +882,10 @@ return function (uri, start, finish) local n = 0 guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async + -- skip virtual source + if source.virtual then + return + end Care(source.type, source, options, results) n = n + 1 if n % 100 == 0 then @@ -874,6 +894,10 @@ return function (uri, start, finish) end) for _, comm in ipairs(state.comms) do + -- skip virtual comment + if comm.virtual then + return + end if start <= comm.start and comm.finish <= finish then local headPos = (comm.type == 'comment.short' and comm.text:match '^%-%s*[@|]()') or (comm.type == 'comment.long' and comm.text:match '^@()') diff --git a/script/core/signature.lua b/script/core/signature.lua index 63b0cd0d..98018b21 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -89,6 +89,39 @@ local function makeOneSignature(source, oop, index) } end +local function isEventNotMatch(call, src) + if not call.args or not src.args then + return false + end + local literal, index + for i = 1, 2 do + if not call.args[i] then + break + end + literal = guide.getLiteral(call.args[i]) + if literal then + index = i + break + end + end + if not literal then + return false + end + local event = src.args[index] + if not event or event.type ~= 'doc.type.arg' then + return false + end + if not event.extends + or #event.extends.types ~= 1 then + return false + end + local eventLiteral = event.extends.types[1] and guide.getLiteral(event.extends.types[1]) + if eventLiteral == nil then + return false + end + return eventLiteral ~= literal +end + ---@async local function makeSignatures(text, call, pos) local func = call.node @@ -139,7 +172,8 @@ local function makeSignatures(text, call, pos) for src in node:eachObject() do if (src.type == 'function' and not vm.isVarargFunctionWithOverloads(src)) or src.type == 'doc.type.function' then - if not mark[src] then + if not mark[src] + and not isEventNotMatch(call, src) then mark[src] = true signs[#signs+1] = makeOneSignature(src, oop, index) end @@ -149,7 +183,8 @@ local function makeSignatures(text, call, pos) if set.type == 'doc.class' then for _, overload in ipairs(set.calls) do local f = overload.overload - if not mark[f] then + if not mark[f] + and not isEventNotMatch(call, src) then mark[f] = true signs[#signs+1] = makeOneSignature(f, oop, index) end |