diff options
author | carsakiller <carsakiller@gmail.com> | 2024-05-23 02:22:29 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-23 02:22:29 -0400 |
commit | 459ac5c06ecca35d135217ca0892cc7787a91d33 (patch) | |
tree | 6b4891e4c6dae06dfdd10d24f6c213bed24bd8fe /script | |
parent | b9c9f9423ff31bb9160acc5d75feaec3c0bf777c (diff) | |
parent | 87abc4245f2a24e1cc35851b6464af9588934286 (diff) | |
download | lua-language-server-459ac5c06ecca35d135217ca0892cc7787a91d33.zip |
Merge branch 'master' into 2175-md-symbol-reference
Diffstat (limited to 'script')
27 files changed, 759 insertions, 207 deletions
diff --git a/script/brave/brave.lua b/script/brave/brave.lua index 5a15a6b2..34c92a72 100644 --- a/script/brave/brave.lua +++ b/script/brave/brave.lua @@ -1,5 +1,6 @@ local channel = require 'bee.channel' local select = require 'bee.select' +local thread = require 'bee.thread' local function channel_init(chan) local selector = select.create() @@ -9,11 +10,14 @@ end local function channel_bpop(ctx) local selector, chan = ctx[1], ctx[2] - for _ in selector:wait() do - local r = table.pack(chan:pop()) - if r[1] == true then - return table.unpack(r, 2) + while true do + for _ in selector:wait() do + local r = table.pack(chan:pop()) + if r[1] == true then + return table.unpack(r, 2) + end end + thread.sleep(10) end end diff --git a/script/cli/check.lua b/script/cli/check.lua index 3902c4aa..8b314f24 100644 --- a/script/cli/check.lua +++ b/script/cli/check.lua @@ -1,130 +1,96 @@ -local lclient = require 'lclient'() -local furi = require 'file-uri' -local ws = require 'workspace' -local files = require 'files' -local diag = require 'provider.diagnostic' -local util = require 'utility' -local jsonb = require 'json-beautify' -local lang = require 'language' -local define = require 'proto.define' -local config = require 'config.config' -local fs = require 'bee.filesystem' -local provider = require 'provider' +local lang = require 'language' +local platform = require 'bee.platform' +local subprocess = require 'bee.subprocess' +local json = require 'json' +local jsonb = require 'json-beautify' +local util = require 'utility' -require 'plugin' -require 'vm' -lang(LOCALE) +local numThreads = tonumber(NUM_THREADS or 1) -if type(CHECK) ~= 'string' then - print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK))) - return +local exe = arg[-1] +-- TODO: is this necessary? got it from the shell.lua helper in bee.lua tests +if platform.os == 'windows' and not exe:match('%.[eE][xX][eE]$') then + exe = exe..'.exe' end -local rootPath = fs.absolute(fs.path(CHECK)):string() -local rootUri = furi.encode(rootPath) -if not rootUri then - print(lang.script('CLI_CHECK_ERROR_URI', rootPath)) - return +local function logFileForThread(threadId) + return LOGPATH .. '/check-partial-' .. threadId .. '.json' end -rootUri = rootUri:gsub("/$", "") -if CHECKLEVEL then - if not define.DiagnosticSeverity[CHECKLEVEL] then - print(lang.script('CLI_CHECK_ERROR_LEVEL', 'Error, Warning, Information, Hint')) - return +local function buildArgs(threadId) + local args = {exe} + local skipNext = false + for i = 1, #arg do + local arg = arg[i] + -- --check needs to be transformed into --check_worker + if arg:lower():match('^%-%-check$') or arg:lower():match('^%-%-check=') then + args[#args + 1] = arg:gsub('%-%-%w*', '--check_worker') + -- --check_out_path needs to be removed if we have more than one thread + elseif arg:lower():match('%-%-check_out_path') and numThreads > 1 then + if not arg:match('%-%-%w*=') then + skipNext = true + end + else + if skipNext then + skipNext = false + else + args[#args + 1] = arg + end + end + end + args[#args + 1] = '--thread_id' + args[#args + 1] = tostring(threadId) + if numThreads > 1 then + args[#args + 1] = '--quiet' + args[#args + 1] = '--check_out_path' + args[#args + 1] = logFileForThread(threadId) end + return args end -local checkLevel = define.DiagnosticSeverity[CHECKLEVEL] or define.DiagnosticSeverity.Warning - -util.enableCloseFunction() -local lastClock = os.clock() -local results = {} - -local function errorhandler(err) - print(err) - print(debug.traceback()) +if numThreads > 1 then + print(lang.script('CLI_CHECK_MULTIPLE_WORKERS', numThreads)) end ----@async -xpcall(lclient.start, errorhandler, lclient, function (client) - client:registerFakers() - - client:initialize { - rootUri = rootUri, - } - - client:register('textDocument/publishDiagnostics', function (params) - results[params.uri] = params.diagnostics - end) - - io.write(lang.script('CLI_CHECK_INITING')) - - provider.updateConfig(rootUri) - - ws.awaitReady(rootUri) - - local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable')) - for name, serverity in pairs(define.DiagnosticDefaultSeverity) do - serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or 'Warning' - if serverity:sub(-1) == '!' then - serverity = serverity:sub(1, -2) - end - if define.DiagnosticSeverity[serverity] > checkLevel then - disables[name] = true - end +local procs = {} +for i = 1, numThreads do + local process, err = subprocess.spawn({buildArgs(i)}) + if err then + print(err) end - config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true)) - - local uris = files.getChildFiles(rootUri) - local max = #uris - for i, uri in ipairs(uris) do - files.open(uri) - diag.doDiagnostic(uri, true) - -- Print regularly but always print the last entry to ensure that logs written to files don't look incomplete. - if os.clock() - lastClock > 0.2 or i == #uris then - lastClock = os.clock() - client:update() - local output = '\x0D' - .. ('>'):rep(math.ceil(i / max * 20)) - .. ('='):rep(20 - math.ceil(i / max * 20)) - .. ' ' - .. ('0'):rep(#tostring(max) - #tostring(i)) - .. tostring(i) .. '/' .. tostring(max) - io.write(output) - local filesWithErrors = 0 - local errors = 0 - for _, diags in pairs(results) do - filesWithErrors = filesWithErrors + 1 - errors = errors + #diags - end - if errors > 0 then - local errorDetails = ' [' .. lang.script('CLI_CHECK_PROGRESS', errors, filesWithErrors) .. ']' - io.write(errorDetails) - end - io.flush() - end + if process then + procs[#procs + 1] = process end - io.write('\x0D') -end) +end -local count = 0 -for uri, result in pairs(results) do - count = count + #result - if #result == 0 then - results[uri] = nil - end +for _, process in ipairs(procs) do + process:wait() end -if count == 0 then - print(lang.script('CLI_CHECK_SUCCESS')) -else - local outpath = CHECK_OUT_PATH - if outpath == nil then - outpath = LOGPATH .. '/check.json' - end - util.saveFile(outpath, jsonb.beautify(results)) +local outpath = CHECK_OUT_PATH +if outpath == nil then + outpath = LOGPATH .. '/check.json' +end - print(lang.script('CLI_CHECK_RESULTS', count, outpath)) +if numThreads > 1 then + local mergedResults = {} + local count = 0 + for i = 1, numThreads do + local result = json.decode(util.loadFile(logFileForThread(i)) or '[]') + for k, v in pairs(result) do + local entries = mergedResults[k] or {} + mergedResults[k] = entries + for _, entry in ipairs(v) do + entries[#entries + 1] = entry + count = count + 1 + end + end + end + util.saveFile(outpath, jsonb.beautify(mergedResults)) + if count == 0 then + print(lang.script('CLI_CHECK_SUCCESS')) + else + print(lang.script('CLI_CHECK_RESULTS', count, outpath)) + end end diff --git a/script/cli/check_worker.lua b/script/cli/check_worker.lua new file mode 100644 index 00000000..0a121344 --- /dev/null +++ b/script/cli/check_worker.lua @@ -0,0 +1,168 @@ +local lclient = require 'lclient'() +local furi = require 'file-uri' +local ws = require 'workspace' +local files = require 'files' +local diag = require 'provider.diagnostic' +local util = require 'utility' +local jsonb = require 'json-beautify' +local lang = require 'language' +local define = require 'proto.define' +local protoDiag = require 'proto.diagnostic' +local config = require 'config.config' +local fs = require 'bee.filesystem' +local provider = require 'provider' +local await = require 'await' +require 'plugin' +require 'vm' + +lang(LOCALE) + +local numThreads = tonumber(NUM_THREADS or 1) +local threadId = tonumber(THREAD_ID or 1) + +if type(CHECK_WORKER) ~= 'string' then + print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK_WORKER))) + return +end + +local rootPath = fs.absolute(fs.path(CHECK_WORKER)):string() +local rootUri = furi.encode(rootPath) +if not rootUri then + print(lang.script('CLI_CHECK_ERROR_URI', rootPath)) + return +end +rootUri = rootUri:gsub("/$", "") + +if CHECKLEVEL then + if not define.DiagnosticSeverity[CHECKLEVEL] then + print(lang.script('CLI_CHECK_ERROR_LEVEL', 'Error, Warning, Information, Hint')) + return + end +end +local checkLevel = define.DiagnosticSeverity[CHECKLEVEL] or define.DiagnosticSeverity.Warning + +util.enableCloseFunction() + +-- Hash function used to distribute work. +local function hashString(str) + local hash = 0 + for i = 1, #str do + hash = (hash * 37 & 0xFFFFFFFF) + str:byte(i, i) + end + return hash +end + +local lastClock = os.clock() +local results = {} + +local function errorhandler(err) + print(err) + print(debug.traceback()) +end + +---@async +xpcall(lclient.start, errorhandler, lclient, function (client) + await.disable() + client:registerFakers() + + client:initialize { + rootUri = rootUri, + } + + client:register('textDocument/publishDiagnostics', function (params) + results[params.uri] = params.diagnostics + end) + + if not QUIET then + io.write(lang.script('CLI_CHECK_INITING')) + end + + provider.updateConfig(rootUri) + + ws.awaitReady(rootUri) + + local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable')) + for name, serverity in pairs(define.DiagnosticDefaultSeverity) do + serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or 'Warning' + if serverity:sub(-1) == '!' then + serverity = serverity:sub(1, -2) + end + if define.DiagnosticSeverity[serverity] > checkLevel then + disables[name] = true + end + end + config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true)) + + -- Downgrade file opened status to Opened for everything to avoid reporting during compilation on files that do not belong to this thread + local diagStatus = config.get(rootUri, 'Lua.diagnostics.neededFileStatus') + for diag, status in pairs(diagStatus) do + if status == 'Any' or status == 'Any!' then + diagStatus[diag] = 'Opened!' + end + end + for diag, status in pairs(protoDiag.getDefaultStatus()) do + if status == 'Any' or status == 'Any!' then + diagStatus[diag] = 'Opened!' + end + end + config.set(rootUri, 'Lua.diagnostics.neededFileStatus', diagStatus) + + local uris = files.getChildFiles(rootUri) + local max = #uris + for i, uri in ipairs(uris) do + local hash = hashString(uri) % numThreads + 1 + if hash == threadId then + files.open(uri) + diag.doDiagnostic(uri, true) + -- Print regularly but always print the last entry to ensure that logs written to files don't look incomplete. + if (os.clock() - lastClock > 0.2 or i == #uris) and not QUIET then + lastClock = os.clock() + client:update() + local output = '\x0D' + .. ('>'):rep(math.ceil(i / max * 20)) + .. ('='):rep(20 - math.ceil(i / max * 20)) + .. ' ' + .. ('0'):rep(#tostring(max) - #tostring(i)) + .. tostring(i) .. '/' .. tostring(max) + io.write(output) + local filesWithErrors = 0 + local errors = 0 + for _, diags in pairs(results) do + filesWithErrors = filesWithErrors + 1 + errors = errors + #diags + end + if errors > 0 then + local errorDetails = ' [' .. lang.script('CLI_CHECK_PROGRESS', errors, filesWithErrors) .. ']' + io.write(errorDetails) + end + io.flush() + end + end + end + if not QUIET then + io.write('\x0D') + end +end) + +local count = 0 +for uri, result in pairs(results) do + count = count + #result + if #result == 0 then + results[uri] = nil + end +end + +local outpath = CHECK_OUT_PATH +if outpath == nil then + outpath = LOGPATH .. '/check.json' +end +-- Always write result, even if it's empty to make sure no one accidentally looks at an old output after a successful run. +util.saveFile(outpath, jsonb.beautify(results)) + +if not QUIET then + if count == 0 then + print(lang.script('CLI_CHECK_SUCCESS')) + else + print(lang.script('CLI_CHECK_RESULTS', count, outpath)) + end +end diff --git a/script/cli/init.lua b/script/cli/init.lua index d37c50ae..65e7e102 100644 --- a/script/cli/init.lua +++ b/script/cli/init.lua @@ -8,6 +8,11 @@ if _G['CHECK'] then os.exit(0, true) end +if _G['CHECK_WORKER'] then + require 'cli.check_worker' + os.exit(0, true) +end + if _G['DOC_UPDATE'] then require 'cli.doc' .runCLI() os.exit(0, true) diff --git a/script/config/env.lua b/script/config/env.lua new file mode 100644 index 00000000..ef5b31f2 --- /dev/null +++ b/script/config/env.lua @@ -0,0 +1,67 @@ +-- Handles loading environment arguments + +---Convert a string to boolean +---@param v string +local function strToBool(v) + return v == "true" +end + +---ENV args are defined here. +---- `name` is the ENV arg name +---- `key` is the value used to index `_G` for setting the argument +---- `converter` if present, will be used to convert the string value into another type +---@type { name: string, key: string, converter: fun(value: string): any }[] +local vars = { + { + name = "LLS_CHECK_LEVEL", + key = "CHECKLEVEL", + }, + { + name = "LLS_CHECK_PATH", + key = "CHECK", + }, + { + name = "LLS_CONFIG_PATH", + key = "CONFIGPATH", + }, + { + name = "LLS_DOC_OUT_PATH", + key = "DOC_OUT_PATH", + }, + { + name = "LLS_DOC_PATH", + key = "DOC", + }, + { + name = "LLS_FORCE_ACCEPT_WORKSPACE", + key = "FORCE_ACCEPT_WORKSPACE", + converter = strToBool, + }, + { + name = "LLS_LOCALE", + key = "LOCALE", + }, + { + name = "LLS_LOG_LEVEL", + key = "LOGLEVEL", + }, + { + name = "LLS_LOG_PATH", + key = "LOGPATH", + }, + { + name = "LLS_META_PATH", + key = "METAPATH", + }, +} + +for _, var in ipairs(vars) do + local value = os.getenv(var.name) + if value then + if var.converter then + value = var.converter(value) + end + + _G[var.key] = value + end +end diff --git a/script/config/template.lua b/script/config/template.lua index 49907419..e74a9f9c 100644 --- a/script/config/template.lua +++ b/script/config/template.lua @@ -242,6 +242,7 @@ local template = { >> util.deepCopy(define.BuiltIn), ['Lua.diagnostics.enable'] = Type.Boolean >> true, ['Lua.diagnostics.globals'] = Type.Array(Type.String), + ['Lua.diagnostics.globalsRegex'] = Type.Array(Type.String), ['Lua.diagnostics.disable'] = Type.Array(Type.String << util.getTableKeys(diag.getDiagAndErrNameMap(), true)), ['Lua.diagnostics.severity'] = Type.Hash( Type.String << util.getTableKeys(define.DiagnosticDefaultNeededFileStatus, true), diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua index 098b41a4..6066ae53 100644 --- a/script/core/diagnostics/duplicate-doc-field.lua +++ b/script/core/diagnostics/duplicate-doc-field.lua @@ -15,7 +15,7 @@ local function getFieldEventName(doc) if docFunc.type ~= 'doc.type.function' then return nil end - for i = 1, 2 do + for i = 1, #docFunc.args do local arg = docFunc.args[i] if arg and arg.extends diff --git a/script/core/diagnostics/global-element.lua b/script/core/diagnostics/global-element.lua index e9dd46ce..a30ebbc6 100644 --- a/script/core/diagnostics/global-element.lua +++ b/script/core/diagnostics/global-element.lua @@ -17,6 +17,20 @@ local function isDocClass(source) return false end +local function isGlobalRegex(name, definedGlobalRegex) + if not definedGlobalRegex then + return false + end + + for _, pattern in ipairs(definedGlobalRegex) do + if name:match(pattern) then + return true + end + end + + return false +end + -- If global elements are discouraged by coding convention, this diagnostic helps with reminding about that -- Exceptions may be added to Lua.diagnostics.globals return function (uri, callback) @@ -26,6 +40,7 @@ return function (uri, callback) end local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) + local definedGlobalRegex = config.get(uri, 'Lua.diagnostics.globalsRegex') guide.eachSourceType(ast.ast, 'setglobal', function (source) local name = guide.getKeyName(source) @@ -36,6 +51,9 @@ return function (uri, callback) if isDocClass(source) then return end + if isGlobalRegex(name, definedGlobalRegex) then + return + end if definedGlobal[name] == nil then definedGlobal[name] = false local global = vm.getGlobal('variable', name) diff --git a/script/core/diagnostics/lowercase-global.lua b/script/core/diagnostics/lowercase-global.lua index 68bec234..c7e9294d 100644 --- a/script/core/diagnostics/lowercase-global.lua +++ b/script/core/diagnostics/lowercase-global.lua @@ -17,6 +17,20 @@ local function isDocClass(source) return false end +local function isGlobalRegex(name, definedGlobalRegex) + if not definedGlobalRegex then + return false + end + + for _, pattern in ipairs(definedGlobalRegex) do + if name:match(pattern) then + return true + end + end + + return false +end + -- 不允许定义首字母小写的全局变量(很可能是拼错或者漏删) return function (uri, callback) local ast = files.getState(uri) @@ -25,6 +39,7 @@ return function (uri, callback) end local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) + local definedGlobalRegex = config.get(uri, 'Lua.diagnostics.globalsRegex') guide.eachSourceType(ast.ast, 'setglobal', function (source) local name = guide.getKeyName(source) @@ -42,6 +57,9 @@ return function (uri, callback) if isDocClass(source) then return end + if isGlobalRegex(name, definedGlobalRegex) then + return + end if definedGlobal[name] == nil then definedGlobal[name] = false local global = vm.getGlobal('variable', name) diff --git a/script/core/implementation.lua b/script/core/implementation.lua new file mode 100644 index 00000000..e48e2f73 --- /dev/null +++ b/script/core/implementation.lua @@ -0,0 +1,171 @@ +local workspace = require 'workspace' +local files = require 'files' +local vm = require 'vm' +local findSource = require 'core.find-source' +local guide = require 'parser.guide' +local rpath = require 'workspace.require-path' +local jumpSource = require 'core.jump-source' + +local function sortResults(results) + -- 先按照顺序排序 + table.sort(results, function (a, b) + local u1 = guide.getUri(a.target) + local u2 = guide.getUri(b.target) + if u1 == u2 then + return a.target.start < b.target.start + else + return u1 < u2 + end + end) + -- 如果2个结果处于嵌套状态,则取范围小的那个 + local lf, lu + for i = #results, 1, -1 do + local res = results[i].target + local f = res.finish + local uri = guide.getUri(res) + if lf and f > lf and uri == lu then + table.remove(results, i) + else + lu = uri + lf = f + end + end +end + +local accept = { + ['local'] = true, + ['setlocal'] = true, + ['getlocal'] = true, + ['label'] = true, + ['goto'] = true, + ['field'] = true, + ['method'] = true, + ['setglobal'] = true, + ['getglobal'] = true, + ['string'] = true, + ['boolean'] = true, + ['number'] = true, + ['integer'] = true, + ['...'] = true, + + ['doc.type.name'] = true, + ['doc.class.name'] = true, + ['doc.extends.name'] = true, + ['doc.alias.name'] = true, + ['doc.cast.name'] = true, + ['doc.enum.name'] = true, + ['doc.field.name'] = true, +} + +local function convertIndex(source) + if not source then + return + end + if source.type == 'string' + or source.type == 'boolean' + or source.type == 'number' + or source.type == 'integer' then + local parent = source.parent + if not parent then + return + end + if parent.type == 'setindex' + or parent.type == 'getindex' + or parent.type == 'tableindex' then + return parent + end + end + return source +end + +---@async +return function (uri, offset) + local ast = files.getState(uri) + if not ast then + return nil + end + + local source = convertIndex(findSource(ast, offset, accept)) + if not source then + return nil + end + + local results = {} + + local defs = vm.getRefs(source) + + for _, src in ipairs(defs) do + if not guide.isAssign(src) then + goto CONTINUE + end + if src.type == 'global' then + goto CONTINUE + end + local root = guide.getRoot(src) + if not root then + goto CONTINUE + end + if src.type == 'self' then + goto CONTINUE + end + src = src.field or src.method or src + if src.type == 'getindex' + or src.type == 'setindex' + or src.type == 'tableindex' then + src = src.index + if not src then + goto CONTINUE + end + if not guide.isLiteral(src) then + goto CONTINUE + end + end + if src.type == 'doc.type.function' + or src.type == 'doc.type.table' + or src.type == 'doc.type.boolean' + or src.type == 'doc.type.integer' + or src.type == 'doc.type.string' then + goto CONTINUE + end + if src.type == 'doc.class' then + goto CONTINUE + end + if src.type == 'doc.alias' then + goto CONTINUE + end + if src.type == 'doc.enum' then + goto CONTINUE + end + if src.type == 'doc.type.field' then + goto CONTINUE + end + if src.type == 'doc.class.name' + or src.type == 'doc.alias.name' + or src.type == 'doc.enum.name' + or src.type == 'doc.field.name' then + goto CONTINUE + end + if src.type == 'doc.generic.name' then + goto CONTINUE + end + if src.type == 'doc.param' then + goto CONTINUE + end + + results[#results+1] = { + target = src, + uri = root.uri, + source = source, + } + ::CONTINUE:: + end + + if #results == 0 then + return nil + end + + sortResults(results) + jumpSource(results) + + return results +end diff --git a/script/core/signature.lua b/script/core/signature.lua index 98018b21..c52dcff3 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -94,10 +94,7 @@ local function isEventNotMatch(call, src) return false end local literal, index - for i = 1, 2 do - if not call.args[i] then - break - end + for i = 1, #call.args do literal = guide.getLiteral(call.args[i]) if literal then index = i diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua index 0a821f25..d9939eb0 100644 --- a/script/core/type-definition.lua +++ b/script/core/type-definition.lua @@ -52,8 +52,9 @@ local accept = { ['doc.class.name'] = true, ['doc.extends.name'] = true, ['doc.alias.name'] = true, + ['doc.cast.name'] = true, ['doc.enum.name'] = true, - ['doc.see.name'] = true, + ['doc.field.name'] = true, } local function checkRequire(source, offset) diff --git a/script/files.lua b/script/files.lua index b9df5695..68c3b8a5 100644 --- a/script/files.lua +++ b/script/files.lua @@ -1,5 +1,6 @@ local platform = require 'bee.platform' local fs = require 'bee.filesystem' +local sys = require 'bee.sys' local config = require 'config' local glob = require 'glob' local furi = require 'file-uri' @@ -70,7 +71,7 @@ local function getRealParent(path) == path :string():gsub('^%w+:', string.lower) then return path end - local res = fs.fullpath(path) + local res = sys.fullpath(path) return getRealParent(parent) / res:filename() end diff --git a/script/filewatch.lua b/script/filewatch.lua index 6520afe6..7fb3e605 100644 --- a/script/filewatch.lua +++ b/script/filewatch.lua @@ -1,6 +1,7 @@ local fw = require 'bee.filewatch' local fs = require 'bee.filesystem' local plat = require 'bee.platform' +local sys = require 'bee.sys' local await = require 'await' local files = require 'files' @@ -16,7 +17,7 @@ local function isExists(filename) if plat.os ~= 'windows' then return true end - local res = fs.fullpath(path) + local res = sys.fullpath(path) if not res then return false end diff --git a/script/global.d.lua b/script/global.d.lua index ead46ca9..daac5f6c 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -98,3 +98,11 @@ FORCE_ACCEPT_WORKSPACE = false -- Trust all plugins that are being loaded by workspace config files. -- This is potentially unsafe for normal use and meant for usage in CI environments only. TRUST_ALL_PLUGINS = false + +NUM_THREADS = 1 + +THREAD_ID = 1 + +CHECK_WORKER = '' + +QUIET = false diff --git a/script/meta/bee/filesystem.lua b/script/meta/bee/filesystem.lua index 0c7e41a8..2a353065 100644 --- a/script/meta/bee/filesystem.lua +++ b/script/meta/bee/filesystem.lua @@ -1,4 +1,4 @@ ----@meta +---@meta bee.filesystem ---@class fs.path ---@operator div: fs.path @@ -75,11 +75,6 @@ end ---@param path fs.path ---@return fs.path -function fs.fullpath(path) -end - ----@param path fs.path ----@return fs.path function fs.absolute(path) end diff --git a/script/meta/bee/filewatch.lua b/script/meta/bee/filewatch.lua index b5211355..813548b2 100644 --- a/script/meta/bee/filewatch.lua +++ b/script/meta/bee/filewatch.lua @@ -1,4 +1,4 @@ ----@meta +---@meta bee.filewatch ---@class bee.filewatch.instance local instance = {} diff --git a/script/meta/bee/socket.lua b/script/meta/bee/socket.lua index 55c349a6..b5e3a7b2 100644 --- a/script/meta/bee/socket.lua +++ b/script/meta/bee/socket.lua @@ -1,4 +1,4 @@ ----@meta +---@meta bee.socket ---@alias bee.socket.protocol ---| 'tcp' diff --git a/script/meta/bee/sys.lua b/script/meta/bee/sys.lua new file mode 100644 index 00000000..ad14702c --- /dev/null +++ b/script/meta/bee/sys.lua @@ -0,0 +1,13 @@ +---@meta bee.sys + +---@class bee.sys +local sys = {} + +---@param path fs.path +---@return fs.path +function sys.fullpath(path) end + +---@return fs.path +function sys.exe_path() end + +return sys diff --git a/script/meta/bee/thread.lua b/script/meta/bee/thread.lua index 6b4323a4..15955aff 100644 --- a/script/meta/bee/thread.lua +++ b/script/meta/bee/thread.lua @@ -1,9 +1,9 @@ ----@meta +---@meta bee.thread ---@class bee.thread local thread = {} ----@param time number +---@param time integer function thread.sleep(time) end ---@param name string @@ -15,7 +15,10 @@ function thread.channel(name) end ---@param script string ---@return bee.thread.thread -function thread.thread(script) end +function thread.create(script) end + +---@return string? +function thread.errlog() end ---@class bee.thread.channel local channel = {} diff --git a/script/provider/provider.lua b/script/provider/provider.lua index 69fb3263..15e78b9a 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -368,6 +368,39 @@ m.register 'textDocument/hover' { end } +local function convertDefinitionResult(state, result) + local response = {} + for i, info in ipairs(result) do + ---@type uri + local targetUri = info.uri + if targetUri then + local targetState = files.getState(targetUri) + if targetState then + if client.getAbility 'textDocument.definition.linkSupport' then + response[i] = converter.locationLink(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(state, info.source.start, info.source.finish) + ) + else + response[i] = converter.location(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + ) + end + else + response[i] = converter.location( + targetUri, + converter.range( + converter.position(guide.rowColOf(info.target.start)), + converter.position(guide.rowColOf(info.target.finish)) + ) + ) + end + end + end + return response +end + m.register 'textDocument/definition' { capability = { definitionProvider = true, @@ -388,35 +421,7 @@ m.register 'textDocument/definition' { if not result then return nil end - local response = {} - for i, info in ipairs(result) do - ---@type uri - local targetUri = info.uri - if targetUri then - local targetState = files.getState(targetUri) - if targetState then - if client.getAbility 'textDocument.definition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(state, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - ) - end - else - response[i] = converter.location( - targetUri, - converter.range( - converter.position(guide.rowColOf(info.target.start)), - converter.position(guide.rowColOf(info.target.finish)) - ) - ) - end - end - end + local response = convertDefinitionResult(state, result) return response end } @@ -441,27 +446,32 @@ m.register 'textDocument/typeDefinition' { if not result then return nil end - local response = {} - for i, info in ipairs(result) do - ---@type uri - local targetUri = info.uri - if targetUri then - local targetState = files.getState(targetUri) - if targetState then - if client.getAbility 'textDocument.typeDefinition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(state, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - ) - end - end - end + local response = convertDefinitionResult(state, result) + return response + end +} + +m.register 'textDocument/implementation' { + capability = { + implementationProvider = true, + }, + abortByFileUpdate = true, + ---@async + function (params) + local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) + local state = files.getState(uri) + if not state then + return + end + local core = require 'core.implementation' + local pos = converter.unpackPosition(state, params.position) + local result = core(uri, pos) + if not result then + return nil end + local response = convertDefinitionResult(state, result) return response end } diff --git a/script/pub/pub.lua b/script/pub/pub.lua index e8051d05..517c3fc7 100644 --- a/script/pub/pub.lua +++ b/script/pub/pub.lua @@ -12,11 +12,14 @@ end local function channel_bpop(ctx) local selector, chan = ctx[1], ctx[2] - for _ in selector:wait() do - local r = table.pack(chan:pop()) - if r[1] == true then - return table.unpack(r, 2) + while true do + for _ in selector:wait() do + local r = table.pack(chan:pop()) + if r[1] == true then + return table.unpack(r, 2) + end end + thread.sleep(10) end end diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 11ba07ab..e1b1b43b 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -550,11 +550,14 @@ local function matchCall(source) or call.node ~= source then return end - local funcs = vm.getMatchedFunctions(source, call.args) local myNode = vm.getNode(source) if not myNode then return end + local funcs = vm.getExactMatchedFunctions(source, call.args) + if not funcs then + return + end local needRemove for n in myNode:eachObject() do if n.type == 'function' @@ -870,7 +873,7 @@ local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex) ---@type integer?, table<any, boolean>? local eventIndex, eventMap if call.args then - for i = 1, 2 do + for i = 1, 10 do local eventArg = call.args[i + fixIndex] if not eventArg then break diff --git a/script/vm/function.lua b/script/vm/function.lua index c6df6349..1e308317 100644 --- a/script/vm/function.lua +++ b/script/vm/function.lua @@ -1,6 +1,7 @@ ---@class vm local vm = require 'vm.vm' local guide = require 'parser.guide' +local util = require 'utility' ---@param arg parser.object ---@return parser.object? @@ -267,6 +268,9 @@ end ---@return integer def function vm.countReturnsOfCall(func, args, mark) local funcs = vm.getMatchedFunctions(func, args, mark) + if not funcs then + return 0, math.huge, 0 + end ---@type integer?, number?, integer? local min, max, def for _, f in ipairs(funcs) do @@ -329,22 +333,71 @@ function vm.countList(list, mark) return min, max, def end +---@param uri uri +---@param args parser.object[] +---@return boolean +local function isAllParamMatched(uri, args, params) + if not params then + return false + end + for i = 1, #args do + if not params[i] then + break + end + local argNode = vm.compileNode(args[i]) + local defNode = vm.compileNode(params[i]) + if not vm.canCastType(uri, defNode, argNode) then + return false + end + end + return true +end + ---@param func parser.object ----@param args parser.object[]? +---@param args? parser.object[] +---@return parser.object[]? +function vm.getExactMatchedFunctions(func, args) + local funcs = vm.getMatchedFunctions(func, args) + if not args or not funcs then + return funcs + end + if #funcs == 1 then + return funcs + end + local uri = guide.getUri(func) + local needRemove + for i, n in ipairs(funcs) do + if vm.isVarargFunctionWithOverloads(n) + or not isAllParamMatched(uri, args, n.args) then + if not needRemove then + needRemove = {} + end + needRemove[#needRemove+1] = i + end + end + if not needRemove then + return funcs + end + if #needRemove == #funcs then + return nil + end + util.tableMultiRemove(funcs, needRemove) + return funcs +end + +---@param func parser.object +---@param args? parser.object[] ---@param mark? table ----@return parser.object[] +---@return parser.object[]? function vm.getMatchedFunctions(func, args, mark) local funcs = {} local node = vm.compileNode(func) for n in node:eachObject() do - if (n.type == 'function' and not vm.isVarargFunctionWithOverloads(n)) + if n.type == 'function' or n.type == 'doc.type.function' then funcs[#funcs+1] = n end end - if #funcs <= 1 then - return funcs - end local amin, amax = vm.countList(args, mark) @@ -357,7 +410,7 @@ function vm.getMatchedFunctions(func, args, mark) end if #matched == 0 then - return funcs + return nil else return matched end @@ -372,23 +425,31 @@ function vm.isVarargFunctionWithOverloads(func) if not func.args then return false end + if func._varargFunction ~= nil then + return func._varargFunction + end if func.args[1] and func.args[1].type == 'self' then if not func.args[2] or func.args[2].type ~= '...' then + func._varargFunction = false return false end else if not func.args[1] or func.args[1].type ~= '...' then + func._varargFunction = false return false end end if not func.bindDocs then + func._varargFunction = false return false end for _, doc in ipairs(func.bindDocs) do if doc.type == 'doc.overload' then + func._varargFunction = true return true end end + func._varargFunction = false return false end diff --git a/script/vm/global.lua b/script/vm/global.lua index e830f6d8..aa987cf4 100644 --- a/script/vm/global.lua +++ b/script/vm/global.lua @@ -539,21 +539,60 @@ function vm.hasGlobalSets(suri, cate, name) return true end +---@param uri uri +---@param key string +---@return boolean +local function checkIsGlobalRegex(uri, key) + local dglobalsregex = config.get(uri, 'Lua.diagnostics.globalsRegex') + if not dglobalsregex then + return false + end + + for _, pattern in ipairs(dglobalsregex) do + if key:match(pattern) then + return true + end + end + + return false +end + ---@param src parser.object local function checkIsUndefinedGlobal(src) + if src.type ~= 'getglobal' then + return false + end + local key = src[1] + if not key then + return false + end + + local node = src.node + if node.tag ~= '_ENV' then + return false + end local uri = guide.getUri(src) - local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) local rspecial = config.get(uri, 'Lua.runtime.special') + if rspecial[key] then + return false + end - local node = src.node - return src.type == 'getglobal' and key and not ( - dglobals[key] or - rspecial[key] or - node.tag ~= '_ENV' or - vm.hasGlobalSets(uri, 'variable', key) - ) + if vm.hasGlobalSets(uri, 'variable', key) then + return false + end + + local dglobals = config.get(uri, 'Lua.diagnostics.globals') + if util.arrayHas(dglobals, key) then + return false + end + + if checkIsGlobalRegex(uri, key) then + return false + end + + return true end ---@param src parser.object diff --git a/script/vm/infer.lua b/script/vm/infer.lua index 3f3d0e3a..bb06ee3a 100644 --- a/script/vm/infer.lua +++ b/script/vm/infer.lua @@ -242,9 +242,6 @@ local viewNodeSwitch;viewNodeSwitch = util.switch() return vm.viewKey(source, uri) end) ----@class vm.node ----@field lastInfer? vm.infer - ---@param node? vm.node ---@return vm.infer local function createInfer(node) diff --git a/script/vm/node.lua b/script/vm/node.lua index bc1dfcb1..fae79cbc 100644 --- a/script/vm/node.lua +++ b/script/vm/node.lua @@ -16,6 +16,7 @@ vm.nodeCache = setmetatable({}, util.MODE_K) ---@field [vm.node.object] true ---@field fields? table<vm.node|string, vm.node> ---@field undefinedGlobal boolean? +---@field lastInfer? vm.infer local mt = {} mt.__index = mt mt.id = 0 @@ -31,6 +32,7 @@ function mt:merge(node) if not node then return self end + self.lastInfer = nil if node.type == 'vm.node' then if node == self then return self |