summaryrefslogtreecommitdiff
path: root/script
diff options
context:
space:
mode:
authorcarsakiller <carsakiller@gmail.com>2024-05-23 02:22:29 -0400
committerGitHub <noreply@github.com>2024-05-23 02:22:29 -0400
commit459ac5c06ecca35d135217ca0892cc7787a91d33 (patch)
tree6b4891e4c6dae06dfdd10d24f6c213bed24bd8fe /script
parentb9c9f9423ff31bb9160acc5d75feaec3c0bf777c (diff)
parent87abc4245f2a24e1cc35851b6464af9588934286 (diff)
downloadlua-language-server-459ac5c06ecca35d135217ca0892cc7787a91d33.zip
Merge branch 'master' into 2175-md-symbol-reference
Diffstat (limited to 'script')
-rw-r--r--script/brave/brave.lua12
-rw-r--r--script/cli/check.lua188
-rw-r--r--script/cli/check_worker.lua168
-rw-r--r--script/cli/init.lua5
-rw-r--r--script/config/env.lua67
-rw-r--r--script/config/template.lua1
-rw-r--r--script/core/diagnostics/duplicate-doc-field.lua2
-rw-r--r--script/core/diagnostics/global-element.lua18
-rw-r--r--script/core/diagnostics/lowercase-global.lua18
-rw-r--r--script/core/implementation.lua171
-rw-r--r--script/core/signature.lua5
-rw-r--r--script/core/type-definition.lua3
-rw-r--r--script/files.lua3
-rw-r--r--script/filewatch.lua3
-rw-r--r--script/global.d.lua8
-rw-r--r--script/meta/bee/filesystem.lua7
-rw-r--r--script/meta/bee/filewatch.lua2
-rw-r--r--script/meta/bee/socket.lua2
-rw-r--r--script/meta/bee/sys.lua13
-rw-r--r--script/meta/bee/thread.lua9
-rw-r--r--script/provider/provider.lua108
-rw-r--r--script/pub/pub.lua11
-rw-r--r--script/vm/compiler.lua7
-rw-r--r--script/vm/function.lua75
-rw-r--r--script/vm/global.lua55
-rw-r--r--script/vm/infer.lua3
-rw-r--r--script/vm/node.lua2
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