diff options
45 files changed, 322 insertions, 118 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7414d190..e8ff3632 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: matrix: include: - { os: ubuntu-20.04, platform: linux-x64 } - - { os: macos-latest, platform: darwin-x64 } + - { os: macos-14, platform: darwin-arm64 } - { os: windows-latest, platform: win32-x64 } runs-on: ${{ matrix.os }} steps: diff --git a/3rd/EmmyLuaCodeStyle b/3rd/EmmyLuaCodeStyle -Subproject e260d04c1906904b6b9ae1a87f63b54ca8ba09e +Subproject 783f269a70e7b794caab4a8b4073d08c4aae6cc diff --git a/3rd/bee.lua b/3rd/bee.lua -Subproject dfed9f99d272fedb70c8161d9250918c17d285b +Subproject 1f01891c5dfcb0a740e2b573eeefd4ea4efd9a3 diff --git a/3rd/luamake b/3rd/luamake -Subproject 13bf00fb858a24c709ddbdc372ec01cc645609b +Subproject ffa2770d1261bf12af2cfed5551b55df85c089d diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua index 80926d12..9ef46b86 100644 --- a/locale/en-us/setting.lua +++ b/locale/en-us/setting.lua @@ -293,6 +293,12 @@ When checking the type of union type, ignore the `nil` in it. When this setting is `false`, the `number|nil` type cannot be assigned to the `number` type. It can be with `true`. ]] +config.type.inferParamType = +[[ +When a parameter type is not annotated, it is inferred from the function's call sites. + +When this setting is `false`, the type of the parameter is `any` when it is not annotated. +]] config.doc.privateName = 'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.' config.doc.protectedName = diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua index 4e23e0ff..6ececcd3 100644 --- a/locale/pt-br/setting.lua +++ b/locale/pt-br/setting.lua @@ -293,6 +293,12 @@ When checking the type of union type, ignore the `nil` in it. When this setting is `false`, the `number|nil` type cannot be assigned to the `number` type. It can be with `true`. ]] +config.type.inferParamType = -- TODO: need translate! +[[ +When the parameter type is not annotated, the parameter type is inferred from the function's incoming parameters. + +When this setting is `false`, the type of the parameter is `any` when it is not annotated. +]] config.doc.privateName = -- TODO: need translate! 'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.' config.doc.protectedName = -- TODO: need translate! diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua index c4ec1b55..78e7fb68 100644 --- a/locale/zh-cn/setting.lua +++ b/locale/zh-cn/setting.lua @@ -292,6 +292,12 @@ config.type.weakNilCheck = 此设置为 `false` 时,`numer|nil` 类型无法赋给 `number` 类型;为 `true` 是则可以。 ]] +config.type.inferParamType = +[[ +未注释参数类型时,参数类型由函数传入参数推断。 + +如果设置为 "false",则在未注释时,参数类型为 "any"。 +]] config.doc.privateName = '将特定名称的字段视为私有,例如 `m_*` 意味着 `XXX.m_id` 与 `XXX.m_type` 是私有字段,只能在定义所在的类中访问。' config.doc.protectedName = diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua index c8e0dbfc..2b43e954 100644 --- a/locale/zh-tw/setting.lua +++ b/locale/zh-tw/setting.lua @@ -292,6 +292,12 @@ When checking the type of union type, ignore the `nil` in it. When this setting is `false`, the `number|nil` type cannot be assigned to the `number` type. It can be with `true`. ]] +config.type.inferParamType = -- TODO: need translate! +[[ +未注释参数类型时,参数类型由函数传入参数推断。 + +如果设置为 "false",则在未注释时,参数类型为 "any"。 +]] config.doc.privateName = -- TODO: need translate! 'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.' config.doc.protectedName = -- TODO: need translate! @@ -18,7 +18,7 @@ local includeCodeFormat = true require "make.detect_platform" -lm:import "3rd/bee.lua" +lm:import "3rd/bee.lua/make.lua" lm:import "make/code_format.lua" lm:source_set 'lpeglabel' { @@ -55,21 +55,21 @@ lm:executable "lua-language-server" { } local platform = require 'bee.platform' -local exe = platform.OS == 'Windows' and ".exe" or "" +local exe = platform.os == 'windows' and ".exe" or "" lm:copy "copy_lua-language-server" { - input = lm.bindir .. "/lua-language-server" .. exe, - output = "bin/lua-language-server" .. exe, + inputs = "$bin/lua-language-server" .. exe, + outputs = "bin/lua-language-server" .. exe, } lm:copy "copy_bootstrap" { - input = "make/bootstrap.lua", - output = "bin/main.lua", + inputs = "make/bootstrap.lua", + outputs = "bin/main.lua", } lm:msvc_copydll 'copy_vcrt' { type = "vcrt", - output = "bin", + outputs = "bin", } lm:phony "all" { @@ -93,13 +93,13 @@ if lm.notest then end lm:rule "run-bee-test" { - lm.bindir .. "/lua-language-server" .. exe, "$in", + args = { "$bin/lua-language-server" .. exe, "$in" }, description = "Run test: $in.", pool = "console", } lm:rule "run-unit-test" { - "bin/lua-language-server" .. exe, "$in", + args = { "bin/lua-language-server" .. exe, "$in" }, description = "Run test: $in.", pool = "console", } @@ -107,13 +107,13 @@ lm:rule "run-unit-test" { lm:build "bee-test" { rule = "run-bee-test", deps = { "lua-language-server", "copy_script" }, - input = "3rd/bee.lua/test/test.lua", + inputs = "3rd/bee.lua/test/test.lua", } lm:build 'unit-test' { rule = "run-unit-test", deps = { "bee-test", "all" }, - input = "test.lua", + inputs = "test.lua", } lm:default { diff --git a/make/detect_platform.lua b/make/detect_platform.lua index 52207f95..4b62298f 100644 --- a/make/detect_platform.lua +++ b/make/detect_platform.lua @@ -2,7 +2,7 @@ local lm = require 'luamake' local platform = require 'bee.platform' -if platform.OS == 'macOS' then +if platform.os == 'macos' then if lm.platform == nil then elseif lm.platform == "darwin-arm64" then lm.target = "arm64-apple-macos11" @@ -11,7 +11,7 @@ if platform.OS == 'macOS' then else error "unknown platform" end -elseif platform.OS == 'Windows' then +elseif platform.os == 'windows' then if lm.platform == nil then elseif lm.platform == "win32-ia32" then lm.arch = "x86" @@ -20,7 +20,7 @@ elseif platform.OS == 'Windows' then else error "unknown platform" end -elseif platform.OS == 'Linux' then +elseif platform.os == 'linux' then if lm.platform == nil then elseif lm.platform == "linux-x64" then elseif lm.platform == "linux-arm64" then @@ -52,7 +52,7 @@ local ARCH <const> = { } local function detectArch() - if platform.OS == 'Windows' then + if platform.os == 'windows' then return detectWindowsArch() end local posixArch = detectPosixArch() @@ -67,5 +67,5 @@ local function targetPlatformArch() end if not lm.notest then - lm.notest = (platform.OS ~= 'Windows' and targetPlatformArch() ~= detectArch()) + lm.notest = (platform.os ~= 'windows' and targetPlatformArch() ~= detectArch()) end diff --git a/script/cli/check.lua b/script/cli/check.lua index 4295fa06..5ac9ea13 100644 --- a/script/cli/check.lua +++ b/script/cli/check.lua @@ -11,6 +11,7 @@ local config = require 'config.config' local fs = require 'bee.filesystem' local provider = require 'provider' +require 'plugin' require 'vm' lang(LOCALE) @@ -26,6 +27,7 @@ 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 @@ -69,7 +71,7 @@ lclient():start(function (client) end config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true)) - local uris = files.getAllUris(rootUri) + local uris = files.getChildFiles(rootUri) local max = #uris for i, uri in ipairs(uris) do files.open(uri) @@ -83,6 +85,7 @@ lclient():start(function (client) .. ('0'):rep(#tostring(max) - #tostring(i)) .. tostring(i) .. '/' .. tostring(max) io.write(output) + io.flush() end end io.write('\x0D') diff --git a/script/cli/doc.lua b/script/cli/doc.lua index fb9b0a8e..c413d354 100644 --- a/script/cli/doc.lua +++ b/script/cli/doc.lua @@ -189,6 +189,13 @@ local function collectTypes(global, results) field.rawdesc = getDesc(source, true) field.extends = packObject(source.value) field.visible = vm.getVisibleType(source) + if vm.isAsync(source, true) then + field.async = true + end + local depr = vm.getDeprecated(source) + if (depr and not depr.versions) then + field.deprecated = true + end return end if source.type == 'tableindex' then @@ -250,6 +257,13 @@ local function collectVars(global, results) result.rawdesc = result.rawdesc or getDesc(set, true) result.defines[#result.defines].extends['desc'] = getDesc(set) result.defines[#result.defines].extends['rawdesc'] = getDesc(set, true) + if vm.isAsync(set, true) then + result.defines[#result.defines].extends['async'] = true + end + local depr = vm.getDeprecated(set) + if (depr and not depr.versions) then + result.defines[#result.defines].extends['deprecated'] = true + end end end if #result.defines == 0 then @@ -264,12 +278,26 @@ local function collectVars(global, results) results[#results+1] = result end +---Add config settings to JSON output. +---@param results table +local function collectConfig(results) + local result = { + name = 'LuaLS', + type = 'luals.config', + DOC = fs.absolute(fs.path(DOC)):string(), + defines = {}, + fields = {} + } + results[#results+1] = result +end + ---@async ---@param callback fun(i, max) function export.export(outputPath, callback) local results = {} local globals = vm.getAllGlobals() + collectConfig(results) local max = 0 for _ in pairs(globals) do max = max + 1 @@ -331,9 +359,53 @@ function export.makeDoc(outputPath) return docPath, mdPath end + +---Find file 'doc.json'. +---@return fs.path +local function findDocJson() + local doc_json_path + if type(DOC_UPDATE) == 'string' then + doc_json_path = fs.absolute(fs.path(DOC_UPDATE)) .. '/doc.json' + else + doc_json_path = fs.current_path() .. '/doc.json' + end + if fs.exists(doc_json_path) then + return doc_json_path + else + error(string.format('Error: File "%s" not found.', doc_json_path)) + end +end + +---@return string # path of 'doc.json' +---@return string # path to be documented +local function getPathDocUpdate() + local doc_json_path = findDocJson() + local ok, doc_path = pcall( + function () + local json = require('json') + local json_file = io.open(doc_json_path:string(), 'r'):read('*all') + local json_data = json.decode(json_file) + for _, section in ipairs(json_data) do + if section.type == 'luals.config' then + return section.DOC + end + end + end) + if ok then + local doc_json_dir = doc_json_path:string():gsub('/doc.json', '') + return doc_json_dir, doc_path + else + error(string.format('Error: Cannot update "%s".', doc_json_path .. '/doc.json')) + end +end + function export.runCLI() lang(LOCALE) + if DOC_UPDATE then + DOC_OUT_PATH, DOC = getPathDocUpdate() + end + if type(DOC) ~= 'string' then print(lang.script('CLI_CHECK_ERROR_TYPE', type(DOC))) return diff --git a/script/cli/init.lua b/script/cli/init.lua index 6d7fc0ff..d37c50ae 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['DOC_UPDATE'] then + require 'cli.doc' .runCLI() + os.exit(0, true) +end + if _G['DOC'] then require 'cli.doc' .runCLI() os.exit(0, true) diff --git a/script/client.lua b/script/client.lua index a8eda9b8..e328dc52 100644 --- a/script/client.lua +++ b/script/client.lua @@ -278,7 +278,7 @@ local function searchPatchInfo(cfg, rawKey) } end ----@param uri uri +---@param uri? uri ---@param cfg table ---@param change config.change ---@return json.patch? @@ -330,7 +330,7 @@ local function makeConfigPatch(uri, cfg, change) return nil end ----@param uri uri +---@param uri? uri ---@param path string ---@param changes config.change[] ---@return string? diff --git a/script/config/template.lua b/script/config/template.lua index 2a30d2ea..49907419 100644 --- a/script/config/template.lua +++ b/script/config/template.lua @@ -397,6 +397,7 @@ local template = { ['Lua.type.castNumberToInteger'] = Type.Boolean >> true, ['Lua.type.weakUnionCheck'] = Type.Boolean >> false, ['Lua.type.weakNilCheck'] = Type.Boolean >> false, + ['Lua.type.inferParamType'] = Type.Boolean >> false, ['Lua.doc.privateName'] = Type.Array(Type.String), ['Lua.doc.protectedName'] = Type.Array(Type.String), ['Lua.doc.packageName'] = Type.Array(Type.String), diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index a96cc918..9f3ff929 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -132,6 +132,7 @@ end ---@async return function (data) + ---@type uri local uri = data.uri local target = data.target local name = data.name @@ -158,5 +159,7 @@ return function (data) end local offset, fmt = findInsertRow(uri) - applyAutoRequire(uri, offset, name, requireName, fmt) + if offset and fmt then + applyAutoRequire(uri, offset, name, requireName, fmt) + end end diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index acb3adbe..d047dd56 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -147,6 +147,9 @@ end local function findParent(state, position) local text = state.lua + if not text then + return + end local offset = guide.positionToOffset(state, position) for i = offset, 1, -1 do local char = text:sub(i, i) @@ -675,6 +678,7 @@ local function checkGlobal(state, word, startPos, position, parent, oop, results end ---@async +---@param parent parser.object local function checkField(state, word, start, position, parent, oop, results) if parent.tag == '_ENV' or parent.special == '_G' then local globals = vm.getGlobalSets(state.uri, 'variable') @@ -955,8 +959,7 @@ local function checkFunctionArgByDocParam(state, word, startPos, results) end end -local function isAfterLocal(state, startPos) - local text = state.lua +local function isAfterLocal(state, text, startPos) local offset = guide.positionToOffset(state, startPos) local pos = lookBackward.skipSpace(text, offset) local word = lookBackward.findWord(text, pos) @@ -965,6 +968,8 @@ end local function collectRequireNames(mode, myUri, literal, source, smark, position, results) local collect = {} + local source_start = source and smark and (source.start + #smark) or position + local source_finish = source and smark and (source.finish - #smark) or position if mode == 'require' then for uri in files.eachFile(myUri) do if myUri == uri then @@ -978,8 +983,8 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position if not collect[info.name] then collect[info.name] = { textEdit = { - start = smark and (source.start + #smark) or position, - finish = smark and (source.finish - #smark) or position, + start = source_start, + finish = source_finish, newText = smark and info.name or util.viewString(info.name), }, path = relative, @@ -1006,8 +1011,8 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position if not collect[open] then collect[open] = { textEdit = { - start = smark and (source.start + #smark) or position, - finish = smark and (source.finish - #smark) or position, + start = source_start, + finish = source_finish, newText = smark and open or util.viewString(open), }, path = path, @@ -1034,8 +1039,8 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position if not collect[path] then collect[path] = { textEdit = { - start = smark and (source.start + #smark) or position, - finish = smark and (source.finish - #smark) or position, + start = source_start, + finish = source_finish, newText = smark and path or util.viewString(path), } } @@ -1097,6 +1102,9 @@ end local function checkLenPlusOne(state, position, results) local text = state.lua + if not text then + return + end guide.eachSourceContain(state.ast, position, function (source) if source.type == 'getindex' or source.type == 'setindex' then @@ -1392,6 +1400,9 @@ end local function checkEqualEnum(state, position, results) local text = state.lua + if not text then + return + end local start = lookBackward.findTargetSymbol(text, guide.positionToOffset(state, position), '=') if not start then return @@ -1493,6 +1504,9 @@ local function tryWord(state, position, triggerCharacter, results) return end local text = state.lua + if not text then + return + end local offset = guide.positionToOffset(state, position) local finish = lookBackward.skipSpace(text, offset) local word, start = lookBackward.findWord(text, offset) @@ -1518,7 +1532,7 @@ local function tryWord(state, position, triggerCharacter, results) checkProvideLocal(state, word, startPos, results) checkFunctionArgByDocParam(state, word, startPos, results) else - local afterLocal = isAfterLocal(state, startPos) + local afterLocal = isAfterLocal(state, text, startPos) local stop = checkKeyWord(state, startPos, position, word, hasSpace, afterLocal, results) if stop then return @@ -1530,8 +1544,10 @@ local function tryWord(state, position, triggerCharacter, results) checkLocal(state, word, startPos, results) checkTableField(state, word, startPos, results) local env = guide.getENV(state.ast, startPos) - checkGlobal(state, word, startPos, position, env, false, results) - checkModule(state, word, startPos, results) + if env then + checkGlobal(state, word, startPos, position, env, false, results) + checkModule(state, word, startPos, results) + end end end end @@ -1592,6 +1608,9 @@ end local function checkTableLiteralField(state, position, tbl, fields, results) local text = state.lua + if not text then + return + end local mark = {} for _, field in ipairs(tbl) do if field.type == 'tablefield' @@ -1610,9 +1629,11 @@ local function checkTableLiteralField(state, position, tbl, fields, results) local left = lookBackward.findWord(text, guide.positionToOffset(state, position)) if not left then local pos = lookBackward.findAnyOffset(text, guide.positionToOffset(state, position)) - local char = text:sub(pos, pos) - if char == '{' or char == ',' or char == ';' then - left = '' + if pos then + local char = text:sub(pos, pos) + if char == '{' or char == ',' or char == ';' then + left = '' + end end end if left then @@ -1801,6 +1822,7 @@ local function getluaDocByContain(state, position) return result end +---@return parser.state.err?, parser.object? local function getluaDocByErr(state, start, position) local targetError for _, err in ipairs(state.errs) do @@ -2008,7 +2030,7 @@ local function tryluaDocByErr(state, position, err, docState, results) for _, doc in ipairs(vm.getDocSets(state.uri)) do if doc.type == 'doc.class' and not used[doc.class[1]] - and doc.class[1] ~= docState.class[1] then + and docState and doc.class[1] ~= docState.class[1] then used[doc.class[1]] = true results[#results+1] = { label = doc.class[1], diff --git a/script/core/diagnostics/undefined-doc-name.lua b/script/core/diagnostics/undefined-doc-name.lua index 3c8ed469..1c55f3bf 100644 --- a/script/core/diagnostics/undefined-doc-name.lua +++ b/script/core/diagnostics/undefined-doc-name.lua @@ -13,16 +13,6 @@ return function (uri, callback) return end - local function hasNameOfGeneric(name, source) - if not source.typeGeneric then - return false - end - if not source.typeGeneric[name] then - return false - end - return true - end - guide.eachSource(state.ast.docs, function (source) if source.type ~= 'doc.extends.name' and source.type ~= 'doc.type.name' then @@ -35,8 +25,7 @@ return function (uri, callback) if name == '...' or name == '_' or name == 'self' then return end - if #vm.getDocSets(uri, name) > 0 - or hasNameOfGeneric(name, source) then + if #vm.getDocSets(uri, name) > 0 then return end callback { diff --git a/script/core/highlight.lua b/script/core/highlight.lua index 80088680..72214672 100644 --- a/script/core/highlight.lua +++ b/script/core/highlight.lua @@ -63,7 +63,7 @@ local function checkInIf(state, source, text, position) local endA = endB - #'end' + 1 if position >= source.finish - #'end' and position <= source.finish - and text:sub(endA, endB) == 'end' then + and text and text:sub(endA, endB) == 'end' then return true end -- 检查每个子模块 @@ -83,7 +83,7 @@ local function makeIf(state, source, text, callback) -- end local endB = guide.positionToOffset(state, source.finish) local endA = endB - #'end' + 1 - if text:sub(endA, endB) == 'end' then + if text and text:sub(endA, endB) == 'end' then callback(source.finish - #'end', source.finish) end -- 每个子模块 diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index 75189b06..0cbcc835 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -449,7 +449,8 @@ local function tyrDocParamComment(source) or source.type == 'getlocal' then source = source.node end - if source.type ~= 'local' then + if source.type ~= 'local' + and source.type ~= '...' then return end if source.parent.type ~= 'funcargs' then diff --git a/script/encoder/ansi.lua b/script/encoder/ansi.lua index 7cb64ec3..f89ddbc1 100644 --- a/script/encoder/ansi.lua +++ b/script/encoder/ansi.lua @@ -1,7 +1,7 @@ local platform = require 'bee.platform' local windows -if platform.OS == 'Windows' then +if platform.os == 'windows' then windows = require 'bee.windows' end diff --git a/script/file-uri.lua b/script/file-uri.lua index 8a075f7e..192f3ab5 100644 --- a/script/file-uri.lua +++ b/script/file-uri.lua @@ -25,7 +25,7 @@ local m = {} ---@return uri uri function m.encode(path) local authority = '' - if platform.OS == 'Windows' then + if platform.os == 'windows' then path = path:gsub('\\', '/') end @@ -82,7 +82,7 @@ function m.decode(uri) else value = path end - if platform.OS == 'Windows' then + if platform.os == 'windows' then value = value:gsub('/', '\\') end return value diff --git a/script/files.lua b/script/files.lua index 8cc7a5ab..b9df5695 100644 --- a/script/files.lua +++ b/script/files.lua @@ -78,7 +78,7 @@ end ---@param uri uri ---@return uri function m.getRealUri(uri) - if platform.OS ~= 'Windows' then + if platform.os ~= 'windows' then return furi.normalize(uri) end if not furi.isValid(uri) then @@ -833,7 +833,7 @@ function m.isDll(uri) if not ext then return false end - if platform.OS == 'Windows' then + if platform.os == 'windows' then if ext == 'dll' then return true end @@ -932,7 +932,7 @@ function m.normalize(path) break end end - if platform.OS == 'Windows' then + if platform.os == 'windows' then path = path:gsub('[/\\]+', '\\') :gsub('[/\\]+$', '') :gsub('^(%a:)$', '%1\\') diff --git a/script/filewatch.lua b/script/filewatch.lua index a8fa925f..d4850ca1 100644 --- a/script/filewatch.lua +++ b/script/filewatch.lua @@ -16,8 +16,8 @@ local function isExists(filename) if plat.OS ~= 'Windows' then return true end - local suc, res = pcall(fs.fullpath, path) - if not suc then + local res = fs.fullpath(path) + if not res then return false end if res :string():gsub('^%w+:', string.lower) diff --git a/script/fs-utility.lua b/script/fs-utility.lua index 9a45b1cc..35b336fe 100644 --- a/script/fs-utility.lua +++ b/script/fs-utility.lua @@ -128,6 +128,7 @@ function dfs:__div(filename) return new end +---@package function dfs:_open(index) local paths = split(self.path, '[/\\]') local current = self.files @@ -147,6 +148,7 @@ function dfs:_open(index) return current end +---@package function dfs:_filename() return self.path:match '[^/\\]+$' end @@ -291,6 +293,7 @@ local function fsIsDirectory(path, option) if path.type == 'dummy' then return path:isDirectory() end + ---@cast path -dummyfs local status = fs.symlink_status(path):type() return status == 'directory' end @@ -347,6 +350,7 @@ local function fsSave(path, text, option) return false end if path.type == 'dummy' then + ---@cast path -fs.path local dir = path:_open(-2) if not dir then option.err[#option.err+1] = '无法打开:' .. path:string() @@ -385,6 +389,7 @@ local function fsLoad(path, option) return nil end else + ---@cast path -dummyfs local text, err = m.loadFile(path) if text then return text @@ -407,6 +412,7 @@ local function fsCopy(source, target, option) end return fsSave(target, sourceText, option) else + ---@cast source -dummyfs if target.type == 'dummy' then local sourceText, err = m.loadFile(source) if not sourceText then @@ -564,7 +570,6 @@ end --- 文件列表 function m.fileList(option) option = option or buildOption(option) - local os = platform.OS local keyMap = {} local fileList = {} local function computeKey(path) diff --git a/script/gc.lua b/script/gc.lua index ff22195e..92739585 100644 --- a/script/gc.lua +++ b/script/gc.lua @@ -1,12 +1,13 @@ local util = require 'utility' ---@class gc ----@field _list table +---@field package _list table local mt = {} mt.__index = mt mt.type = 'gc' mt._removed = false +---@package mt._max = 10 local function destroyGCObject(obj) diff --git a/script/global.d.lua b/script/global.d.lua index cee9e01b..ead46ca9 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -56,6 +56,10 @@ DOC = '' ---@type string DOC_OUT_PATH = '' +---update an existing doc.json +---@type string +DOC_UPDATE = '' + ---@type string | '"Error"' | '"Warning"' | '"Information"' | '"Hint"' CHECKLEVEL = 'Warning' @@ -90,3 +94,7 @@ SOCKET = 0 -- Allowing the use of the root directory or home directory as the workspace 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 diff --git a/script/json-edit.lua b/script/json-edit.lua index 30a55250..efa1216f 100644 --- a/script/json-edit.lua +++ b/script/json-edit.lua @@ -384,6 +384,7 @@ end local JsonEmpty = function () end +---@return {s: integer, d:integer, f:integer, v: any} local function decode_ast(str) if type(str) ~= "string" then error("expected argument of type string, got " .. type(str)) @@ -607,7 +608,11 @@ function OP.add(str, option, path, value) end local ast = decode_ast(str) if ast.v == JsonEmpty then - local pathlst = split_path(path) + local pathlst, err = split_path(path) + if not pathlst then + error(err) + return + end value = add_prefix(value, pathlst) return json.beautify(value, option) end @@ -674,7 +679,11 @@ function OP.replace(str, option, path, value) end local ast = decode_ast(str) if ast.v == JsonEmpty then - local pathlst = split_path(path) + local pathlst, err = split_path(path) + if not pathlst then + error(err) + return + end value = add_prefix(value, pathlst) return json.beautify(value, option) end diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 5321d9b8..8dd772db 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -239,6 +239,16 @@ local LocalLimit = 200 local parseExp, parseAction +---@class parser.state.err +---@field type string +---@field start? parser.position +---@field finish? parser.position +---@field info? table +---@field fix? table +---@field version? string[]|string +---@field level? string | 'Error' | 'Warning' + +---@type fun(err:parser.state.err):parser.state.err|nil local pushError local function addSpecial(name, obj) @@ -711,6 +721,7 @@ local function parseLocalAttrs() return attrs end +---@param obj table local function createLocal(obj, attrs) obj.type = 'local' obj.effect = obj.finish @@ -1709,6 +1720,9 @@ local function parseTable() end local function addDummySelf(node, call) + if not node then + return + end if node.type ~= 'getmethod' then return end @@ -1736,6 +1750,9 @@ local function checkAmbiguityCall(call, parenPos) return end local node = call.node + if not node then + return + end local nodeRow = guide.rowColOf(node.finish) local callRow = guide.rowColOf(parenPos) if nodeRow == callRow then @@ -2470,7 +2487,10 @@ local function parseExpUnit() local node = parseName() if node then - return parseSimple(resolveName(node), false) + local nameNode = resolveName(node) + if nameNode then + return parseSimple(nameNode, false) + end end return nil @@ -3421,6 +3441,7 @@ local function parseFor() forStateVars = 3 LocalCount = LocalCount + forStateVars if name then + ---@cast name parser.object local loc = createLocal(name) loc.parent = action action.finish = name.finish @@ -3523,7 +3544,9 @@ local function parseFor() list.range = lastName and lastName.range or inRight action.keys = list for i = 1, #list do - local loc = createLocal(list[i]) + local obj = list[i] + ---@cast obj parser.object + local loc = createLocal(obj) loc.parent = action loc.effect = action.finish end diff --git a/script/parser/guide.lua b/script/parser/guide.lua index fd779da0..ac7a5ce0 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -74,9 +74,12 @@ local type = type ---@field hasBreak? true ---@field hasExit? true ---@field [integer] parser.object|any +---@field dot { type: string, start: integer, finish: integer } +---@field colon { type: string, start: integer, finish: integer } ---@field package _root parser.object ---@field package _eachCache? parser.object[] ---@field package _isGlobal? boolean +---@field package _typeCache? parser.object[][] ---@class guide ---@field debugMode boolean diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index edbfd34e..e18e34aa 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -1668,7 +1668,7 @@ local function trimTailComment(text) and comment:find '[\'"%]]%s*$' then local state = compile(comment:gsub('^%s+', ''), 'String') if state and state.ast then - comment = state.ast[1] + comment = state.ast[1] --[[@as string]] end end return util.trim(comment) diff --git a/script/plugin.lua b/script/plugin.lua index b297cd9b..26211a42 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -62,6 +62,7 @@ end function m.getVmPlugin(uri) local scp = scope.getScope(uri) + ---@type pluginInterfaces local interfaces = scp:get('pluginInterfaces') if not interfaces then return @@ -72,6 +73,9 @@ end ---@async ---@param scp scope local function checkTrustLoad(scp) + if TRUST_ALL_PLUGINS then + return true + end local pluginPath = scp:get('pluginPath') local filePath = LOGPATH .. '/trusted' local trusted = util.loadFile(filePath) @@ -146,6 +150,7 @@ local function initPlugin(uri) return end local args = config.get(scp.uri, 'Lua.runtime.pluginArgs') + if args == nil then args = {} end if type(pluginConfigPaths) == 'string' then pluginConfigPaths = { pluginConfigPaths } end diff --git a/script/proto/proto.lua b/script/proto/proto.lua index 2460b4ec..b0d5d1a9 100644 --- a/script/proto/proto.lua +++ b/script/proto/proto.lua @@ -232,7 +232,7 @@ end function m.listen(mode, socketPort) m.mode = mode if mode == 'stdio' then - if platform.OS == 'Windows' then + if platform.os == 'windows' then local windows = require 'bee.windows' windows.filemode(io.stdin, 'b') windows.filemode(io.stdout, 'b') diff --git a/script/timer.lua b/script/timer.lua index 09bbce0f..14d33f6a 100644 --- a/script/timer.lua +++ b/script/timer.lua @@ -14,6 +14,7 @@ local curIndex = 0 local tarFrame = 0 local fwFrame = 0 local freeQueue = {} +---@type (timer|false)[][] local timer = {} local function allocQueue() @@ -101,9 +102,10 @@ end local m = {} ---@class timer ----@field _onTimer? fun(self: timer) ----@field _timeoutFrame integer ----@field _timeout integer +---@field package _onTimer? fun(self: timer) +---@field package _timeoutFrame integer +---@field package _timeout integer +---@field package _timerCount integer local mt = {} mt.__index = mt mt.type = 'timer' @@ -119,6 +121,7 @@ function mt:__call() end function mt:remove() + ---@package self._removed = true end @@ -126,7 +129,9 @@ function mt:pause() if self._removed or self._pauseRemaining then return end + ---@package self._pauseRemaining = getRemaining(self) + ---@package self._running = false local ti = self._timeoutFrame local q = timer[ti] @@ -145,6 +150,7 @@ function mt:resume() return end local timeout = self._pauseRemaining + ---@package self._pauseRemaining = nil mTimeout(self, timeout) end @@ -163,6 +169,7 @@ function mt:restart() end end end + ---@package self._running = false mTimeout(self, self._timeout) end diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 2253c83a..fc8f7c52 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -1031,6 +1031,7 @@ local function compileForVars(source, target) return false end +---@param func parser.object ---@param source parser.object local function compileFunctionParam(func, source) -- local call ---@type fun(f: fun(x: number));call(function (x) end) --> x -> number @@ -1050,33 +1051,31 @@ local function compileFunctionParam(func, source) end end end - if func.parent.type == 'local' then + + local derviationParam = config.get(guide.getUri(func), 'Lua.type.inferParamType') + if derviationParam and func.parent.type == 'local' and func.parent.ref then local refs = func.parent.ref - local findCall - if refs then - for i, ref in ipairs(refs) do - if ref.parent.type == 'call' then - findCall = ref.parent - break - end + local found + for _, ref in ipairs(refs) do + if ref.parent.type ~= 'call' then + goto continue end - end - if findCall and findCall.args then - local index - for i, arg in ipairs(source.parent) do - if arg == source then - index = i - break - end + local caller = ref.parent + if not caller.args then + goto continue end - if index then - local callerArg = findCall.args[index] - if callerArg then - vm.setNode(source, vm.compileNode(callerArg)) - return true + for index, arg in ipairs(source.parent) do + if arg == source then + local callerArg = caller.args[index] + if callerArg then + vm.setNode(source, vm.compileNode(callerArg)) + finded = true + end end end + ::continue:: end + return finded end end @@ -1121,24 +1120,9 @@ local function compileLocal(source) end if source.parent.type == 'funcargs' and not hasMarkDoc and not hasMarkParam then local func = source.parent.parent - -- local call ---@type fun(f: fun(x: number));call(function (x) end) --> x -> number - local funcNode = vm.compileNode(func) - local hasDocArg - for n in funcNode:eachObject() do - if n.type == 'doc.type.function' then - for index, arg in ipairs(n.args) do - if func.args[index] == source then - local argNode = vm.compileNode(arg) - for an in argNode:eachObject() do - if an.type ~= 'doc.generic.name' then - vm.setNode(source, an) - end - end - hasDocArg = true - end - end - end - end + local vmPlugin = plugin.getVmPlugin(guide.getUri(source)) + local hasDocArg = vmPlugin and vmPlugin.OnCompileFunctionParam(compileFunctionParam, func, source) + or compileFunctionParam(func, source) if not hasDocArg then vm.setNode(source, vm.declareGlobal('type', 'any')) end diff --git a/script/vm/visible.lua b/script/vm/visible.lua index d13ecf1f..0f486d6b 100644 --- a/script/vm/visible.lua +++ b/script/vm/visible.lua @@ -31,6 +31,10 @@ local function getVisibleType(source) source._visibleType = 'protected' return 'protected' end + if doc.type == 'doc.package' then + source._visibleType = 'package' + return 'package' + end end end @@ -50,6 +54,12 @@ local function getVisibleType(source) source._visibleType = 'protected' return 'protected' end + + local packageNames = config.get(uri, 'Lua.doc.packageName') + if #packageNames > 0 and glob.glob(packageNames)(fieldName) then + source._visibleType = 'package' + return 'package' + end end source._visibleType = 'public' diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index c319cbad..1507183c 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -123,7 +123,7 @@ function mt:getRequireResultByPath(path) cutedPath = currentPath:sub(pos) head = currentPath:sub(1, pos - 1) pos = currentPath:match('[/\\]+()', pos) - if platform.OS == 'Windows' then + if platform.os == 'windows' then searcher = searcher :gsub('[/\\]+', '\\') else searcher = searcher :gsub('[/\\]+', '/') diff --git a/script/workspace/scope.lua b/script/workspace/scope.lua index 789b5f81..cfdfdc90 100644 --- a/script/workspace/scope.lua +++ b/script/workspace/scope.lua @@ -235,11 +235,11 @@ function m.getLinkedScope(uri) return nil end ----@param uri uri +---@param uri? uri ---@return scope function m.getScope(uri) - return m.getFolder(uri) - or m.getLinkedScope(uri) + return uri and (m.getFolder(uri) + or m.getLinkedScope(uri)) or m.fallback end diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index 97518e84..fe7c6c15 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -188,7 +188,7 @@ function m.getNativeMatcher(scp) local matcher = glob.gitignore(pattern, { root = scp.uri and furi.decode(scp.uri), - ignoreCase = platform.OS == 'Windows', + ignoreCase = platform.os == 'windows', }, globInteferFace) scp:set('nativeMatcher', matcher) @@ -236,7 +236,7 @@ function m.getLibraryMatchers(scp) local nPath = fs.absolute(fs.path(path)):string() local matcher = glob.gitignore(pattern, { root = path, - ignoreCase = platform.OS == 'Windows', + ignoreCase = platform.os == 'windows', }, globInteferFace) matchers[#matchers+1] = { uri = furi.encode(nPath), diff --git a/test/code_action/init.lua b/test/code_action/init.lua index 264cfacf..75c116a4 100644 --- a/test/code_action/init.lua +++ b/test/code_action/init.lua @@ -49,6 +49,7 @@ function TEST(script) end end +---@param testfiles [string, {path:string, content:string}] local function TEST_CROSSFILE(testfiles) local mainscript = table.remove(testfiles, 1) return function(expected) diff --git a/test/crossfile/completion.lua b/test/crossfile/completion.lua index 227350cb..79b40cb3 100644 --- a/test/crossfile/completion.lua +++ b/test/crossfile/completion.lua @@ -918,7 +918,7 @@ local z: table } } -if platform.OS == 'Windows' then +if platform.os == 'windows' then Cared['detail'] = true Cared['additionalTextEdits'] = true TEST { diff --git a/test/crossfile/definition.lua b/test/crossfile/definition.lua index b13b57f3..49a5d8d3 100644 --- a/test/crossfile/definition.lua +++ b/test/crossfile/definition.lua @@ -689,7 +689,7 @@ TEST { } -if platform.OS == 'Linux' then +if platform.os == 'linux' then TEST { { diff --git a/test/document_symbol/init.lua b/test/document_symbol/init.lua index dff43c7d..53e0dab2 100644 --- a/test/document_symbol/init.lua +++ b/test/document_symbol/init.lua @@ -50,6 +50,7 @@ function TEST(script) return function (expect) files.setText(TESTURI, script) local result = core(TESTURI) + assert(result) assert(eq(expect, result)) checkArcoss(result) files.remove(TESTURI) diff --git a/test/signature/init.lua b/test/signature/init.lua index f46ce017..bd4d40cd 100644 --- a/test/signature/init.lua +++ b/test/signature/init.lua @@ -372,3 +372,30 @@ t:event("onTimer", <??>) { '(method) (ev: "onTimer", <!t: integer!>)', } + +local config = require 'config' +config.set(nil, "Lua.type.inferParamType", true) + +TEST [[ +local function x(a, b) +end + +x("1", <??>) +]] +{ +'function x(a: string, <!b: any!>)' +} + +TEST [[ +local function x(a) + +end +x('str') +x(1) +x(<??>) +]] +{ +'function x(<!a: string|integer!>)', +} + +config.set(nil, "Lua.type.inferParamType", false) diff --git a/test/tclient/tests/jump-source.lua b/test/tclient/tests/jump-source.lua index 84a4dcd5..c9e093c0 100644 --- a/test/tclient/tests/jump-source.lua +++ b/test/tclient/tests/jump-source.lua @@ -163,7 +163,7 @@ print(D3) position = { line = 9, character = 7 }, }) - if platform.OS == 'Windows' then + if platform.os == 'windows' then assert(util.equal(locations, { { uri = 'file:///d%3A/xxx/2.lua', |