diff options
author | AlexCai2019 <89138532+AlexCai2019@users.noreply.github.com> | 2022-04-15 03:24:58 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-15 03:24:58 +0800 |
commit | 756adfb2283e9ebe4a55257cf6cb51d27eb5ad2e (patch) | |
tree | c359ac5a6016d7b359f0439e3b227b1e45a429c5 | |
parent | 798e8af99bc052c51afe53bb77026e9fb04f4b4c (diff) | |
parent | c45d862d3a3f0e0588a27b9e7cd8ba84afd454a8 (diff) | |
download | lua-language-server-756adfb2283e9ebe4a55257cf6cb51d27eb5ad2e.zip |
Merge branch 'sumneko:master' into master
161 files changed, 9629 insertions, 8935 deletions
diff --git a/.editorconfig b/.editorconfig index 95014d8b..48df1807 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,7 +43,8 @@ if_condition_no_continuation_indent = false # optional crlf/lf -end_of_line = crlf +end_of_line = auto +detect_end_of_line = true # [line layout] # The following configuration supports three expressions diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31c8d7f8..5e953267 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,12 +21,19 @@ jobs: matrix: include: - { os: ubuntu-18.04, target: linux, platform: linux-x64 } + - { os: ubuntu-18.04, target: linux, platform: linux-arm64 } - { os: macos-11, target: darwin, platform: darwin-x64 } - { os: macos-11, target: darwin, platform: darwin-arm64 } - { os: windows-latest, target: windows, platform: win32-ia32 } - { os: windows-latest, target: windows, platform: win32-x64 } runs-on: ${{ matrix.os }} steps: + - name: Install aarch64-linux-gnu + if: ${{ matrix.platform == 'linux-arm64' }} + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + - uses: actions/checkout@v2 with: submodules: recursive diff --git a/.gitmodules b/.gitmodules index 454056b0..1dfe374c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "3rd/lpeglabel"] path = 3rd/lpeglabel url = https://github.com/sqmedeiros/lpeglabel -[submodule "3rd/rcedit"] - path = 3rd/rcedit - url = https://github.com/electron/rcedit [submodule "3rd/love-api"] path = 3rd/love-api url = https://github.com/love2d-community/love-api diff --git a/.luarc.json b/.luarc.json index 3a325ee7..7be0e621 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,8 +1,4 @@ { - "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", - "color": { - "mode": "SemanticEnhanced" - }, "diagnostics": { "disable": [ "close-non-object" diff --git a/.vscode/launch.json b/.vscode/launch.json index 94991839..e6811966 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,13 +15,14 @@ "luaVersion": "5.4", "consoleCoding": "utf8", "sourceCoding": "utf8", + "console": "internalConsole", "outputCapture": [ "print", "stderr", ], }, { - "name": "🪡attach", + "name": "🍄attach", "type": "lua", "request": "attach", "stopOnEntry": false, diff --git a/.vscode/settings.json b/.vscode/settings.json index 0260f483..846e9d8c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "--preview", "--develop=true", "--dbgport=11413", + "--loglevel=trace", ] } diff --git a/3rd/EmmyLuaCodeStyle b/3rd/EmmyLuaCodeStyle -Subproject 8fa05abbbeab5bd3e20d8710eb6d5e804f8800c +Subproject 8246ca187f166f9fa88c08076b4c1e16817de11 diff --git a/3rd/bee.lua b/3rd/bee.lua -Subproject 55c9c6bfd6ae4a576cd8777e232d7be48253b94 +Subproject c117563a62be1b5704d6d76320cfc5e23df782a diff --git a/3rd/love-api b/3rd/love-api -Subproject 6532c3890915ceb265afc64a33ca1b838a28acb +Subproject 75e55c1f6550344c48daaa6d1bb0d1bc133108b diff --git a/3rd/lovr-api b/3rd/lovr-api -Subproject 0cfe3e962df3d34ca996b9c74960cb52f9f2be6 +Subproject d0d8e4e6e29b24edcc0ac7c3b7406225a9ec925 diff --git a/3rd/luamake b/3rd/luamake -Subproject 909a3bf9770c61efc8a3050402ecbfa4daf24dd +Subproject 296c430808afd1a9e10162e89f94a3a14356af0 diff --git a/3rd/rcedit b/3rd/rcedit deleted file mode 160000 -Subproject b807b34a644c86c0b0d89c7f073967e79202731 @@ -114,7 +114,6 @@ If you need to compile by yourself, please refer to [here](https://github.com/su * [lni](https://github.com/actboy168/lni) * [LPegLabel](https://github.com/sqmedeiros/lpeglabel) * [LuaParser](https://github.com/sumneko/LuaParser) -* [rcedit](https://github.com/electron/rcedit) * [ScreenToGif](https://github.com/NickeManarin/ScreenToGif) * [vscode-languageclient](https://github.com/microsoft/vscode-languageserver-node) * [lua.tmbundle](https://github.com/textmate/lua.tmbundle) @@ -124,6 +123,7 @@ If you need to compile by yourself, please refer to [here](https://github.com/su * [vscode-lua-doc](https://github.com/actboy168/vscode-lua-doc) * [json.lua](https://github.com/actboy168/json.lua) * [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle) +* [inspect.lua](https://github.com/kikito/inspect.lua) ## Acknowledgement @@ -144,6 +144,7 @@ If you need to compile by yourself, please refer to [here](https://github.com/su * [Folke Lemaitre](https://github.com/folke) * [Vikas Raj](https://github.com/numToStr) * [kevinhwang91](https://github.com/kevinhwang91) +* [Cassolette](https://github.com/Cassolette) ## Telemetry diff --git a/changelog.md b/changelog.md index 4f885750..05d233d8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,49 @@ # changelog +## 3.0.2 +* `FIX` `table<string, boolean>[string] -> boolean` +* `FIX` goto `type definition` +* `FIX` [#1050](https://github.com/sumneko/lua-language-server/issues/1050) + +## 3.0.1 +`2022-4-11` +* `FIX` [#1033](https://github.com/sumneko/lua-language-server/issues/1033) +* `FIX` [#1034](https://github.com/sumneko/lua-language-server/issues/1034) +* `FIX` [#1035](https://github.com/sumneko/lua-language-server/issues/1035) +* `FIX` [#1036](https://github.com/sumneko/lua-language-server/issues/1036) +* `FIX` runtime errors reported by telemetry, see [#1037](https://github.com/sumneko/lua-language-server/issues/1037) + +## 3.0.0 +`2022-4-10` +* `CHG` [break changes](https://github.com/sumneko/lua-language-server/issues/980) +* `CHG` diagnostic: + + `type-check`: removed for now + + `no-implicit-any`: renamed to `no-unknown` +* `CHG` formatter: no longer need` --preview` +* `CHG` `LuaDoc`: supports `---@type (string|integer)[]` +* `FIX` semantic: color of `function` +* `FIX` [#1027](https://github.com/sumneko/lua-language-server/issues/1027) +* `FIX` [#1028](https://github.com/sumneko/lua-language-server/issues/1028) + +## 2.6.8 +`2022-4-9` +* `CHG` completion: call snippet shown as `Function` instead of `Snippet` when `Lua.completion.callSnippet` is `Replace` +* `FIX` [#976](https://github.com/sumneko/lua-language-server/issues/976) +* `FIX` [#995](https://github.com/sumneko/lua-language-server/issues/995) +* `FIX` [#1004](https://github.com/sumneko/lua-language-server/issues/1004) +* `FIX` [#1008](https://github.com/sumneko/lua-language-server/issues/1008) +* `FIX` [#1009](https://github.com/sumneko/lua-language-server/issues/1009) +* `FIX` [#1011](https://github.com/sumneko/lua-language-server/issues/1011) +* `FIX` [#1014](https://github.com/sumneko/lua-language-server/issues/1014) +* `FIX` [#1016](https://github.com/sumneko/lua-language-server/issues/1016) +* `FIX` [#1017](https://github.com/sumneko/lua-language-server/issues/1017) +* `FIX` runtime errors reported by telemetry + ## 2.6.7 +`2022-3-9` * `NEW` offline diagnostic, [read more](https://github.com/sumneko/lua-language-server/wiki/Offline-Diagnostic) * `CHG` `VSCode`: 1.65 has built in new `Lua` syntax files, so this extension no longer provides syntax files, which means you can install other syntax extensions in the marketplace. If you have any suggestions or issues, please [open issues here](https://github.com/sumneko/lua.tmbundle). +* `CHG` telemetry: the prompt will only appear in VSCode to avoid repeated prompts in other platforms due to the inability to automatically modify the settings. * `FIX` [#965](https://github.com/sumneko/lua-language-server/issues/965) * `FIX` [#975](https://github.com/sumneko/lua-language-server/issues/975) diff --git a/locale/en-us/meta.lua b/locale/en-us/meta.lua index 858cde13..839dc27a 100644 --- a/locale/en-us/meta.lua +++ b/locale/en-us/meta.lua @@ -677,6 +677,12 @@ Returns the elements from the given list. This function is equivalent to ``` By default, `i` is `1` and `j` is `#list`. ]] +table.foreach = +'Executes the given f over all elements of table. For each element, f is called with the index and respective value as arguments. If f returns a non-nil value, then the loop is broken, and this value is returned as the final value of foreach.' +table.foreachi = +'Executes the given f over the numerical indices of table. For each index, f is called with the index and respective value as arguments. Indices are visited in sequential order, from 1 to n, where n is the size of the table. If f returns a non-nil value, then the loop is broken and this value is returned as the result of foreachi.' +table.getn = +'Returns the number of elements in the table. This function is equivalent to `#list`.' utf8 = '' diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index f5a3ec30..f16235b4 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -34,7 +34,7 @@ DIAG_PREFIELD_CALL = 'Will be interpreted as `{}{}`. It may be necessary to add a `,` or `;`.' DIAG_OVER_MAX_ARGS = 'The function takes only {:d} parameters, but you passed {:d}.' -DIAG_OVER_MAX_ARGS = +DIAG_OVER_MAX_VALUES = 'Only has {} variables, but you set {} values.' DIAG_AMBIGUITY_1 = 'Compute `{}` first. You may need to add brackets.' @@ -80,7 +80,7 @@ DIAG_COSE_NON_OBJECT = 'Cannot close a value of this type. (Unless set `__close` meta method)' DIAG_COUNT_DOWN_LOOP = 'Do you mean `{}` ?' -DIAG_IMPLICIT_ANY = +DIAG_UNKNOWN = 'Can not infer type.' DIAG_DEPRECATED = 'Deprecated.' @@ -464,6 +464,10 @@ This icon is a cat, Not a dog nor a fox! ↓↓↓ ]] +WINDOW_LUA_STATUS_DIAGNOSIS_TITLE= +'Perform workspace diagnosis' +WINDOW_LUA_STATUS_DIAGNOSIS_MSG = +'Do you want to perform workspace diagnosis?' WINDOW_APPLY_SETTING = 'Apply setting' WINDOW_CHECK_SEMANTIC = @@ -490,6 +494,8 @@ WINDOW_APPLY_WHITOUT_SETTING = 'Apply but do not modify settings' WINDOW_ASK_APPLY_LIBRARY = 'Do you need to configure your work environment as `{}`?' +WINDOW_SEARCHING_IN_FILES = +'Searching in files...' CONFIG_LOAD_FAILED = 'Unable to read the settings file: {}' @@ -531,3 +537,4 @@ CLI_CHECK_SUCCESS = 'Diagnosis completed, no problems found' CLI_CHECK_RESULTS = 'Diagnosis complete, {} problems found, see {}' + diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua index aa16c3d3..f40dc0c6 100644 --- a/locale/pt-br/script.lua +++ b/locale/pt-br/script.lua @@ -80,7 +80,7 @@ DIAG_COSE_NON_OBJECT = 'Não é possível fechar um valor desse tipo. (A menos que se defina o meta método `__close`)' DIAG_COUNT_DOWN_LOOP = 'Você quer dizer `{}` ?' -DIAG_IMPLICIT_ANY = +DIAG_UNKNOWN = 'Não pode inferir tipo.' DIAG_DEPRECATED = 'Descontinuada.' diff --git a/locale/zh-cn/meta.lua b/locale/zh-cn/meta.lua index 8e7c37e8..61ba6c9b 100644 --- a/locale/zh-cn/meta.lua +++ b/locale/zh-cn/meta.lua @@ -655,6 +655,12 @@ table.unpack = ``` i 默认为 1 ,j 默认为 #list。 ]] +table.foreach = +'遍历表中的每一个元素,并以key和value执行回调函数。如果回调函数返回一个非nil值则循环终止,并且返回这个值。该函数等同pair(list),比pair(list)更慢。不推荐使用' +table.foreachi = +'遍历数组中的每一个元素,并以索引号index和value执行回调函数。如果回调函数返回一个非nil值则循环终止,并且返回这个值。该函数等同ipair(list),比ipair(list)更慢。不推荐使用' +table.getn = +'返回表的长度。该函数等价于#list。' utf8 = '' diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index f30257fb..75026526 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -80,7 +80,7 @@ DIAG_COSE_NON_OBJECT = '无法 close 此类型的值。(除非给此类型设置 `__close` 元方法)' DIAG_COUNT_DOWN_LOOP = '你的意思是 `{}` 吗?' -DIAG_IMPLICIT_ANY = +DIAG_UNKNOWN = '无法推测出类型。' DIAG_DEPRECATED = '已废弃。' @@ -489,6 +489,8 @@ WINDOW_APPLY_WHITOUT_SETTING = '应用但不修改设置' WINDOW_ASK_APPLY_LIBRARY = '是否需要将你的工作环境配置为 `{}` ?' +WINDOW_SEARCHING_IN_FILES = +'正在文件中搜索...' CONFIG_LOAD_FAILED = '无法读取设置文件:{}' @@ -58,11 +58,15 @@ collectgarbage('incremental', 120, 120, 0) ---@diagnostic disable-next-line: lowercase-global log = require 'log' log.init(ROOT, fs.path(LOGPATH) / 'service.log') +if LOGLEVEL then + log.level = tostring(LOGLEVEL):lower() +end + log.info('Lua Lsp startup, root: ', ROOT) -log.debug('ROOT:', ROOT:string()) -log.debug('LOGPATH:', LOGPATH) -log.debug('METAPATH:', METAPATH) -log.debug('VERSION:', version.getVersion()) +log.info('ROOT:', ROOT:string()) +log.info('LOGPATH:', LOGPATH) +log.info('METAPATH:', METAPATH) +log.info('VERSION:', version.getVersion()) require 'tracy' require 'cli' @@ -3,10 +3,14 @@ local platform = require 'bee.platform' local exe = platform.OS == 'Windows' and ".exe" or "" lm.bindir = "bin" +lm.c = lm.compiler == 'msvc' and 'c89' or 'c11' +lm.cxx = 'c++17' ---@diagnostic disable-next-line: codestyle-check lm.EXE_DIR = "" +local includeCodeFormat = true + if platform.OS == 'macOS' then if lm.platform == nil then elseif lm.platform == "darwin-arm64" then @@ -29,9 +33,7 @@ elseif platform.OS == 'Linux' then if lm.platform == nil then elseif lm.platform == "linux-x64" then elseif lm.platform == "linux-arm64" then - -- TODO: not implement - lm.compiler = "clang" - lm.target = "arm64-pc-linux-gnu" + lm.cc = 'aarch64-linux-gnu-gcc' else error "unknown platform" end @@ -50,7 +52,11 @@ lm:source_set 'lpeglabel' { } lm:executable "lua-language-server" { - deps = { "lpeglabel", "source_bootstrap", "code_format" }, + deps = { + "lpeglabel", + "source_bootstrap", + includeCodeFormat and "code_format" or nil, + }, includes = { "3rd/bee.lua", "3rd/bee.lua/3rd/lua", @@ -61,6 +67,9 @@ lm:executable "lua-language-server" { "make/lua-language-server.rc", } }, + defines = { + includeCodeFormat and 'CODE_FORMAT' or nil, + }, linux = { crt = "static", } @@ -116,7 +125,7 @@ local function targetPlatformArch() return lm.platform:match "^[^-]*-(.*)$" end -local notest = platform.OS == 'macOS' +local notest = (platform.OS == 'macOS' or platform.OS == 'Linux') and targetPlatformArch() == "arm64" and detectArch() == "x86_64" diff --git a/make/code_format.lua b/make/code_format.lua index 17007fdd..6f5f120b 100644 --- a/make/code_format.lua +++ b/make/code_format.lua @@ -1,5 +1,8 @@ local lm = require 'luamake' +lm.c = lm.compiler == 'msvc' and 'c89' or 'c11' +lm.cxx = 'c++17' + lm:source_set 'code_format' { rootdir = '../3rd/EmmyLuaCodeStyle', includes = { @@ -20,8 +23,20 @@ lm:source_set 'code_format' { "CodeService/src/FormatElement/*.cpp", "CodeService/src/NameStyle/*.cpp" }, + windows = { + -- 不要开哦 + -- flasg = "/W3 /WX" + }, macos = { - -- macosx10.12不支持完整的std filesystem,只好砍功能 - defines = "NOT_SURPPORT_FILE_SYSTEM", + flags = "-Wall -Werror", + defines = "NOT_SUPPORT_FILE_SYSTEM" }, + linux = { + defines = (function() + if lm.platform == "linux-arm64" then + return "NOT_SUPPORT_FILE_SYSTEM" + end + end)(), + flags = "-Wall -Werror" + } } diff --git a/make/modules.cpp b/make/modules.cpp index 8fe065a8..802e6916 100644 --- a/make/modules.cpp +++ b/make/modules.cpp @@ -3,6 +3,8 @@ extern "C" int luaopen_lpeglabel (lua_State *L); static ::bee::lua::callfunc _init(::bee::lua::register_module, "lpeglabel", luaopen_lpeglabel); +#ifdef CODE_FORMAT extern "C" int luaopen_code_format(lua_State *L); static ::bee::lua::callfunc _init_code_format(::bee::lua::register_module, "code_format", luaopen_code_format); +#endif diff --git a/meta/3rd/lovr/library/lovr.math.lua b/meta/3rd/lovr/library/lovr.math.lua index 7cf348c6..3ce62dd9 100644 --- a/meta/3rd/lovr/library/lovr.math.lua +++ b/meta/3rd/lovr/library/lovr.math.lua @@ -644,6 +644,20 @@ local Vec2 = {} function Vec2:add(u) end --- +---Returns the angle between vectors. +--- +--- +---### NOTE: +---If any of the two vectors have a length of zero, the angle between them is not well defined. +--- +---In this case the function returns `math.pi / 2`. +--- +---@overload fun(self: lovr.Vec2, x: number, y: number):number +---@param u lovr.Vec2 # The other vector. +---@return number angle # The angle to the other vector, in radians. +function Vec2:angle(u) end + +--- ---Returns the distance to another vector. --- ---@overload fun(self: lovr.Vec2, x: number, y: number):number @@ -749,6 +763,20 @@ local Vec3 = {} function Vec3:add(u) end --- +---Returns the angle between vectors. +--- +--- +---### NOTE: +---If any of the two vectors have a length of zero, the angle between them is not well defined. +--- +---In this case the function returns `math.pi / 2`. +--- +---@overload fun(self: lovr.Vec3, x: number, y: number, z: number):number +---@param u lovr.Vec3 # The other vector. +---@return number angle # The angle to the other vector, in radians. +function Vec3:angle(u) end + +--- ---Sets this vector to be equal to the cross product between this vector and another one. --- ---The new `v` will be perpendicular to both the old `v` and `u`. @@ -873,6 +901,20 @@ local Vec4 = {} function Vec4:add(u) end --- +---Returns the angle between vectors. +--- +--- +---### NOTE: +---If any of the two vectors have a length of zero, the angle between them is not well defined. +--- +---In this case the function returns `math.pi / 2`. +--- +---@overload fun(self: lovr.Vec4, x: number, y: number, z: number, w: number):number +---@param u lovr.Vec4 # The other vector. +---@return number angle # The angle to other vector, in radians. +function Vec4:angle(u) end + +--- ---Returns the distance to another vector. --- ---@overload fun(self: lovr.Vec4, x: number, y: number, z: number, w: number):number diff --git a/meta/template/basic.lua b/meta/template/basic.lua index b26d3959..aed3085e 100644 --- a/meta/template/basic.lua +++ b/meta/template/basic.lua @@ -11,7 +11,7 @@ arg = {} ---@return T function assert(v, message) end ----@alias cgopt +---@alias gcoptions ---|>'"collect"' # ---#DESTAIL 'cgopt.collect' ---| '"stop"' # ---#DESTAIL 'cgopt.stop' ---| '"restart"' # ---#DESTAIL 'cgopt.restart' @@ -28,12 +28,12 @@ function assert(v, message) end ---#if VERSION >= 5.4 then ---#DES 'collectgarbage' ----@param opt? cgopt +---@param opt? gcoptions ---@return any function collectgarbage(opt, ...) end ---#else ---#DES 'collectgarbage' ----@param opt? cgopt +---@param opt? gcoptions ---@param arg? integer ---@return any function collectgarbage(opt, arg) end @@ -127,6 +127,12 @@ function loadfile(filename, mode, env) end function loadstring(text, chunkname) end ---@version 5.1 +---@param proxy boolean|table +---@return userdata +---@nodiscard +function newproxy(proxy) end + +---@version 5.1 ---#DES 'module' ---@param name string function module(name, ...) end @@ -274,8 +280,10 @@ function xpcall(f, msgh, arg1, ...) end ---@version 5.1 ---#DES 'unpack' ----@param list table +---@generic T +---@param list T[] ---@param i? integer ---@param j? integer +---@return T ---@nodiscard function unpack(list, i, j) end diff --git a/meta/template/builtin.lua b/meta/template/builtin.lua index 06974076..2ef14f53 100644 --- a/meta/template/builtin.lua +++ b/meta/template/builtin.lua @@ -5,14 +5,10 @@ ---@class nil ---@class boolean ---@class number ----#if VERSION >= 5.3 then ----@class integer ----#else ----@alias integer number ----#end +---@class integer: number ---@class thread ----@class table ----@class string +---@class table<K, V>: { [K]: V } +---@class string: stringlib ---@class userdata ---@class lightuserdata ---@class function diff --git a/meta/template/debug.lua b/meta/template/debug.lua index c8b506f5..6e74e1f6 100644 --- a/meta/template/debug.lua +++ b/meta/template/debug.lua @@ -15,7 +15,7 @@ debug = {} ---@field currentline integer ---@field istailcall boolean ---@field nups integer ----#if VERSION >= 5.2 then +---#if VERSION >= 5.2 or JIT then ---@field nparams integer ---@field isvararg boolean ---#end diff --git a/meta/template/table.lua b/meta/template/table.lua index c55c3160..21c8b619 100644 --- a/meta/template/table.lua +++ b/meta/template/table.lua @@ -56,10 +56,39 @@ function table.sort(list, comp) end ---@version >5.2, JIT ---#DES 'table.unpack' ----@param list table +---@generic T +---@param list T[] ---@param i? integer ---@param j? integer +---@return T ---@nodiscard function table.unpack(list, i, j) end +---@version <5.1, JIT +---#DES 'table.foreach' +---@generic T +---@param list any +---@param callback fun(key: string, value: any):T|nil +---@return T|nil +---@deprecated +function table.foreach(list, callback) end + +---@version <5.1, JIT +---#DES 'table.foreachi' +---@generic T +---@param list any +---@param callback fun(key: string, value: any):T|nil +---@return T|nil +---@deprecated +function table.foreachi(list, callback) end + +---@version <5.1, JIT +---#DES 'table.getn' +---@generic T +---@param list T[] +---@return integer +---@nodiscard +---@deprecated +function table.getn(list) end + return table diff --git a/script/await.lua b/script/await.lua index 4fdab0a2..4fb81cd8 100644 --- a/script/await.lua +++ b/script/await.lua @@ -185,7 +185,7 @@ function m.stop() end local function warnStepTime(passed, waker) - if passed < 1 then + if passed < 2 then log.warn(('Await step takes [%.3f] sec.'):format(passed)) return end @@ -215,7 +215,7 @@ function m.step() local clock = os.clock() resume() local passed = os.clock() - clock - if passed > 0.1 then + if passed > 0.5 then warnStepTime(passed, resume) end return true diff --git a/script/client.lua b/script/client.lua index 6c3e503d..daa9bc52 100644 --- a/script/client.lua +++ b/script/client.lua @@ -8,6 +8,7 @@ local converter = require 'proto.converter' local json = require 'json-beautify' local await = require 'await' local scope = require 'workspace.scope' +local inspect = require 'inspect' local m = {} @@ -210,7 +211,13 @@ local function applyConfig(cfg, uri, changes) for _, change in ipairs(changes) do if scp:isChildUri(change.uri) or scp:isLinkedUri(change.uri) then - cfg[change.key] = config.getRaw(change.uri, change.key) + local value = config.getRaw(change.uri, change.key) + local key = change.key:match('^Lua%.(.+)$') + if cfg[key] then + cfg[key] = value + else + cfg[change.key] = value + end ok = true end end @@ -398,7 +405,7 @@ local function hookPrint() end function m.init(t) - log.debug('Client init', util.dump(t)) + log.debug('Client init', inspect(t)) m.info = t nonil.enable() m.client(t.clientInfo.name) diff --git a/script/config/config.lua b/script/config/config.lua index 8c06278f..bde214b0 100644 --- a/script/config/config.lua +++ b/script/config/config.lua @@ -196,6 +196,7 @@ local Template = { ['Lua.hover.viewNumber'] = Type.Boolean >> true, ['Lua.hover.previewFields'] = Type.Integer >> 20, ['Lua.hover.enumsLimit'] = Type.Integer >> 5, + ['Lua.hover.expandAlias'] = Type.Boolean >> true, ['Lua.semantic.enable'] = Type.Boolean >> true, ['Lua.semantic.variable'] = Type.Boolean >> true, ['Lua.semantic.annotation'] = Type.Boolean >> true, @@ -211,10 +212,6 @@ local Template = { ['Lua.format.enable'] = Type.Boolean >> true, ['Lua.format.defaultConfig'] = Type.Hash(Type.String, Type.String) >> {}, - ['Lua.IntelliSense.traceLocalSet'] = Type.Boolean >> false, - ['Lua.IntelliSense.traceReturn'] = Type.Boolean >> false, - ['Lua.IntelliSense.traceBeSetted'] = Type.Boolean >> false, - ['Lua.IntelliSense.traceFieldInject'] = Type.Boolean >> false, ['Lua.telemetry.enable'] = Type.Or(Type.Boolean >> false, Type.Nil) >> nil, ['files.associations'] = Type.Hash(Type.String, Type.String), ['files.exclude'] = Type.Hash(Type.String, Type.Boolean), diff --git a/script/config/loader.lua b/script/config/loader.lua index c0bd66d7..c53f9399 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -4,6 +4,7 @@ local lang = require 'language' local util = require 'utility' local workspace = require 'workspace' local scope = require 'workspace.scope' +local inspect = require 'inspect' local function errorMessage(msg) proto.notify('window/showMessage', { @@ -105,7 +106,7 @@ function m.loadClientConfig(uri) }, }) if not configs or not configs[1] then - log.warn('No config?', util.dump(configs)) + log.warn('No config?', inspect(configs)) return nil end diff --git a/script/core/code-action.lua b/script/core/code-action.lua index 4b1ebd57..6bba0a82 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -415,6 +415,12 @@ local function checkSwapParams(results, uri, start, finish) elseif source.type == 'funcargs' then local var = source.parent.parent if guide.isSet(var) then + if var.type == 'tablefield' then + var = var.field + end + if var.type == 'tableindex' then + var = var.index + end node = text:sub( guide.positionToOffset(state, var.start) + 1, guide.positionToOffset(state, var.finish) diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 969de95a..428c8e96 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -1,11 +1,9 @@ -local sp = require 'bee.subprocess' local define = require 'proto.define' local files = require 'files' -local searcher = require 'core.searcher' local matchKey = require 'core.matchkey' local vm = require 'vm' local getName = require 'core.hover.name' -local getArg = require 'core.hover.arg' +local getArgs = require 'core.hover.args' local getHover = require 'core.hover' local config = require 'config' local util = require 'utility' @@ -18,10 +16,10 @@ local rpath = require 'workspace.require-path' local lang = require 'language' local lookBackward = require 'core.look-backward' local guide = require 'parser.guide' -local infer = require 'core.infer' -local noder = require 'core.noder' +local infer = require 'vm.infer' local await = require 'await' local postfix = require 'core.completion.postfix' +local globalMgr = require 'vm.global-manager' local diagnosticModes = { 'disable-next-line', @@ -155,23 +153,44 @@ end local function buildFunctionSnip(source, value, oop) local name = (getName(source) or ''):gsub('^.+[$.:]', '') - local args = getArg(value, oop) - local id = 0 - args = args:gsub('[^,]+', function (arg) - id = id + 1 - return arg:gsub('^(%s*)(.+)', function (sp, word) + local args = getArgs(value) + if oop then + table.remove(args, 1) + end + local len = #args + local truncated = false + if len > 0 and args[len]:match('^%s*%.%.%.:') then + table.remove(args) + truncated = true + end + for i = #args, 1, -1 do + if args[i]:match('^%s*[^?]+%?:') then + table.remove(args) + truncated = true + else + break + end + end + + local snipArgs = {} + for id, arg in ipairs(args) do + local str = arg:gsub('^(%s*)(.+)', function (sp, word) return ('%s${%d:%s}'):format(sp, id, word) end) - end) - return ('%s(%s)'):format(name, args) + table.insert(snipArgs, str) + end + if truncated and #snipArgs == 0 then + snipArgs = {'$1'} + end + return ('%s(%s)'):format(name, table.concat(snipArgs, ', ')) end local function buildDetail(source) if source.type == 'dummy' then return end - local types = infer.searchAndViewInfers(source) - local literals = infer.searchAndViewLiterals(source) + local types = infer.getInfer(source):view() + local literals = infer.getInfer(source):viewLiterals() if literals then return types .. ' = ' .. literals else @@ -184,9 +203,8 @@ local function getSnip(source) if context <= 0 then return nil end - local defs = vm.getRefs(source) + local defs = vm.getDefs(source) for _, def in ipairs(defs) do - def = searcher.getObjectValue(def) or def if def ~= source and def.type == 'function' then local uri = guide.getUri(def) local text = files.getText(uri) @@ -199,7 +217,7 @@ local function getSnip(source) goto CONTINUE end local firstRow = guide.rowColOf(def.start) - local lastRow = firstRow + context + local lastRow = math.min(guide.rowColOf(def.finish) + 1, firstRow + context) local lastOffset = lines[lastRow] and (lines[lastRow] - 1) or #text local snip = text:sub(lines[firstRow], lastOffset) return snip @@ -229,7 +247,9 @@ local function buildFunction(results, source, value, oop, data) if snipType == 'Both' or snipType == 'Replace' then local snipData = util.deepCopy(data) - snipData.kind = define.CompletionItemKind.Snippet + snipData.kind = snipType == 'Both' + and define.CompletionItemKind.Snippet + or data.kind snipData.insertText = buildFunctionSnip(source, value, oop) snipData.insertTextFormat = 2 snipData.command = { @@ -290,7 +310,7 @@ local function checkLocal(state, word, position, results) if name:sub(1, 1) == '@' then goto CONTINUE end - if infer.hasType(source, 'function') then + if infer.getInfer(source):hasFunction() then for _, def in ipairs(vm.getDefs(source)) do if def.type == 'function' or def.type == 'doc.type.function' then @@ -338,7 +358,7 @@ local function checkModule(state, word, position, results) local fileName = path:match '[^/\\]*$' local stemName = fileName:gsub('%..+', '') if not locals[stemName] - and not vm.hasGlobalSets(state.uri, stemName) + and not globalMgr.hasGlobalSets(state.uri, 'variable', stemName) and not config.get(state.uri, 'Lua.diagnostics.globals')[stemName] and stemName:match '^[%a_][%w_]*$' and matchKey(word, stemName) then @@ -461,7 +481,7 @@ local function checkFieldFromFieldToIndex(state, name, src, parent, word, startP end local function checkFieldThen(state, name, src, word, startPos, position, parent, oop, results) - local value = searcher.getObjectValue(src) or src + local value = vm.getObjectValue(src) or src local kind = define.CompletionItemKind.Field if value.type == 'function' or value.type == 'doc.type.function' then @@ -485,7 +505,7 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent }) return end - if oop and not infer.hasType(src, 'function') then + if oop and not infer.getInfer(src):hasFunction() then return end local literal = guide.getLiteral(value) @@ -532,6 +552,7 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o if isSameSource(state, src, startPos) then goto CONTINUE end + name = tostring(name) if isGlobal and locals and locals[name] then goto CONTINUE end @@ -540,7 +561,7 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o end local funcLabel if config.get(state.uri, 'Lua.completion.showParams') then - local value = searcher.getObjectValue(src) or src + local value = vm.getObjectValue(src) or src if value.type == 'function' or value.type == 'doc.type.function' then funcLabel = name .. getParams(value, oop) @@ -587,17 +608,17 @@ end ---@async local function checkGlobal(state, word, startPos, position, parent, oop, results) local locals = guide.getVisibleLocals(state.ast, position) - local globals = vm.getGlobalSets(state.uri, '*') + local globals = globalMgr.getGlobalSets(state.uri, 'variable') checkFieldOfRefs(globals, state, word, startPos, position, parent, oop, results, locals, 'global') end ---@async 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, '*') + local globals = globalMgr.getGlobalSets(state.uri, 'variable') checkFieldOfRefs(globals, state, word, start, position, parent, oop, results) else - local refs = vm.getRefs(parent, '*') + local refs = vm.getFields(parent) checkFieldOfRefs(refs, state, word, start, position, parent, oop, results) end end @@ -1080,49 +1101,36 @@ local function tryLabelInString(label, source) return util.viewString(state.ast[1], source[2]) end -local function mergeEnums(a, b, source) - local mark = {} - for _, enum in ipairs(a) do - mark[enum.label] = true - end - for _, enum in ipairs(b) do +local function cleanEnums(enums, source) + for i = #enums, 1, -1 do + local enum = enums[i] local label = tryLabelInString(enum.label, source) - if label and not mark[label] then - mark[label] = true - local result = { - label = label, - kind = enum.kind, - description = enum.description, - insertText = enum.insertText, - textEdit = source and { - start = source.start, - finish = source.finish, - newText = enum.insertText or label, - }, + if label then + enum.label = label + enum.textEdit = source and { + start = source.start, + finish = source.finish, + newText = enum.insertText or label, } - a[#a+1] = result end end + return enums end local function checkTypingEnum(state, position, defs, str, results) local enums = {} for _, def in ipairs(defs) do - if def.type == 'doc.type.enum' - or def.type == 'doc.resume' then + if def.type == 'doc.type.string' + or def.type == 'doc.type.integer' then enums[#enums+1] = { - label = def[1], + label = util.viewLiteral(def[1]), description = def.comment and def.comment.text, kind = define.CompletionItemKind.EnumMember, } end end - local myResults = {} - mergeEnums(myResults, enums, str) - table.sort(myResults, function (a, b) - return a.label < b.label - end) - for _, res in ipairs(myResults) do + cleanEnums(enums, str) + for _, res in ipairs(enums) do results[#results+1] = res end end @@ -1315,107 +1323,6 @@ function (%s)\ end"):format(table.concat(args, ', ')) end -local function pushCallEnumsAndFuncs(defs) - local results = {} - for _, def in ipairs(defs) do - if def.type == 'doc.type.enum' - or def.type == 'doc.resume' then - results[#results+1] = { - label = def[1], - description = def.comment, - kind = define.CompletionItemKind.EnumMember, - } - end - if def.type == 'doc.type.function' then - results[#results+1] = { - label = infer.viewDocFunction(def), - description = def.comment, - kind = define.CompletionItemKind.Function, - insertText = buildInsertDocFunction(def), - } - end - end - return results -end - -local function getCallEnumsAndFuncs(source, index, oop, call) - if source.type == 'function' and source.bindDocs then - if not source.args then - return - end - local arg - if index <= #source.args then - arg = source.args[index] - else - local lastArg = source.args[#source.args] - if lastArg.type == '...' then - arg = lastArg - else - return - end - end - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.param' - and doc.param[1] == arg[1] then - return pushCallEnumsAndFuncs(vm.getDefs(doc.extends)) - elseif doc.type == 'doc.vararg' - and arg.type == '...' then - return pushCallEnumsAndFuncs(vm.getDefs(doc.vararg)) - end - end - end - if source.type == 'doc.type.function' then - local arg = source.args[index] - if arg and arg.extends then - return pushCallEnumsAndFuncs(vm.getDefs(arg.extends)) - end - end - if source.type == 'doc.field.name' then - local currentIndex = index - if oop then - currentIndex = index - 1 - end - local class = source.parent.class - if not class then - return - end - local results = {} - if currentIndex == 1 then - for _, doc in ipairs(class.fields) do - if doc.field ~= source - and doc.field[1] == source[1] then - local eventName = noder.getFieldEventName(doc) - if eventName then - results[#results+1] = { - label = ('%q'):format(eventName), - description = doc.comment, - kind = define.CompletionItemKind.EnumMember, - } - end - end - end - elseif currentIndex == 2 then - local myEventName = call.args[index - 1][1] - for _, doc in ipairs(class.fields) do - if doc.field ~= source - and doc.field[1] == source[1] then - local eventName = noder.getFieldEventName(doc) - if eventName and eventName == myEventName then - local docFunc = doc.extends.types[1].args[2].extends.types[1] - results[#results+1] = { - label = infer.viewDocFunction(docFunc), - description = doc.comment, - kind = define.CompletionItemKind.Function, - insertText = buildInsertDocFunction(docFunc), - } - end - end - end - end - return results - end -end - local function findCall(state, position) local call guide.eachSourceContain(state.ast, position, function (src) @@ -1430,15 +1337,14 @@ end local function getCallArgInfo(call, position) if not call.args then - return 1, nil, nil + return 1, nil end - local oop = call.node.type == 'getmethod' for index, arg in ipairs(call.args) do if arg.start <= position and arg.finish >= position then - return index, arg, oop + return index, arg end end - return #call.args + 1, nil, oop + return #call.args + 1, nil end local function checkTableLiteralField(state, position, tbl, fields, results) @@ -1455,7 +1361,7 @@ local function checkTableLiteralField(state, position, tbl, fields, results) end end table.sort(fields, function (a, b) - return guide.getKeyName(a) < guide.getKeyName(b) + return tostring(guide.getKeyName(a)) < tostring(guide.getKeyName(b)) end) -- {$} local left = lookBackward.findWord(text, guide.positionToOffset(state, position)) @@ -1469,7 +1375,9 @@ local function checkTableLiteralField(state, position, tbl, fields, results) if left then for _, field in ipairs(fields) do local name = guide.getKeyName(field) - if not mark[name] and matchKey(left, guide.getKeyName(field)) then + if name + and not mark[name] + and matchKey(left, tostring(name)) then results[#results+1] = { label = guide.getKeyName(field), kind = define.CompletionItemKind.Property, @@ -1490,20 +1398,48 @@ local function tryCallArg(state, position, results) if not call then return end - local myResults = {} - local argIndex, arg, oop = getCallArgInfo(call, position) + local argIndex, arg = getCallArgInfo(call, position) if arg and arg.type == 'function' then return end - local defs = vm.getDefs(call.node) - for _, def in ipairs(defs) do - def = searcher.getObjectValue(def) or def - local enums = getCallEnumsAndFuncs(def, argIndex, oop, call) - if enums then - mergeEnums(myResults, enums, arg) + local node = vm.compileCallArg({ type = 'dummyarg' }, call, argIndex) + if not node then + return + end + local enums = {} + for src in node:eachObject() do + if src.type == 'doc.type.string' + or src.type == 'doc.type.integer' + or src.type == 'doc.type.boolean' then + enums[#enums+1] = { + label = util.viewLiteral(src[1]), + description = src.comment, + kind = define.CompletionItemKind.EnumMember, + } + end + if src.type == 'doc.type.function' then + local insertText = buildInsertDocFunction(src) + local description + if src.comment then + description = src.comment + else + local descText = insertText:gsub('%$%{%d+:([^}]+)%}', function (val) + return val + end):gsub('%$%{?%d+%}?', '') + description = markdown() + : add('lua', descText) + : string() + end + enums[#enums+1] = { + label = infer.getInfer(src):view(), + description = description, + kind = define.CompletionItemKind.Function, + insertText = insertText, + } end end - for _, enum in ipairs(myResults) do + cleanEnums(enums, arg) + for _, enum in ipairs(enums) do results[#results+1] = enum end end @@ -1523,7 +1459,7 @@ local function tryTable(state, position, results) if source.type ~= 'table' then tbl = source.parent end - local defs = vm.getDefs(tbl, '*') + local defs = vm.getFields(tbl) for _, field in ipairs(defs) do local name = guide.getKeyName(field) if name and not mark[name] then @@ -1638,19 +1574,20 @@ local function tryluaDocBySource(state, position, source, results) if source.type == 'doc.extends.name' then if source.parent.type == 'doc.class' then local used = {} - for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do - if doc.type == 'doc.class.name' - and doc.parent ~= source.parent - and not used[doc[1]] - and matchKey(source[1], doc[1]) then - used[doc[1]] = true + for _, doc in ipairs(vm.getDocSets(state.uri)) do + local name = doc.type == 'doc.class' and doc.class[1] + if name + and name ~= source.parent.class[1] + and not used[name] + and matchKey(source[1], name) then + used[name] = true results[#results+1] = { - label = doc[1], + label = name, kind = define.CompletionItemKind.Class, - textEdit = doc[1]:find '[^%w_]' and { + textEdit = name:find '[^%w_]' and { start = source.start, finish = position, - newText = doc[1], + newText = name, }, } end @@ -1659,19 +1596,20 @@ local function tryluaDocBySource(state, position, source, results) return true elseif source.type == 'doc.type.name' then local used = {} - for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do - if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') - and doc.parent ~= source.parent - and not used[doc[1]] - and matchKey(source[1], doc[1]) then - used[doc[1]] = true + for _, doc in ipairs(vm.getDocSets(state.uri)) do + local name = (doc.type == 'doc.class' and doc.class[1]) + or (doc.type == 'doc.alias' and doc.alias[1]) + if name + and not used[name] + and matchKey(source[1], name) then + used[name] = true results[#results+1] = { - label = doc[1], + label = name, kind = define.CompletionItemKind.Class, - textEdit = doc[1]:find '[^%w_]' and { + textEdit = name:find '[^%w_]' and { start = source.start, finish = position, - newText = doc[1], + newText = name, }, } end @@ -1737,20 +1675,34 @@ end local function tryluaDocByErr(state, position, err, docState, results) if err.type == 'LUADOC_MISS_CLASS_EXTENDS_NAME' then - for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do - if doc.type == 'doc.class.name' - and doc.parent ~= docState then + local used = {} + 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 + used[doc.class[1]] = true results[#results+1] = { - label = doc[1], + label = doc.class[1], kind = define.CompletionItemKind.Class, } end end elseif err.type == 'LUADOC_MISS_TYPE_NAME' then - for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do - if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') then + local used = {} + for _, doc in ipairs(vm.getDocSets(state.uri)) do + if doc.type == 'doc.class' + and not used[doc.class[1]] then + used[doc.class[1]] = true + results[#results+1] = { + label = doc.class[1], + kind = define.CompletionItemKind.Class, + } + end + if doc.type == 'doc.alias' + and not used[doc.alias[1]] then + used[doc.alias[1]] = true results[#results+1] = { - label = doc[1], + label = doc.alias[1], kind = define.CompletionItemKind.Class, } end @@ -1772,7 +1724,7 @@ local function tryluaDocByErr(state, position, err, docState, results) local label = {} local insertText = {} for i, arg in ipairs(func.args) do - if arg[1] and not arg.dummy then + if arg[1] and arg.type ~= 'self' then label[#label+1] = arg[1] if #label == 1 then insertText[#insertText+1] = ('%s ${%d:any}'):format(arg[1], #label) @@ -1822,21 +1774,21 @@ local function buildluaDocOfFunction(func) local returns = {} if func.args then for _, arg in ipairs(func.args) do - args[#args+1] = infer.searchAndViewInfers(arg) + args[#args+1] = infer.getInfer(arg):view() end end if func.returns then for _, rtns in ipairs(func.returns) do for n = 1, #rtns do if not returns[n] then - returns[n] = infer.searchAndViewInfers(rtns[n]) + returns[n] = infer.getInfer(rtns[n]):view() end end end end for n, arg in ipairs(args) do local funcArg = func.args[n] - if funcArg[1] and not funcArg.dummy then + if funcArg[1] and funcArg.type ~= 'self' then index = index + 1 buf[#buf+1] = ('---@param %s ${%d:%s}'):format( funcArg[1], diff --git a/script/core/completion/postfix.lua b/script/core/completion/postfix.lua index 98bd0639..c5988ef6 100644 --- a/script/core/completion/postfix.lua +++ b/script/core/completion/postfix.lua @@ -297,11 +297,14 @@ local function checkPostFix(state, word, wordPosition, position, symbol, results for i, action in ipairs(actions) do if matchKey(word, action.key) then action.data[1](state, source, function (newText) + local descText = newText:gsub('%$%{%d+:([^}]+)%}', function (val) + return val + end):gsub('%$%{?%d+%}?', '') results[#results+1] = { label = action.key, kind = define.CompletionItemKind.Event, description = markdown() - : add('lua', newText) + : add('lua', descText) : string(), textEdit = { start = wordPosition + #symbol, diff --git a/script/core/definition.lua b/script/core/definition.lua index b08b7706..b89aa751 100644 --- a/script/core/definition.lua +++ b/script/core/definition.lua @@ -1,4 +1,3 @@ -local searcher = require 'core.searcher' local workspace = require 'workspace' local files = require 'files' local vm = require 'vm' @@ -131,26 +130,16 @@ return function (uri, offset) end end - local defs = vm.getAllDefs(source) - local values = {} - for _, src in ipairs(defs) do - local value = searcher.getObjectValue(src) - if value and value ~= src and guide.isLiteral(value) then - values[value] = true - end - end + local defs = vm.getDefs(source) for _, src in ipairs(defs) do - if src.dummy then - goto CONTINUE - end - if values[src] 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' @@ -162,6 +151,19 @@ return function (uri, offset) if not guide.isLiteral(src) then goto CONTINUE end + else + if guide.isLiteral(src) + and src.type ~= 'function' + and src.type ~= 'doc.type.function' + and src.type ~= 'doc.type.table' then + goto CONTINUE + end + end + if src.type == 'doc.class' then + src = src.class + end + if src.type == 'doc.alias' then + src = src.alias end if src.type == 'doc.class.name' or src.type == 'doc.alias.name' then @@ -171,10 +173,8 @@ return function (uri, offset) goto CONTINUE end end - if src.type == 'doc.type.name' then - if src.typeGeneric then - goto CONTINUE - end + if src.type == 'doc.generic.name' then + goto CONTINUE end if src.type == 'doc.param' then goto CONTINUE diff --git a/script/core/diagnostics/circle-doc-class.lua b/script/core/diagnostics/circle-doc-class.lua index 61dc46b4..40d4afeb 100644 --- a/script/core/diagnostics/circle-doc-class.lua +++ b/script/core/diagnostics/circle-doc-class.lua @@ -1,5 +1,4 @@ local files = require 'files' -local searcher = require 'core.searcher' local lang = require 'language' local vm = require 'vm' local guide = require 'parser.guide' @@ -38,12 +37,12 @@ return function (uri, callback) } goto CONTINUE end - if not mark[newName] then + if newName and not mark[newName] then mark[newName] = true - local docs = vm.getDocDefines(uri, newName) + local docs = vm.getDocSets(uri, newName) for _, otherDoc in ipairs(docs) do - if otherDoc.type == 'doc.class.name' then - list[#list+1] = otherDoc.parent + if otherDoc.type == 'doc.class' then + list[#list+1] = otherDoc end end end diff --git a/script/core/diagnostics/deprecated.lua b/script/core/diagnostics/deprecated.lua index 649f4dab..a5d623d2 100644 --- a/script/core/diagnostics/deprecated.lua +++ b/script/core/diagnostics/deprecated.lua @@ -5,7 +5,6 @@ local guide = require 'parser.guide' local config = require 'config' local define = require 'proto.define' local await = require 'await' -local noder = require 'core.noder' local types = {'getglobal', 'getfield', 'getindex', 'getmethod'} ---@async @@ -15,7 +14,9 @@ return function (uri, callback) return end - local cache = {} + local dglobals = config.get(uri, 'Lua.diagnostics.globals') + local rspecial = config.get(uri, 'Lua.runtime.special') + local cache = {} guide.eachSourceTypes(ast.ast, types, function (src) ---@async if src.type == 'getglobal' then @@ -23,37 +24,17 @@ return function (uri, callback) if not key then return end - if config.get(uri, 'Lua.diagnostics.globals')[key] then + if dglobals[key] then return end - if config.get(uri, 'Lua.runtime.special')[key] then + if rspecial[key] then return end end - local id = noder.getID(src) - if not id then - return - end - - if cache[id] == false then - return - end - - if cache[id] then - callback { - start = src.start, - finish = src.finish, - tags = { define.DiagnosticTag.Deprecated }, - message = cache[id].message, - data = cache[id].data, - } - end - await.delay() if not vm.isDeprecated(src, true) then - cache[id] = false return end @@ -90,12 +71,6 @@ return function (uri, callback) ) end end - cache[id] = { - message = message, - data = { - versions = versions, - }, - } callback { start = src.start, diff --git a/script/core/diagnostics/duplicate-doc-class.lua b/script/core/diagnostics/duplicate-doc-alias.lua index 5114a54f..3df6f972 100644 --- a/script/core/diagnostics/duplicate-doc-class.lua +++ b/script/core/diagnostics/duplicate-doc-alias.lua @@ -1,5 +1,4 @@ local files = require 'files' -local searcher = require 'core.searcher' local lang = require 'language' local vm = require 'vm' local guide = require 'parser.guide' @@ -19,11 +18,11 @@ return function (uri, callback) if doc.type == 'doc.alias' then local name = guide.getKeyName(doc) if not cache[name] then - local docs = vm.getDocDefines(uri, name) + local docs = vm.getDocSets(uri, name) cache[name] = {} for _, otherDoc in ipairs(docs) do - if otherDoc.type == 'doc.class.name' - or otherDoc.type == 'doc.alias.name' then + if otherDoc.type == 'doc.alias' + or otherDoc.type == 'doc.class' then cache[name][#cache[name]+1] = { start = otherDoc.start, finish = otherDoc.finish, @@ -34,8 +33,8 @@ return function (uri, callback) end if #cache[name] > 1 then callback { - start = doc.start, - finish = doc.finish, + start = doc.alias.start, + finish = doc.alias.finish, related = cache, message = lang.script('DIAG_DUPLICATE_DOC_CLASS', name) } diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua index 71610ef5..8d355aac 100644 --- a/script/core/diagnostics/duplicate-doc-field.lua +++ b/script/core/diagnostics/duplicate-doc-field.lua @@ -1,6 +1,33 @@ local files = require 'files' local lang = require 'language' -local noder = require 'core.noder' +local infer = require 'vm.infer' + +local function getFieldEventName(doc) + if not doc.extends then + return nil + end + if #doc.extends.types ~= 1 then + return nil + end + local docFunc = doc.extends.types[1] + if docFunc.type ~= 'doc.type.function' then + return nil + end + for i = 1, 2 do + local arg = docFunc.args[i] + if arg + and arg.extends + and #arg.extends.types == 1 then + local literal = arg.extends.types[1] + if literal.type == 'doc.type.boolean' + or literal.type == 'doc.type.string' + or literal.type == 'doc.type.integer' then + return ('%q'):format(literal[1]) + end + end + end + return nil +end return function (uri, callback) local state = files.getState(uri) @@ -19,8 +46,8 @@ return function (uri, callback) mark = {} elseif doc.type == 'doc.field' then if mark then - local name = doc.field[1] - local eventName = noder.getFieldEventName(doc) + local name = ('%q'):format(doc.field[1]) + local eventName = getFieldEventName(doc) if eventName then name = name .. '|' .. eventName end diff --git a/script/core/diagnostics/duplicate-index.lua b/script/core/diagnostics/duplicate-index.lua index d1141901..5097ab3a 100644 --- a/script/core/diagnostics/duplicate-index.lua +++ b/script/core/diagnostics/duplicate-index.lua @@ -2,8 +2,6 @@ local files = require 'files' local guide = require 'parser.guide' local lang = require 'language' local define = require 'proto.define' -local vm = require 'vm' -local noder = require 'core.noder' return function (uri, callback) local ast = files.getState(uri) @@ -17,8 +15,8 @@ return function (uri, callback) if obj.type == 'tablefield' or obj.type == 'tableindex' or obj.type == 'tableexp' then - local name = noder.getID(obj) - if name and name:sub(-1) ~= '*' then + local name = guide.getKeyName(obj) + if name then if not mark[name] then mark[name] = {} end diff --git a/script/core/diagnostics/duplicate-set-field.lua b/script/core/diagnostics/duplicate-set-field.lua index 492793b1..8052c420 100644 --- a/script/core/diagnostics/duplicate-set-field.lua +++ b/script/core/diagnostics/duplicate-set-field.lua @@ -1,8 +1,8 @@ local files = require 'files' -local searcher = require 'core.searcher' local lang = require 'language' local define = require 'proto.define' -local guide = require "parser.guide" +local guide = require 'parser.guide' +local vm = require 'vm' return function (uri, callback) local ast = files.getState(uri) @@ -30,7 +30,7 @@ return function (uri, callback) if not name then goto CONTINUE end - local value = searcher.getObjectValue(nxt) + local value = vm.getObjectValue(nxt) if not value or value.type ~= 'function' then goto CONTINUE end diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index 4368f51a..369a6ba2 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -6,8 +6,10 @@ local vm = require "vm.vm" -- 把耗时最长的诊断放到最后面 local diagSort = { - ['deprecated'] = 98, - ['undefined-field'] = 99, + ['redundant-value'] = 96, + ['not-yieldable'] = 97, + ['deprecated'] = 98, + ['undefined-field'] = 99, ['redundant-parameter'] = 100, } diff --git a/script/core/diagnostics/no-implicit-any.lua b/script/core/diagnostics/no-unknown.lua index 6ff17c81..2199b6a8 100644 --- a/script/core/diagnostics/no-implicit-any.lua +++ b/script/core/diagnostics/no-unknown.lua @@ -1,7 +1,7 @@ local files = require 'files' local guide = require 'parser.guide' local lang = require 'language' -local infer = require 'core.infer' +local infer = require 'vm.infer' return function (uri, callback) local ast = files.getState(uri) @@ -20,11 +20,11 @@ return function (uri, callback) and source.type ~= 'tableindex' then return end - if infer.searchAndViewInfers(source) == 'any' then + if infer.getInfer(source):view() == 'unknown' then callback { start = source.start, finish = source.finish, - message = lang.script('DIAG_IMPLICIT_ANY'), + message = lang.script('DIAG_UNKNOWN'), } end end) diff --git a/script/core/diagnostics/not-yieldable.lua b/script/core/diagnostics/not-yieldable.lua index 5736b1e2..0588bbde 100644 --- a/script/core/diagnostics/not-yieldable.lua +++ b/script/core/diagnostics/not-yieldable.lua @@ -3,7 +3,7 @@ local await = require 'await' local guide = require 'parser.guide' local vm = require 'vm' local lang = require 'language' -local infer = require 'core.infer' +local infer = require 'vm.infer' local function isYieldAble(defs, i) local hasFuncDef @@ -12,7 +12,7 @@ local function isYieldAble(defs, i) local arg = def.args and def.args[i] if arg then hasFuncDef = true - if infer.hasType(arg, 'any') + if infer.getInfer(arg):hasType 'any' or vm.isAsync(arg, true) or arg.type == '...' then return true @@ -23,7 +23,7 @@ local function isYieldAble(defs, i) local arg = def.args and def.args[i] if arg then hasFuncDef = true - if infer.hasType(arg.extends, 'any') + if infer.getInfer(arg.extends):hasType 'any' or vm.isAsync(arg.extends, true) then return true end diff --git a/script/core/diagnostics/redefined-local.lua b/script/core/diagnostics/redefined-local.lua index 503347d0..2157ae71 100644 --- a/script/core/diagnostics/redefined-local.lua +++ b/script/core/diagnostics/redefined-local.lua @@ -13,9 +13,6 @@ return function (uri, callback) or name == ast.ENVMode then return end - if source.tag == 'self' then - return - end local exist = guide.getLocal(source, name, source.start-1) if exist then callback { diff --git a/script/core/diagnostics/redundant-parameter.lua b/script/core/diagnostics/redundant-parameter.lua index 8176f0af..4adf169e 100644 --- a/script/core/diagnostics/redundant-parameter.lua +++ b/script/core/diagnostics/redundant-parameter.lua @@ -71,13 +71,7 @@ return function (uri, callback) return end - local cache = vm.getCache 'redundant-parameter' - guide.eachSourceType(ast.ast, 'call', function (source) - -- parameters be expanded by iterator - if source.node.iterator then - return - end local callArgs = countCallArgs(source) if callArgs == 0 then return diff --git a/script/core/diagnostics/type-check.lua b/script/core/diagnostics/type-check.lua index 58574bfa..cc2b3228 100644 --- a/script/core/diagnostics/type-check.lua +++ b/script/core/diagnostics/type-check.lua @@ -1,461 +1,3 @@ -local files = require 'files' -local guide = require 'parser.guide' -local vm = require 'vm' -local infer = require 'core.infer' -local await = require 'await' -local hasVarargs, errType - -local tableMap = { - ['table'] = true, - ['array'] = true, - ['ltable'] = true, - ['[]'] = true, -} - -local typeNameMap = { - ['doc.extends.name'] = true, - ['doc.class.name'] = true, - ['doc.alias.name'] = true, - ['doc.type.name'] = true, - ['doc.type.enum'] = true, - ['doc.resume'] = true, - -} - -local function isTable(name) - if type(name) ~= 'string' then - return - end - if tableMap[name] - ---table<K: number, V: string> table - or tableMap[name:sub(1, 5)] - ---string[] - or tableMap[name:sub(-2, -1)] then - return true - end - return false -end - -local function isUserDefineClass(uri, name) - if vm.isBuiltinType(name) then - return false - else - local defs = vm.getDocDefines(uri, name) - for _, v in ipairs(defs) do - if v.type == 'doc.class.name' then - return true - end - end - end - return false -end - -local function isClassOralias(typeName) - if not typeName then - return false - elseif typeNameMap[typeName] - or vm.isBuiltinType(typeName) then - return true - else - return false - end -end - -local function isGeneric(type) - if type.typeGeneric then - return true - end - return false -end - -local function compatibleType(param, args) - if string.sub(param.type, 1, 9) == 'doc.type.' - and not param[1] then - param[1] = string.sub(param.type, 10) - end - for _, v in ipairs(args) do - if v[1] == 'any' then - return true - elseif param[1] == v[1] then - return true - elseif (param[1] == 'number' or param[1] == 'integer') - and (v[1] == 'integer' or v[1] == 'number') then - return true - elseif v[1] == 'string' then - ---处理alias - --@alias searchmode '"ref"'|'"def"' - if param[1] and param[1]:sub(1, 1) == '"' then - return true - end - elseif (isTable(v.type) or isTable(v[1])) - and (isTable(param[1]) or isTable(param.type)) then - return true - end - end - return false -end - --- local function addFatherClass(types, type) --- if not type[1] then --- return --- end --- local docDefs = vm.getDocDefines(type[1]) --- for _, doc in ipairs(docDefs) do --- if doc.parent --- and doc.parent.type == 'doc.class' --- and doc.parent.extends then --- for _, tp in ipairs(doc.parent.extends) do --- if tp.type == 'doc.extends.name' then --- types[#types+1] = { --- [1] = tp[1], --- type = 'doc.class.name' --- } --- end --- end --- end --- end --- end - -local function addFatherClass(uri, infers) - for k in pairs(infers) do - if type(k) == 'string' then - local docDefs = vm.getDocDefines(uri, k) - for _, doc in ipairs(docDefs) do - if doc.parent - and doc.parent.type == 'doc.class' - and doc.parent.extends then - for _, tp in ipairs(doc.parent.extends) do - if tp.type == 'doc.extends.name' then - infers[tp[1]] = true - end - end - end - end - end - end -end - -local function getParamTypes(arg) - if not arg then - return false - end - local types - if arg.type == 'doc.type.arg' then - ---处理doc.type.function - if arg.name and arg.name[1] == '...' then - types = { - [1] = { - [1] = '...', - type = 'varargs' - } - } - return true, types - end - types = arg.extends.types - return true, types - elseif arg.type == 'local' then - ---处理function - local argDefs = vm.getDefs(arg) - if #argDefs == 0 then - return false - end - types = {} - if arg.tag == 'self' then - ---method, 如果self没有定义为一个class或者type,则认为它为any - for _, argDef in ipairs(argDefs) do - if argDef.type == 'doc.class.name' - or argDef.type == 'doc.type.name' - or argDef.type == 'doc.type.enum' - or argDef.type == 'doc.type.ltable' then - types[#types+1] = argDef - end - end - if #types == 0 then - return false - end - return true, types - else - for _, argDef in ipairs(argDefs) do - if argDef.type == 'doc.param' and argDef.extends then - types = argDef.extends.types - if argDef.optional then - types[#types+1] = { - [1] = 'nil', - type = 'nil' - } - end - elseif argDef.type == 'doc.type.enum' - or argDef.type == 'doc.type.ltable' then - types[#types+1] = argDef - ---变长参数 - elseif argDef.name and argDef.name[1] == '...' then - types = { - [1] = { - [1] = '...', - type = 'varargs' - } - } - break - end - end - if #types == 0 then - return false - else - return true, types - end - end - elseif arg.type == '...' then - ---处理只有一个可变参数 - types = { - [1] = { - [1] = '...', - type = 'varargs' - } - } - return true, types - end -end - -local function getInfoFromDefs(defs) - local paramsTypes = {} - local funcArgsType - local mark = {} - for _, def in ipairs(defs) do - funcArgsType = {} - if def.value then - def = def.value - end - if not mark[def] then - mark[def] = true - if def.type == 'function' - or def.type == 'doc.type.function' then - if def.args then - for _, arg in ipairs(def.args) do - local suc, types = getParamTypes(arg) - if suc then - local plusAlias = {} - for i, tp in ipairs(types) do - local aliasDefs = vm.getDefs(tp) - for _, v in ipairs(aliasDefs) do - ---TODO(arthur) - -- if not v.type then - -- end - if v[1] ~= tp[1] - and isClassOralias(v.type) then - plusAlias[#plusAlias+1] = v - end - end - plusAlias[#plusAlias+1] = types[i] - end - funcArgsType[#funcArgsType+1] = plusAlias - else - ---如果有一个参数没有定义type,都会跳过检查 - funcArgsType = {} - break - end - end - end - if #funcArgsType > 0 then - paramsTypes[#paramsTypes+1] = funcArgsType - end - end - end - end - return paramsTypes -end - -local function getArgsInfo(uri, callArgs) - local callArgsType = {} - for _, arg in ipairs(callArgs) do - -- local defs = vm.getDefs(arg) - local infers = infer.searchInfers(arg) - if infers['_G'] or infer['_ENV'] then - infers['_G'] = nil - infers['_ENV'] = nil - infers['table'] = true - end - local hasAny = infers['any'] - ---处理继承 - addFatherClass(uri, infers) - if not hasAny then - infers['any'] = nil - infers['unknown'] = nil - end - local types = {} - if not infers['table'] then - for k in pairs(infers) do - if not vm.isBuiltinType(k) - and isUserDefineClass(uri, k) then - infers['table'] = true - break - end - end - end - for k in pairs(infers) do - if k then - types[#types+1] = { - [1] = k, - type = k - } - end - end - if #types < 1 then - return false - end - types.start = arg.start - types.finish = arg.finish - callArgsType[#callArgsType+1] = types - -- local defs = vm.getDefs(arg) - -- local types = {} - -- types.typeMap = {} - -- for _, def in ipairs(defs) do - -- if vm.isBuiltinType(def.type) then - -- types[#types+1] = { - -- [1] = def.type, - -- type = def.type - -- } - -- elseif def.type == 'doc.class.name' - -- or def.type == 'doc.type.name' - -- or def.type == 'doc.type.enum' - -- or def.type == 'doc.type.ltable' then - -- if def[1] then - -- if not types.typeMap[def[1]] then - -- types[#types+1] = def - -- types.typeMap[def[1]] = true - -- end - -- else - -- types[#types+1] = def - -- end - -- elseif def.type == 'doc.type' then - -- print(1) - -- elseif def.type == 'doc.type.arg' then - -- for _, tp in ipairs(arg.extends.types) do - -- types[#types+1] = arg.extends.types[1] - -- end - -- elseif def.type == 'doc.param' then - -- for i, tp in ipairs(def.extends.types) do - -- types[#types+1] = def.extends.types[i] - -- end - -- if def.optional then - -- types[#types+1] = { - -- [1] = 'nil', - -- type = 'nil' - -- } - -- end - -- end - -- end - -- for _, tp in ipairs(types) do - -- if not vm.isBuiltinType(tp.type) then - -- addFatherClass(types, tp) - -- end - -- end - -- types.start = arg.start - -- types.finish = arg.finish - -- if #types == 0 then - -- types = { - -- [1] = { - -- [1] = 'any', - -- type = 'any', - -- } - -- } - -- end - -- callArgsType[#callArgsType+1] = types - end - return true, callArgsType -end - -local function matchParams(paramsTypes, i, arg) - local flag = '' - local messages = {} - ---paramsTypes 存的是多个定义的参数信息 - ---paramTypes 存的是单独一个定义的参数信息 - ---param 是某一个定义中的第i个参数的信息 - for _, paramTypes in ipairs(paramsTypes) do - if not paramTypes[i] then - goto CONTINUE - end - flag = '' - for _, param in ipairs(paramTypes[i]) do - if param[1] == '...' then - hasVarargs = true - return true - elseif compatibleType(param, arg) - or param[1] == 'any' then - ---如果形参的类型在实参里面 - flag = '' - return true - elseif isGeneric(param) then - ---如果是泛型,不检查 - return true - else - if param[1] and not errType[param[1]] then - ---TODO(arthur) 什么时候param[1]是nil? - errType[param[1]] = true - flag = flag .. ' ' .. (param[1] or '') - end - end - end - if flag ~= '' then - local argm = '[ ' - for _, v in ipairs(arg) do - argm = argm .. v[1] .. ' ' - end - argm = argm .. ']' - local message = 'Argument of type in ' - .. argm - .. ' is not assignable to parameter of type in [' - .. flag - .. ' ]' - if not messages[message] then - messages[message] = true - messages[#messages+1] = message - end - end - ::CONTINUE:: - end - return false, messages -end - ---@async return function(uri, callback) - local ast = files.getState(uri) - if not ast then - return - end - guide.eachSourceType(ast.ast, 'call', function(source) ---@async - if not source.args then - return - end - await.delay() - local callArgs = source.args - local suc, callArgsType = getArgsInfo(uri, callArgs) - if not suc then - return - end - local func = source.node - local defs = vm.getDefs(func) - ---只检查有emmy注释定义的函数 - local paramsTypes = getInfoFromDefs(defs) - ---遍历实参 - for i, arg in ipairs(callArgsType) do - ---遍历形参 - hasVarargs = false - errType = {} - local match, messages = matchParams(paramsTypes, i, arg) - if hasVarargs then - return - end - ---都不匹配 - if not match then - if #messages > 0 then - callback { - start = arg.start, - finish = arg.finish, - message = table.concat(messages, '\n') - } - end - end - end - ---所有参数都匹配了 - end) end diff --git a/script/core/diagnostics/undefined-doc-class.lua b/script/core/diagnostics/undefined-doc-class.lua index 715583e7..8f1aa097 100644 --- a/script/core/diagnostics/undefined-doc-class.lua +++ b/script/core/diagnostics/undefined-doc-class.lua @@ -12,10 +12,8 @@ return function (uri, callback) return end - local cache = { - ['any'] = true, - ['nil'] = true, - } + local cache = {} + for _, doc in ipairs(state.ast.docs) do if doc.type == 'doc.class' then if not doc.extends then @@ -23,11 +21,11 @@ return function (uri, callback) end for _, ext in ipairs(doc.extends) do local name = ext[1] - local docs = vm.getDocDefines(uri, name) + local docs = vm.getDocSets(uri, name) if cache[name] == nil then cache[name] = false for _, otherDoc in ipairs(docs) do - if otherDoc.type == 'doc.class.name' then + if otherDoc.type == 'doc.class' then cache[name] = true break end diff --git a/script/core/diagnostics/undefined-doc-name.lua b/script/core/diagnostics/undefined-doc-name.lua index a8c75c3c..69edb380 100644 --- a/script/core/diagnostics/undefined-doc-name.lua +++ b/script/core/diagnostics/undefined-doc-name.lua @@ -35,7 +35,7 @@ return function (uri, callback) if name == '...' then return end - if vm.isDocDefined(uri, name) + if #vm.getDocSets(uri, name) > 0 or hasNameOfGeneric(name, source) then return end diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index 7d309096..025c217a 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -2,8 +2,8 @@ local files = require 'files' local vm = require 'vm' local lang = require 'language' local guide = require 'parser.guide' -local noder = require 'core.noder' local await = require 'await' +local infer = require 'vm.infer' local skipCheckClass = { ['unknown'] = true, @@ -25,37 +25,23 @@ return function (uri, callback) return end - local cache = {} - ---@async local function checkUndefinedField(src) - local id = noder.getID(src) - if not id then - return - end - if cache[id] then - return - end - await.delay() if #vm.getDefs(src) > 0 then - cache[id] = true return end local node = src.node if node then - local defs = vm.getDefs(node) local ok - for _, def in ipairs(defs) do - if def.type == 'doc.class.name' - and not skipCheckClass[def[1]] then + for view in infer.getInfer(node):eachView() do + if not skipCheckClass[view] then ok = true break end end if not ok then - cache[id] = true return end end diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index b570ca65..139fa74f 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -3,9 +3,8 @@ local vm = require 'vm' local lang = require 'language' local config = require 'config' local guide = require 'parser.guide' -local noder = require 'core.noder' -local collector = require 'core.collector' 'searcher' local await = require 'await' +local globalMgr = require 'vm.global-manager' local requireLike = { ['include'] = true, @@ -21,35 +20,40 @@ return function (uri, callback) return end + local dglobals = config.get(uri, 'Lua.diagnostics.globals') + local rspecial = config.get(uri, 'Lua.runtime.special') + local cache = {} + -- 遍历全局变量,检查所有没有 set 模式的全局变量 guide.eachSourceType(ast.ast, 'getglobal', function (src) ---@async local key = src[1] if not key then return end - if config.get(uri, 'Lua.diagnostics.globals')[key] then + if dglobals[key] then return end - if config.get(uri, 'Lua.runtime.special')[key] then + if rspecial[key] then return end local node = src.node if node.tag ~= '_ENV' then return end - await.delay() - local id = 'def:' .. noder.getID(src) - if not collector:has(uri, id) then - local message = lang.script('DIAG_UNDEF_GLOBAL', key) - if requireLike[key:lower()] then - message = ('%s(%s)'):format(message, lang.script('DIAG_REQUIRE_LIKE', key)) - end - callback { - start = src.start, - finish = src.finish, - message = message, - } + if cache[key] == nil then + cache[key] = globalMgr.hasGlobalSets(uri, 'variable', key) + end + if cache[key] then return end + local message = lang.script('DIAG_UNDEF_GLOBAL', key) + if requireLike[key:lower()] then + message = ('%s(%s)'):format(message, lang.script('DIAG_REQUIRE_LIKE', key)) + end + callback { + start = src.start, + finish = src.finish, + message = message, + } end) end diff --git a/script/core/diagnostics/unused-local.lua b/script/core/diagnostics/unused-local.lua index 7e7bd9d7..d12ceb2b 100644 --- a/script/core/diagnostics/unused-local.lua +++ b/script/core/diagnostics/unused-local.lua @@ -88,9 +88,6 @@ return function (uri, callback) or name == ast.ENVMode then return end - if source.tag == 'self' then - return - end if isToBeClosed(source) then return end diff --git a/script/core/document-symbol.lua b/script/core/document-symbol.lua index 33970042..6629ccbc 100644 --- a/script/core/document-symbol.lua +++ b/script/core/document-symbol.lua @@ -33,7 +33,7 @@ local function buildFunctionParams(func) end local params = {} for _, arg in ipairs(func.args) do - if arg.dummy then + if arg.type == 'self' then goto CONTINUE end if arg.type == '...' then @@ -183,9 +183,6 @@ local function buildValue(source, text, symbols) end local function buildSet(source, text, used, symbols) - if source.dummy then - return - end local value = source.value if value and value.type == 'function' then used[value] = true diff --git a/script/core/generic.lua b/script/core/generic.lua deleted file mode 100644 index f260dc0b..00000000 --- a/script/core/generic.lua +++ /dev/null @@ -1,283 +0,0 @@ -local guide = require 'parser.guide' -local noder = require "core.noder" - ----@class generic.value: parser.guide.object ----@field type string ----@field closure generic.closure ----@field proto parser.guide.object ----@field parent parser.guide.object - ----@class generic.closure: parser.guide.object ----@field type string ----@field proto parser.guide.object ----@field upvalues table<string, generic.value[]> ----@field params generic.value[] ----@field returns generic.value[] - -local m = {} - ----@param closure generic.closure ----@param proto parser.guide.object -local function instantValue(closure, proto) - ---@type generic.value - local value = { - type = 'generic.value', - closure = closure, - proto = proto, - parent = proto.parent, - } - closure.values[#closure.values+1] = value - return value -end - ----递归实例化对象 ----@param proto parser.guide.object ----@return generic.value -local function createValue(closure, proto, callback, road) - road = road or {} - if proto.type == 'doc.type' then - local types = {} - local hasGeneric - for i, tp in ipairs(proto.types) do - local genericValue = createValue(closure, tp, callback, road) - if genericValue then - hasGeneric = true - types[i] = genericValue - else - types[i] = tp - end - end - if not hasGeneric then - return nil - end - local value = instantValue(closure, proto) - value.types = types - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.name' then - if not proto.typeGeneric then - return nil - end - local key = proto[1] - local value = instantValue(closure, proto) - if callback then - callback(road, key, proto) - end - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.function' then - local hasGeneric - local args = {} - local returns = {} - for i, arg in ipairs(proto.args) do - local value = createValue(closure, arg, callback, road) - if value then - hasGeneric = true - end - args[i] = value or arg - end - for i, rtn in ipairs(proto.returns) do - local value = createValue(closure, rtn, callback, road) - if value then - hasGeneric = true - end - returns[i] = value or rtn - end - if not hasGeneric then - return nil - end - local value = instantValue(closure, proto) - value.args = args - value.returns = returns - value.isGeneric = true - noder.pushSource(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.array' then - road[#road+1] = noder.WEAK_ANY_FIELD - local node = createValue(closure, proto.node, callback, road) - road[#road] = nil - if not node then - return nil - end - local value = instantValue(closure, proto) - value.node = node - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.table' then - road[#road+1] = noder.WEAK_TABLE_KEY - local tkey = createValue(closure, proto.tkey, callback, road) - road[#road] = nil - - road[#road+1] = noder.WEAK_ANY_FIELD - local tvalue = createValue(closure, proto.tvalue, callback, road) - road[#road] = nil - - if not tkey and not tvalue then - return nil - end - local value = instantValue(closure, proto) - value.tkey = tkey or proto.tkey - value.tvalue = tvalue or proto.tvalue - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.ltable' then - local fields = {} - for i, field in ipairs(proto.fields) do - fields[i] = createValue(closure, field, callback, road) or field - end - if #fields == 0 then - return nil - end - local value = instantValue(closure, proto) - value.fields = fields - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.field' then - local name = proto.name[1] - if type(name) == 'string' then - road[#road+1] = ('%s%s'):format( - noder.STRING_FIELD, - name - ) - else - road[#road+1] = ('%s%s'):format( - noder.SPLIT_CHAR, - name - ) - end - local typeUnit = createValue(closure, proto.extends, callback, road) - road[#road] = nil - if not typeUnit then - return nil - end - local value = instantValue(closure, proto) - value.name = proto.name - value.extends = typeUnit - noder.compileNode(noder.getNoders(proto), value) - return value - end -end - -local function buildValue(road, key, proto, param, upvalues) - local paramID - if proto.literal then - local str = param.type == 'string' and param[1] - if not str then - return - end - paramID = 'dn:' .. str - else - paramID = noder.getID(param) - end - if not paramID then - return - end - local myUri = guide.getUri(param) - local myHead = noder.URI_CHAR .. myUri .. noder.URI_CHAR - paramID = myHead .. paramID - if not upvalues[key] then - upvalues[key] = {} - end - upvalues[key][#upvalues[key]+1] = paramID .. table.concat(road) -end - --- 为所有的 param 与 return 创建副本 ----@param closure generic.closure -local function buildValues(closure) - local protoFunction = closure.proto - local upvalues = closure.upvalues - local params = closure.call.args - local args = protoFunction.args - local paramMap = {} - if params then - for i, param in ipairs(params) do - local arg = args and args[i] - if arg then - if arg.type == 'local' then - paramMap[arg[1]] = param - elseif arg.type == 'doc.type.arg' then - paramMap[arg.name[1]] = param - end - end - end - end - - if protoFunction.type == 'function' then - for _, doc in ipairs(protoFunction.bindDocs) do - if doc.type == 'doc.param' then - local name = doc.param[1] - local extends = doc.extends - if name and extends then - local param = paramMap[name] - closure.params[name] = param and createValue(closure, extends, function (road, key, proto) - buildValue(road, key, proto, param, upvalues) - end) or extends - end - end - end - for _, doc in ipairs(protoFunction.bindDocs) do - if doc.type == 'doc.return' then - for _, rtn in ipairs(doc.returns) do - closure.returns[rtn.returnIndex] = createValue(closure, rtn) or rtn - end - end - end - end - if protoFunction.type == 'doc.type.function' then - for index, arg in ipairs(protoFunction.args) do - local name = arg.name[1] - local extends = arg.extends - local param = paramMap[name] - - closure.params[name] = param and createValue(closure, extends, function (road, key, proto) - buildValue(road, key, proto, param, upvalues) - end) or extends - end - for index, rtn in ipairs(protoFunction.returns) do - closure.returns[index] = createValue(closure, rtn) or rtn - end - end -end - ----创建一个闭包 ----@param proto parser.guide.object|generic.value # 原型函数|泛型值 ----@return generic.closure -function m.createClosure(proto, call) - local protoFunction, parentClosure - if proto.type == 'function' then - protoFunction = proto - elseif proto.type == 'doc.type.function' then - protoFunction = proto - elseif proto.type == 'generic.value' then - protoFunction = proto.proto - parentClosure = proto.closure - end - ---@type generic.closure - local closure = { - type = 'generic.closure', - parent = protoFunction.parent, - proto = protoFunction, - call = call, - upvalues = parentClosure and parentClosure.upvalues or {}, - params = {}, - returns = {}, - values = {}, - } - buildValues(closure) - - if #closure.returns == 0 then - return nil - end - - noder.compileNode(noder.getNoders(proto), closure) - - return closure -end - -return m diff --git a/script/core/highlight.lua b/script/core/highlight.lua index 9db4b508..edd8c95d 100644 --- a/script/core/highlight.lua +++ b/script/core/highlight.lua @@ -1,4 +1,3 @@ -local searcher = require 'core.searcher' local files = require 'files' local vm = require 'vm' local define = require 'proto.define' @@ -6,10 +5,13 @@ local findSource = require 'core.find-source' local util = require 'utility' local guide = require 'parser.guide' +---@async local function eachRef(source, callback) - local results = vm.getRefs(source) - for i = 1, #results do - callback(results[i]) + local refs = vm.getRefs(source, function () + return false + end) + for _, ref in ipairs(refs) do + callback(ref) end end @@ -22,6 +24,7 @@ local function eachLocal(source, callback) end end +---@async local function find(source, uri, callback) if source.type == 'local' then eachLocal(source, callback) @@ -237,6 +240,7 @@ local function isLiteralValue(source) return true end +---@async return function (uri, offset) local state = files.getState(uri) if not state then @@ -254,9 +258,6 @@ return function (uri, offset) if not target then return end - if target.dummy then - return - end if mark[target] then return end diff --git a/script/core/hint.lua b/script/core/hint.lua index e6eb483c..15eff0bf 100644 --- a/script/core/hint.lua +++ b/script/core/hint.lua @@ -1,5 +1,5 @@ local files = require 'files' -local infer = require 'core.infer' +local infer = require 'vm.infer' local vm = require 'vm' local config = require 'config' local guide = require 'parser.guide' @@ -22,9 +22,6 @@ local function typeHint(uri, results, start, finish) and source.type ~= 'setindex' then return end - if source.dummy then - return - end if source[1] == '_' then return end @@ -41,8 +38,9 @@ local function typeHint(uri, results, start, finish) end end await.delay() - local view = infer.searchAndViewInfers(source) + local view = infer.getInfer(source):view() if view == 'any' + or view == 'unknown' or view == 'nil' then return end diff --git a/script/core/hover/arg.lua b/script/core/hover/args.lua index d03f55f2..a53136b0 100644 --- a/script/core/hover/arg.lua +++ b/script/core/hover/args.lua @@ -1,6 +1,5 @@ local guide = require 'parser.guide' -local infer = require 'core.infer' -local vm = require 'vm' +local infer = require 'vm.infer' local function optionalArg(arg) if not arg.bindDocs then @@ -14,7 +13,7 @@ local function optionalArg(arg) end end -local function asFunction(source, oop) +local function asFunction(source) local args = {} local methodDef local parent = source.parent @@ -22,12 +21,12 @@ local function asFunction(source, oop) methodDef = true end if methodDef then - args[#args+1] = ('self: %s'):format(infer.searchAndViewInfers(parent.node)) + args[#args+1] = ('self: %s'):format(infer.getInfer(parent.node):view 'any') end if source.args then for i = 1, #source.args do local arg = source.args[i] - if arg.dummy then + if arg.type == 'self' then goto CONTINUE end local name = arg.name or guide.getKeyName(arg) @@ -35,27 +34,23 @@ local function asFunction(source, oop) args[#args+1] = ('%s%s: %s'):format( name, optionalArg(arg) and '?' or '', - infer.searchAndViewInfers(arg) + infer.getInfer(arg):view 'any' ) elseif arg.type == '...' then args[#args+1] = ('%s: %s'):format( '...', - infer.searchAndViewInfers(arg) + infer.getInfer(arg):view 'any' ) else - args[#args+1] = ('%s'):format(infer.searchAndViewInfers(arg)) + args[#args+1] = ('%s'):format(infer.getInfer(arg):view 'any') end ::CONTINUE:: end end - if oop then - return table.concat(args, ', ', 2) - else - return table.concat(args, ', ') - end + return args end -local function asDocFunction(source, oop) +local function asDocFunction(source) if not source.args then return '' end @@ -66,22 +61,18 @@ local function asDocFunction(source, oop) args[i] = ('%s%s: %s'):format( name, arg.optional and '?' or '', - arg.extends and infer.searchAndViewInfers(arg.extends) or 'any' + arg.extends and infer.getInfer(arg.extends):view 'any' or 'any' ) end - if oop then - return table.concat(args, ', ', 2) - else - return table.concat(args, ', ') - end + return args end -return function (source, oop) +return function (source) if source.type == 'function' then - return asFunction(source, oop) + return asFunction(source) end if source.type == 'doc.type.function' then - return asDocFunction(source, oop) + return asDocFunction(source) end - return '' + return {} end diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index d694660b..5d350cf7 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -1,13 +1,12 @@ local vm = require 'vm' local ws = require 'workspace' -local searcher = require 'core.searcher' local markdown = require 'provider.markdown' local config = require 'config' local lang = require 'language' local util = require 'utility' local guide = require 'parser.guide' -local noder = require 'core.noder' local rpath = require 'workspace.require-path' +local infer = require 'vm.infer' local function collectRequire(mode, literal, uri) local result, searchers @@ -57,6 +56,9 @@ end local function asStringView(source, literal) -- 内部包含转义符? + if not source[2] then + return + end local rawLen = source.finish - source.start - 2 * #source[2] if config.get(guide.getUri(source), 'Lua.hover.viewString') and (source[2] == '"' or source[2] == "'") @@ -126,24 +128,14 @@ end local function tryDocClassComment(source) for _, def in ipairs(vm.getDefs(source)) do - if def.type == 'doc.class.name' - or def.type == 'doc.alias.name' then - local class = noder.getDocState(def) - local comment = getBindComment(class, class.bindGroup, class) + if def.type == 'doc.class' + or def.type == 'doc.alias' then + local comment = getBindComment(def, def.bindGroup, def) if comment then return comment end end end - if source.bindDocs then - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.class' - or doc.type == 'doc.alias' then - local comment = getBindComment(doc, source.bindDocs, doc) - return comment - end - end - end end local function tryDocModule(source) @@ -154,29 +146,32 @@ local function tryDocModule(source) end local function buildEnumChunk(docType, name) - local enums = vm.getDocEnums(docType) - if not enums or #enums == 0 then - return + if not docType then + return nil end + local enums = {} local types = {} - for _, tp in ipairs(docType.types) do - if tp.type ~= 'doc.enum' - and tp.type ~= 'doc.resume' then - types[#types+1] = tp[1] - end - end local lines = {} - for _, typeUnit in ipairs(docType.types) do - local comment = tryDocClassComment(typeUnit) + for _, tp in ipairs(vm.getDefs(docType)) do + types[#types+1] = infer.getInfer(tp):view() + if tp.type == 'doc.type.string' + or tp.type == 'doc.type.integer' + or tp.type == 'doc.type.boolean' then + enums[#enums+1] = tp + end + local comment = tryDocClassComment(tp) if comment then for line in util.eachLine(comment) do lines[#lines+1] = ('-- %s'):format(line) end end end - lines[#lines+1] = ('%s: %s'):format(name, table.concat(types, '|')) + if #enums == 0 then + return nil + end + lines[#lines+1] = ('%s:'):format(name) for _, enum in ipairs(enums) do - local enumDes = (' %s %s'):format( + local enumDes = (' %s %q'):format( (enum.default and '->') or (enum.additional and '+>') or ' |', @@ -199,19 +194,8 @@ local function buildEnumChunk(docType, name) return table.concat(lines, '\n') end -local function isFunction(source) - if source.type == 'function' then - return true - end - local value = searcher.getObjectValue(source) - if not value then - return false - end - return value.type == 'function' -end - local function getBindEnums(source, docGroup) - if not isFunction(source) then + if source.type ~= 'function' then return end @@ -319,7 +303,7 @@ local function tryDocComment(source) if not source.bindDocs then return end - if not isFunction(source) then + if source.type ~= 'function' then local comment = getBindComment(source, source.bindDocs) return comment end @@ -354,11 +338,15 @@ local function tyrDocParamComment(source) if source.parent.type ~= 'funcargs' then return end - for _, def in ipairs(vm.getDefs(source)) do - if def.type == 'doc.param' then - if def.comment then - return def.comment.text - end + if not source.bindDocs then + return + end + for i = #source.bindDocs, 1, -1 do + local doc = source.bindDocs[i] + if doc.type == 'doc.param' + and doc.param[1] == source[1] + and doc.comment then + return doc.comment.text end end end diff --git a/script/core/hover/init.lua b/script/core/hover/init.lua index baa24139..bc2f40eb 100644 --- a/script/core/hover/init.lua +++ b/script/core/hover/init.lua @@ -5,7 +5,7 @@ local getDesc = require 'core.hover.description' local util = require 'utility' local findSource = require 'core.find-source' local markdown = require 'provider.markdown' -local infer = require 'core.infer' +local infer = require 'vm.infer' local guide = require 'parser.guide' ---@async @@ -40,7 +40,7 @@ local function getHover(source) end local oop - if infer.searchAndViewInfers(source) == 'function' then + if infer.getInfer(source):view() == 'function' then local hasFunc for _, def in ipairs(vm.getDefs(source)) do if guide.isOOP(def) then diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua index d07212f8..c2239344 100644 --- a/script/core/hover/label.lua +++ b/script/core/hover/label.lua @@ -1,8 +1,8 @@ local buildName = require 'core.hover.name' -local buildArg = require 'core.hover.arg' +local buildArgs = require 'core.hover.args' local buildReturn = require 'core.hover.return' local buildTable = require 'core.hover.table' -local infer = require 'core.infer' +local infer = require 'vm.infer' local vm = require 'vm' local util = require 'utility' local lang = require 'language' @@ -12,7 +12,7 @@ local guide = require 'parser.guide' local function asFunction(source, oop) local name = buildName(source, oop) - local arg = buildArg(source, oop) + local args = buildArgs(source) local rtn = buildReturn(source) local lines = {} @@ -20,7 +20,7 @@ local function asFunction(source, oop) , vm.isAsync(source) and 'async ' or '' , oop and 'method' or 'function' , name or '' - , arg + , oop and table.concat(args, ', ', 2) or table.concat(args, ', ') ) lines[2] = rtn @@ -30,12 +30,11 @@ end local function asDocTypeName(source) local defs = vm.getDefs(source) for _, doc in ipairs(defs) do - if doc.type == 'doc.class.name' then - return 'class ' .. doc[1] + if doc.type == 'doc.class' then + return 'class ' .. doc.class[1] end - if doc.type == 'doc.alias.name' then - local extends = doc.parent.extends - return lang.script('HOVER_EXTENDS', infer.searchAndViewInfers(extends)) + if doc.type == 'doc.alias' then + return lang.script('HOVER_EXTENDS', infer.getInfer(doc.extends):view()) end end end @@ -43,16 +42,10 @@ end ---@async local function asValue(source, title) local name = buildName(source, false) or '' - local type = infer.searchAndViewInfers(source) - local literal = infer.searchAndViewLiterals(source) - local cont - if not infer.hasType(source, 'string') - and not type:find('%[%]$') then - if #vm.getRefs(source, '*') > 0 - or infer.hasType(source, 'table') then - cont = buildTable(source) - end - end + local ifr = infer.getInfer(source) + local type = ifr:view() + local literal = ifr:viewLiterals() + local cont = buildTable(source) local pack = {} pack[#pack+1] = title pack[#pack+1] = name .. ':' @@ -62,6 +55,7 @@ local function asValue(source, title) if cont and ( type == 'table' or type == 'any' + or type == 'unknown' or type == 'nil') then type = nil end @@ -122,16 +116,15 @@ local function asField(source) end local function asDocFieldName(source) - local name = source[1] - local docField = source.parent + local name = source.field[1] local class - for _, doc in ipairs(docField.bindGroup) do + for _, doc in ipairs(source.bindGroup) do if doc.type == 'doc.class' then class = doc break end end - local view = infer.searchAndViewInfers(docField.extends) + local view = infer.getInfer(source.extends):view() if not class then return ('field ?.%s: %s'):format(name, view) end @@ -205,7 +198,7 @@ return function (source, oop) return asNumber(source) elseif source.type == 'doc.type.name' then return asDocTypeName(source) - elseif source.type == 'doc.field.name' then + elseif source.type == 'doc.field' then return asDocFieldName(source) end end diff --git a/script/core/hover/name.lua b/script/core/hover/name.lua index 5d8f0b3d..905c5ec7 100644 --- a/script/core/hover/name.lua +++ b/script/core/hover/name.lua @@ -1,7 +1,5 @@ -local searcher = require 'core.searcher' -local infer = require 'core.infer' +local infer = require 'vm.infer' local guide = require 'parser.guide' -local vm = require 'vm' local buildName @@ -21,7 +19,7 @@ end local function asField(source, oop) local class if source.node.type ~= 'getglobal' then - class = infer.getClass(source.node) + class = infer.getInfer(source.node):viewClass() end local node = class or buildName(source.node, false) diff --git a/script/core/hover/return.lua b/script/core/hover/return.lua index 681e9747..77710148 100644 --- a/script/core/hover/return.lua +++ b/script/core/hover/return.lua @@ -1,94 +1,82 @@ -local infer = require 'core.infer' +local infer = require 'vm.infer' local guide = require 'parser.guide' +local vm = require 'vm.vm' + +---@param source parser.object +---@return integer +local function countReturns(source) + local n = 0 -local function getReturnDualByDoc(source) local docs = source.bindDocs - if not docs then - return - end - local dual - for _, doc in ipairs(docs) do - if doc.type == 'doc.return' then - for _, rtn in ipairs(doc.returns) do - if not dual then - dual = {} + if docs then + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' then + for _, rtn in ipairs(doc.returns) do + if rtn.returnIndex and rtn.returnIndex > n then + n = rtn.returnIndex + end end - dual[#dual+1] = { rtn } end end end - return dual -end -local function getReturnDualByGrammar(source) - if not source.returns then - return nil - end - local dual - for _, rtn in ipairs(source.returns) do - if not dual then - dual = {} + local returns = source.returns + if returns then + for _, rtn in ipairs(returns) do + if #rtn > n then + n = #rtn + end end - for n = 1, #rtn do - if not dual[n] then - dual[n] = {} + end + + return n +end + +---@param source parser.object +---@return parser.object[] +local function getReturnDocs(source) + local returns = {} + + local docs = source.bindDocs + if docs then + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' then + for _, rtn in ipairs(doc.returns) do + returns[rtn.returnIndex] = rtn + end end - dual[n][#dual[n]+1] = rtn[n] end end - return dual + + return returns end local function asFunction(source) - local dual = getReturnDualByDoc(source) - or getReturnDualByGrammar(source) - if not dual then - return + local num = countReturns(source) + if num == 0 then + return nil end + + local docs = getReturnDocs(source) + local returns = {} - for i, rtn in ipairs(dual) do - local line = {} - local infers = {} + + for i = 1, num do + local rtn = vm.getReturnOfFunction(source, i) + local doc = docs[i] + local name = doc and doc.name and doc.name[1] and (doc.name[1] .. ': ') + local text = ('%s%s%s'):format( + name or '', + infer.getInfer(rtn):view(), + doc and doc.optional and '?' or '' + ) if i == 1 then - line[#line+1] = ' -> ' - else - line[#line+1] = ('% 3d. '):format(i) - end - for n = 1, #rtn do - if rtn[n].type == 'doc.type' then - for _, typeUnit in ipairs(rtn[n].types) do - if typeUnit[1] == 'nil' then - infers['nil'] = true - end - end - end - local values = infer.searchInfers(rtn[n]) - for tp in pairs(values) do - infers[tp] = true - end - end - if next(infers) or rtn[1] then - local tp = infer.viewInfers(guide.getUri(source), infers) - if rtn[1].name then - line[#line+1] = ('%s%s: %s'):format( - rtn[1].name[1], - rtn[1].optional and '?' or '', - tp - ) - else - line[#line+1] = ('%s%s'):format( - tp, - rtn[1].optional and '?' or '' - ) - end + returns[i] = (' -> %s'):format(text) else - break + returns[i] = ('% 3d. %s'):format(i, text) end - returns[i] = table.concat(line) - end - if #returns == 0 then - return nil end + return table.concat(returns, '\n') end @@ -99,7 +87,7 @@ local function asDocFunction(source) local returns = {} for i, rtn in ipairs(source.returns) do local rtnText = ('%s%s'):format( - infer.searchAndViewInfers(rtn), + infer.getInfer(rtn):view(), rtn.optional and '?' or '' ) if i == 1 then diff --git a/script/core/hover/table.lua b/script/core/hover/table.lua index 374f756d..31036edd 100644 --- a/script/core/hover/table.lua +++ b/script/core/hover/table.lua @@ -1,7 +1,7 @@ local vm = require 'vm' local util = require 'utility' local config = require 'config' -local infer = require 'core.infer' +local infer = require 'vm.infer' local await = require 'await' local guide = require 'parser.guide' @@ -16,23 +16,23 @@ local function formatKey(key) return ('[%s]'):format(key) end -local function buildAsHash(keys, inferMap, literalMap, optMap, reachMax) +local function buildAsHash(keys, typeMap, literalMap, optMap, reachMax) local lines = {} lines[#lines+1] = '{' for _, key in ipairs(keys) do - local inferView = inferMap[key] + local typeView = typeMap[key] local literalView = literalMap[key] if literalView then lines[#lines+1] = (' %s%s: %s = %s,'):format( formatKey(key), optMap[key] and '?' or '', - inferView, + typeView, literalView) else lines[#lines+1] = (' %s%s: %s,'):format( formatKey(key), optMap[key] and '?' or '', - inferView + typeView ) end end @@ -43,27 +43,27 @@ local function buildAsHash(keys, inferMap, literalMap, optMap, reachMax) return table.concat(lines, '\n') end -local function buildAsConst(keys, inferMap, literalMap, optMap, reachMax) +local function buildAsConst(keys, typeMap, literalMap, optMap, reachMax) table.sort(keys, function (a, b) return tonumber(literalMap[a]) < tonumber(literalMap[b]) end) local lines = {} lines[#lines+1] = '{' for _, key in ipairs(keys) do - local inferView = inferMap[key] + local typeView = typeMap[key] local literalView = literalMap[key] if literalView then lines[#lines+1] = (' %s%s: %s = %s,'):format( formatKey(key), optMap[key] and '?' or '', - inferView, + typeView, literalView ) else lines[#lines+1] = (' %s%s: %s,'):format( formatKey(key), optMap[key] and '?' or '', - inferView + typeView ) end end @@ -82,17 +82,11 @@ local typeSorter = { local function getKeyMap(fields) local keys = {} - local mark = {} + local map = {} for _, field in ipairs(fields) do local key = vm.getKeyName(field) - local tp = vm.getKeyType(field) - if tp == 'number' or tp == 'integer' then - key = tonumber(key) - elseif tp == 'boolean' then - key = key == 'true' - end - if key and not mark[key] then - mark[key] = true + if key and not map[key] then + map[key] = true keys[#keys+1] = key end end @@ -113,72 +107,95 @@ local function getKeyMap(fields) return tsa < tsb end end) - return keys + return keys, map end -local function getOptionalMap(fields) - local optionals = {} +local function getOptMap(fields, keyMap) + local optMap = {} for _, field in ipairs(fields) do - if field.type == 'doc.field.name' then - if field.parent.optional then + if field.type == 'doc.field' then + if field.optional then local key = vm.getKeyName(field) - local tp = vm.getKeyType(field) - if tp == 'number' or tp == 'integer' then - key = tonumber(key) - elseif tp == 'boolean' then - key = key == 'true' + if keyMap[key] then + optMap[key] = true end - optionals[key] = true end end if field.type == 'doc.type.field' then if field.optional then local key = vm.getKeyName(field) - local tp = vm.getKeyType(field) - if tp == 'number' or tp == 'integer' then - key = tonumber(key) - elseif tp == 'boolean' then - key = key == 'true' + if keyMap[key] then + optMap[key] = true end - optionals[key] = true end end end - return optionals + return optMap +end + +---@async +local function getInferMap(fields, keyMap) + ---@type table<string, vm.infer> + local inferMap = {} + for _, field in ipairs(fields) do + local key = vm.getKeyName(field) + if not keyMap[key] then + goto CONTINUE + end + await.delay() + local ifr = infer.getInfer(field) + if inferMap[key] then + inferMap[key] = inferMap[key]:merge(ifr) + else + inferMap[key] = ifr + end + ::CONTINUE:: + end + return inferMap end ---@async +---@return string? return function (source) - local maxFields = config.get(guide.getUri(source), 'Lua.hover.previewFields') + local uri = guide.getUri(source) + local maxFields = config.get(uri, 'Lua.hover.previewFields') if maxFields <= 0 then - return 'table' + return nil end - local fields = vm.getRefs(source, '*') - local keys = getKeyMap(fields) - local optMap = getOptionalMap(fields) + for view in infer.getInfer(source):eachView() do + if view == 'string' + or vm.isSubType(uri, view, 'string') then + return nil + end + end + local fields = vm.getFields(source) + local keys, map = getKeyMap(fields) if #keys == 0 then - return '{}' + return nil end - local inferMap = {} - local literalMap = {} - local reachMax = #keys - maxFields if #keys > maxFields then for i = maxFields + 1, #keys do + map[keys[i]] = nil keys[i] = nil end end + local optMap = getOptMap(fields, map) + local inferMap = getInferMap(fields, map) + + local typeMap = {} + local literalMap = {} local isConsts = true for i = 1, #keys do await.delay() local key = keys[i] - inferMap[key] = infer.searchAndViewInfers(source, key) - literalMap[key] = infer.searchAndViewLiterals(source, key) + typeMap[key] = inferMap[key]:view('unknown', uri) + literalMap[key] = inferMap[key]:viewLiterals() if not tonumber(literalMap[key]) then isConsts = false end @@ -187,9 +204,9 @@ return function (source) local result if isConsts then - result = buildAsConst(keys, inferMap, literalMap, optMap, reachMax) + result = buildAsConst(keys, typeMap, literalMap, optMap, reachMax) else - result = buildAsHash(keys, inferMap, literalMap, optMap, reachMax) + result = buildAsHash(keys, typeMap, literalMap, optMap, reachMax) end --if timeUp then diff --git a/script/core/infer.lua b/script/core/infer.lua deleted file mode 100644 index 88028a6c..00000000 --- a/script/core/infer.lua +++ /dev/null @@ -1,639 +0,0 @@ -local searcher = require 'core.searcher' -local config = require 'config' -local noder = require 'core.noder' -local util = require 'utility' -local vm = require "vm.vm" -local guide = require "parser.guide" - -local CLASS = { 'CLASS' } -local TABLE = { 'TABLE' } -local CACHE = { 'CACHE' } - -local typeSort = { - ['boolean'] = 1, - ['string'] = 2, - ['integer'] = 3, - ['number'] = 4, - ['table'] = 5, - ['function'] = 6, - ['true'] = 101, - ['false'] = 102, -} - -local m = {} - -local function mergeTable(a, b) - if not b then - return - end - for v in pairs(b) do - a[v] = true - end - a[CACHE] = nil -end - -local function isBaseType(source, mark) - return m.hasType(source, 'number', mark) - or m.hasType(source, 'integer', mark) - or m.hasType(source, 'string', mark) -end - -local function searchInferOfUnary(value, infers, mark) - local op = value.op.type - if op == 'not' then - infers['boolean'] = true - return - end - if op == '#' then - if m.hasType(value[1], 'table', mark) - or m.hasType(value[1], 'string', mark) then - infers['integer'] = true - end - return - end - if op == '-' then - if m.hasType(value[1], 'integer', mark) then - infers['integer'] = true - elseif isBaseType(value[1], mark) then - infers['number'] = true - end - return - end - if op == '~' then - if isBaseType(value[1], mark) then - infers['integer'] = true - end - return - end -end - -local function searchInferOfBinary(value, infers, mark) - local op = value.op.type - if op == 'and' then - if m.isTrue(value[1], mark) then - mergeTable(infers, m.searchInfers(value[2], nil, mark)) - else - mergeTable(infers, m.searchInfers(value[1], nil, mark)) - end - return - end - if op == 'or' then - if m.isTrue(value[1], mark) then - mergeTable(infers, m.searchInfers(value[1], nil, mark)) - else - mergeTable(infers, m.searchInfers(value[2], nil, mark)) - end - return - end - -- must return boolean - if op == '==' - or op == '~=' - or op == '<' - or op == '>' - or op == '<=' - or op == '>=' then - infers['boolean'] = true - return - end - -- check number - if op == '<<' - or op == '>>' - or op == '~' - or op == '&' - or op == '|' then - if isBaseType(value[1], mark) - and isBaseType(value[2], mark) then - infers['integer'] = true - end - return - end - if op == '..' then - if isBaseType(value[1], mark) - and isBaseType(value[2], mark) then - infers['string'] = true - end - return - end - if op == '^' - or op == '/' then - if isBaseType(value[1], mark) - and isBaseType(value[2], mark) then - infers['number'] = true - end - return - end - if op == '+' - or op == '-' - or op == '*' - or op == '%' - or op == '//' then - if m.hasType(value[1], 'integer', mark) - and m.hasType(value[2], 'integer', mark) then - infers['integer'] = true - elseif isBaseType(value[1], mark) - and isBaseType(value[2], mark) then - infers['number'] = true - end - return - end -end - -local function searchInferOfValue(value, infers, mark) - if value.type == 'string' then - infers['string'] = true - return true - end - if value.type == 'boolean' then - infers['boolean'] = true - return true - end - if value.type == 'table' then - if value.array then - local node = m.searchAndViewInfers(value.array, nil, mark) - if node ~= 'any' then - local infer = node .. '[]' - infers[infer] = true - end - else - infers['table'] = true - end - return true - end - if value.type == 'integer' then - infers['integer'] = true - return true - end - if value.type == 'number' then - infers['number'] = true - return true - end - if value.type == 'function' then - infers['function'] = true - return true - end - if value.type == 'unary' then - searchInferOfUnary(value, infers, mark) - return true - end - if value.type == 'binary' then - searchInferOfBinary(value, infers, mark) - return true - end - return false -end - -local function searchLiteralOfValue(value, literals, mark) - if value.type == 'string' - or value.type == 'boolean' - or value.type == 'number' - or value.type == 'integer' then - local v = value[1] - if v ~= nil then - literals[v] = true - end - return - end - if value.type == 'unary' then - local op = value.op.type - if op == '-' then - local subLiterals = m.searchLiterals(value[1], nil, mark) - if subLiterals then - for subLiteral in pairs(subLiterals) do - local num = tonumber(subLiteral) - if num then - literals[-num] = true - end - end - end - end - if op == '~' then - local subLiterals = m.searchLiterals(value[1], nil, mark) - if subLiterals then - for subLiteral in pairs(subLiterals) do - local num = math.tointeger(subLiteral) - if num then - literals[~num] = true - end - end - end - end - end -end - -local function bindClassOrType(source) - if not source.bindDocs then - return false - end - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.class' - or doc.type == 'doc.type' then - return true - end - end - return false -end - -local function cleanInfers(uri, infers) - local version = config.get(uri, 'Lua.runtime.version') - local enableInteger = version == 'Lua 5.3' or version == 'Lua 5.4' - infers['unknown'] = nil - if infers['number'] then - enableInteger = false - end - if not enableInteger and infers['integer'] then - infers['integer'] = nil - infers['number'] = true - end - -- stringlib 就是 string - if infers['stringlib'] and infers['string'] then - infers['stringlib'] = nil - end - -- 如果有doc标记,则先移除table类型 - if infers[CLASS] then - infers[CLASS] = nil - infers['table'] = nil - end - -- 用doc标记的table,加入table类型 - if infers[TABLE] then - infers[TABLE] = nil - infers['table'] = true - end - if infers['function'] then - for k in pairs(infers) do - if k:sub(1, 4) == 'fun(' then - infers[k] = nil - end - end - end -end - ----合并对象的推断类型 ----@param infers string[] ----@return string -function m.viewInfers(uri, infers) - if infers[CACHE] then - return infers[CACHE] - end - -- 如果有显性的 any ,则直接显示为 any - if infers['any'] then - infers[CACHE] = 'any' - return 'any' - end - local result = {} - local count = 0 - for infer in pairs(infers) do - count = count + 1 - result[count] = infer - end - -- 如果没有任何显性类型,则推测为 unkonwn ,显示为 any - if count == 0 then - infers[CACHE] = 'any' - return 'any' - end - table.sort(result, function (a, b) - local sa = typeSort[a] or 100 - local sb = typeSort[b] or 100 - if sa == sb then - return a < b - else - return sa < sb - end - end) - local limit = config.get(uri, 'Lua.hover.enumsLimit') - if limit < 0 then - limit = 0 - end - infers[CACHE] = table.concat(result, '|', 1, math.min(count, limit)) - if count > limit then - infers[CACHE] = ('%s...(+%d)'):format(infers[CACHE], count - limit) - end - return infers[CACHE] -end - ----合并对象的值 ----@param literals string[] ----@return string -function m.viewLiterals(literals) - local result = {} - local count = 0 - for infer in pairs(literals) do - count = count + 1 - result[count] = util.viewLiteral(infer) - end - if count == 0 then - return nil - end - table.sort(result) - local view = table.concat(result, '|') - return view -end - -function m.viewDocName(doc) - if not doc then - return nil - end - if doc.type == 'doc.type' then - local list = {} - for _, tp in ipairs(doc.types) do - list[#list+1] = m.getDocName(tp) - end - return table.concat(list, '|') - end - return m.getDocName(doc) -end - -function m.getDocName(doc) - if not doc then - return nil - end - if doc.type == 'doc.class.name' - or doc.type == 'doc.type.name' then - local name = doc[1] or '?' - if doc.typeGeneric then - return '<' .. name .. '>' - else - return tostring(name) - end - end - if doc.type == 'doc.type.array' then - local nodeName = m.viewDocName(doc.node) or '?' - return nodeName .. '[]' - end - if doc.type == 'doc.type.table' then - local node = m.viewDocName(doc.node) or '?' - local key = m.viewDocName(doc.tkey) or '?' - local value = m.viewDocName(doc.tvalue) or '?' - return ('%s<%s, %s>'):format(node, key, value) - end - if doc.type == 'doc.type.function' then - return m.viewDocFunction(doc) - end - if doc.type == 'doc.type.enum' - or doc.type == 'doc.resume' then - local value = doc[1] or '?' - return tostring(value) - end - if doc.type == 'doc.type.ltable' then - return 'table' - end -end - -function m.viewDocFunction(doc) - if doc.type ~= 'doc.type.function' then - return '' - end - local args = {} - for i, arg in ipairs(doc.args) do - args[i] = ('%s: %s'):format(arg.name[1], arg.extends and m.viewDocName(arg.extends) or 'any') - end - local label = ('fun(%s)'):format(table.concat(args, ', ')) - if #doc.returns > 0 then - local returns = {} - for i, rtn in ipairs(doc.returns) do - returns[i] = m.viewDocName(rtn) - end - label = ('%s:%s'):format(label, table.concat(returns, ', ')) - end - return label -end - ----显示对象的推断类型 ----@param source parser.guide.object ----@param mark table ----@return string -local function searchInfer(source, infers, mark) - if mark[source] then - return - end - mark[source] = true - if bindClassOrType(source) then - return - end - if searchInferOfValue(source, infers, mark) then - return - end - local value = searcher.getObjectValue(source) - if value then - if value.type ~= 'function' - and value.type ~= 'table' - and value.type ~= 'nil' then - searchInferOfValue(value, infers, mark) - end - return - end - -- check LuaDoc - local docName = m.getDocName(source) - if docName and docName ~= 'nil' and docName ~= 'unknown' then - infers[docName] = true - if not vm.isBuiltinType(docName) then - infers[CLASS] = true - end - if docName == 'table' then - infers[TABLE] = true - end - end -end - -local function searchLiteral(source, literals, mark) - if mark[source] then - return - end - mark[source] = true - searchLiteralOfValue(source, literals, mark) - local value = searcher.getObjectValue(source) - if value then - if value.type ~= 'function' - and value.type ~= 'table' then - searchLiteralOfValue(value, literals, mark) - end - return - end -end - -local function getCachedInfers(source, field) - local inferCache = vm.getCache 'infers' - local sourceCache = inferCache[source] - if not sourceCache then - sourceCache = {} - inferCache[source] = sourceCache - end - if not field then - field = '' - end - if sourceCache[field] then - return true, sourceCache[field] - end - local infers = {} - sourceCache[field] = infers - return false, infers -end - ----搜索对象的推断类型 ----@param source parser.guide.object ----@param field? string ----@param mark? table ----@return string[] -function m.searchInfers(source, field, mark) - if not source then - return nil - end - if source.type == 'setlocal' - or source.type == 'getlocal' then - source = source.node - end - local suc, infers = getCachedInfers(source, field) - if suc then - return infers - end - local isParam = source.parent.type == 'funcargs' - local defs = vm.getDefs(source, field) - mark = mark or {} - if not field then - searchInfer(source, infers, mark) - end - for _, def in ipairs(defs) do - if def.typeGeneric and not isParam then - goto CONTINUE - end - if def.type == 'setlocal' then - goto CONTINUE - end - searchInfer(def, infers, mark) - ::CONTINUE:: - end - if source.type == 'doc.type' then - for _, def in ipairs(source.types) do - if def.typeGeneric then - searchInfer(def, infers, mark) - end - end - end - cleanInfers(guide.getUri(source), infers) - return infers -end - ----搜索对象的字面量值 ----@param source parser.guide.object ----@param field? string ----@param mark? table ----@return table -function m.searchLiterals(source, field, mark) - if not source then - return nil - end - local defs = vm.getDefs(source, field) - local literals = {} - mark = mark or {} - if not field then - searchLiteral(source, literals, mark) - end - for _, def in ipairs(defs) do - searchLiteral(def, literals, mark) - end - return literals -end - ----搜索并显示推断值 ----@param source parser.guide.object ----@param field? string ----@return string -function m.searchAndViewLiterals(source, field, mark) - if not source then - return nil - end - local literals = m.searchLiterals(source, field, mark) - if not literals then - return nil - end - local view = m.viewLiterals(literals) - return view -end - ----判断对象的推断值是否是 true ----@param source parser.guide.object ----@param mark? table -function m.isTrue(source, mark) - if not source then - return false - end - mark = mark or {} - if not mark.isTrue then - mark.isTrue = {} - end - if mark.isTrue[source] == nil then - mark.isTrue[source] = false - local literals = m.searchLiterals(source, nil, mark) - if literals then - for literal in pairs(literals) do - if literal ~= false then - mark.isTrue[source] = true - break - end - end - end - end - return mark.isTrue[source] -end - ----判断对象的推断类型是否包含某个类型 -function m.hasType(source, tp, mark) - mark = mark or {} - local infers = m.searchInfers(source, nil, mark) - if not infers then - return false - end - if infers[tp] then - return true - end - if tp == 'function' then - for infer in pairs(infers) do - if infer ~= CACHE and infer:sub(1, 4) == 'fun(' then - return true - end - end - end - return false -end - ----搜索并显示推断类型 ----@param source parser.guide.object ----@param field? string ----@return string -function m.searchAndViewInfers(source, field, mark) - if not source then - return 'any' - end - local infers = m.searchInfers(source, field, mark) - local view = m.viewInfers(guide.getUri(source), infers) - if type(view) == 'boolean' then - log.error('Why view is boolean?', util.dump(infers)) - return 'any' - end - return view -end - ----搜索并显示推断的class ----@param source parser.guide.object ----@return string? -function m.getClass(source) - if not source then - return nil - end - local infers = {} - local defs = vm.getDefs(source) - for _, def in ipairs(defs) do - if def.type == 'doc.class.name' then - if not vm.isBuiltinType(def[1]) then - infers[def[1]] = true - end - end - end - cleanInfers(guide.getUri(source), infers) - local view = m.viewInfers(guide.getUri(source), infers) - if view == 'any' then - return nil - end - return view -end - -return m diff --git a/script/core/noder.lua b/script/core/noder.lua deleted file mode 100644 index fcc6b6f4..00000000 --- a/script/core/noder.lua +++ /dev/null @@ -1,1914 +0,0 @@ -local util = require 'utility' -local guide = require 'parser.guide' -local collector = require 'core.collector' 'searcher' -local files = require 'files' -local config = require 'config' - -local tostring = tostring -local error = error -local ipairs = ipairs -local type = type -local next = next -local log = log -local ssub = string.sub -local sformat = string.format -local sgsub = string.gsub -local smatch = string.match -local sfind = string.find - -_ENV = nil - -local SPLIT_CHAR = '\x1F' -local LAST_REGEX = SPLIT_CHAR .. '[^' .. SPLIT_CHAR .. ']*$' -local FIRST_REGEX = '^[^' .. SPLIT_CHAR .. ']*' -local HEAD_REGEX = '^' .. SPLIT_CHAR .. '?[^' .. SPLIT_CHAR .. ']*' -local STRING_CHAR = '.' -local ANY_FIELD_CHAR = '*' -local INDEX_CHAR = '[' -local RETURN_INDEX = SPLIT_CHAR .. '#' -local PARAM_INDEX = SPLIT_CHAR .. '&' -local EVENT_ENUM = SPLIT_CHAR .. '>' -local TABLE_KEY = SPLIT_CHAR .. '<' -local WEAK_TABLE_KEY = SPLIT_CHAR .. '<<' -local STRING_FIELD = SPLIT_CHAR .. STRING_CHAR -local INDEX_FIELD = SPLIT_CHAR .. INDEX_CHAR -local ANY_FIELD = SPLIT_CHAR .. ANY_FIELD_CHAR -local WEAK_ANY_FIELD = SPLIT_CHAR .. ANY_FIELD_CHAR .. ANY_FIELD_CHAR -local URI_CHAR = '@' -local URI_REGEX = URI_CHAR .. '([^' .. URI_CHAR .. ']*)' .. URI_CHAR .. '(.*)' - -local INFO_DEEP = { - deep = true, -} -local INFO_REJECT_SET = { - reject = 'set', -} -local INFO_DEEP_AND_REJECT_SET = { - reject = 'set', - deep = true, -} -local INFO_META_INDEX = { - filter = function (id, field) - if field then - return true - end - return ssub(id, 1, 2) ~= 'f:' - end, - filterValid = function (id, field) - return not field - end -} -local INFO_CLASS_TO_EXNTENDS = { - filter = function (_, field, mode) - return field ~= nil - or mode == 'field' - or mode == 'allfield' - end, - filterValid = function (_, field) - return not field - end, - reject = 'set', -} -local INFO_DEEP_AND_DONT_CROSS = { - deep = true, - dontCross = true, -} - ----@alias node.id string ----@alias node.filter fun(id: string, field?: string):boolean - ----@class noders --- 使用该ID的单元 ----@field source table<node.id, parser.guide.object> --- 使用该ID的单元 ----@field sources table<node.id, parser.guide.object[]> --- 前进的关联ID ----@field forward table<node.id, node.id> --- 第一个前进关联的info ----@field finfo? table<node.id, node.info> --- 前进的关联ID与info ----@field forwards table<node.id, node.id[]|table<node.id, node.info>> --- 后退的关联ID ----@field backward table<node.id, node.id> --- 第一个后退关联的info ----@field binfo? table<node.id, node.info> --- 后退的关联ID与info ----@field backwards table<node.id, node.id[]|table<node.id, node.info>> --- 第一个继承 ----@field extend table<node.id, node.id> --- 其他继承 ----@field extends table<node.id, node.id[]> --- 函数调用参数信息(用于泛型) ----@field call table<node.id, parser.guide.object> ----@field require table<node.id, string> ----@field skip table<node.id, boolean> - ----@class node.info ----@field reject? string ----@field deep? boolean ----@field filter? node.filter ----@field filterValid? node.filter ----@field dontCross? boolean - ----如果对象是 arg self, 则认为 id 是 method 的 node ----@param source parser.guide.object ----@return nil -local function getMethodNode(source) - if source.type ~= 'local' or source[1] ~= 'self' then - return nil - end - if source._mnode ~= nil then - return source._mnode or nil - end - source._mnode = false - local func = guide.getParentFunction(source) - if not func then - return - end - if func.isGeneric then - return - end - if source.parent.type ~= 'funcargs' then - return - end - local setmethod = func.parent - if setmethod and ( setmethod.type == 'setmethod' - or setmethod.type == 'setfield' - or setmethod.type == 'setindex') then - source._mnode = setmethod.node - return setmethod.node - end -end - -local function getFieldEventName(field) - if field._eventName then - return field._eventName or nil - end - field._eventName = false - local fieldType = field.extends - if not fieldType then - return nil - end - local docFunc = fieldType.types[1] - if not docFunc or docFunc.type ~= 'doc.type.function' then - return nil - end - local firstArg = docFunc.args and docFunc.args[1] - if not firstArg then - return nil - end - local secondArg - if firstArg.name[1] == 'self' then - firstArg = docFunc.args[2] - if not firstArg then - return nil - end - secondArg = docFunc.args[3] - else - secondArg = docFunc.args[2] - end - if not secondArg then - return - end - local firstType = firstArg.extends - if not firstType then - return nil - end - local firstEnum = firstType.types[1] - if not firstEnum then - return nil - end - local secondType = secondArg.extends - if not secondType then - return nil - end - local secondTypeUnit = secondType.types[1] - if not secondTypeUnit or secondTypeUnit.type ~= 'doc.type.function' then - return nil - end - local enmuStr = firstEnum[1] - if type(enmuStr) ~= 'string' then - return nil - end - local eventName = enmuStr:match [[^['"](.+)['"]$]] - field._eventName = eventName - return eventName -end - -local getKey, getID -local getKeyMap = util.switch() - : case 'local' - : call(function (source) - if source.parent.type == 'funcargs' then - return 'p:' .. source.start, nil - end - return 'l:' .. source.start, nil - end) - : case 'setlocal' - : case 'getlocal' - : call(function (source) - return getKey(source.node) - end) - : case 'setglobal' - : case 'getglobal' - : call(function (source) - local node = source.node - if node.tag == '_ENV' then - return STRING_CHAR .. (source[1] or ''), nil - else - return STRING_CHAR .. (source[1] or ''), node - end - end) - : case 'getfield' - : case 'setfield' - : call(function (source) - return STRING_CHAR .. (source.field and source.field[1] or ''), source.node - end) - : case 'tablefield' - : call(function (source) - local t = source.parent - local parent = t.parent - local node - if parent.value == t then - node = parent - else - node = t - end - return STRING_CHAR .. (source.field and source.field[1] or ''), node - end) - : case 'getmethod' - : case 'setmethod' - : call(function (source) - return STRING_CHAR .. (source.method and source.method[1] or ''), source.node - end) - : case 'setindex' - : case 'getindex' - : call(function (source) - local index = source.index - if not index then - return INDEX_CHAR, source.node - end - if index.type == 'string' then - return STRING_CHAR .. (index[1] or ''), source.node - elseif index.type == 'boolean' - or index.type == 'integer' - or index.type == 'number' then - return tostring(index[1] or ''), source.node - else - return INDEX_CHAR, source.node - end - end) - : case 'tableindex' - : call(function (source) - local t = source.parent - local parent = t.parent - local node - if parent.value == t then - node = parent - else - node = t - end - local index = source.index - if not index then - return ANY_FIELD_CHAR, node - end - if index.type == 'string' then - return STRING_CHAR .. (index[1] or ''), node - elseif index.type == 'boolean' - or index.type == 'integer' - or index.type == 'number' then - return tostring(index[1] or ''), node - elseif index.type ~= 'function' - and index.type ~= 'table' then - return ANY_FIELD_CHAR, node - end - end) - : case 'tableexp' - : call(function (source) - local t = source.parent - local parent = t.parent - local node - if parent.value == t then - node = parent - else - node = t - end - return tostring(source.tindex), node - end) - : case 'table' - : call(function (source) - return 't:' .. source.start, nil - end) - : case 'label' - : call(function (source) - return 'l:' .. source.start, nil - end) - : case 'goto' - : call(function (source) - if source.node then - return 'l:' .. source.node.start, nil - end - return nil, nil - end) - : case 'function' - : call(function (source) - return 'f:' .. source.start, nil - end) - : case 'string' - : call(function (source) - return 'str:', nil - end) - : case 'integer' - : call(function (source) - return 'int:', nil - end) - : case 'number' - : call(function (source) - return 'num:', nil - end) - : case 'boolean' - : call(function (source) - return 'bool:', nil - end) - : case 'nil' - : call(function (source) - return 'nil:', nil - end) - : case '...' - : call(function (source) - return 'va:' .. source.start, nil - end) - : case 'varargs' - : call(function (source) - if source.node then - return 'va:' .. source.node.start, nil - end - end) - : case 'select' - : call(function (source) - return sformat('s:%d%s%d', source.start, RETURN_INDEX, source.sindex) - end) - : case 'call' - : call(function (source) - local node = source.node - if node.special == 'rawget' - or node.special == 'rawset' then - if not source.args then - return nil, nil - end - local tbl, key = source.args[1], source.args[2] - if not tbl or not key then - return nil, nil - end - if key.type == 'string' then - return STRING_CHAR .. (key[1] or ''), tbl - else - return '', tbl - end - end - return 'c:' .. source.finish, nil - end) - : case 'doc.class.name' - : case 'doc.alias.name' - : case 'doc.extends.name' - : call(function (source) - local name = source[1] - return 'dn:' .. name, nil - end) - : case 'doc.type.name' - : call(function (source) - local name = source[1] - if source.typeGeneric then - local first = source.typeGeneric[name][1] - if first then - return 'dg:' .. first.start, nil - end - else - return 'dn:' .. name, nil - end - end) - : case 'doc.see.name' - : call(function (source) - local name = source[1] - return 'dsn:' .. name, nil - end) - : case 'doc.class' - : call(function (source) - return 'dc:' .. source.start - end) - : case 'doc.type' - : call(function (source) - return 'dt:' .. source.start - end) - : case 'doc.param' - : call(function (source) - return 'dp:' .. source.start - end) - : case 'doc.vararg' - : call(function (source) - return 'dv:' .. source.start - end) - : case 'doc.field.name' - : call(function (source) - return 'dfn:' .. source.start - end) - : case 'doc.type.enum' - : case 'doc.resume' - : call(function (source) - return 'de:' .. source.start - end) - : case 'doc.type.table' - : call(function (source) - return 'dtable:' .. source.start - end) - : case 'doc.type.ltable' - : call(function (source) - return 'dltable:' .. source.start - end) - : case 'doc.type.field' - : call(function (source) - return 'dfield:' .. source.start - end) - : case 'doc.type.array' - : call(function (source) - return 'darray:' .. source.finish - end) - : case 'doc.type.function' - : call(function (source) - return 'dfun:' .. source.start, nil - end) - : case 'doc.see.field' - : call(function (source) - return STRING_CHAR .. (source[1]), source.parent.name - end) - : case 'generic.closure' - : call(function (source) - return 'gc:' .. source.call.start, nil - end) - : case 'generic.value' - : call(function (source) - local tail = '' - if guide.getUri(source.closure.call) ~= guide.getUri(source.proto) then - tail = URI_CHAR .. guide.getUri(source.closure.call) - end - return sformat('gv:%s|%s%s' - , source.closure.call.start - , getKey(source.proto) - , tail - ) - end) - : getMap() - ----获取语法树单元的key ----@param source parser.guide.object ----@return string? key ----@return parser.guide.object? node -function getKey(source) - local f = getKeyMap[source.type] - if f then - return f(source) - end - return nil -end - -local function getLocalValueID(source) - if source.type ~= 'local' then - return nil - end - local value = source.value - if not value then - return nil - end - local id = getID(value) - if not id then - return nil - end - local ct = id:sub(1, 2) - if ct == 'g:' - or ct == 'p:' - or ct == 'l:' then - return id - end - return nil -end - -local function getNodeKey(source) - if source.type == 'getlocal' - or source.type == 'setlocal' then - source = source.node - end - local methodNode = getMethodNode(source) - if methodNode then - return getNodeKey(methodNode) - end - if config.get(guide.getUri(source), 'Lua.IntelliSense.traceFieldInject') then - local localValueID = getLocalValueID(source) - if localValueID then - return localValueID - end - end - local key, node = getKey(source) - if key and guide.isGlobal(source) then - return 'g:' .. key, nil - end - return key, node -end - ----获取语法树单元的字符串ID ----@param source parser.guide.object ----@return string? id -function getID(source) - if not source then - return nil - end - if source._id ~= nil then - return source._id or nil - end - if source.type == 'field' - or source.type == 'method' then - source._id = false - return nil - end - local current = source - while current.type == 'paren' do - current = current.exp - if not current then - source._id = false - return nil - end - end - local id, node = getNodeKey(current) - if not id then - source._id = false - return nil - end - if node then - local pid = getID(node) - if not pid then - source._id = false - return nil - end - id = pid .. SPLIT_CHAR .. id - end - source._id = id - return id -end - ----添加关联的前进ID ----@param noders noders ----@param id node.id ----@param forwardID node.id ----@param info? node.info -local function pushForward(noders, id, forwardID, info) - if not id - or not forwardID - or forwardID == '' - or id == forwardID then - return - end - if not noders.forward[id] then - noders.forward[id] = forwardID - noders.finfo[id] = info - return - end - if noders.forward[id] == forwardID then - return - end - local forwards = noders.forwards[id] - if not forwards then - forwards = {} - noders.forwards[id] = forwards - end - if forwards[forwardID] ~= nil then - return - end - forwards[forwardID] = info or false - forwards[#forwards+1] = forwardID -end - ----添加关联的后退ID ----@param noders noders ----@param id node.id ----@param backwardID node.id ----@param info? node.info -local function pushBackward(noders, id, backwardID, info) - if not id - or not backwardID - or backwardID == '' - or id == backwardID then - return - end - if not noders.backward[id] then - noders.backward[id] = backwardID - noders.binfo[id] = info - return - end - if noders.backward[id] == backwardID then - return - end - local backwards = noders.backwards[id] - if not backwards then - backwards = {} - noders.backwards[id] = backwards - end - if backwards[backwardID] ~= nil then - return - end - backwards[backwardID] = info or false - backwards[#backwards+1] = backwardID -end - ----添加继承的关联ID ----@param noders noders ----@param id node.id ----@param extendID node.id -local function pushExtend(noders, id, extendID) - if not id - or not extendID - or extendID == '' - or id == extendID then - return - end - if not noders.extend[id] then - noders.extend[id] = extendID - return - end - if noders.extend[id] == extendID then - return - end - local extends = noders.extends[id] - if not extends then - extends = {} - noders.extends[id] = extends - end - if extends[extendID] ~= nil then - return - end - extends[extendID] = false - extends[#extends+1] = extendID -end - ----@class noder -local m = {} - -m.SPLIT_CHAR = SPLIT_CHAR -m.STRING_CHAR = STRING_CHAR -m.STRING_FIELD = STRING_FIELD -m.RETURN_INDEX = RETURN_INDEX -m.PARAM_INDEX = PARAM_INDEX -m.TABLE_KEY = TABLE_KEY -m.ANY_FIELD = ANY_FIELD -m.URI_CHAR = URI_CHAR -m.INDEX_FIELD = INDEX_FIELD -m.WEAK_TABLE_KEY = WEAK_TABLE_KEY -m.WEAK_ANY_FIELD = WEAK_ANY_FIELD - ---- 寻找doc的主体 ----@param obj parser.guide.object ----@return parser.guide.object -local function getDocStateWithoutCrossFunction(obj) - for _ = 1, 1000 do - local parent = obj.parent - if not parent then - return obj - end - if parent.type == 'doc' then - return obj - end - if parent.type == 'doc.type.function' then - return nil - end - obj = parent - end - error('guide.getDocState overstack') -end - -local dontPushSourceMap = util.arrayToHash { - 'str:', 'nil:', 'num:', 'int:', 'bool:' -} - ----添加关联单元 ----@param noders noders ----@param source parser.guide.object -function m.pushSource(noders, source, id) - id = id or getID(source) - if not id then - return - end - if dontPushSourceMap[id] then - return - end - if not noders.source[id] then - noders.source[id] = source - return - end - local sources = noders.sources[id] - if not sources then - sources = {} - noders.sources[id] = sources - end - sources[#sources+1] = source -end - -local DUMMY_FUNCTION = function () end - ----遍历关联单元 ----@param noders noders ----@param id node.id ----@return fun():parser.guide.object -function m.eachSource(noders, id) - local source = noders.source[id] - if not source then - return DUMMY_FUNCTION - end - local index - local sources = noders.sources[id] - return function () - if not index then - index = 0 - return source - end - if not sources then - return nil - end - index = index + 1 - return sources[index] - end -end - ----遍历forward ----@param noders noders ----@param id node.id ----@return fun():string, node.info -function m.eachForward(noders, id) - local forward = noders.forward[id] - if not forward then - return DUMMY_FUNCTION - end - local index - local forwards = noders.forwards[id] - return function () - if not index then - index = 0 - return forward, noders.finfo[id] - end - if not forwards then - return nil - end - index = index + 1 - local id = forwards[index] - local tag = forwards[id] - return id, tag - end -end - ----遍历backward ----@param noders noders ----@param id node.id ----@return fun():string, node.info -function m.eachBackward(noders, id) - local backward = noders.backward[id] - if not backward then - return DUMMY_FUNCTION - end - local index - local backwards = noders.backwards[id] - return function () - if not index then - index = 0 - return backward, noders.binfo[id] - end - if not backwards then - return nil - end - index = index + 1 - local id = backwards[index] - local tag = backwards[id] - return id, tag - end -end - ----遍历extend ----@param noders noders ----@param id node.id ----@return fun():string, node.info -function m.eachExtend(noders, id) - local extend = noders.extend[id] - if not extend then - return DUMMY_FUNCTION - end - local index - local extends = noders.extends[id] - return function () - if not index then - index = 0 - return extend - end - if not extends then - return nil - end - index = index + 1 - local id = extends[index] - return id - end -end - -local function bindValue(noders, source, id) - local value = source.value - if not value then - return - end - local valueID = getID(value) - if not valueID then - return - end - - local bindDocs = source.bindDocs - if source.type == 'getlocal' - or source.type == 'setlocal' then - if not config.get(guide.getUri(source), 'Lua.IntelliSense.traceLocalSet') then - return - end - bindDocs = source.node.bindDocs - end - if bindDocs and value.type ~= 'table' then - for _, doc in ipairs(bindDocs) do - if doc.type == 'doc.class' - or doc.type == 'doc.type' then - return - end - end - end - -- x = y : x -> y - pushForward(noders, id, valueID, INFO_REJECT_SET) - if not config.get(guide.getUri(source), 'Lua.IntelliSense.traceBeSetted') - and source.type ~= 'local' then - return - end - -- 参数/call禁止反向查找赋值 - local valueType = smatch(valueID, '^(.-:).') - if not valueType then - return - end - pushBackward(noders, valueID, id, INFO_DEEP_AND_REJECT_SET) -end - -local function compileCallParam(noders, call, sourceID) - if not sourceID then - return - end - if not call.args then - return - end - local node = call.node - local fixIndex = 0 - if call.node.special == 'pcall' then - fixIndex = 1 - node = call.args[1] - elseif call.node.special == 'xpcall' then - fixIndex = 2 - node = call.args[1] - end - local nodeID = getID(node) - if not nodeID then - return - end - local methodIndex = 0 - if node.type == 'getmethod' then - fixIndex = fixIndex + 1 - methodIndex = 1 - end - local eventNodeID - for firstIndex, callArg in ipairs(call.args) do - firstIndex = firstIndex - fixIndex - if firstIndex == 1 and callArg.type == 'string' then - if callArg[1] then - eventNodeID = sformat('%s%s%s' - , nodeID - , EVENT_ENUM - , callArg[1] - ) - end - end - if firstIndex > 0 and callArg.type == 'function' then - if callArg.args then - for secondIndex, funcParam in ipairs(callArg.args) do - local paramID = sformat('%s%s%s%s%s' - , nodeID - , PARAM_INDEX - , firstIndex + methodIndex - , PARAM_INDEX - , secondIndex - ) - pushForward(noders, getID(funcParam), paramID) - if eventNodeID then - local eventParamID = sformat('%s%s%s%s%s' - , eventNodeID - , PARAM_INDEX - , firstIndex + methodIndex - , PARAM_INDEX - , secondIndex - ) - pushForward(noders, getID(funcParam), eventParamID) - end - end - end - end - if callArg.type == 'table' then - local paramID = sformat('%s%s%s' - , nodeID - , PARAM_INDEX - , firstIndex + methodIndex - ) - pushForward(noders, getID(callArg), paramID) - end - end -end - -local function compileCallReturn(noders, call, sourceID, returnIndex) - if not sourceID then - return - end - local node = call.node - local nodeID = getID(node) - if not nodeID then - return - end - local callID = getID(call) - if not callID then - return - end - -- 将setmetatable映射到 param1 以及 param2.__index 上 - if node.special == 'setmetatable' then - local tblID = getID(call.args and call.args[1]) - local metaID = getID(call.args and call.args[2]) - local indexID - if metaID then - indexID = sformat('%s%s%s' - , metaID - , STRING_FIELD - , '__index' - ) - end - pushForward(noders, sourceID, tblID) - pushForward(noders, sourceID, indexID, INFO_META_INDEX) - pushBackward(noders, tblID, sourceID) - --pushBackward(noders, indexID, callID) - return - end - if node.special == 'require' then - local arg1 = call.args and call.args[1] - if arg1 and arg1.type == 'string' then - noders.require[sourceID] = arg1[1] - end - pushBackward(noders, callID, sourceID, INFO_DEEP) - return - end - if node.special == 'pcall' - or node.special == 'xpcall' then - local index = returnIndex - 1 - if index <= 0 then - return - end - local funcID = call.args and getID(call.args[1]) - if not funcID then - return - end - local pfuncXID = sformat('%s%s%s' - , funcID - , RETURN_INDEX - , index - ) - pushForward(noders, sourceID, pfuncXID) - pushBackward(noders, pfuncXID, sourceID, INFO_DEEP) - return - end - local funcXID = sformat('%s%s%s' - , nodeID - , RETURN_INDEX - , returnIndex - ) - noders.call[sourceID] = call - pushForward(noders, sourceID, funcXID) - pushBackward(noders, funcXID, sourceID, INFO_DEEP) -end - -local specialMap = util.arrayToHash { - 'require', 'dofile', 'loadfile', - 'rawset', 'rawget', 'setmetatable', -} - -local compileNodeMap -compileNodeMap = util.switch() - : case 'string' - : call(function (noders, id, source) - pushForward(noders, id, 'str:') - end) - : case 'boolean' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:boolean') - end) - : case 'number' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:number') - end) - : case 'integer' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:integer') - end) - : case 'nil' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:nil') - end) - : case 'doc.type' - : call(function (noders, id, source) - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.parent.type ~= 'funcargs' - and not src.dummy then - pushForward(noders, getID(src), id) - end - end - end - for _, typeUnit in ipairs(source.types) do - local unitID = getID(typeUnit) - pushForward(noders, id, unitID) - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.parent.type ~= 'funcargs' - and not src.dummy then - pushBackward(noders, unitID, getID(src)) - end - end - end - end - end) - : case 'doc.type.table' - : call(function (noders, id, source) - if source.node then - pushForward(noders, id, getID(source.node), INFO_CLASS_TO_EXNTENDS) - end - if source.tkey then - local keyID = id .. TABLE_KEY - pushForward(noders, keyID, getID(source.tkey)) - end - if source.tvalue then - local valueID = id .. ANY_FIELD - pushForward(noders, valueID, getID(source.tvalue)) - end - end) - : case 'doc.type.ltable' - : call(function (noders, id, source) - local firstField = source.fields[1] - if not firstField then - return - end - local keyID = id .. WEAK_TABLE_KEY - local valueID = id .. WEAK_ANY_FIELD - pushForward(noders, keyID, 'dn:string') - pushForward(noders, valueID, getID(firstField.extends)) - for _, field in ipairs(source.fields) do - local fname = field.name[1] - local extendsID - if type(fname) == 'string' then - extendsID = sformat('%s%s%s' - , id - , STRING_FIELD - , fname - ) - else - extendsID = sformat('%s%s%s' - , id - , SPLIT_CHAR - , fname - ) - end - pushForward(noders, extendsID, getID(field)) - pushForward(noders, extendsID, getID(field.extends)) - end - end) - : case 'doc.type.array' - : call(function (noders, id, source) - if source.node then - local nodeID = id .. ANY_FIELD - pushForward(noders, nodeID, getID(source.node)) - end - local keyID = id .. TABLE_KEY - pushForward(noders, keyID, 'dn:integer') - end) - : case 'doc.alias' - : call(function (noders, id, source) - pushForward(noders, getID(source.alias), getID(source.extends)) - end) - : case 'doc.class' - : call(function (noders, id, source) - pushForward(noders, id, getID(source.class)) - pushForward(noders, getID(source.class), id) - if source.extends then - for _, ext in ipairs(source.extends) do - pushExtend(noders, id, getID(ext)) - end - end - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.parent.type ~= 'funcargs' - and src.type ~= 'setmethod' - and not src.dummy then - pushForward(noders, getID(src), id) - pushForward(noders, id, getID(src)) - end - end - end - for _, field in ipairs(source.fields) do - local key = field.field[1] - if key then - local keyID - if type(key) == 'string' then - keyID = sformat('%s%s%s' - , id - , STRING_FIELD - , key - ) - local eventName = getFieldEventName(field) - if eventName then - keyID = sformat('%s%s%s' - , keyID - , EVENT_ENUM - , eventName - ) - end - else - keyID = sformat('%s%s%s' - , id - , SPLIT_CHAR - , key - ) - end - pushForward(noders, keyID, getID(field.field)) - pushForward(noders, getID(field.field), keyID) - pushForward(noders, keyID, getID(field.extends)) - end - end - end) - : case 'doc.module' - : call(function (noders, id, source) - if not source.module then - return - end - for _, src in ipairs(source.bindSources) do - if guide.isSet(src) then - local sourceID = getID(src) - if sourceID then - noders.require[sourceID] = source.module - end - end - end - end) - : case 'doc.param' - : call(function (noders, id, source) - pushForward(noders, id, getID(source.extends)) - for _, src in ipairs(source.bindSources) do - if src.type == 'local' and src.parent.type == 'in' then - pushForward(noders, getID(src), id) - end - end - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.type == 'function' and src.args then - for _, arg in ipairs(src.args) do - if arg[1] == source.param[1] then - pushForward(noders, getID(arg), id) - end - end - end - end - end - end) - : case 'doc.vararg' - : call(function (noders, id, source) - pushForward(noders, getID(source), getID(source.vararg)) - end) - : case 'doc.see' - : call(function (noders, id, source) - local nameID = getID(source.name) - local classID = sgsub(nameID, '^dsn:', 'dn:') - pushForward(noders, nameID, classID) - if source.field then - local fieldID = getID(source.field) - local fieldClassID = sgsub(fieldID, '^dsn:', 'dn:') - pushForward(noders, fieldID, fieldClassID) - end - end) - : case 'call' - : call(function (noders, id, source) - if source.parent.type ~= 'select' then - compileCallReturn(noders, source, id, 1) - end - compileCallParam(noders, source, id) - end) - : case 'select' - : call(function (noders, id, source) - if source.vararg.type == 'call' then - local call = source.vararg - compileCallReturn(noders, call, id, source.sindex) - end - if source.vararg.type == 'varargs' then - pushForward(noders, id, getID(source.vararg)) - end - end) - : case 'doc.type.function' - : call(function (noders, id, source) - if source.args then - for index, param in ipairs(source.args) do - local indexID = sformat('%s%s%s' - , id - , PARAM_INDEX - , index - ) - pushForward(noders, indexID, getID(param.extends)) - end - end - if source.returns then - for index, rtn in ipairs(source.returns) do - local returnID = sformat('%s%s%s' - , id - , RETURN_INDEX - , index - ) - pushForward(noders, returnID, getID(rtn)) - end - end - -- @type fun(x: T):T 的情况 - local docType = getDocStateWithoutCrossFunction(source) - if docType and docType.type == 'doc.type' then - guide.eachSourceType(source, 'doc.type.name', function (typeName) - if typeName.typeGeneric then - source.isGeneric = true - return false - end - end) - end - end) - : case 'doc.type.name' - : call(function (noders, id, source) - local uri = guide.getUri(source) - collector:subscribe(uri, id, noders) - end) - : case 'doc.class.name' - : case 'doc.alias.name' - : call(function (noders, id, source) - local uri = guide.getUri(source) - collector:subscribe(uri, id, noders) - - local defID = 'def:' .. id - collector:subscribe(uri, defID, noders) - - local defAnyID = 'def:dn:' - collector:subscribe(uri, defAnyID, noders) - end) - : case 'function' - : call(function (noders, id, source) - local hasDocReturn - -- 检查 luadoc - if source.bindDocs then - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.return' then - hasDocReturn = true - end - if doc.type == 'doc.vararg' then - if source.args then - for _, param in ipairs(source.args) do - if param.type == '...' then - pushForward(noders, getID(param), getID(doc)) - end - end - end - end - if doc.type == 'doc.generic' then - source.isGeneric = true - end - if doc.type == 'doc.overload' then - pushForward(noders, id, getID(doc.overload)) - end - end - end - if source.args then - local parent = source.parent - local parentID = guide.isSet(parent) and getID(parent) - for i, arg in ipairs(source.args) do - if arg[1] == 'self' then - goto CONTINUE - end - local indexID = sformat('%s%s%s' - , id - , PARAM_INDEX - , i - ) - pushForward(noders, indexID, getID(arg)) - if arg.type ~= 'local' then - for j = i + 1, i + 10 do - pushForward(noders, sformat('%s%s%s' - , id - , PARAM_INDEX - , j - ), getID(arg)) - end - end - ::CONTINUE:: - end - end - -- 检查实体返回值 - if source.returns and not hasDocReturn then - for _, rtn in ipairs(source.returns) do - for index, rtnObj in ipairs(rtn) do - local returnID = sformat('%s%s%s' - , id - , RETURN_INDEX - , index - ) - pushForward(noders, returnID, getID(rtnObj)) - if config.get(guide.getUri(source), 'Lua.IntelliSense.traceReturn') then - pushBackward(noders, getID(rtnObj), returnID, INFO_DEEP_AND_DONT_CROSS) - end - end - end - end - end) - : case 'table' - : call(function (noders, id, source) - local firstField = source[1] - if firstField then - if firstField.type == 'varargs' then - local keyID = id .. TABLE_KEY - local valueID = id .. ANY_FIELD - source.array = firstField - pushForward(noders, keyID, 'dn:integer') - pushForward(noders, valueID, getID(firstField)) - else - local keyID = id .. WEAK_TABLE_KEY - local valueID = id .. WEAK_ANY_FIELD - if firstField.type == 'tablefield' then - pushForward(noders, keyID, 'dn:string') - pushForward(noders, valueID, getID(firstField.value)) - elseif firstField.type == 'tableindex' then - pushForward(noders, keyID, getID(firstField.index)) - pushForward(noders, valueID, getID(firstField.value)) - elseif firstField.type == 'tableexp' then - pushForward(noders, keyID, 'dn:integer') - pushForward(noders, valueID, getID(firstField)) - end - end - end - local parent = source.parent - if guide.isSet(parent) then - pushForward(noders, id, getID(parent)) - end - end) - : case 'loop' - : call(function (noders, id, source) - local loc = source.loc - if loc then - pushForward(noders, getID(loc), 'dn:integer') - end - end) - : case 'in' - : call(function (noders, id, source) - local keys = source.keys - ---@type parser.guide.object[] - local exps = source.exps - if not keys or not exps then - return - end - local node = exps[1] - local param1 = exps[2] - local param2 = exps[3] - if node.type == 'call' then - if not param1 then - param1 = { - type = 'select', - dummy = true, - sindex = 2, - start = node.start, - finish = node.finish, - vararg = node, - parent = source, - } - compileCallReturn(noders, node, getID(param1), 2) - if not param2 then - param2 = { - type = 'select', - dummy = true, - sindex = 3, - start = node.start, - finish = node.finish, - vararg = node, - parent = source, - } - compileCallReturn(noders, node, getID(param2), 3) - end - end - end - local call = { - type = 'call', - dummy = true, - start = source.keyword[3], - finish = exps[#exps].finish, - node = node, - args = { param1, param2 }, - parent = source, - } - for i = 1, #keys do - compileCallReturn(noders, call, getID(keys[i]), i) - end - end) - : case 'main' - : call(function (noders, id, source) - if source.returns then - for _, rtn in ipairs(source.returns) do - local rtnObj = rtn[1] - if rtnObj then - pushForward(noders, 'mainreturn', getID(rtnObj), INFO_REJECT_SET) - pushBackward(noders, getID(rtnObj), 'mainreturn', INFO_DEEP_AND_REJECT_SET) - end - end - end - end) - : case 'doc.return' - : call(function (noders, id, source) - if not source.bindSources then - return - end - for _, rtn in ipairs(source.returns) do - for _, src in ipairs(source.bindSources) do - if src.type == 'function' then - local fullID = sformat('%s%s%s' - , getID(src) - , RETURN_INDEX - , rtn.returnIndex - ) - pushForward(noders, fullID, getID(rtn)) - for _, typeUnit in ipairs(rtn.types) do - pushBackward(noders, getID(typeUnit), fullID, INFO_DEEP_AND_DONT_CROSS) - end - end - end - end - end) - : case 'generic.closure' - : call(function (noders, id, source) - for i, rtn in ipairs(source.returns) do - local closureID = sformat('%s%s%s' - , id - , RETURN_INDEX - , i - ) - local returnID = getID(rtn) - pushForward(noders, closureID, returnID) - end - end) - : case 'generic.value' - : call(function (noders, id, source) - local proto = source.proto - local closure = source.closure - local upvalues = closure.upvalues - if proto.type == 'doc.type.name' then - local key = proto[1] - if upvalues[key] then - for _, paramID in ipairs(upvalues[key]) do - pushForward(noders, id, paramID) - end - end - end - local f = compileNodeMap[proto.type] - if f then - f(noders, id, source) - end - end) - : getMap() - ----@param noders noders ----@param source parser.guide.object ----@return parser.guide.object[] -function m.compileNode(noders, source) - if source._noded then - return - end - source._noded = true - m.pushSource(noders, source) - local id = getID(source) - bindValue(noders, source, id) - - if id and specialMap[source.special] then - noders.skip[id] = true - end - - local f = compileNodeMap[source.type] - if f then - f(noders, id, source) - end - - if id and ssub(id, 1, 2) == 'g:' then - local uri = guide.getUri(source) - collector:subscribe(uri, id, noders) - if guide.isSet(source) - -- local t = Global --> t: g:.Global - and source.type ~= 'local' - and source.type ~= 'setlocal' then - - local defID = 'def:' .. id - collector:subscribe(uri, defID, noders) - - local fieldID = m.getLastID(id) - if fieldID then - local defNodeID = 'field:' .. fieldID - collector:subscribe(uri, defNodeID, noders) - end - - if guide.isGlobal(source) then - local defAnyID = 'def:g:' - collector:subscribe(uri, defAnyID, noders) - end - end - end -end - ----根据ID来获取第一个节点的ID ----@param id string ----@return string -function m.getFirstID(id) - local firstID, count = smatch(id, FIRST_REGEX) - if count == 0 then - return nil - end - if firstID == '' then - return nil - end - return firstID -end - ----根据ID来获取第一个节点的ID或field ----@param id string ----@return string -function m.getHeadID(id) - local headID, count = smatch(id, HEAD_REGEX) - if count == 0 then - return nil - end - if headID == '' then - return nil - end - return headID -end - ----根据ID来获取上个节点的ID ----@param id string ----@return string -function m.getLastID(id) - local lastID, count = sgsub(id, LAST_REGEX, '') - if count == 0 then - return nil - end - if lastID == '' then - return nil - end - return lastID -end - -function m.getFieldID(id) - local fieldID = smatch(id, LAST_REGEX) - return fieldID -end - ----获取ID的长度 ----@param id string ----@return integer -function m.getIDLength(id) - if not id then - return 0 - end - local _, count = sgsub(id, SPLIT_CHAR, SPLIT_CHAR) - return count + 1 -end - ----测试id是否包含field,如果遇到函数调用则中断 ----@param id string ----@return boolean -function m.hasField(id) - local firstID = m.getFirstID(id) - if firstID == id or not firstID then - return false - end - local nextChar = ssub(id, #firstID + 1, #firstID + 1) - if nextChar ~= SPLIT_CHAR then - return false - end - local next2Char = ssub(id, #firstID + 2, #firstID + 2) - if next2Char == RETURN_INDEX - or next2Char == PARAM_INDEX then - return false - end - return true -end - ----把形如 `@file:\\\XXXXX@gv:1|1`拆分成uri与id ----@param id string ----@return uri? string ----@return string id -function m.getUriAndID(id) - local uri, newID = smatch(id, URI_REGEX) - return uri, newID -end - ----是否是普通的field,例如数字或字符串,而不是函数返回值等 ----@param field any -function m.isCommonField(field) - if not field then - return false - end - if ssub(field, 1, #RETURN_INDEX) == RETURN_INDEX then - return false - end - return true -end - ----是否是普通的field,例如数字或字符串,而不是函数返回值等 -function m.hasCall(field) - if not field then - return false - end - if sfind(field, RETURN_INDEX, 1, true) then - return true - end - return false -end - -function m.isGlobalID(id) - return ssub(id, 1, 2) == 'g:' - or ssub(id, 1, 3) == 'dn:' -end - ----获取source的ID ----@param source parser.guide.object ----@return string -function m.getID(source) - return getID(source) -end - ----获取source的key ----@param source parser.guide.object ----@return string -function m.getKey(source) - return getKey(source) -end - ----清除临时id(用于泛型的临时对象) ----@param noders noders ----@param id string -function m.removeID(noders, id) - if not id then - return - end - for _, t in next, noders do - t[id] = nil - end -end - ----寻找doc的主体 ----@param doc parser.guide.object -function m.getDocState(doc) - return getDocStateWithoutCrossFunction(doc) -end - ----@param noders noders ----@return fun():node.id -function m.eachID(noders) - return next, noders.source -end - -m.getFieldEventName = getFieldEventName - ----获取对象的noders ----@param source parser.guide.object ----@return noders -function m.getNoders(source) - local root = guide.getRoot(source) - if not root._noders then - ---@type noders - root._noders = { - source = {}, - sources = {}, - forward = {}, - finfo = {}, - forwards = {}, - backward = {}, - binfo = {}, - backwards = {}, - extend = {}, - extends = {}, - call = {}, - require = {}, - skip = {}, - } - end - return root._noders -end - ----获取对象的noders ----@param uri uri ----@return noders -function m.getNodersByUri(uri) - local state = files.getState(uri) - if not state then - return nil - end - return m.getNoders(state.ast) -end - ----编译整个文件的node ----@param source parser.guide.object ----@return table -function m.compileAllNodes(source) - local root = guide.getRoot(source) - local noders = m.getNoders(source) - if root._initedNoders then - return noders - end - root._initedNoders = true - if not root._compiledGlobals then - collector:dropUri(guide.getUri(root)) - end - root._compiledGlobals = true - --log.debug('compileNodes:', guide.getUri(root)) - guide.eachSource(root, function (src) - m.compileNode(noders, src) - end) - --log.debug('compileNodes finish:', guide.getUri(root)) - return noders -end - -local partNodersMap = util.switch() - : case 'local' - : call(function (noders, source) - local refs = source.ref - if refs then - for i = 1, #refs do - local ref = refs[i] - m.compilePartNodes(noders, ref) - end - end - end) - : case 'setlocal' - : case 'getlocal' - : call(function (noders, source) - m.compilePartNodes(noders, source.node) - - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end - - local parent = source.parent - if parent.value == source then - m.compilePartNodes(noders, parent) - end - - if parent.type == 'call' then - local node = parent.node - if node.special == 'rawset' - or node.special == 'rawget' then - m.compilePartNodes(noders, parent) - end - end - end) - : case 'setfield' - : case 'getfield' - : case 'setmethod' - : case 'getmethod' - : call(function (noders, source) - local node = source.node - m.compilePartNodes(noders, node) - - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end - - local parent = source.parent - if parent.value == source then - m.compilePartNodes(noders, parent) - end - end) - : case 'setglobal' - : case 'getglobal' - : call(function (noders, source) - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end - - local parent = source.parent - if parent.value == source then - m.compilePartNodes(noders, parent) - end - - if parent.type == 'call' then - local node = parent.node - if node.special == 'rawset' - or node.special == 'rawget' then - m.compilePartNodes(noders, parent) - end - end - end) - : case 'label' - : call(function (noders, source) - local refs = source.ref - if not refs then - return - end - for i = 1, #refs do - local ref = refs[i] - m.compilePartNodes(noders, ref) - end - end) - : case 'goto' - : call(function (noders, source) - m.compilePartNodes(noders, source.node) - end) - : case 'table' - : call(function (noders, source) - for i = 1, #source do - local field = source[i] - m.compilePartNodes(noders, field) - end - end) - : case 'tablefield' - : case 'tableindex' - : call(function (noders, source) - m.compilePartNodes(noders, source.parent) - end) - : getMap() - ----编译Class的node ----@param noders noders ----@param source parser.guide.object ----@return table -function m.compilePartNodes(noders, source) - if not source then - return - end - if source._noded then - return - end - m.compileNode(noders, source) - local f = partNodersMap[source.type] - if f then - f(noders, source) - end -end - ----编译全局变量的node ----@param root parser.guide.object ----@return table -function m.compileGlobalNodes(root) - if root._initedNoders then - return - end - if not root._compiledGlobals then - collector:dropUri(guide.getUri(root)) - end - root._compiledGlobals = true - local noders = m.getNoders(root) - local env = guide.getENV(root) - - m.compilePartNodes(noders, env) - - local docs = root.docs - guide.eachSourceTypes(docs, { - 'doc.class.name', - 'doc.alias.name', - 'doc.type.name', - }, function (doc) - m.compileNode(noders, doc) - end) -end - -for uri in files.eachFile() do - local state = files.getState(uri) - if state then - m.compileGlobalNodes(state.ast) - end -end - -files.watch(function (ev, uri) - if ev == 'update' then - local state = files.getState(uri) - if state then - m.compileGlobalNodes(state.ast) - end - end - if ev == 'remove' then - collector:dropUri(uri) - end -end) - -return m diff --git a/script/core/reference.lua b/script/core/reference.lua index 5e4a4cbf..4c9c193d 100644 --- a/script/core/reference.lua +++ b/script/core/reference.lua @@ -1,4 +1,3 @@ -local searcher = require 'core.searcher' local guide = require 'parser.guide' local files = require 'files' local vm = require 'vm' @@ -52,6 +51,7 @@ local accept = { ['doc.alias.name'] = true, } +---@async return function (uri, position) local ast = files.getState(uri) if not ast then @@ -65,23 +65,10 @@ return function (uri, position) local metaSource = vm.isMetaFile(uri) - local refs = vm.getAllRefs(source) - local values = {} - for _, src in ipairs(refs) do - local value = searcher.getObjectValue(src) - if value and value ~= src and guide.isLiteral(value) then - values[value] = true - end - end + local refs = vm.getRefs(source) local results = {} for _, src in ipairs(refs) do - if src.dummy then - goto CONTINUE - end - if values[src] then - goto CONTINUE - end local root = guide.getRoot(src) if not root then goto CONTINUE @@ -89,34 +76,53 @@ return function (uri, position) if not metaSource and vm.isMetaFile(root.uri) then goto CONTINUE end - if ( src.type == 'doc.class.name' - or src.type == 'doc.type.name' - or src.type == 'doc.extends.name' - ) - and source.type ~= 'doc.type.name' - and source.type ~= 'doc.class.name' then + if src.type == 'self' then goto CONTINUE end - if src.type == 'setfield' - or src.type == 'getfield' - or src.type == 'tablefield' then - src = src.field - elseif src.type == 'setindex' - or src.type == 'getindex' - or src.type == 'tableindex' then + src = src.field or src.method or src + if src.type == 'getindex' + or src.type == 'setindex' + or src.type == 'tableindex' then src = src.index - elseif src.type == 'getmethod' - or src.type == 'setmethod' then - src = src.method - elseif src.type == 'table' and src.parent.type ~= 'return' then + if not src then + goto CONTINUE + end + if not guide.isLiteral(src) then + goto CONTINUE + end + else + if guide.isLiteral(src) and src.type ~= 'function' then + goto CONTINUE + end + end + if src.type == 'doc.class' then + src = src.class + end + if src.type == 'doc.alias' then + src = src.alias + end + if src.type == 'doc.class.name' + or src.type == 'doc.alias.name' + or src.type == 'doc.type.name' + or src.type == 'doc.extends.name' then + if source.type ~= 'doc.type.name' + and source.type ~= 'doc.class.name' + and source.type ~= 'doc.extends.name' + and source.type ~= 'doc.see.name' then + goto CONTINUE + end + end + if src.type == 'doc.generic.name' then goto CONTINUE end - if not src then + if src.type == 'doc.param' then goto CONTINUE end + results[#results+1] = { target = src, uri = root.uri, + source = source, } ::CONTINUE:: end diff --git a/script/core/rename.lua b/script/core/rename.lua index 69021197..ec21e87c 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -1,11 +1,9 @@ local files = require 'files' local vm = require 'vm' -local proto = require 'proto' -local define = require 'proto.define' local util = require 'utility' local findSource = require 'core.find-source' local guide = require 'parser.guide' -local noder = require 'core.noder' +local globalMgr = require 'vm.global-manager' local Forcing @@ -181,42 +179,64 @@ local function ofFieldThen(key, src, newname, callback) end end +---@async local function ofField(source, newname, callback) - local key = guide.getKeyName(source) - for _, src in ipairs(vm.getAllRefs(source)) do - ofFieldThen(key, src, newname, callback) + local key = guide.getKeyName(source) + local refs = vm.getRefs(source) + for _, ref in ipairs(refs) do + ofFieldThen(key, ref, newname, callback) end end +---@async local function ofGlobal(source, newname, callback) local key = guide.getKeyName(source) - for _, src in ipairs(vm.getAllRefs(source)) do - ofFieldThen(key, src, newname, callback) + local global = globalMgr.getGlobal('variable', key) + if not global then + return + end + local uri = guide.getUri(source) + for _, set in ipairs(global:getSets(uri)) do + ofFieldThen(key, set, newname, callback) + end + for _, get in ipairs(global:getGets(uri)) do + ofFieldThen(key, get, newname, callback) end end +---@async local function ofLabel(source, newname, callback) - for _, src in ipairs(vm.getAllRefs(source)) do + for _, src in ipairs(vm.getRefs(source)) do callback(src, src.start, src.finish, newname) end end +---@async local function ofDocTypeName(source, newname, callback) local oldname = source[1] - for _, doc in ipairs(vm.getAllRefs(source)) do - if doc.type == 'doc.class.name' - or doc.type == 'doc.type.name' - or doc.type == 'doc.alias.name' then - if oldname == doc[1] then - callback(doc, doc.start, doc.finish, newname) - end + local global = globalMgr.getGlobal('type', oldname) + if not global then + return + end + local uri = guide.getUri(source) + for _, doc in ipairs(global:getSets(uri)) do + if doc.type == 'doc.class' then + callback(doc, doc.class.start, doc.class.finish, newname) + end + if doc.type == 'doc.alias' then + callback(doc, doc.alias.start, doc.alias.finish, newname) + end + end + for _, doc in ipairs(global:getGets(uri)) do + if doc.type == 'doc.type.name' then + callback(doc, doc.start, doc.finish, newname) end end end local function ofDocParamName(source, newname, callback) callback(source, source.start, source.finish, newname) - local doc = noder.getDocState(source) + local doc = source.parent if doc.bindSources then for _, src in ipairs(doc.bindSources) do if src.type == 'local' @@ -233,6 +253,7 @@ local function ofDocParamName(source, newname, callback) end end +---@async local function rename(source, newname, callback) if source.type == 'label' or source.type == 'goto' then @@ -329,6 +350,7 @@ local accept = { local m = {} +---@async function m.rename(uri, pos, newname) if not newname then return nil diff --git a/script/core/searcher.lua b/script/core/searcher.lua deleted file mode 100644 index eb70c349..00000000 --- a/script/core/searcher.lua +++ /dev/null @@ -1,1350 +0,0 @@ -local noder = require 'core.noder' -local guide = require 'parser.guide' -local files = require 'files' -local generic = require 'core.generic' -local rpath = require 'workspace.require-path' -local vm = require 'vm.vm' -local collector = require 'core.collector' 'searcher' -local util = require 'utility' - -local TRACE = TRACE -local FOOTPRINT = FOOTPRINT -local TEST = TEST -local log = log -local select = select -local tostring = tostring -local next = next -local error = error -local type = type -local setmetatable = setmetatable -local ipairs = ipairs -local tconcat = table.concat -local ssub = string.sub -local sfind = string.find -local sformat = string.format - -local getUri = guide.getUri - -local getState = files.getState - -local getNoders = noder.getNoders -local getID = noder.getID -local getLastID = noder.getLastID -local removeID = noder.removeID -local getNodersByUri = noder.getNodersByUri -local getFirstID = noder.getFirstID -local getHeadID = noder.getHeadID -local eachForward = noder.eachForward -local getUriAndID = noder.getUriAndID -local eachBackward = noder.eachBackward -local eachExtend = noder.eachExtend -local eachSource = noder.eachSource -local compileAllNodes = noder.compileAllNodes -local hasCall = noder.hasCall - -local SPLIT_CHAR = noder.SPLIT_CHAR -local RETURN_INDEX = noder.RETURN_INDEX -local TABLE_KEY = noder.TABLE_KEY -local STRING_CHAR = noder.STRING_CHAR -local STRING_FIELD = noder.STRING_FIELD -local WEAK_TABLE_KEY = noder.WEAK_TABLE_KEY -local ANY_FIELD = noder.ANY_FIELD -local WEAK_ANY_FIELD = noder.WEAK_ANY_FIELD - -_ENV = nil - -local ignoredSources = { - ['int:'] = true, - ['num:'] = true, - ['str:'] = true, - ['bool:'] = true, - ['nil:'] = true, -} - -local ignoredIDs = { - ['dn:unknown'] = true, - ['dn:nil'] = true, - ['dn:any'] = true, - ['dn:boolean'] = true, - ['dn:table'] = true, - ['dn:number'] = true, - ['dn:integer'] = true, - ['dn:userdata'] = true, - ['dn:lightuserdata'] = true, - ['dn:function'] = true, - ['dn:thread'] = true, -} - ----@class searcher -local m = {} - ----@alias guide.searchmode '"ref"'|'"def"'|'"field"'|'"allref"'|'"alldef"'|'"allfield"' - -local pushDefResultsMap = util.switch() - : case 'local' - : case 'setlocal' - : case 'setglobal' - : call(function (source, status) - if source.type ~= 'local' then - source = source.node - end - if source[1] == 'self' - and source.parent.type == 'funcargs' then - local func = source.parent.parent - if status.source.start < func.start - or status.source.start > func.finish then - return false - end - end - return true - end) - : case 'label' - : case 'setfield' - : case 'setmethod' - : case 'setindex' - : case 'tableindex' - : case 'tablefield' - : case 'tableexp' - : case 'function' - : case 'table' - : case 'doc.class.name' - : case 'doc.alias.name' - : case 'doc.field.name' - : case 'doc.type.enum' - : case 'doc.resume' - : case 'doc.param' - : case 'doc.type.array' - : case 'doc.type.table' - : case 'doc.type.ltable' - : case 'doc.type.field' - : case 'doc.type.function' - : call(function (source, status) - return true - end) - : case 'doc.type.name' - : call(function (source, status) - return source.typeGeneric ~= nil - end) - : case 'call' - : call(function (source, status) - if source.node.special == 'rawset' then - return true - end - end) - : getMap() - -local pushRefResultsMap = util.switch() - : case 'local' - : case 'setlocal' - : case 'getlocal' - : case 'setglobal' - : case 'getglobal' - : case 'label' - : case 'goto' - : case 'setfield' - : case 'getfield' - : case 'setmethod' - : case 'getmethod' - : case 'setindex' - : case 'getindex' - : case 'tableindex' - : case 'tablefield' - : case 'tableexp' - : case 'function' - : case 'table' - : case 'string' - : case 'boolean' - : case 'number' - : case 'integer' - : case 'nil' - : case 'doc.class.name' - : case 'doc.type.name' - : case 'doc.alias.name' - : case 'doc.extends.name' - : case 'doc.field.name' - : case 'doc.type.enum' - : case 'doc.resume' - : case 'doc.type.array' - : case 'doc.type.table' - : case 'doc.type.ltable' - : case 'doc.type.field' - : case 'doc.type.function' - : call(function (source, status) - return true - end) - : case 'call' - : call(function (source, status) - if source.node.special == 'rawset' - or source.node.special == 'rawget' then - return true - end - end) - : getMap() - ----添加结果 ----@param status guide.status ----@param mode guide.searchmode ----@param source parser.guide.object ----@param force boolean -local function pushResult(status, mode, source, force) - if not source then - return false - end - local results = status.results - local mark = status.rmark - if mark[source] then - return true - end - mark[source] = true - if force then - results[#results+1] = source - return true - end - - if mode == 'def' - or mode == 'alldef' then - local f = pushDefResultsMap[source.type] - if f and f(source, status) then - results[#results+1] = source - return true - end - elseif mode == 'ref' - or mode == 'field' - or mode == 'allfield' - or mode == 'allref' then - local f = pushRefResultsMap[source.type] - if f and f(source, status) then - results[#results+1] = source - return true - end - end - - --local parent = source.parent - --if parent.type == 'return' then - -- if source ~= status.source then - -- results[#results+1] = source - -- return true - -- end - --end - - return false -end - ----@param obj parser.guide.object ----@return parser.guide.object? -function m.getObjectValue(obj) - while obj.type == 'paren' do - obj = obj.exp - if not obj then - return nil - end - end - if obj.type == 'boolean' - or obj.type == 'number' - or obj.type == 'integer' - or obj.type == 'string' then - return obj - end - if obj.value then - return obj.value - end - if obj.type == 'field' - or obj.type == 'method' then - return obj.parent and obj.parent.value - end - if obj.type == 'call' then - if obj.node.special == 'rawset' then - return obj.args and obj.args[3] - else - return obj - end - end - if obj.type == 'select' then - return obj - end - return nil -end - -local strs = {} -local function footprint(status, ...) - if TRACE then - log.debug(...) - end - if FOOTPRINT then - local n = select('#', ...) - for i = 1, n do - strs[i] = tostring(select(i, ...)) - end - status.footprint[#status.footprint+1] = tconcat(strs, '\t', 1, n) - end -end - -local function checkCache(status, uri, expect, mode) - local cache = status.cache - local fileCache = cache[uri] - local results = fileCache[expect] - if results then - for i = 1, #results do - local res = results[i] - pushResult(status, mode, res, true) - end - return true - end - return false, function () - fileCache[expect] = status.results - if mode == 'def' - or mode == 'alldef' then - return - end - for id in next, status.ids do - fileCache[id] = status.results - end - end -end - -local function stop(status, msg) - if TEST then - if FOOTPRINT then - log.debug(status.mode) - log.debug(tconcat(status.footprint, '\n')) - end - error(msg) - else - log.warn(msg) - if FOOTPRINT then - FOOTPRINT = false - log.error(status.mode) - log.debug(tconcat(status.footprint, '\n')) - end - return - end -end - -local function isCallID(field) - if not field then - return false - end - if ssub(field, 1, 2) == RETURN_INDEX then - return true - end - return false -end - -local genercCache = { - mark = {}, - genericCallArgs = {}, - closureCache = {}, -} - -local function flushGeneric() - --清除来自泛型的临时对象 - for _, closure in next, genercCache.closureCache do - if closure then - local noders = getNoders(closure) - removeID(noders, getID(closure)) - local values = closure.values - for i = 1, #values do - local value = values[i] - removeID(noders, getID(value)) - end - end - end - genercCache.mark = {} - genercCache.closureCache = {} - genercCache.genericCallArgs = {} -end - -files.watch(function (ev) - if ev == 'version' then - flushGeneric() - end -end) - -local nodersMapMT = {__index = function (self, uri) - local noders = getNodersByUri(uri) - self[uri] = noders or false - return noders -end} - -local uriMapMT = {__index = function (self, uri) - local t = {} - self[uri] = t - return t -end} - -function m.searchRefsByID(status, suri, expect, mode) - local state = getState(suri) - if not state then - return - end - local searchStep - - status.id = expect - - local callStack = status.callStack - local ids = status.ids - local dontCross = 0 - ---@type table<uri, noders> - local nodersMap = setmetatable({}, nodersMapMT) - local frejectMap = setmetatable({}, uriMapMT) - local brejectMap = setmetatable({}, uriMapMT) - local slockMap = setmetatable({}, uriMapMT) - local elockMap = setmetatable({}, uriMapMT) - local ecallMap = setmetatable({}, uriMapMT) - local slockGlobalMap = slockMap['@global'] - - compileAllNodes(state.ast) - - local function lockExpanding(elock, ecall, id, field) - if not field then - field = '' - end - local call = callStack[#callStack] - local locked = elock[id] - local called = ecall[id] - if locked and called == call then - if #locked <= #field then - if ssub(field, -#locked) == locked then - footprint(status, 'elocked:', id, locked, field) - return false - end - end - end - elock[id] = field - ecall[id] = call - return true - end - - local function releaseExpanding(elock, ecall, id, field) - elock[id] = nil - ecall[id] = nil - end - - local function search(uri, id, field) - local firstID = getFirstID(id) - if ignoredIDs[firstID] and (field or firstID ~= id) then - return - end - - footprint(status, 'search:', id, field) - searchStep(uri, id, field) - footprint(status, 'pop:', id, field) - end - - local function splitID(uri, id, field) - if field then - return - end - local leftID = '' - local rightID - - while true do - local firstID = getHeadID(rightID or id) - if not firstID or firstID == id then - break - end - leftID = leftID .. firstID - if leftID == id then - break - end - rightID = ssub(id, #leftID + 1) - search(uri, leftID, rightID) - local isCall = isCallID(firstID) - if isCall then - break - end - end - end - - local function searchID(uri, id, field, sourceUri) - if not id then - return - end - if not nodersMap[uri] then - return - end - if field then - id = id .. field - end - ids[id] = true - if slockMap[uri][id] then - footprint(status, 'slocked:', id) - return - end - slockMap[uri][id] = true - if sourceUri and uri ~= sourceUri then - footprint(status, 'cross uri:', uri) - compileAllNodes(getState(uri).ast) - end - search(uri, id, nil) - if sourceUri and uri ~= sourceUri then - footprint(status, 'back uri:', sourceUri) - end - end - - ---@return parser.guide.object? - local function findLastCall() - for i = #callStack, 1, -1 do - local call = callStack[i] - if call then - -- 标记此处的call失效,等待在堆栈平衡时弹出 - callStack[i] = false - return call - end - end - return nil - end - - local genericCallArgs = genercCache.genericCallArgs - local closureCache = genercCache.closureCache - local function checkGeneric(uri, source, field) - if not source.isGeneric then - return - end - if not isCallID(field) then - return - end - local call = findLastCall() - if not call then - return - end - local id = genercCache.mark[call] - if id == false then - return - end - if id then - searchID(uri, id, field) - return - end - - local args = call.args - if args then - for i = 1, #args do - local arg = args[i] - genericCallArgs[arg] = true - end - end - - local cacheID = uri .. getID(source) .. getID(call) - local closure = closureCache[cacheID] - if closure == false then - genercCache.mark[call] = false - return - end - if not closure then - closure = generic.createClosure(source, call) - closureCache[cacheID] = closure or false - if not closure then - genercCache.mark[call] = false - return - end - end - id = getID(closure) - genercCache.mark[call] = id - searchID(uri, id, field) - end - - local function checkENV(uri, source, field) - if not field then - return - end - if source.special ~= '_G' then - return - end - local newID = 'g:' .. ssub(field, 2) - searchID(uri, newID) - end - - ---@param ward '"forward"'|'"backward"' - ---@param info node.info - local function pushThenCheckReject(uri, ward, info) - local reject = info.reject - local checkReject - local pushReject - if ward == 'forward' then - checkReject = brejectMap[uri] - pushReject = frejectMap[uri] - else - checkReject = frejectMap[uri] - pushReject = brejectMap[uri] - end - pushReject[reject] = (pushReject[reject] or 0) + 1 - if checkReject[reject] and checkReject[reject] > 0 then - return false - end - return true - end - - ---@param ward '"forward"'|'"backward"' - ---@param info node.info - local function popReject(uri, ward, info) - local reject = info.reject - local popTags - if ward == 'forward' then - popTags = frejectMap[uri] - else - popTags = brejectMap[uri] - end - popTags[reject] = popTags[reject] - 1 - end - - ---@type table<node.filter, integer> - local filters = {} - - ---@param id string - ---@param info node.info - local function pushInfoFilter(id, field, info) - local filter = info.filter - local filterValid = info.filterValid - if filterValid and not filterValid(id, field) then - return - end - filters[filter] = (filters[filter] or 0) + 1 - end - - ---@param id string - ---@param info node.info - local function releaseInfoFilter(id, field, info) - local filter = info.filter - local filterValid = info.filterValid - if filterValid and not filterValid(id, field) then - return - end - if filters[filter] <= 1 then - filters[filter] = nil - else - filters[filter] = filters[filter] - 1 - end - end - - ---@param id string - ---@param info node.info - local function checkInfoFilter(id, field, info) - for filter in next, filters do - if not filter(id, field, mode) then - return false - end - end - return true - end - - local function checkForward(uri, id, field) - for forwardID, info in eachForward(nodersMap[uri], id) do - local targetUri, targetID - - --#region checkBeforeForward - if info then - if info.filter then - pushInfoFilter(forwardID, field, info) - end - if info.reject then - if not pushThenCheckReject(uri, 'forward', info) then - goto RELEASE_THEN_CONTINUE - end - end - end - if not checkInfoFilter(forwardID, field, info) then - goto RELEASE_THEN_CONTINUE - end - --#endregion - - targetUri, targetID = getUriAndID(forwardID) - if targetUri and targetUri ~= uri then - if dontCross == 0 then - searchID(targetUri, targetID, field, uri) - end - else - searchID(uri, targetID or forwardID, field) - end - - ::RELEASE_THEN_CONTINUE:: - --#region releaseAfterForward - if info then - if info.reject then - popReject(uri, 'forward', info) - end - if info.filter then - releaseInfoFilter(id, field, info) - end - end - --#endregion - end - end - - local function checkBackward(uri, id, field) - if ignoredIDs[id] - or id == 'dn:string' then - return - end - if mode ~= 'ref' - and mode ~= 'allfield' - and mode ~= 'allref' - and not field then - return - end - for backwardID, info in eachBackward(nodersMap[uri], id) do - local targetUri, targetID - if info and info.deep and mode ~= 'allref' and mode ~= 'allfield' then - goto CONTINUE - end - - --#region checkBeforeBackward - if info then - if info.dontCross then - dontCross = dontCross + 1 - end - if info.filter then - pushInfoFilter(backwardID, field, info) - end - if info.reject then - if not pushThenCheckReject(uri, 'backward', info) then - goto RELEASE_THEN_CONTINUE - end - end - end - if not checkInfoFilter(backwardID, field, info) then - goto RELEASE_THEN_CONTINUE - end - --#endregion - - targetUri, targetID = getUriAndID(backwardID) - if targetUri and targetUri ~= uri then - if dontCross == 0 then - searchID(targetUri, targetID, field, uri) - end - else - searchID(uri, targetID or backwardID, field) - end - - ::RELEASE_THEN_CONTINUE:: - --#region releaseAfterBackward - if info then - if info.reject then - popReject(uri, 'backward', info) - end - if info.filter then - releaseInfoFilter(backwardID, field, info) - end - if info.dontCross then - dontCross = dontCross - 1 - end - end - --#endregion - - ::CONTINUE:: - end - end - - local function checkExtend(uri, id, field) - if not field - and mode ~= 'field' - and mode ~= 'allfield' then - return - end - if field then - local results = status.results - for i = #results, 1, -1 do - local res = results[i] - if res.type == 'setfield' - or res.type == 'setmethod' - or res.type == 'setindex' then - local resField = noder.getFieldID(getID(res)) - if field == resField then - return - end - end - if res.type == 'doc.field.name' then - local resField = STRING_FIELD .. res[1] - if field == resField then - return - end - end - end - end - for extendID in eachExtend(nodersMap[uri], id) do - local targetUri, targetID - - targetUri, targetID = getUriAndID(extendID) - if targetUri and targetUri ~= uri then - if dontCross == 0 then - searchID(targetUri, targetID, field, uri) - end - else - searchID(uri, targetID or extendID, field) - end - end - end - - local function searchSpecial(uri, id, field) - -- Special rule: ('').XX -> stringlib.XX - if id == 'str:' - or id == 'dn:string' then - if field or mode == 'field' or mode == 'allfield' then - searchID(uri, 'dn:stringlib', field) - else - searchID(uri, 'dn:string', field) - end - end - end - - local function checkRequire(uri, requireName, field) - if not requireName then - return - end - local uris = rpath.findUrisByRequirePath(suri, requireName) - footprint(status, 'require:', requireName) - for i = 1, #uris do - local ruri = uris[i] - if uri ~= ruri then - searchID(ruri, 'mainreturn', field, uri) - break - end - end - end - - local function searchGlobal(uri, id, field) - if dontCross ~= 0 then - return - end - if ssub(id, 1, 2) ~= 'g:' then - return - end - footprint(status, 'searchGlobal:', id, field) - local crossed = {} - if mode == 'def' - or mode == 'alldef' - or field - or hasCall(field) then - for _, guri in collector:each(suri, 'def:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - elseif mode == 'field' - or mode == 'allfield' then - for _, guri in collector:each(suri, 'def:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - for _, guri in collector:each(suri, 'field:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - else - for _, guri in collector:each(suri, id) do - if crossed[guri] then - goto CONTINUE - end - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - end - end - - local function searchClass(uri, id, field) - if dontCross ~= 0 then - return - end - if ssub(id, 1, 3) ~= 'dn:' then - return - end - footprint(status, 'searchClass:', id, field) - local crossed = {} - if mode == 'def' - or mode == 'alldef' - or mode == 'field' - or ignoredIDs[id] - or id == 'dn:string' - or hasCall(field) then - for _, guri in collector:each(suri, 'def:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - else - for _, guri in collector:each(suri, id) do - if crossed[guri] then - goto CONTINUE - end - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - end - end - - local function checkMainReturn(uri, id, field) - if id ~= 'mainreturn' then - return - end - local calls = vm.getLinksTo(uri) - for i = 1, #calls do - local call = calls[i] - local curi = getUri(call) - local cid = getID(call) - if curi ~= uri then - searchID(curi, cid, field, uri) - end - end - end - - local function searchNode(uri, id, field) - local noders = nodersMap[uri] - local call = noders.call[id] - callStack[#callStack+1] = call - - if field == nil and not ignoredSources[id] then - for source in eachSource(noders, id) do - local force = genericCallArgs[source] - pushResult(status, mode, source, force) - end - end - - local requireName = noders.require[id] - if requireName then - checkRequire(uri, requireName, field) - end - - local elock = elockMap[uri] - local ecall = ecallMap[uri] - - if lockExpanding(elock, ecall, id, field) then - if noders.forward[id] then - checkForward(uri, id, field) - end - if noders.backward[id] then - checkBackward(uri, id, field) - end - if noders.extend[id] then - checkExtend(uri, id, field) - end - releaseExpanding(elock, ecall, id, field) - end - - local source = noders.source[id] - if source then - checkGeneric(uri, source, field) - checkENV(uri, source, field) - end - - if mode == 'allref' or mode == 'alldef' then - checkMainReturn(uri, id, field) - end - - if call then - callStack[#callStack] = nil - end - - return false - end - - local function searchAnyField(uri, id, field) - if mode == 'ref' or mode == 'allref' then - return - end - local lastID = getLastID(id) - if not lastID then - return - end - local originField = ssub(id, #lastID + 1) - if originField == TABLE_KEY - or originField == WEAK_TABLE_KEY then - return - end - local anyFieldID = lastID .. ANY_FIELD - searchNode(uri, anyFieldID, field) - end - - local function searchWeak(uri, id, field) - local lastID = getLastID(id) - if not lastID then - return - end - local originField = ssub(id, #lastID + 1) - if originField == WEAK_TABLE_KEY then - local newID = lastID .. TABLE_KEY - searchNode(uri, newID, field) - end - if originField == WEAK_ANY_FIELD then - local newID = lastID .. ANY_FIELD - searchNode(uri, newID, field) - end - end - - local stepCount = 0 - local stepMaxCount = 1e4 - if mode == 'allref' or mode == 'alldef' then - stepMaxCount = 1e5 - end - - function searchStep(uri, id, field) - stepCount = stepCount + 1 - if stepCount > stepMaxCount then - stop(status, 'too deep!') - return - end - searchSpecial(uri, id, field) - searchNode(uri, id, field) - if field and nodersMap[uri].skip[id] then - return - end - - local fullID = id .. (field or '') - if not slockGlobalMap[fullID] then - slockGlobalMap[fullID] = true - searchGlobal(uri, id, field) - searchClass(uri, id, field) - slockGlobalMap[fullID] = nil - end - - splitID(uri, id, field) - searchAnyField(uri, id, field) - searchWeak(uri, id, field) - end - - search(suri, expect, nil) - flushGeneric() -end - -local function prepareSearch(source) - if not source then - return - end - if source.type == 'field' - or source.type == 'method' then - source = source.parent - end - if not source then - return - end - local uri = getUri(source) - local id = getID(source) - -- return function - if source.type == 'function' and source.parent.type == 'return' then - local func = guide.getParentFunction(source) - if func.type == 'function' then - for index, rtn in ipairs(source.parent) do - if rtn == source then - id = sformat('%s%s%s' - , getID(func) - , RETURN_INDEX - , index - ) - break - end - end - end - end - return uri, id -end - -local fieldNextTypeMap = util.switch() - : case 'getmethod' - : case 'setmethod' - : case 'getfield' - : case 'setfield' - : case 'getindex' - : case 'setindex' - : call(pushResult) - : getMap() - -local function getField(status, source, mode) - if source.type == 'table' then - for i = 1, #source do - local field = source[i] - pushResult(status, mode, field) - end - return - end - if source.type == 'doc.type.ltable' then - local fields = source.fields - for i = 1, #fields do - local field = fields[i] - pushResult(status, mode, field) - end - end - if source.type == 'doc.class.name' then - local class = source.parent - local fields = class.fields - for i = 1, #fields do - local field = fields[i] - pushResult(status, mode, field.field) - end - return - end - local field = source.next - if field then - local ftype = field.type - local pushResultOrNil = fieldNextTypeMap[ftype] - if pushResultOrNil then - pushResultOrNil(status, mode, field) - end - return - end -end - -local function searchAllGlobalByUri(status, mode, uri, fullID) - local ast = files.getState(uri) - if not ast then - return - end - local root = ast.ast - --compileAllNodes(root) - local noders = getNoders(root) - if fullID then - for source in eachSource(noders, fullID) do - pushResult(status, mode, source) - end - else - for id in next, noders.source do - if ssub(id, 1, 2) == 'g:' - and not sfind(id, SPLIT_CHAR) then - for source in eachSource(noders, id) do - pushResult(status, mode, source) - end - end - end - end -end - -local function searchAllGlobals(status, mode) - for uri in files.eachFile() do - searchAllGlobalByUri(status, mode, uri) - end -end - ----查找全局变量 ----@param uri uri ----@param mode guide.searchmode ----@param name string ----@return parser.guide.object[] -function m.findGlobals(uri, mode, name) - local status = m.status(nil, nil, mode) - - if name then - local fullID - if type(name) == 'string' then - fullID = sformat('%s%s%s', 'g:', STRING_CHAR, name) - else - fullID = sformat('%s%s%s', 'g:', '', name) - end - searchAllGlobalByUri(status, mode, uri, fullID) - else - searchAllGlobalByUri(status, mode, uri) - end - - return status.results -end - ----搜索对象的引用 ----@param status guide.status ----@param source parser.guide.object ----@param mode guide.searchmode -function m.searchRefs(status, source, mode) - local uri, id = prepareSearch(source) - if not id then - return - end - - local cached, makeCache = checkCache(status, uri, id, mode) - - if cached then - return - end - - if TRACE then - log.debug('searchRefs:', id) - end - m.searchRefsByID(status, uri, id, mode) - if makeCache then - makeCache() - end -end - ----搜索对象的field ----@param status guide.status ----@param source parser.guide.object ----@param mode guide.searchmode ----@param field string -function m.searchFields(status, source, mode, field) - local uri, id = prepareSearch(source) - if not id then - return - end - if TRACE then - log.debug('searchFields:', id, field) - end - if field == '*' then - if source.special == '_G' then - local cached, makeCache = checkCache(status, uri, '*', mode) - if cached then - return - end - searchAllGlobals(status, mode) - if makeCache then - makeCache() - end - else - local cached, makeCache = checkCache(status, uri, id .. '*', mode) - if cached then - return - end - local fieldMode = 'field' - if mode == 'allref' then - fieldMode = 'allfield' - end - local newStatus = m.status(source, field, fieldMode) - m.searchRefsByID(newStatus, uri, id, fieldMode) - local results = newStatus.results - for i = 1, #results do - local def = results[i] - getField(status, def, mode) - end - if makeCache then - makeCache() - end - end - else - if source.special == '_G' then - local fullID - if type(field) == 'string' then - fullID = sformat('%s%s%s', 'g:', STRING_CHAR, field) - else - fullID = sformat('%s%s%s', 'g:', '', field) - end - local cahced, makeCache = checkCache(status, uri, fullID, mode) - if cahced then - return - end - m.searchRefsByID(status, uri, fullID, mode) - if makeCache then - makeCache() - end - else - local fullID - if type(field) == 'string' then - fullID = sformat('%s%s%s', id, STRING_FIELD, field) - else - fullID = sformat('%s%s%s', id, SPLIT_CHAR, field) - end - local cahced, makeCache = checkCache(status, uri, fullID, mode) - if cahced then - return - end - m.searchRefsByID(status, uri, fullID, mode) - if makeCache then - makeCache() - end - end - end -end - ----@class guide.status ----搜索结果 ----@field results parser.guide.object[] ----@field rmark table ----@field id string ----@field source parser.guide.object ----@field field string - ----创建搜索状态 ----@param mode guide.searchmode ----@return guide.status -function m.status(source, field, mode) - local status = { - callStack = {}, - results = {}, - rmark = {}, - footprint = {}, - ids = {}, - mode = mode, - source = source, - field = field, - cache = setmetatable(vm.getCache('searcher:' .. mode), uriMapMT), - } - return status -end - ---- 请求对象的引用 ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestReference(obj, field) - local status = m.status(obj, field, 'ref') - - if field then - m.searchFields(status, obj, 'ref', field) - else - m.searchRefs(status, obj, 'ref') - end - - return status.results -end - ---- 请求对象的全部引用(深度搜索) ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestAllReference(obj, field) - local status = m.status(obj, field, 'allref') - - if field then - m.searchFields(status, obj, 'allref', field) - else - m.searchRefs(status, obj, 'allref') - end - - return status.results -end - ---- 请求对象的定义 ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestDefinition(obj, field) - local status = m.status(obj, field, 'def') - - if field then - m.searchFields(status, obj, 'def', field) - else - m.searchRefs(status, obj, 'def') - end - - return status.results -end - ---- 请求对象的全部定义(深度搜索) ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestAllDefinition(obj, field) - local status = m.status(obj, field, 'alldef') - - if field then - m.searchFields(status, obj, 'alldef', field) - else - m.searchRefs(status, obj, 'alldef') - end - - return status.results -end - ---m.requestReference = m.requestAllReference ---m.requestDefinition = m.requestAllDefinition - -return m diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index ef426633..568bb222 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -1,12 +1,11 @@ local files = require 'files' -local searcher = require 'core.searcher' local await = require 'await' local define = require 'proto.define' local vm = require 'vm' local util = require 'utility' local guide = require 'parser.guide' local converter = require 'proto.converter' -local infer = require 'core.infer' +local infer = require 'vm.infer' local config = require 'config' local linkedTable = require 'linked-table' @@ -18,7 +17,7 @@ local Care = util.switch() return end local isLib = vm.isGlobalLibraryName(source[1]) - local isFunc = infer.hasType(source, 'function') + local isFunc = infer.getInfer(source):hasFunction() local type = isFunc and define.TokenTypes['function'] or define.TokenTypes.variable local modifier = isLib and define.TokenModifiers.defaultLibrary or define.TokenModifiers.static @@ -67,7 +66,7 @@ local Care = util.switch() return end end - if infer.hasType(source, 'function') then + if infer.getInfer(source):hasFunction() then results[#results+1] = { start = source.start, finish = source.finish, @@ -93,6 +92,7 @@ local Care = util.switch() } end) : case 'local' + : case 'self' : case 'getlocal' : case 'setlocal' : call(function (source, options, results) @@ -120,13 +120,14 @@ local Care = util.switch() end local loc = source.node or source -- 1. 值为函数的局部变量 | Local variable whose value is a function - if loc.refs then - for _, ref in ipairs(loc.refs) do + if loc.ref then + for _, ref in ipairs(loc.ref) do if ref.value and ref.value.type == 'function' then results[#results+1] = { start = source.start, finish = source.finish, type = define.TokenTypes['function'], + modifieres = define.TokenModifiers.declaration, } return end @@ -161,20 +162,14 @@ local Care = util.switch() } return end - -- 5. References to other functions - if infer.hasType(loc, 'function') then - results[#results+1] = { - start = source.start, - finish = source.finish, - type = define.TokenTypes['function'], - modifieres = source.type == 'setlocal' and define.TokenModifiers.declaration or nil, - } - return - end - -- 6. Class declaration + -- 5. Class declaration -- only search this local if loc.bindDocs then - for i, doc in ipairs(loc.bindDocs) do + for i = #loc.bindDocs, 1, -1 do + local doc = loc.bindDocs[i] + if doc.type == 'doc.type' then + break + end if doc.type == "doc.class" and doc.bindSources then for _, src in ipairs(doc.bindSources) do if src == loc then @@ -189,7 +184,17 @@ local Care = util.switch() end end end - -- 6. const 变量 | Const variable + -- 6. References to other functions + if infer.getInfer(loc):hasFunction() then + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes['function'], + modifieres = guide.isSet(source) and define.TokenModifiers.declaration or nil, + } + return + end + -- 7. const 变量 | Const variable if loc.attrs then for _, attr in ipairs(loc.attrs) do local name = attr[1] @@ -502,7 +507,7 @@ local Care = util.switch() modifieres = define.TokenModifiers.modification, } end) - : case 'doc.type.enum' + : case 'doc.type.string' : call(function (source, options, results) if not options.annotation then return @@ -514,24 +519,6 @@ local Care = util.switch() modifieres = define.TokenModifiers.static, } end) - : case 'doc.resume' - : call(function (source, options, results) - if not options.annotation then - return - end - results[#results+1] = { - start = source.start, - finish = source.finish, - type = define.TokenTypes.string, - modifieres = define.TokenModifiers.static, - } - local row = guide.rowColOf(source.start) - results[#results+1] = { - start = source.finish, - finish = guide.positionOf(row, guide.getLineRange(options.state, row)), - type = define.TokenTypes.comment, - } - end) : case 'doc.type.function' : call(function (source, options, results) if not options.annotation then @@ -669,7 +656,6 @@ local Care = util.switch() type = define.TokenTypes.keyword, } end) - : getMap() local function buildTokens(uri, results) local tokens = {} @@ -806,11 +792,7 @@ return function (uri, start, finish) local results = {} guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async - local method = Care[source.type] - if not method then - return - end - method(source, options, results) + Care(source.type, source, options, results) await.delay() end) diff --git a/script/core/signature.lua b/script/core/signature.lua index d55866f5..505526b6 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -1,5 +1,4 @@ local files = require 'files' -local searcher = require 'core.searcher' local vm = require 'vm' local hoverLabel = require 'core.hover.label' local hoverDesc = require 'core.hover.description' @@ -88,7 +87,7 @@ local function makeSignatures(text, call, pos) if call.args then local args = {} for _, arg in ipairs(call.args) do - if not arg.dummy then + if arg.type ~= 'self' then args[#args+1] = arg end end @@ -126,7 +125,6 @@ local function makeSignatures(text, call, pos) local defs = vm.getDefs(node) local mark = {} for _, src in ipairs(defs) do - src = searcher.getObjectValue(src) or src if src.type == 'function' or src.type == 'doc.type.function' then if not mark[src] then diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua index 1f021fb3..92f81997 100644 --- a/script/core/type-definition.lua +++ b/script/core/type-definition.lua @@ -1,10 +1,9 @@ -local searcher = require 'core.searcher' local workspace = require 'workspace' local files = require 'files' local vm = require 'vm' local findSource = require 'core.find-source' local guide = require 'parser.guide' -local infer = require 'core.infer' +local infer = require 'vm.infer' local rpath = require 'workspace.require-path' local function sortResults(results) @@ -132,33 +131,26 @@ return function (uri, offset) end end - local defs = vm.getAllDefs(source) - local values = {} - for _, src in ipairs(defs) do - local value = searcher.getObjectValue(src) - if value and value ~= src and guide.isLiteral(value) then - values[value] = true - end - end + local defs = vm.getDefs(source) for _, src in ipairs(defs) do - if src.dummy then - goto CONTINUE - end - if values[src] then - goto CONTINUE - end local root = guide.getRoot(src) if not root then goto CONTINUE end src = src.field or src.method or src.index or src + if src.type == 'doc.class' then + src = src.class + end + if src.type == 'doc.alias' then + src = src.alias + end if src.type == 'doc.class.name' or src.type == 'doc.alias.name' or src.type == 'doc.type.function' or src.type == 'doc.type.array' or src.type == 'doc.type.table' - or src.type == 'doc.type.ltable' then + or src.type == 'function' then results[#results+1] = { target = src, uri = root.uri, diff --git a/script/core/workspace-symbol.lua b/script/core/workspace-symbol.lua index 5fb4a741..9dd768db 100644 --- a/script/core/workspace-symbol.lua +++ b/script/core/workspace-symbol.lua @@ -5,9 +5,6 @@ local define = require 'proto.define' local await = require 'await' local function buildSource(uri, source, key, results) - if source.dummy then - return - end if source.type == 'local' or source.type == 'setlocal' or source.type == 'setglobal' then diff --git a/script/files.lua b/script/files.lua index 7bd96611..05b7ab23 100644 --- a/script/files.lua +++ b/script/files.lua @@ -35,14 +35,11 @@ end m.reset() -local fixedUri = {} ---- 获取文件的真实uri(真实大小写) +local uriMap = {} +-- 获取文件的真实uri,但不穿透软链接 ---@param uri uri ---@return uri function m.getRealUri(uri) - if platform.OS ~= 'Windows' then - return uri - end local filename = furi.decode(uri) local path = fs.path(filename) local suc, res = pcall(fs.exists, path) @@ -50,16 +47,19 @@ function m.getRealUri(uri) return uri end suc, res = pcall(fs.canonical, path) - if not suc or res:string():gsub('/', '\\') == filename then + if not suc then return uri end filename = res:string() local ruri = furi.encode(filename) - if not fixedUri[ruri] then - fixedUri[ruri] = true + if uri == ruri then + return ruri + end + if not uriMap[ruri] then + uriMap[ruri] = uri log.warn(('Fix real file uri: %s -> %s'):format(uri, ruri)) end - return ruri + return uriMap[ruri] end --- 打开文件 @@ -80,8 +80,10 @@ function m.close(uri) file.trusted = false end m.onWatch('close', uri) - if (file._ref or 0) <= 0 and not m.isOpen(uri) then - m.remove(uri) + if file then + if (file._ref or 0) <= 0 and not m.isOpen(uri) then + m.remove(uri) + end end end @@ -478,7 +480,7 @@ function m.compileState(uri, text) end return nil end - local prog <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_COMPILING, 0.5) + local prog <close> = progress.create(uri, lang.script.WINDOW_COMPILING, 0.5) prog:setMessage(ws.getRelativePath(uri)) local clock = os.clock() local state, err = parser.compile(text diff --git a/script/fs-utility.lua b/script/fs-utility.lua index a0fdc02f..c845c769 100644 --- a/script/fs-utility.lua +++ b/script/fs-utility.lua @@ -210,6 +210,9 @@ function dfs:exists() end function dfs:createDirectories(path) + if not path then + return false + end if type(path) ~= 'string' then path = path:string() end @@ -230,6 +233,9 @@ function dfs:createDirectories(path) end function dfs:saveFile(path, text) + if not path then + return false, 'no path' + end if type(path) ~= 'string' then path = path:string() end @@ -269,6 +275,9 @@ local function fsAbsolute(path, option) end local function fsIsDirectory(path, option) + if not path then + return false + end if path.type == 'dummy' then return path:isDirectory() end @@ -281,6 +290,9 @@ local function fsIsDirectory(path, option) end local function fsPairs(path, option) + if not path then + return function () end + end if path.type == 'dummy' then return path:listDirectory() end @@ -293,6 +305,9 @@ local function fsPairs(path, option) end local function fsRemove(path, option) + if not path then + return false + end if path.type == 'dummy' then return path:remove() end @@ -304,6 +319,9 @@ local function fsRemove(path, option) end local function fsExists(path, option) + if not path then + return false + end if path.type == 'dummy' then return path:exists() end @@ -316,6 +334,9 @@ local function fsExists(path, option) end local function fsSave(path, text, option) + if not path then + return false + end if path.type == 'dummy' then local dir = path:_open(-2) if not dir then @@ -343,6 +364,9 @@ local function fsSave(path, text, option) end local function fsLoad(path, option) + if not path then + return nil + end if path.type == 'dummy' then local text = path:_open() if type(text) == 'string' then @@ -363,6 +387,9 @@ local function fsLoad(path, option) end local function fsCopy(source, target, option) + if not source or not target then + return + end if source.type == 'dummy' then local sourceText = source:_open() if not sourceText then @@ -390,6 +417,9 @@ local function fsCopy(source, target, option) end local function fsCreateDirectories(path, option) + if not path then + return + end if path.type == 'dummy' then return path:createDirectories() end @@ -402,6 +432,9 @@ local function fsCreateDirectories(path, option) end local function fileRemove(path, option) + if not path then + return + end if option.onRemove and option.onRemove(path) == false then return end @@ -416,6 +449,9 @@ local function fileRemove(path, option) end local function fileCopy(source, target, option) + if not source or not target then + return + end local isDir1 = fsIsDirectory(source, option) local isDir2 = fsIsDirectory(target, option) local isExists = fsExists(target, option) @@ -446,6 +482,9 @@ local function fileCopy(source, target, option) end local function fileSync(source, target, option) + if not source or not target then + return + end local isDir1 = fsIsDirectory(source, option) local isDir2 = fsIsDirectory(target, option) local isExists = fsExists(target, option) diff --git a/script/gc.lua b/script/gc.lua index 24a72f15..7bb81569 100644 --- a/script/gc.lua +++ b/script/gc.lua @@ -5,6 +5,7 @@ local util = require 'utility' local mt = {} mt.__index = mt mt.type = 'gc' +mt._removed = false mt._max = 10 diff --git a/script/global.d.lua b/script/global.d.lua index 793f687d..56f3019f 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -46,3 +46,6 @@ CHECK = '' ---@type string | '"Error"' | '"Warning"' | '"Information"' | '"Hint"' CHECKLEVEL = 'Warning' + +---@type 'trace' | 'debug' | 'info' | 'warn' | 'error' +LOGLEVEL = 'warn' diff --git a/script/inspect.lua b/script/inspect.lua new file mode 100644 index 00000000..f8d69dc7 --- /dev/null +++ b/script/inspect.lua @@ -0,0 +1,337 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local inspect = {Options = {}, } + + + + + + + + + + + + + + + + + +inspect._VERSION = 'inspect.lua 3.1.0' +inspect._URL = 'http://github.com/kikito/inspect.lua' +inspect._DESCRIPTION = 'human-readable representations of tables' +inspect._LICENSE = [[ + MIT LICENSE + + Copyright (c) 2022 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] +inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end }) +inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end }) + +local tostring = tostring +local rep = string.rep +local match = string.match +local char = string.char +local gsub = string.gsub +local fmt = string.format + +local function rawpairs(t) + return next, t, nil +end + + + +local function smartQuote(str) + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' +end + + +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", +} +local longControlCharEscapes = { ["\127"] = "\127" } +for i = 0, 31 do + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end +end + +local function escape(str) + return (gsub(gsub(gsub(str, "\\", "\\\\"), + "(%c)%f[0-9]", longControlCharEscapes), + "%c", shortControlCharEscapes)) +end + +local function isIdentifier(str) + return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$") +end + +local flr = math.floor +local function isSequenceKey(k, sequenceLength) + return type(k) == "number" and + flr(k) == k and + 1 <= (k) and + k <= sequenceLength +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + + if ta == tb and (ta == 'string' or ta == 'number') then + return (a) < (b) + end + + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 + + + return dta == dtb and ta < tb or dta < dtb +end + +local function getKeys(t) + + local seqLen = 1 + while rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen +end + +local function countCycles(x, cycles) + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end +end + +local function makePath(path, a, b) + local newPath = {} + local len = #path + for i = 1, len do newPath[i] = path[i] end + + newPath[len + 1] = a + newPath[len + 2] = b + + return newPath +end + + +local function processRecursive(process, + item, + path, + visited) + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= 'table' then mt = nil end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +local function puts(buf, str) + buf.n = buf.n + 1 + buf[buf.n] = str +end + + + +local Inspector = {} + + + + + + + + + + +local Inspector_mt = { __index = Inspector } + +local function tabify(inspector) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) +end + +function Inspector:getId(v) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) +end + +function Inspector:putValue(v) + local buf = self.buf + local tv = type(v) + if tv == 'string' then + puts(buf, smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or + tv == 'cdata' or tv == 'ctype' then + puts(buf, tostring(v)) + elseif tv == 'table' and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, '{...}') + else + if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, '{') + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then puts(buf, ',') end + if i <= seqLen then + puts(buf, ' ') + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, ' = ') + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == 'table' then + if seqLen + keysLen > 0 then puts(buf, ',') end + tabify(self) + puts(buf, '<metatable> = ') + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == 'table' then + tabify(self) + elseif seqLen > 0 then + puts(buf, ' ') + end + + puts(buf, '}') + end + + else + puts(buf, fmt('<%s %d>', tv, self:getId(v))) + end +end + + + + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or (math.huge) + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local cycles = {} + countCycles(root, cycles) + + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buf) +end + +setmetatable(inspect, { + __call = function(_, root, options) + return inspect.inspect(root, options) + end, +}) + +return inspect diff --git a/script/jsonrpc.lua b/script/jsonrpc.lua index 583bb6e2..91d6c9dd 100644 --- a/script/jsonrpc.lua +++ b/script/jsonrpc.lua @@ -1,7 +1,7 @@ local json = require 'json' +local inspect = require 'inspect' local pcall = pcall local tonumber = tonumber -local util = require 'utility' ---@class jsonrpc local m = {} @@ -44,7 +44,7 @@ function m.decode(reader) end local len = head['Content-Length'] if not len then - return nil, 'Proto header error: ' .. util.dump(head) + return nil, 'Proto header error: ' .. inspect(head) end local content = reader(len) if not content then diff --git a/script/library.lua b/script/library.lua index 501f70d4..66c4d364 100644 --- a/script/library.lua +++ b/script/library.lua @@ -13,6 +13,7 @@ local timer = require 'timer' local encoder = require 'encoder' local ws = require 'workspace.workspace' local scope = require 'workspace.scope' +local inspect = require 'inspect' local m = {} @@ -108,7 +109,7 @@ local function compileSingleMetaDoc(uri, script, metaLang, status) version = 5.1 jit = true else - version = tonumber(config.get(uri, 'Lua.runtime.version'):sub(-3)) + version = tonumber(config.get(uri, 'Lua.runtime.version'):sub(-3)) or 5.4 jit = false end @@ -220,7 +221,7 @@ local function initBuiltIn(uri) end if scp:get('metaPath') == metaPath:string() then - log.info('Has meta path, skip:', metaPath:string()) + log.debug('Has meta path, skip:', metaPath:string()) return end scp:set('metaPath', metaPath:string()) @@ -230,14 +231,14 @@ local function initBuiltIn(uri) end end, log.error) if not suc then - log.info('Init builtin failed.') + log.warn('Init builtin failed.') return end local out = fsu.dummyFS() local templateDir = ROOT / 'meta' / 'template' for libName, status in pairs(define.BuiltIn) do status = config.get(uri, 'Lua.runtime.builtin')[libName] or status - log.info('Builtin status:', libName, status) + log.debug('Builtin status:', libName, status) if status == 'disable' then goto CONTINUE end @@ -249,13 +250,13 @@ local function initBuiltIn(uri) out:saveFile(libName, metaDoc) local outputPath = metaPath / libName m.metaPaths[outputPath:string()] = true - log.info('Meta path:', outputPath:string()) + log.debug('Meta path:', outputPath:string()) end ::CONTINUE:: end local result = fsu.fileSync(out, metaPath) if #result.err > 0 then - log.warn('File sync error:', util.dump(result)) + log.warn('File sync error:', inspect(result)) end end diff --git a/script/log.lua b/script/log.lua index 507051ae..2076348c 100644 --- a/script/log.lua +++ b/script/log.lua @@ -18,6 +18,14 @@ m.file = nil m.startTime = time.time() - monotonic() m.size = 0 m.maxSize = 100 * 1024 * 1024 +m.level = 'info' +m.levelMap = { + ['trace'] = 1, + ['debug'] = 2, + ['info'] = 3, + ['warn'] = 4, + ['error'] = 5, +} local function trimSrc(src) if src:sub(1, 1) == '@' then @@ -64,16 +72,16 @@ local function pushLog(level, ...) return text end -function m.info(...) - pushLog('info', ...) +function m.trace(...) + pushLog('trace', ...) end function m.debug(...) pushLog('debug', ...) end -function m.trace(...) - pushLog('trace', ...) +function m.info(...) + pushLog('info', ...) end function m.warn(...) @@ -85,6 +93,9 @@ function m.error(...) end function m.raw(thd, level, msg, source, currentline, clock) + if m.levelMap[level] < (m.levelMap[m.level] or m.levelMap['info']) then + return msg + end if level == 'error' then ioStdErr:write(msg .. '\n') if not m.firstError then @@ -92,7 +103,7 @@ function m.raw(thd, level, msg, source, currentline, clock) end end if m.size > m.maxSize then - return + return msg end init_log_file() local sec, ms = mathModf((m.startTime + clock) / 1000) diff --git a/script/parser/ast.lua b/script/parser/ast.lua index 18bff05c..648a6890 100644 --- a/script/parser/ast.lua +++ b/script/parser/ast.lua @@ -1577,7 +1577,6 @@ local Defs = { symbol = symbol, } } - return end, UnknownAction = function (start, symbol) PushError { diff --git a/script/parser/guide.lua b/script/parser/guide.lua index eda85c97..c0f33c28 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -1,66 +1,62 @@ local error = error local type = type ----@class parser.guide.object ----@field bindDocs parser.guide.object[] ----@field bindGroup parser.guide.object[] ----@field bindSources parser.guide.object[] ----@field value parser.guide.object ----@field parent parser.guide.object +---@class parser.object +---@field bindDocs parser.object[] +---@field bindGroup parser.object[] +---@field bindSources parser.object[] +---@field value parser.object +---@field parent parser.object ---@field type string ---@field special string ---@field tag string ----@field args parser.guide.object[] ----@field locals parser.guide.object[] ----@field returns parser.guide.object[] +---@field args parser.object[] +---@field locals parser.object[] +---@field returns parser.object[] ---@field uri uri ---@field start integer ---@field finish integer ---@field effect integer ---@field attrs string[] ----@field specials parser.guide.object[] ----@field labels parser.guide.object[] ----@field node parser.guide.object ----@field dummy boolean ----@field field parser.guide.object ----@field method parser.guide.object ----@field index parser.guide.object ----@field extends parser.guide.object[] ----@field types parser.guide.object[] ----@field fields parser.guide.object[] ----@field typeGeneric table<integer, parser.guide.object[]> ----@field tkey parser.guide.object ----@field tvalue parser.guide.object +---@field specials parser.object[] +---@field labels parser.object[] +---@field node parser.object +---@field field parser.object +---@field method parser.object +---@field index parser.object +---@field extends parser.object[]|parser.object +---@field types parser.object[] +---@field fields parser.object[] +---@field tkey parser.object +---@field tvalue parser.object ---@field tindex integer ----@field op parser.guide.object ----@field next parser.guide.object ----@field docParam parser.guide.object +---@field op parser.object +---@field next parser.object +---@field docParam parser.object ---@field sindex integer ----@field name parser.guide.object ----@field call parser.guide.object ----@field closure parser.guide.object ----@field proto parser.guide.object ----@field exp parser.guide.object ----@field isGeneric boolean ----@field alias parser.guide.object ----@field class parser.guide.object ----@field vararg parser.guide.object ----@field param parser.guide.object ----@field overload parser.guide.object +---@field name parser.object +---@field call parser.object +---@field closure parser.object +---@field proto parser.object +---@field exp parser.object +---@field alias parser.object +---@field class parser.object +---@field vararg parser.object +---@field param parser.object +---@field overload parser.object ---@field docParamMap table<string, integer> ---@field upvalues table<string, string[]> ----@field ref parser.guide.object[] +---@field ref parser.object[] ---@field returnIndex integer ----@field docs parser.guide.object[] +---@field docs parser.object[] ---@field state table ---@field comment table ---@field optional boolean ----@field _root parser.guide.object ----@field _noders noders ----@field _mnode parser.guide.object ----@field _noded boolean ----@field _initedNoders boolean ----@field _compiledGlobals boolean +---@field max parser.object +---@field init parser.object +---@field step parser.object +---@field redundant { max: integer, passed: integer } +---@field _root parser.object ---@class guide ---@field debugMode boolean @@ -125,8 +121,9 @@ local childMap = { ['unary'] = {1}, ['doc'] = {'#'}, - ['doc.class'] = {'class', '#extends', 'comment'}, + ['doc.class'] = {'class', '#extends', '#signs', 'comment'}, ['doc.type'] = {'#types', 'name', 'comment'}, + ['doc.type.name'] = {'#signs'}, ['doc.alias'] = {'alias', 'extends', 'comment'}, ['doc.param'] = {'param', 'extends', 'comment'}, ['doc.return'] = {'#returns', 'comment'}, @@ -135,19 +132,18 @@ local childMap = { ['doc.generic.object'] = {'generic', 'extends', 'comment'}, ['doc.vararg'] = {'vararg', 'comment'}, ['doc.type.array'] = {'node'}, - ['doc.type.table'] = {'tkey', 'tvalue', 'comment'}, ['doc.type.function'] = {'#args', '#returns', 'comment'}, - ['doc.type.ltable'] = {'#fields', 'comment'}, + ['doc.type.table'] = {'#fields', 'comment'}, ['doc.type.literal'] = {'node'}, ['doc.type.arg'] = {'name', 'extends'}, - ['doc.type.field'] = {'extends'}, + ['doc.type.field'] = {'name', 'extends'}, ['doc.overload'] = {'overload', 'comment'}, ['doc.see'] = {'name', 'field'}, ['doc.version'] = {'#versions'}, ['doc.diagnostic'] = {'#names'}, } ----@type table<string, fun(obj: parser.guide.object, list: parser.guide.object[])> +---@type table<string, fun(obj: parser.object, list: parser.object[])> local compiledChildMap = setmetatable({}, {__index = function (self, name) local defs = childMap[name] if not defs then @@ -227,7 +223,7 @@ local function formatNumber(n) end --- 是否是字面量 ----@param obj parser.guide.object +---@param obj parser.object ---@return boolean function m.isLiteral(obj) local tp = obj.type @@ -238,10 +234,15 @@ function m.isLiteral(obj) or tp == 'integer' or tp == 'table' or tp == 'function' + or tp == 'doc.type.function' + or tp == 'doc.type.table' + or tp == 'doc.type.string' + or tp == 'doc.type.integer' + or tp == 'doc.type.boolean' end --- 获取字面量 ----@param obj parser.guide.object +---@param obj parser.object ---@return any function m.getLiteral(obj) local tp = obj.type @@ -258,8 +259,8 @@ function m.getLiteral(obj) end --- 寻找父函数 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getParentFunction(obj) for _ = 1, 10000 do obj = obj.parent @@ -275,8 +276,8 @@ function m.getParentFunction(obj) end --- 寻找所在区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getBlock(obj) for _ = 1, 10000 do if not obj then @@ -304,8 +305,8 @@ function m.getBlock(obj) end --- 寻找所在父区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getParentBlock(obj) for _ = 1, 10000 do obj = obj.parent @@ -321,8 +322,8 @@ function m.getParentBlock(obj) end --- 寻找所在可break的父区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getBreakBlock(obj) for _ = 1, 10000 do obj = obj.parent @@ -341,8 +342,8 @@ function m.getBreakBlock(obj) end --- 寻找doc的主体 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getDocState(obj) for _ = 1, 10000 do local parent = obj.parent @@ -358,8 +359,8 @@ function m.getDocState(obj) end --- 寻找所在父类型 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getParentType(obj, want) for _ = 1, 10000 do obj = obj.parent @@ -374,8 +375,8 @@ function m.getParentType(obj, want) end --- 寻找根区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getRoot(obj) local source = obj if source._root then @@ -399,7 +400,7 @@ function m.getRoot(obj) error('guide.getRoot overstack') end ----@param obj parser.guide.object +---@param obj parser.object ---@return uri function m.getUri(obj) if obj.uri then @@ -653,10 +654,10 @@ function m.eachSourceBetween(ast, start, finish, callback) end local function getSourceTypeCache(ast) - local cache = ast.typeCache + local cache = ast._typeCache if not cache then cache = {} - ast.typeCache = cache + ast._typeCache = cache m.eachSource(ast, function (source) local tp = source.type if not tp then @@ -674,6 +675,10 @@ local function getSourceTypeCache(ast) end --- 遍历所有指定类型的source +---@param ast parser.object +---@param type string +---@param callback fun(src: parser.object) +---@return any function m.eachSourceType(ast, type, callback) local cache = getSourceTypeCache(ast) local myCache = cache[type] @@ -681,10 +686,16 @@ function m.eachSourceType(ast, type, callback) return end for i = 1, #myCache do - callback(myCache[i]) + local res = callback(myCache[i]) + if res ~= nil then + return res + end end end +---@param ast parser.object +---@param tps string[] +---@param callback fun(src: parser.object) function m.eachSourceTypes(ast, tps, callback) local cache = getSourceTypeCache(ast) for x = 1, #tps do @@ -698,11 +709,13 @@ function m.eachSourceTypes(ast, tps, callback) end --- 遍历所有的source +---@param ast parser.object +---@param callback fun(src: parser.object) function m.eachSource(ast, callback) - local cache = ast.eachCache + local cache = ast._eachCache if not cache then cache = { ast } - ast.eachCache = cache + ast._eachCache = cache local mark = {} local index = 1 while true do @@ -803,6 +816,9 @@ function m.offsetToPosition(state, offset) end function m.getLineRange(state, row) + if not state.lines[row] then + return 0 + end local nextLineStart = state.lines[row + 1] or #state.lua for i = nextLineStart - 1, state.lines[row], -1 do local w = state.lua:sub(i, i) @@ -814,20 +830,24 @@ function m.getLineRange(state, row) end local isSetMap = { - ['setglobal'] = true, - ['local'] = true, - ['setlocal'] = true, - ['setfield'] = true, - ['setmethod'] = true, - ['setindex'] = true, - ['tablefield'] = true, - ['tableindex'] = true, - ['tableexp'] = true, - ['doc.class.name'] = true, - ['doc.alias.name'] = true, - ['doc.field.name'] = true, - ['doc.field'] = true, - ['doc.type.field'] = true, + ['setglobal'] = true, + ['local'] = true, + ['self'] = true, + ['setlocal'] = true, + ['setfield'] = true, + ['setmethod'] = true, + ['setindex'] = true, + ['tablefield'] = true, + ['tableindex'] = true, + ['tableexp'] = true, + ['label'] = true, + ['doc.class'] = true, + ['doc.alias'] = true, + ['doc.field'] = true, + ['doc.class.name'] = true, + ['doc.alias.name'] = true, + ['doc.field.name'] = true, + ['doc.type.field'] = true, } function m.isSet(source) local tp = source.type @@ -887,17 +907,17 @@ function m.getKeyNameOfLiteral(obj) elseif tp == 'number' then local n = obj[1] if n then - return formatNumber(obj[1]) + return obj[1] end elseif tp == 'integer' then local n = obj[1] if n then - return formatNumber(obj[1]) + return obj[1] end elseif tp == 'boolean' then local b = obj[1] if b then - return tostring(b) + return b end end end @@ -911,6 +931,7 @@ function m.getKeyName(obj) or tp == 'setglobal' then return obj[1] elseif tp == 'local' + or tp == 'self' or tp == 'getlocal' or tp == 'setlocal' then return obj[1] @@ -930,7 +951,7 @@ function m.getKeyName(obj) or tp == 'tableindex' then return m.getKeyNameOfLiteral(obj.index) elseif tp == 'tableexp' then - return tostring(obj.tindex) + return obj.tindex elseif tp == 'field' or tp == 'method' or tp == 'doc.see.field' then @@ -940,11 +961,11 @@ function m.getKeyName(obj) elseif tp == 'doc.alias' then return obj.alias[1] elseif tp == 'doc.field' then - return tostring(obj.field[1]) + return obj.field[1] elseif tp == 'doc.field.name' then - return tostring(obj[1]) + return obj[1] elseif tp == 'doc.type.field' then - return tostring(obj.name[1]) + return obj.name[1] elseif tp == 'dummy' then return obj[1] end @@ -979,6 +1000,7 @@ function m.getKeyType(obj) or tp == 'setglobal' then return 'string' elseif tp == 'local' + or tp == 'self' or tp == 'getlocal' or tp == 'setlocal' then return 'local' @@ -1106,7 +1128,7 @@ function m.getPath(a, b, sameFunction) end ---是否是全局变量(包括 _G.XXX 形式) ----@param source parser.guide.object +---@param source parser.object ---@return boolean function m.isGlobal(source) if source._isGlobal ~= nil then @@ -1184,4 +1206,25 @@ function m.isOOP(source) return false end +local basicTypeMap = { + ['unknown'] = true, + ['any'] = true, + ['true'] = true, + ['false'] = true, + ['nil'] = true, + ['boolean'] = true, + ['number'] = true, + ['string'] = true, + ['table'] = true, + ['function'] = true, + ['thread'] = true, + ['userdata'] = true, +} + +---@param str string +---@return boolean +function m.isBasicType(str) + return basicTypeMap[str] == true +end + return m diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index d15fd95a..22a2df1b 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -6,6 +6,7 @@ local parser = require 'parser.newparser' local TokenTypes, TokenStarts, TokenFinishs, TokenContents, TokenMarks local Ci, Offset, pushWarning, NextComment, Lines local parseType +---@type any local Parser = re.compile([[ Main <- (Token / Sp)* Sp <- %s+ @@ -120,6 +121,10 @@ Symbol <- ({} { end, }) +---@class parser.object +---@field literal boolean +---@field signs parser.object[] + local function trim(str) return str:match '^%s*(%S+)%s*$' end @@ -189,14 +194,14 @@ local function parseName(tp, parent) return nil end nextToken() - local class = { + local name = { type = tp, start = getStart(), finish = getFinish(), parent = parent, [1] = nameText, } - return class + return name end local function nextSymbolOrError(symbol) @@ -220,25 +225,138 @@ local function parseIndexField(tp, parent) return nil end nextToken() - local class = { - type = tp, - start = getStart(), - finish = getFinish(), - parent = parent, + local start = getFinish() - 1 + local indexTP, index = peekToken() + if indexTP == 'name' then + local field = parseType(parent) + nextSymbolOrError ']' + return field + else + nextToken() + local class = { + type = tp, + start = start, + finish = getFinish(), + parent = parent, + } + class[1] = index + nextSymbolOrError ']' + class.finish = getFinish() + return class + end +end + +local function parseTable(parent) + if not checkToken('symbol', '{', 1) then + return nil + end + nextToken() + local typeUnit = { + type = 'doc.type.table', + start = getStart(), + parent = parent, + fields = {}, } - local indexTP, index = nextToken() - if indexTP ~= 'integer' - and indexTP ~= 'string' then + + while true do + if checkToken('symbol', '}', 1) then + nextToken() + break + end + local field = { + type = 'doc.type.field', + parent = typeUnit, + } + + do + field.name = parseName('doc.field.name', field) + or parseIndexField('doc.field.name', field) + if not field.name then + pushWarning { + type = 'LUADOC_MISS_FIELD_NAME', + start = getFinish(), + finish = getFinish(), + } + break + end + if not field.start then + field.start = field.name.start + end + if checkToken('symbol', '?', 1) then + nextToken() + field.optional = true + end + field.finish = getFinish() + if not nextSymbolOrError(':') then + break + end + field.extends = parseType(field) + if not field.extends then + break + end + field.finish = getFinish() + end + + typeUnit.fields[#typeUnit.fields+1] = field + if checkToken('symbol', ',', 1) then + nextToken() + else + nextSymbolOrError('}') + break + end + end + typeUnit.finish = getFinish() + return typeUnit +end + +local function parseSigns(parent, mode) + if not checkToken('symbol', '<', 1) then + return nil + end + nextToken() + local signs = {} + while true do + local sign + if mode == 'name' then + sign = parseName('doc.generic.name', parent) + if not sign then + pushWarning { + type = 'LUADOC_MISS_SIGN_NAME', + start = getFinish(), + finish = getFinish(), + } + break + end + elseif mode == 'type' then + sign = parseType(parent) + if not sign then + pushWarning { + type = 'LUADOC_MISS_TYPE_NAME', + start = getFinish(), + finish = getFinish(), + } + break + end + end + signs[#signs+1] = sign + if checkToken('symbol', ',', 1) then + nextToken() + else + break + end + end + if not checkToken('symbol', '>', 1) then pushWarning { - type = 'LUADOC_INDEX_MUST_INT', - start = getStart(), + type = 'LUADOC_MISS_SYMBOL', + start = getFinish(), finish = getFinish(), + symbol = { + symbol = '>', + } } end - class[1] = index - nextSymbolOrError ']' - class.finish = getFinish() - return class + nextToken() + return signs end local function parseClass(parent) @@ -258,6 +376,7 @@ local function parseClass(parent) end result.start = getStart() result.finish = getFinish() + result.signs = parseSigns(result, 'name') if not checkToken('symbol', ':', 1) then return result end @@ -267,6 +386,7 @@ local function parseClass(parent) while true do local extend = parseName('doc.extends.name', result) + or parseTable(result) if not extend then pushWarning { type = 'LUADOC_MISS_CLASS_EXTENDS_NAME', @@ -301,39 +421,6 @@ local function parseTypeUnitArray(parent, node) return result end -local function parseTypeUnitTable(parent, node) - if not checkToken('symbol', '<', 1) then - return nil - end - if not nextSymbolOrError('<') then - return nil - end - - local result = { - type = 'doc.type.table', - start = node.start, - node = node, - parent = parent, - } - - local key = parseType(result) - if not key or not nextSymbolOrError(',') then - return nil - end - local value = parseType(result) - if not value then - return nil - end - nextSymbolOrError('>') - - node.parent = result - result.finish = getFinish() - result.tkey = key - result.tvalue = value - - return result -end - local function parseDots(tp, parent) if not checkToken('symbol', '...', 1) then return @@ -349,9 +436,14 @@ local function parseDots(tp, parent) return dots end -local function parseTypeUnitFunction() +local function parseTypeUnitFunction(parent) + if not checkToken('name', 'fun', 1) then + return nil + end + nextToken() local typeUnit = { type = 'doc.type.function', + parent = parent, start = getStart(), args = {}, returns = {}, @@ -422,74 +514,17 @@ local function parseTypeUnitFunction() return typeUnit end -local function parseTypeUnitLiteralTable() - local typeUnit = { - type = 'doc.type.ltable', - start = getStart(), - fields = {}, - } - - while true do - if checkToken('symbol', '}', 1) then - nextToken() - break - end - local field = { - type = 'doc.type.field', - parent = typeUnit, - } - - do - field.name = parseName('doc.field.name', field) - or parseIndexField('doc.field.name', field) - if not field.name then - pushWarning { - type = 'LUADOC_MISS_FIELD_NAME', - start = getFinish(), - finish = getFinish(), - } - break - end - if not field.start then - field.start = field.name.start - end - if checkToken('symbol', '?', 1) then - nextToken() - field.optional = true - end - field.finish = getFinish() - if not nextSymbolOrError(':') then - break - end - field.extends = parseType(field) - if not field.extends then - break - end - field.finish = getFinish() - end - - typeUnit.fields[#typeUnit.fields+1] = field - if checkToken('symbol', ',', 1) then - nextToken() - else - nextSymbolOrError('}') - break - end - end - typeUnit.finish = getFinish() - return typeUnit -end - local parseTypeUnit -local function parseDocFunction(parent, content) +local function parseFunction(parent) + local _, content = peekToken() if content == 'async' then + nextToken() local pos = getStart() local tp, cont = peekToken() if tp == 'name' then if cont == 'fun' then - nextToken() - local func = parseTypeUnit(parent, cont) + local func = parseTypeUnit(parent) if func then func.async = true func.asyncPos = pos @@ -499,32 +534,105 @@ local function parseDocFunction(parent, content) end end if content == 'fun' then - return parseTypeUnitFunction() + return parseTypeUnitFunction(parent) end end -function parseTypeUnit(parent, content) - local result = parseDocFunction(parent, content) - if not result then - if content == '{' then - result = parseTypeUnitLiteralTable() +local function parseString(parent) + local tp, content = peekToken() + if not tp or tp ~= 'string' then + return nil + end + + nextToken() + -- compatibility + if content:sub(1, 1) == '"' + or content:sub(1, 1) == "'" then + if content:sub(1, 1) == content:sub(-1, -1) then + content = content:sub(2, -2) end end - if not result then - result = { - type = 'doc.type.name', - start = getStart(), - finish = getFinish(), - [1] = content, - } + local str = { + type = 'doc.type.string', + start = getStart(), + finish = getFinish(), + parent = parent, + [1] = content, + } + return str +end + +local function parseInteger(parent) + local tp, content = peekToken() + if not tp or tp ~= 'integer' then + return nil end - if not result then + + nextToken() + local integer = { + type = 'doc.type.integer', + start = getStart(), + finish = getFinish(), + parent = parent, + [1] = content, + } + return integer +end + +local function parseBoolean(parent) + local tp, content = peekToken() + if not tp + or tp ~= 'name' + or (content ~= 'true' and content ~= 'false') then return nil end - result.parent = parent + + nextToken() + local boolean = { + type = 'doc.type.boolean', + start = getStart(), + finish = getFinish(), + parent = parent, + [1] = content == 'true' and true or false, + } + return boolean +end + +local function parseParen(parent) + if not checkToken('symbol', '(', 1) then + return + end + nextToken() + local tp = parseType(parent) + nextSymbolOrError(')') + return tp +end + +function parseTypeUnit(parent) + local result = parseFunction(parent) + or parseTable(parent) + or parseString(parent) + or parseInteger(parent) + or parseBoolean(parent) + or parseDots('doc.type.name', parent) + or parseParen(parent) + if not result then + local literal = checkToken('symbol', '`', 1) + if literal then + nextToken() + end + result = parseName('doc.type.name', parent) + if not result then + return nil + end + if literal then + result.literal = true + nextSymbolOrError '`' + end + result.signs = parseSigns(result, 'type') + end while true do local newResult = parseTypeUnitArray(parent, result) - or parseTypeUnitTable(parent, result) if not newResult then break end @@ -534,34 +642,23 @@ function parseTypeUnit(parent, content) end local function parseResume(parent) - local result = { - type = 'doc.resume', - parent = parent, - } - + local default, additional if checkToken('symbol', '>', 1) then nextToken() - result.default = true + default = true end if checkToken('symbol', '+', 1) then nextToken() - result.additional = true + additional = true end - local tp = peekToken() - if tp ~= 'string' then - pushWarning { - type = 'LUADOC_MISS_STRING', - start = getFinish(), - finish = getFinish(), - } - return nil + local result = parseTypeUnit(parent) + if result then + result.default = default + result.additional = additional end - local _, str = nextToken() - result[1] = str - result.start = getStart() - result.finish = getFinish() + return result end @@ -572,77 +669,16 @@ function parseType(parent) types = {}, } while true do - local tp, content = peekToken() - if not tp then + local typeUnit = parseTypeUnit(result) + if not typeUnit then break end - -- 处理 `T` 的情况 - local typeLiteral = nil - if tp == 'symbol' and content == '`' then - nextToken() - if not checkToken('symbol', '`', 2) then - break - end - tp, content = peekToken() - if not tp then - break - end - -- TypeLiteral,指代类型的字面值。比如,对于类 Cat 来说,它的 TypeLiteral 是 "Cat" - typeLiteral = true + result.types[#result.types+1] = typeUnit + if not result.start then + result.start = typeUnit.start end - if tp == 'name' then - nextToken() - local typeUnit = parseTypeUnit(result, content) - if not typeUnit then - break - end - if typeLiteral then - nextToken() - typeUnit.literal = true - end - result.types[#result.types+1] = typeUnit - if not result.start then - result.start = typeUnit.start - end - elseif tp == 'string' then - nextToken() - local typeEnum = { - type = 'doc.type.enum', - start = getStart(), - finish = getFinish(), - parent = result, - [1] = content, - } - result.types[#result.types+1] = typeEnum - if not result.start then - result.start = typeEnum.start - end - elseif tp == 'symbol' and content == '{' then - nextToken() - local typeUnit = parseTypeUnit(result, content) - if not typeUnit then - break - end - result.types[#result.types+1] = typeUnit - if not result.start then - result.start = typeUnit.start - end - elseif tp == 'symbol' and content == '...' then - nextToken() - local vararg = { - type = 'doc.type.name', - start = getStart(), - finish = getFinish(), - parent = result, - [1] = content, - } - result.types[#result.types+1] = vararg - if not result.start then - result.start = vararg.start - end - end if not checkToken('symbol', '|', 1) then break end @@ -741,6 +777,7 @@ local function parseAlias() return nil end result.start = getStart() + result.signs = parseSigns(result, 'name') result.extends = parseType(result) if not result.extends then pushWarning { @@ -933,11 +970,10 @@ local function parseOverload() } return nil end - nextToken() local result = { type = 'doc.overload', } - result.overload = parseDocFunction(result, name) + result.overload = parseFunction(result) if not result.overload then return nil end @@ -983,6 +1019,7 @@ local function parseVersion() end local version = { type = 'doc.version.unit', + parent = result, start = getStart(), } if tp == 'symbol' then @@ -1221,6 +1258,7 @@ local function buildLuaDoc(comment) type = 'doc.tailcomment', start = cstart + comment.start, finish = comment.finish, + parent = result, text = trimTailComment(text:sub(cstart)), } end @@ -1261,19 +1299,30 @@ end local function bindGeneric(binded) local generics = {} for _, doc in ipairs(binded) do - if doc.type == 'doc.generic' then + if doc.type == 'doc.generic' then for _, obj in ipairs(doc.generics) do local name = obj.generic[1] - generics[name] = {} + generics[name] = true end - elseif doc.type == 'doc.param' - or doc.type == 'doc.return' - or doc.type == 'doc.type' then + end + if doc.type == 'doc.class' + or doc.type == 'doc.alias' then + if doc.signs then + for _, sign in ipairs(doc.signs) do + local name = sign[1] + generics[name] = true + end + end + end + if doc.type == 'doc.param' + or doc.type == 'doc.return' + or doc.type == 'doc.type' + or doc.type == 'doc.class' + or doc.type == 'doc.alias' then guide.eachSourceType(doc, 'doc.type.name', function (src) local name = src[1] if generics[name] then - generics[name][#generics[name]+1] = src - src.typeGeneric = generics + src.type = 'doc.generic.name' end end) end @@ -1318,6 +1367,7 @@ local function bindDocsBetween(sources, binded, bindSources, start, finish) end if src.start >= start then if src.type == 'local' + or src.type == 'self' or src.type == 'setglobal' or src.type == 'tablefield' or src.type == 'tableindex' @@ -1388,7 +1438,7 @@ end local bindDocAccept = { 'local' , 'setlocal' , 'setglobal', 'setfield' , 'setmethod' , 'setindex' , - 'tablefield', 'tableindex', + 'tablefield', 'tableindex', 'self' , 'function' , 'table' , '...' , } diff --git a/script/parser/newparser.lua b/script/parser/newparser.lua index e4884212..e226417f 100644 --- a/script/parser/newparser.lua +++ b/script/parser/newparser.lua @@ -628,10 +628,13 @@ local function parseLocalAttrs() break end if not attrs then - attrs = {} + attrs = { + type = 'localattrs', + } end local attr = { type = 'localattr', + parent = attrs, start = getPosition(Tokens[Index], 'left'), finish = getPosition(Tokens[Index], 'right'), } @@ -694,10 +697,7 @@ local function createLocal(obj, attrs) if attrs then obj.attrs = attrs - for i = 1, #attrs do - local attr = attrs[i] - attr.parent = obj - end + attrs.parent = obj end local chunk = Chunk[#Chunk] @@ -1405,7 +1405,7 @@ local function parseName(asAction) } end -local function parseNameOrList() +local function parseNameOrList(parent) local first = parseName() if not first then return nil @@ -1428,6 +1428,7 @@ local function parseNameOrList() type = 'list', start = first.start, finish = first.finish, + parent = parent, [1] = first } end @@ -1748,15 +1749,14 @@ local function addDummySelf(node, call) parent = call, } end - local newNode = {} - for k, v in next, call.node.node do - newNode[k] = v - end - newNode.mirror = call.node.node - newNode.dummy = true - newNode.parent = call.args - call.node.node.mirror = newNode - tinsert(call.args, 1, newNode) + local self = { + type = 'self', + start = node.colon.start, + finish = node.colon.finish, + parent = call.args, + [1] = 'self', + } + tinsert(call.args, 1, self) end local function parseSimple(node, funcName) @@ -2300,12 +2300,10 @@ local function parseFunction(isLocal, isAction) params[1] = createLocal { start = funcRight, finish = funcRight, - method = func.name, parent = params, - tag = 'self', - dummy = true, [1] = 'self', } + params[1].type = 'self' end end if hasLeftParen then @@ -2588,9 +2586,9 @@ local function skipSeps() end end ----@return parser.guide.object first ----@return parser.guide.object second ----@return parser.guide.object[] rest +---@return parser.object first +---@return parser.object second +---@return parser.object[] rest local function parseSetValues() skipSpace() local first = parseExp() @@ -2645,8 +2643,8 @@ local function pushActionIntoCurrentChunk(action) end end ----@return parser.guide.object second ----@return parser.guide.object[] rest +---@return parser.object second +---@return parser.object[] rest local function parseVarTails(parser, isLocal) if Tokens[Index + 1] ~= ',' then return @@ -3266,7 +3264,7 @@ local function parseFor() pushActionIntoCurrentChunk(action) pushChunk(action) skipSpace() - local nameOrList = parseNameOrList() + local nameOrList = parseNameOrList(action) if not nameOrList then missName() end @@ -3292,15 +3290,16 @@ local function parseFor() action.loc = loc end if expList then + expList.parent = action local value = expList[1] if value then - value.parent = action + value.parent = expList action.init = value action.finish = expList[#expList].finish end local max = expList[2] if max then - max.parent = action + max.parent = expList action.max = max action.finish = max.finish else @@ -3312,7 +3311,7 @@ local function parseFor() end local step = expList[3] if step then - step.parent = action + step.parent = expList action.step = step action.finish = step.finish end @@ -3346,6 +3345,7 @@ local function parseFor() type = 'list', start = nameOrList.start, finish = nameOrList.finish, + parent = action, [1] = nameOrList, } else @@ -3359,9 +3359,10 @@ local function parseFor() end action.exps = exps + exps.parent = action for i = 1, #exps do local exp = exps[i] - exp.parent = action + exp.parent = exps end else missExp() diff --git a/script/progress.lua b/script/progress.lua index 5388ef6e..b43ed05b 100644 --- a/script/progress.lua +++ b/script/progress.lua @@ -11,7 +11,7 @@ local m = {} m.map = {} ---@class progress ----@field _scp scope +---@field _uri uri local mt = {} mt.__index = mt mt._token = nil @@ -85,7 +85,7 @@ function mt:_update() and self._clock + self._delay <= time.time() then self._updated = time.time() self._dirty = false - if not config.get(self._scp.uri, 'Lua.window.progressBar') then + if not config.get(self._uri, 'Lua.window.progressBar') then return end proto.request('window/workDoneProgress/create', { @@ -108,7 +108,7 @@ function mt:_update() if not self._showed then return end - if not config.get(self._scp.uri, 'Lua.window.progressBar') then + if not config.get(self._uri, 'Lua.window.progressBar') then self:remove() return end @@ -145,16 +145,16 @@ function m.update() end ---创建一个进度条 ----@param scp scope +---@param uri uri ---@param title string # 标题 ---@param delay number # 至少经过这么久之后才会显示出来 -function m.create(scp, title, delay) +function m.create(uri, title, delay) local prog = setmetatable({ _token = nextToken(), _title = title, _clock = time.time(), _delay = delay * 1000, - _scp = scp, + _uri = uri, }, mt) m.map[prog._token] = prog diff --git a/script/proto/define.lua b/script/proto/define.lua index f1487a4d..389cdf88 100644 --- a/script/proto/define.lua +++ b/script/proto/define.lua @@ -41,7 +41,7 @@ m.DiagnosticDefaultSeverity = { ['unbalanced-assignments'] = 'Warning', ['close-non-object'] = 'Warning', ['count-down-loop'] = 'Warning', - ['no-implicit-any'] = 'Information', + ['no-unknown'] = 'Information', ['deprecated'] = 'Warning', ['different-requires'] = 'Warning', ['await-in-sync'] = 'Warning', @@ -49,7 +49,7 @@ m.DiagnosticDefaultSeverity = { ['discard-returns'] = 'Warning', ['type-check'] = 'Warning', - ['duplicate-doc-class'] = 'Warning', + ['duplicate-doc-alias'] = 'Warning', ['undefined-doc-class'] = 'Warning', ['undefined-doc-name'] = 'Warning', ['circle-doc-class'] = 'Warning', @@ -100,7 +100,7 @@ m.DiagnosticDefaultNeededFileStatus = { ['unbalanced-assignments'] = 'Any', ['close-non-object'] = 'Any', ['count-down-loop'] = 'Any', - ['no-implicit-any'] = 'None', + ['no-unknown'] = 'None', ['deprecated'] = 'Opened', ['different-requires'] = 'Any', ['await-in-sync'] = 'None', @@ -108,7 +108,7 @@ m.DiagnosticDefaultNeededFileStatus = { ['discard-returns'] = 'Opened', ['type-check'] = 'None', - ['duplicate-doc-class'] = 'Any', + ['duplicate-doc-alias'] = 'Any', ['undefined-doc-class'] = 'Any', ['undefined-doc-name'] = 'Any', ['circle-doc-class'] = 'Any', @@ -276,21 +276,6 @@ m.BuiltIn = { ['utf8'] = 'default', } -m.BuiltinType = { - ['unknown'] = true, - ['any'] = true, - ['nil'] = true, - ['boolean'] = true, - ['number'] = true, - ['integer'] = true, - ['thread'] = true, - ['table'] = true, - ['string'] = true, - ['userdata'] = true, - ['lightuserdata'] = true, - ['function'] = true, -} - m.InlayHintKind = { Other = 0, Type = 1, diff --git a/script/proto/proto.lua b/script/proto/proto.lua index 83a188f9..024b17e5 100644 --- a/script/proto/proto.lua +++ b/script/proto/proto.lua @@ -5,6 +5,7 @@ local pub = require 'pub' local jsonrpc = require 'jsonrpc' local define = require 'proto.define' local json = require 'json' +local inspect = require 'inspect' local reqCounter = util.counter() @@ -12,14 +13,14 @@ local function logSend(buf) if not RPCLOG then return end - log.debug('rpc send:', buf) + log.info('rpc send:', buf) end local function logRecieve(proto) if not RPCLOG then return end - log.debug('rpc recieve:', json.encode(proto)) + log.info('rpc recieve:', json.encode(proto)) end local m = {} @@ -49,7 +50,7 @@ end function m.response(id, res) if id == nil then - log.error('Response id is nil!', util.dump(res)) + log.error('Response id is nil!', inspect(res)) return end assert(m.holdon[id]) @@ -62,7 +63,7 @@ end function m.responseErr(id, code, message) if id == nil then - log.error('Response id is nil!', util.dump(message)) + log.error('Response id is nil!', inspect(message)) return end assert(m.holdon[id]) @@ -128,16 +129,15 @@ function m.request(name, params, callback) end local secretOption = { - format = { - ['text'] = function (value, _, _, stack) - if stack[1] == 'params' - and stack[2] == 'textDocument' - and stack[3] == nil then - return '"***"' - end - return ('%q'):format(value) + process = function (item, path) + if path[1] == 'params' + and path[2] == 'textDocument' + and path[3] == 'text' + and path[4] == nil then + return '"***"' end - } + return item + end } function m.doMethod(proto) @@ -167,8 +167,8 @@ function m.doMethod(proto) -- 任务可能在执行过程中被中断,通过close来捕获 local response <close> = function () local passed = os.clock() - clock - if passed > 0.2 then - log.debug(('Method [%s] takes [%.3f]sec. %s'):format(method, passed, util.dump(proto, secretOption))) + if passed > 0.5 then + log.warn(('Method [%s] takes [%.3f]sec. %s'):format(method, passed, inspect(proto, secretOption))) end --log.debug('Finish method:', method) if not proto.id then @@ -201,7 +201,7 @@ function m.doResponse(proto) local id = proto.id local waiting = m.waiting[id] if not waiting then - log.warn('Response id not found: ' .. util.dump(proto)) + log.warn('Response id not found: ' .. inspect(proto)) return end m.waiting[id] = nil diff --git a/script/provider/completion.lua b/script/provider/completion.lua index 3c0c82d7..46960709 100644 --- a/script/provider/completion.lua +++ b/script/provider/completion.lua @@ -7,7 +7,7 @@ local ws = require 'workspace' local isEnable = false local function allWords() - local str = '\t\n.:(\'"[,#*@|=-{/\\ +?' + local str = '\t\n.:(\'"[,#*@|=-{ +?' local mark = {} local list = {} for c in str:gmatch '.' do @@ -20,6 +20,11 @@ local function allWords() list[#list+1] = postfix mark[postfix] = true end + local separator = config.get(scp.uri, 'Lua.completion.requireSeparator') + if not mark[separator] then + list[#list+1] = separator + mark[separator] = true + end end return list end @@ -35,7 +40,7 @@ local function enable(uri) end nonil.disable() isEnable = true - log.debug('Enable completion.') + log.info('Enable completion.') proto.request('client/registerCapability', { registrations = { { @@ -61,7 +66,7 @@ local function disable(uri) end nonil.disable() isEnable = false - log.debug('Disable completion.') + log.info('Disable completion.') proto.request('client/unregisterCapability', { unregisterations = { { diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index ac93dc52..b359c21c 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -141,7 +141,7 @@ function m.clear(uri) uri = uri, diagnostics = {}, }) - log.debug('clearDiagnostics', uri) + log.info('clearDiagnostics', uri) end function m.clearCache(uri) @@ -221,9 +221,8 @@ function m.doDiagnostic(uri, isScopeDiag) end local version = files.getVersion(uri) - local scp = scope.getScope(uri) - local prog <close> = progress.create(scp, lang.script.WINDOW_DIAGNOSING, 0.5) + local prog <close> = progress.create(uri, lang.script.WINDOW_DIAGNOSING, 0.5) prog:setMessage(ws.getRelativePath(uri)) --log.debug('Diagnostic file:', uri) @@ -367,10 +366,10 @@ function m.awaitDiagnosticsScope(suri) await.sleep(1.0) end local clock = os.clock() - local bar <close> = progress.create(scope.getScope(suri), lang.script.WORKSPACE_DIAGNOSTIC, 1) + local bar <close> = progress.create(suri, lang.script.WORKSPACE_DIAGNOSTIC, 1) local cancelled bar:onCancel(function () - log.debug('Cancel workspace diagnostics') + log.info('Cancel workspace diagnostics') cancelled = true ---@async await.call(function () @@ -398,12 +397,12 @@ function m.awaitDiagnosticsScope(suri) xpcall(m.doDiagnostic, log.error, uri, true) await.delay() if cancelled then - log.debug('Break workspace diagnostics') + log.info('Break workspace diagnostics') break end end bar:remove() - log.debug(('Diagnostics scope [%s] finished, takes [%.3f] sec.'):format(scp:getName(), os.clock() - clock)) + log.info(('Diagnostics scope [%s] finished, takes [%.3f] sec.'):format(scp:getName(), os.clock() - clock)) end function m.diagnosticsScope(uri, force) diff --git a/script/provider/formatting.lua b/script/provider/formatting.lua index f73b0dc0..73b6608d 100644 --- a/script/provider/formatting.lua +++ b/script/provider/formatting.lua @@ -1,4 +1,8 @@ -local codeFormat = require 'code_format' +local suc, codeFormat = pcall(require, 'code_format') +if not suc then + return +end + local ws = require 'workspace' local furi = require 'file-uri' local fs = require 'bee.filesystem' diff --git a/script/provider/provider.lua b/script/provider/provider.lua index eef22b21..35b5147b 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -18,14 +18,15 @@ local filewatch = require 'filewatch' local json = require 'json' local scope = require 'workspace.scope' local furi = require 'file-uri' +local inspect = require 'inspect' ---@async local function updateConfig(uri) config.addNullSymbol(json.null) local specified = cfgLoader.loadLocalConfig(uri, CONFIGPATH) if specified then - log.debug('Load config from specified', CONFIGPATH) - log.debug(util.dump(specified)) + log.info('Load config from specified', CONFIGPATH) + log.debug(inspect(specified)) -- watch directory filewatch.watch(workspace.getAbsolutePath(uri, CONFIGPATH):gsub('[^/\\]+$', '')) config.update(scope.override, specified) @@ -34,22 +35,22 @@ local function updateConfig(uri) for _, folder in ipairs(scope.folders) do local clientConfig = cfgLoader.loadClientConfig(folder.uri) if clientConfig then - log.debug('Load config from client', folder.uri) - log.debug(util.dump(clientConfig)) + log.info('Load config from client', folder.uri) + log.debug(inspect(clientConfig)) end local rc = cfgLoader.loadRCConfig(folder.uri, '.luarc.json') if rc then - log.debug('Load config from luarc.json', folder.uri) - log.debug(util.dump(rc)) + log.info('Load config from luarc.json', folder.uri) + log.debug(inspect(rc)) end config.update(folder, clientConfig, rc) end local global = cfgLoader.loadClientConfig() - log.debug('Load config from client', 'fallback') - log.debug(util.dump(global)) + log.info('Load config from client', 'fallback') + log.debug(inspect(global)) config.update(scope.fallback, global) end @@ -119,7 +120,7 @@ m.register 'initialized'{ ---@async function (params) files.init() - local _ <close> = progress.create(workspace.getFirstScope(), lang.script.WINDOW_INITIALIZING, 0.5) + local _ <close> = progress.create(workspace.getFirstScope().uri, lang.script.WINDOW_INITIALIZING, 0.5) updateConfig() local registrations = {} @@ -169,7 +170,7 @@ m.register 'workspace/didChangeConfiguration' { m.register 'workspace/didCreateFiles' { ---@async function (params) - log.debug('workspace/didCreateFiles', util.dump(params)) + log.debug('workspace/didCreateFiles', inspect(params)) for _, file in ipairs(params.files) do if workspace.isValidLuaUri(file.uri) then files.setText(file.uri, util.loadFile(furi.decode(file.uri)), false) @@ -180,7 +181,7 @@ m.register 'workspace/didCreateFiles' { m.register 'workspace/didDeleteFiles' { function (params) - log.debug('workspace/didDeleteFiles', util.dump(params)) + log.debug('workspace/didDeleteFiles', inspect(params)) for _, file in ipairs(params.files) do files.remove(file.uri) local childs = files.getChildFiles(file.uri) @@ -195,7 +196,7 @@ m.register 'workspace/didDeleteFiles' { m.register 'workspace/didRenameFiles' { ---@async function (params) - log.debug('workspace/didRenameFiles', util.dump(params)) + log.debug('workspace/didRenameFiles', inspect(params)) for _, file in ipairs(params.files) do local text = files.getOriginText(file.oldUri) if text then @@ -292,7 +293,7 @@ m.register 'textDocument/hover' { } } end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_HOVER, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_HOVER, 0.5) local core = require 'core.hover' if not files.exists(uri) then return nil @@ -324,7 +325,7 @@ m.register 'textDocument/definition' { if not files.exists(uri) then return nil end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) local core = require 'core.definition' local pos = converter.unpackPosition(uri, params.position) local result = core(uri, pos) @@ -366,7 +367,7 @@ m.register 'textDocument/typeDefinition' { if not files.exists(uri) then return nil end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) local core = require 'core.type-definition' local pos = converter.unpackPosition(uri, params.position) local result = core(uri, pos) @@ -408,7 +409,7 @@ m.register 'textDocument/references' { if not files.exists(uri) then return nil end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) local core = require 'core.reference' local pos = converter.unpackPosition(uri, params.position) local result = core(uri, pos) @@ -468,7 +469,7 @@ m.register 'textDocument/rename' { if not files.exists(uri) then return nil end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_RENAME, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_RENAME, 0.5) local core = require 'core.rename' local pos = converter.unpackPosition(uri, params.position) local result = core.rename(uri, pos, params.newName) @@ -528,7 +529,7 @@ m.register 'textDocument/completion' { } } end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_COMPLETION, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_COMPLETION, 0.5) --log.info(util.dump(params)) local core = require 'core.completion' --log.debug('textDocument/completion') @@ -618,6 +619,9 @@ m.register 'textDocument/completion' { end items[i] = item end + if result.incomplete == nil then + result.incomplete = false + end return { isIncomplete = result.incomplete, items = items, @@ -679,7 +683,7 @@ m.register 'textDocument/signatureHelp' { if not files.exists(uri) then return nil end - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) local pos = converter.unpackPosition(uri, params.position) local core = require 'core.signature' local results = core(uri, pos) @@ -722,7 +726,7 @@ m.register 'textDocument/documentSymbol' { function (params) local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) local core = require 'core.document-symbol' local symbols = core(uri) @@ -848,7 +852,7 @@ m.register 'workspace/symbol' { abortByFileUpdate = true, ---@async function (params) - local _ <close> = progress.create(workspace.getFirstScope(), lang.script.WINDOW_PROCESSING_WS_SYMBOL, 0.5) + local _ <close> = progress.create(workspace.getFirstScope().uri, lang.script.WINDOW_PROCESSING_WS_SYMBOL, 0.5) local core = require 'core.workspace-symbol' local symbols = core(params.query) @@ -902,7 +906,7 @@ m.register 'textDocument/semanticTokens/full' { log.debug('textDocument/semanticTokens/full') local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) local core = require 'core.semantic-tokens' local results = core(uri, 0, math.huge) return { @@ -926,7 +930,7 @@ m.register 'textDocument/semanticTokens/range' { log.debug('textDocument/semanticTokens/range') local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) local core = require 'core.semantic-tokens' local start, finish = converter.unpackRange(uri, params.range) local results = core(uri, start, finish) @@ -995,8 +999,8 @@ m.register '$/status/click' { ---@async function () -- TODO: translate - local titleDiagnostic = '进行工作区诊断' - local result = client.awaitRequestMessage('Info', 'xxx', { + local titleDiagnostic = lang.script.WINDOW_LUA_STATUS_DIAGNOSIS_TITLE + local result = client.awaitRequestMessage('Info', lang.script.WINDOW_LUA_STATUS_DIAGNOSIS_MSG, { titleDiagnostic, }) if not result then @@ -1015,7 +1019,6 @@ m.register 'textDocument/formatting' { capability = { documentFormattingProvider = true, }, - preview = true, ---@async function(params) local uri = files.getRealUri(params.textDocument.uri) @@ -1053,7 +1056,6 @@ m.register 'textDocument/rangeFormatting' { capability = { documentRangeFormattingProvider = true, }, - preview = true, ---@async function(params) local uri = files.getRealUri(params.textDocument.uri) @@ -1099,7 +1101,7 @@ m.register 'textDocument/onTypeFormatting' { function (params) local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) local ch = params.ch if not files.exists(uri) then return nil @@ -1175,7 +1177,7 @@ do workspace.awaitReady(uri) local edits = {} local hint = require 'core.hint' - local _ <close> = progress.create(scope.getScope(uri), lang.script.WINDOW_PROCESSING_HINT, 0.5) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_HINT, 0.5) for _, visible in ipairs(visibles) do local piece = hint(uri, visible.start, visible.finish) if piece then diff --git a/script/pub/pub.lua b/script/pub/pub.lua index 1b2dbcac..e73aea51 100644 --- a/script/pub/pub.lua +++ b/script/pub/pub.lua @@ -45,7 +45,7 @@ end function m.recruitBraves(num) for _ = 1, num do local id = #m.braves + 1 - log.info('Create brave:', id) + log.debug('Create brave:', id) m.braves[id] = { id = id, thread = thread.thread(braveTemplate:format( diff --git a/script/service/service.lua b/script/service/service.lua index a1db02a8..26790c63 100644 --- a/script/service/service.lua +++ b/script/service/service.lua @@ -128,7 +128,7 @@ function m.reportProto() end function m.report() - local t = timer.loop(60.0, function () + local t = timer.loop(600.0, function () local lines = {} lines[#lines+1] = '' lines[#lines+1] = '========= Medical Examination Report =========' @@ -138,7 +138,7 @@ function m.report() lines[#lines+1] = m.reportProto() lines[#lines+1] = '==============================================' - log.debug(table.concat(lines, '\n')) + log.info(table.concat(lines, '\n')) end) t:onTimer() end diff --git a/script/service/telemetry.lua b/script/service/telemetry.lua index d975a986..50af39b1 100644 --- a/script/service/telemetry.lua +++ b/script/service/telemetry.lua @@ -19,7 +19,7 @@ if not token then util.saveFile(tokenPath, token) end -log.info('Telemetry Token:', token) +log.debug('Telemetry Token:', token) local function getClientName() nonil.enable() @@ -90,7 +90,6 @@ local function pushErrorLog(link) )) end -local validMap = {} local isValid = false timer.wait(5, function () @@ -100,7 +99,7 @@ timer.wait(5, function () end local suc, link = pcall(net.connect, 'tcp', 'moe-moe.love', 11577) if not suc then - suc, link = pcall(net.connect, 'tcp', '119.45.194.183', 11577) + suc, link = pcall(net.connect, 'tcp', '154.23.191.94', 11577) end if not suc or not link then return @@ -145,6 +144,9 @@ function m.updateConfig() if isValid ~= nil then return end + if not client.getOption 'changeConfiguration' then + return + end if m.hasShowedMessage then return end diff --git a/script/utility.lua b/script/utility.lua index 6758a47f..41229f53 100644 --- a/script/utility.lua +++ b/script/utility.lua @@ -21,9 +21,8 @@ local getupvalue = debug.getupvalue local mathHuge = math.huge local inf = 1 / 0 local nan = 0 / 0 -local utf8 = utf8 local error = error -local upvalueid = debug.upvalueid +local assert = assert _ENV = nil @@ -674,29 +673,67 @@ function m.arrayToHash(l) return t end -function m.switch() - local map = {} - local cachedCases = {} - local obj = { - case = function (self, name) - cachedCases[#cachedCases+1] = name - return self - end, - call = function (self, callback) - for i = 1, #cachedCases do - local name = cachedCases[i] - cachedCases[i] = nil - if map[name] then - error('Repeated fields:' .. tostring(name)) - end - map[name] = callback - end - return self - end, - getMap = function (self) - return map +---@class switch +---@field cachedCases string[] +---@field map table<string, function> +---@field _default fun(...):... +local switchMT = {} +switchMT.__index = switchMT + +---@param name string +---@return switch +function switchMT:case(name) + self.cachedCases[#self.cachedCases+1] = name + return self +end + +---@param callback async fun(...):... +---@return switch +function switchMT:call(callback) + for i = 1, #self.cachedCases do + local name = self.cachedCases[i] + self.cachedCases[i] = nil + if self.map[name] then + error('Repeated fields:' .. tostring(name)) end - } + self.map[name] = callback + end + return self +end + +---@param callback fun(...):... +---@return switch +function switchMT:default(callback) + self._default = callback + return self +end + +function switchMT:getMap() + return self.map +end + +---@param name string +---@return boolean +function switchMT:has(name) + return self.map[name] ~= nil +end + +---@param name string +---@return ... +function switchMT:__call(name, ...) + local callback = self.map[name] or self._default + if not callback then + return + end + return callback(...) +end + +---@return switch +function m.switch() + local obj = setmetatable({ + map = {}, + cachedCases = {}, + }, switchMT) return obj end @@ -730,4 +767,32 @@ function m.defaultTable(default) end }) end +function m.multiTable(count, default) + local current + if default then + current = setmetatable({}, { __index = function (t, k) + local v = default(k) + t[k] = v + return v + end }) + else + current = setmetatable({}, { __index = function (t, k) + local v = {} + t[k] = v + return v + end }) + end + for _ = 3, count do + current = setmetatable({}, { __index = function (t, k) + t[k] = current + return current + end }) + end + return current +end + +m.MODE_K = { __mode = 'k' } +m.MODE_V = { __mode = 'v' } +m.MODE_KV = { __mode = 'kv' } + return m diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua new file mode 100644 index 00000000..7b6531aa --- /dev/null +++ b/script/vm/compiler.lua @@ -0,0 +1,1670 @@ +local guide = require 'parser.guide' +local util = require 'utility' +local localID = require 'vm.local-id' +local globalMgr = require 'vm.global-manager' +local signMgr = require 'vm.sign' +local config = require 'config' +local genericMgr = require 'vm.generic' +local rpath = require 'workspace.require-path' +local files = require 'files' +---@class vm +local vm = require 'vm.vm' + +---@class parser.object +---@field _compiledNodes boolean +---@field _node vm.node +---@field _localBase table +---@field _globalBase table + +local searchFieldSwitch = util.switch() + : case 'table' + : call(function (suri, source, key, pushResult) + local hasFiled = false + for _, field in ipairs(source) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + local fieldKey = guide.getKeyName(field) + if key == nil + or key == fieldKey then + hasFiled = true + pushResult(field) + end + end + if field.type == 'tableexp' then + if key == nil + or key == field.tindex then + hasFiled = true + pushResult(field) + end + end + if field.type == 'varargs' then + if not hasFiled + and type(key) == 'number' + and key >= 1 + and math.tointeger(key) then + hasFiled = true + pushResult(field) + end + if key == nil then + pushResult(field) + end + end + end + end) + : case 'string' + : call(function (suri, source, key, pushResult) + -- change to `string: stringlib` ? + local stringlib = globalMgr.getGlobal('type', 'stringlib') + if stringlib then + vm.getClassFields(suri, stringlib, key, pushResult) + end + end) + : case 'local' + : case 'self' + : call(function (suri, node, key, pushResult) + local fields + if key then + fields = localID.getSources(node, key) + else + fields = localID.getFields(node) + end + if fields then + for _, src in ipairs(fields) do + pushResult(src) + end + end + end) + : case 'doc.type.array' + : call(function (suri, source, key, pushResult) + if type(key) == 'number' then + if key < 1 + or not math.tointeger(key) then + return + end + end + pushResult(source.node) + end) + : case 'doc.type.table' + : call(function (suri, source, key, pushResult) + for _, field in ipairs(source.fields) do + local fieldKey = field.name + if fieldKey.type == 'doc.type' then + local fieldNode = vm.compileNode(fieldKey) + for fn in fieldNode:eachObject() do + if fn.type == 'global' and fn.cate == 'type' then + if key == nil + or fn.name == 'any' + or (fn.name == 'boolean' and type(key) == 'boolean') + or (fn.name == 'number' and type(key) == 'number') + or (fn.name == 'integer' and math.tointeger(key)) + or (fn.name == 'string' and type(key) == 'string') then + pushResult(field) + end + end + end + end + if fieldKey.type == 'doc.field.name' then + if key == nil or fieldKey[1] == key then + pushResult(field) + end + end + end + end) + : case 'global' + : call(function (suri, node, key, pushResult) + if node.cate == 'variable' then + if key then + if type(key) ~= 'string' then + return + end + local global = globalMgr.getGlobal('variable', node.name, key) + if global then + for _, set in ipairs(global:getSets(suri)) do + pushResult(set) + end + for _, get in ipairs(global:getGets(suri)) do + pushResult(get) + end + end + else + local globals = globalMgr.getFields('variable', node.name) + for _, global in ipairs(globals) do + for _, set in ipairs(global:getSets(suri)) do + pushResult(set) + end + for _, get in ipairs(global:getGets(suri)) do + pushResult(get) + end + end + end + end + if node.cate == 'type' then + vm.getClassFields(suri, node, key, pushResult) + end + end) + : default(function (suri, source, key, pushResult) + local node = source._globalNode + if not node then + return + end + if node.cate == 'variable' then + if key then + if type(key) ~= 'string' then + return + end + local global = globalMgr.getGlobal('variable', node.name, key) + if global then + for _, set in ipairs(global:getSets(suri)) do + pushResult(set) + end + for _, get in ipairs(global:getGets(suri)) do + pushResult(get) + end + end + else + local globals = globalMgr.getFields('variable', node.name) + for _, global in ipairs(globals) do + for _, set in ipairs(global:getSets(suri)) do + pushResult(set) + end + for _, get in ipairs(global:getGets(suri)) do + pushResult(get) + end + end + end + end + if node.cate == 'type' then + vm.getClassFields(suri, node, key, pushResult) + end + end) + + +function vm.getClassFields(suri, node, key, pushResult) + local mark = {} + + local function searchClass(class, searchedFields) + local name = class.name + if mark[name] then + return + end + mark[name] = true + searchedFields = searchedFields or {} + for _, set in ipairs(class:getSets(suri)) do + if set.type == 'doc.class' then + -- check ---@field + local hasFounded = {} + for _, field in ipairs(set.fields) do + local fieldKey = guide.getKeyName(field) + if key == nil + or fieldKey == key then + if not searchedFields[fieldKey] then + pushResult(field) + hasFounded[fieldKey] = true + end + end + end + -- check local field and global field + if set.bindSources then + for _, src in ipairs(set.bindSources) do + searchFieldSwitch(src.type, suri, src, key, function (field) + local fieldKey = guide.getKeyName(field) + if not searchedFields[fieldKey] + and guide.isSet(field) then + hasFounded[fieldKey] = true + pushResult(field) + end + end) + if src.value and src.value.type == 'table' then + searchFieldSwitch('table', suri, src.value, key, function (field) + local fieldKey = guide.getKeyName(field) + if not searchedFields[fieldKey] + and guide.isSet(field) then + hasFounded[fieldKey] = true + pushResult(field) + end + end) + end + end + end + -- look into extends(if field not found) + if not hasFounded[key] and set.extends then + for fieldKey in pairs(hasFounded) do + searchedFields[fieldKey] = true + end + for _, extend in ipairs(set.extends) do + if extend.type == 'doc.extends.name' then + local extendType = globalMgr.getGlobal('type', extend[1]) + if extendType then + searchClass(extendType, searchedFields) + end + end + end + end + end + end + end + + local function searchGlobal(class) + if class.cate == 'type' and class.name == '_G' then + local sets = globalMgr.getGlobalSets(suri, 'variable') + for _, set in ipairs(sets) do + pushResult(set) + end + end + end + + searchClass(node) + searchGlobal(node) +end + +---@class parser.object +---@field _sign? vm.sign + +---@param source parser.object +---@return vm.sign? +local function getObjectSign(source) + if source._sign ~= nil then + return source._sign + end + source._sign = false + if source.type == 'function' then + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.generic' then + if not source._sign then + source._sign = signMgr() + break + end + end + end + if not source._sign then + return false + end + if source.args then + for _, arg in ipairs(source.args) do + local argNode = vm.compileNode(arg) + if arg.optional then + argNode:addOptional() + end + source._sign:addSign(argNode) + end + end + end + if source.type == 'doc.type.function' + or source.type == 'doc.type.table' + or source.type == 'doc.type.array' then + local hasGeneric + guide.eachSourceType(source, 'doc.generic.name', function () + hasGeneric = true + end) + if not hasGeneric then + return false + end + source._sign = signMgr() + if source.type == 'doc.type.function' then + for _, arg in ipairs(source.args) do + local argNode = vm.compileNode(arg.extends) + if arg.optional then + argNode:addOptional() + end + source._sign:addSign(argNode) + end + end + end + return source._sign +end + +---@param func parser.object +---@param index integer +---@return vm.object? +function vm.getReturnOfFunction(func, index) + if func.type == 'function' then + if not func._returns then + func._returns = {} + end + if not func._returns[index] then + func._returns[index] = { + type = 'function.return', + parent = func, + index = index, + } + end + return func._returns[index] + end + if func.type == 'doc.type.function' then + local rtn = func.returns[index] + if not rtn then + return nil + end + local sign = getObjectSign(func) + if not sign then + return rtn + end + return genericMgr(rtn, sign) + end +end + +---@return vm.node +local function getReturnOfSetMetaTable(args) + local tbl = args[1] + local mt = args[2] + local node = vm.createNode() + if tbl then + node:merge(vm.compileNode(tbl)) + end + if mt then + vm.compileByParentNode(mt, '__index', function (src) + for n in vm.compileNode(src):eachObject() do + if n.type == 'global' + or n.type == 'local' + or n.type == 'table' + or n.type == 'doc.type.table' then + node:merge(n) + end + end + end) + end + return node +end + +---@return vm.node? +local function getReturn(func, index, args) + if func.special == 'setmetatable' then + if not args then + return nil + end + return getReturnOfSetMetaTable(args) + end + if func.special == 'pcall' and index > 1 then + if not args then + return nil + end + local newArgs = {} + for i = 2, #args do + newArgs[#newArgs+1] = args[i] + end + return getReturn(args[1], index - 1, newArgs) + end + if func.special == 'xpcall' and index > 1 then + if not args then + return nil + end + local newArgs = {} + for i = 3, #args do + newArgs[#newArgs+1] = args[i] + end + return getReturn(args[1], index - 1, newArgs) + end + if func.special == 'require' then + if not args then + return nil + end + local nameArg = args[1] + if not nameArg or nameArg.type ~= 'string' then + return nil + end + local name = nameArg[1] + if not name or type(name) ~= 'string' then + return nil + end + local uri = rpath.findUrisByRequirePath(guide.getUri(func), name)[1] + if not uri then + return nil + end + local state = files.getState(uri) + local ast = state and state.ast + if not ast then + return nil + end + return vm.compileNode(ast) + end + local node = vm.compileNode(func) + ---@type vm.node? + local result + for cnode in node:eachObject() do + if cnode.type == 'function' + or cnode.type == 'doc.type.function' then + local returnObject = vm.getReturnOfFunction(cnode, index) + if returnObject then + local returnNode = vm.compileNode(returnObject) + for rnode in returnNode:eachObject() do + if rnode.type == 'generic' then + returnNode = rnode:resolve(guide.getUri(func), args) + break + end + end + if returnNode then + for rnode in returnNode:eachObject() do + -- TODO: narrow type + if rnode.type ~= 'doc.generic.name' then + result = result or vm.createNode() + result:merge(rnode) + end + end + end + end + end + end + return result +end + +local function bindDocs(source) + local isParam = source.parent.type == 'funcargs' + or source.parent.type == 'in' + local docs = source.bindDocs + for i = #docs, 1, -1 do + local doc = docs[i] + if doc.type == 'doc.type' then + if not isParam then + vm.setNode(source, vm.compileNode(doc)) + return true + end + end + if doc.type == 'doc.class' then + if (source.type == 'local' and not isParam) + or (source._globalNode and guide.isSet(source)) + or source.type == 'tablefield' + or source.type == 'tableindex' then + vm.setNode(source, vm.compileNode(doc)) + return true + end + end + if doc.type == 'doc.param' then + if isParam and source[1] == doc.param[1] then + vm.setNode(source, vm.compileNode(doc)) + return true + end + end + if doc.type == 'doc.module' then + local name = doc.module + local uri = rpath.findUrisByRequirePath(guide.getUri(source), name)[1] + if not uri then + return nil + end + local state = files.getState(uri) + local ast = state and state.ast + if not ast then + return nil + end + vm.setNode(source, vm.compileNode(ast)) + return true + end + end + return false +end + +local function compileByLocalID(source) + local sources = localID.getSources(source) + if not sources then + return + end + local hasMarkDoc + for _, src in ipairs(sources) do + if src.bindDocs then + if bindDocs(src) then + hasMarkDoc = true + vm.setNode(source, vm.compileNode(src)) + end + end + end + for _, src in ipairs(sources) do + if src.value then + if not hasMarkDoc or guide.isLiteral(src.value) then + if src.value.type ~= 'nil' then + vm.setNode(source, vm.compileNode(src.value)) + end + end + end + end +end + +---@param source vm.node +---@param key? any +---@param pushResult fun(source: parser.object) +function vm.compileByParentNode(source, key, pushResult) + local parentNode = vm.compileNode(source) + local suri = guide.getUri(source) + for node in parentNode:eachObject() do + searchFieldSwitch(node.type, suri, node, key, pushResult) + end +end + +---@return vm.node? +local function selectNode(source, list, index) + if not list then + return nil + end + local exp + if list[index] then + exp = list[index] + else + for i = index, 1, -1 do + if list[i] then + local last = list[i] + if last.type == 'call' + or last.type == '...' then + index = index - i + 1 + exp = last + end + break + end + end + end + if not exp then + return nil + end + local result + if exp.type == 'call' then + result = getReturn(exp.node, index, exp.args) + if not result then + vm.setNode(source, globalMgr.getGlobal('type', 'unknown')) + return vm.getNode(source) + end + else + result = vm.compileNode(exp) + end + if source.type == 'function.return' then + -- remove any for returns + local rtnNode = vm.createNode() + local hasKnownType + for n in result:eachObject() do + if guide.isLiteral(n) then + hasKnownType = true + rtnNode:merge(n) + end + if n.type == 'global' and n.cate == 'type' then + if n.name ~= 'any' + and n.name ~= 'unknown' then + hasKnownType = true + rtnNode:merge(n) + end + else + rtnNode:merge(n) + end + end + if not hasKnownType then + rtnNode:merge(globalMgr.getGlobal('type', 'unknown')) + end + vm.setNode(source, rtnNode) + return rtnNode + end + vm.setNode(source, result) + return result +end + +---@param source parser.object +---@param node vm.object +---@return boolean +local function isValidCallArgNode(source, node) + if source.type == 'function' then + return node.type == 'doc.type.function' + end + if source.type == 'table' then + return node.type == 'doc.type.table' + or (node.type == 'global' and node.cate == 'type' and not guide.isBasicType(node.name)) + end + if source.type == 'dummyarg' then + return true + end + return false +end + +---@param func parser.object +---@param index integer +---@return parser.object? +local function getFuncArg(func, index) + local args = func.args + if not args then + return nil + end + if args[index] then + return args[index] + end + local lastArg = args[#args] + if lastArg and lastArg.type == '...' then + return lastArg + end + return nil +end + +---@param arg parser.object +---@param call parser.object +---@param callNode vm.node +---@param fixIndex integer +---@param myIndex integer +local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex) + local eventIndex, eventMap + if call.args then + for i = 1, 2 do + local eventArg = call.args[i + fixIndex] + if not eventArg then + break + end + eventMap = vm.getLiterals(eventArg) + if eventMap then + eventIndex = i + break + end + end + end + + for n in callNode:eachObject() do + if n.type == 'function' then + local farg = getFuncArg(n, myIndex) + if farg then + for fn in vm.compileNode(farg):eachObject() do + if isValidCallArgNode(arg, fn) then + vm.setNode(arg, fn) + end + end + end + end + if n.type == 'doc.type.function' then + local myEvent + if n.args[eventIndex] then + local argNode = vm.compileNode(n.args[eventIndex]) + myEvent = argNode:get(1) + end + if not myEvent + or not eventMap + or myIndex <= eventIndex + or myEvent.type ~= 'doc.type.string' + or eventMap[myEvent[1]] then + local farg = getFuncArg(n, myIndex) + if farg then + for fn in vm.compileNode(farg):eachObject() do + if isValidCallArgNode(arg, fn) then + vm.setNode(arg, fn) + end + end + end + end + end + end +end + +---@param arg parser.object +---@param call parser.object +---@param index? integer +function vm.compileCallArg(arg, call, index) + if not index then + for i, carg in ipairs(call.args) do + if carg == arg then + index = i + break + end + end + end + + local callNode = vm.compileNode(call.node) + compileCallArgNode(arg, call, callNode, 0, index) + + if call.node.special == 'pcall' + or call.node.special == 'xpcall' then + local fixIndex = call.node.special == 'pcall' and 1 or 2 + callNode = vm.compileNode(call.args[1]) + compileCallArgNode(arg, call, callNode, fixIndex, index - fixIndex) + end + return vm.getNode(arg) +end + +---@param source parser.object +---@return vm.node +local function compileLocalBase(source) + if not source._localBase then + source._localBase = { + type = 'localbase', + parent = source, + } + end + local baseNode = vm.getNode(source._localBase) + if baseNode then + return baseNode + end + baseNode = vm.createNode() + vm.setNode(source._localBase, baseNode, true) + + vm.setNode(source, source) + local hasMarkDoc + if source.bindDocs then + hasMarkDoc = bindDocs(source) + end + local hasMarkParam + if source.type == 'self' and not hasMarkDoc then + hasMarkParam = true + if source.parent.type == 'callargs' then + -- obj:func(...) + vm.setNode(source, vm.compileNode(source.parent.parent.node.node)) + else + -- function obj:func(...) + vm.setNode(source, vm.compileNode(source.parent.parent.parent.node)) + end + end + local hasMarkValue + if source.value then + if not hasMarkDoc or guide.isLiteral(source.value) then + hasMarkValue = true + if source.value.type == 'table' then + vm.setNode(source, source.value) + elseif source.value.type ~= 'nil' then + vm.setNode(source, vm.compileNode(source.value)) + end + end + end + -- function x.y(self, ...) --> function x:y(...) + if source[1] == 'self' + and not hasMarkDoc + and source.parent.type == 'funcargs' + and source.parent[1] == source then + local setfield = source.parent.parent.parent + if setfield.type == 'setfield' then + hasMarkParam = true + vm.setNode(source, vm.compileNode(setfield.node)) + end + end + if source.parent.type == 'funcargs' and not hasMarkDoc and not hasMarkParam then + local func = source.parent.parent + 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 + vm.setNode(source, vm.compileNode(arg)) + hasDocArg = true + end + end + end + end + if not hasDocArg then + vm.setNode(source, globalMgr.getGlobal('type', 'any')) + end + end + -- for x in ... do + if source.parent.type == 'in' then + vm.compileNode(source.parent) + end + + -- for x = ... do + if source.parent.type == 'loop' then + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + end + + baseNode:merge(vm.getNode(source)) + vm.removeNode(source) + + baseNode:setData('hasDefined', hasMarkDoc or hasMarkParam or hasMarkValue) + + return baseNode +end + +local compilerSwitch = util.switch() + : case 'nil' + : case 'boolean' + : case 'integer' + : case 'number' + : case 'string' + : case 'doc.type.function' + : case 'doc.type.table' + : case 'doc.type.array' + : call(function (source) + vm.setNode(source, source) + end) + : case 'table' + : call(function (source) + vm.setNode(source, source) + + if source.parent.type == 'callargs' then + local call = source.parent.parent + vm.compileCallArg(source, call) + end + + if source.parent.type == 'setglobal' + or source.parent.type == 'local' + or source.parent.type == 'setlocal' + or source.parent.type == 'tablefield' + or source.parent.type == 'tableindex' + or source.parent.type == 'setfield' + or source.parent.type == 'setindex' then + vm.setNode(source, vm.compileNode(source.parent)) + end + end) + : case 'function' + : call(function (source) + vm.setNode(source, source) + + if source.bindDocs then + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.overload' then + vm.setNode(source, vm.compileNode(doc)) + end + end + end + + -- table.sort(string[], function (<?x?>) end) + if source.parent.type == 'callargs' then + local call = source.parent.parent + vm.compileCallArg(source, call) + end + end) + : case 'paren' + : call(function (source) + if source.exp then + vm.setNode(source, vm.compileNode(source.exp)) + end + end) + : case 'local' + : case 'self' + : call(function (source) + local baseNode = compileLocalBase(source) + vm.setNode(source, baseNode, true) + if not baseNode:getData 'hasDefined' and source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + vm.setNode(source, vm.compileNode(ref)) + end + end + end + end) + : case 'setlocal' + : call(function (source) + local baseNode = compileLocalBase(source.node) + if not baseNode:getData 'hasDefined' and source.value then + if source.value.type == 'table' then + vm.setNode(source, source.value) + else + vm.setNode(source, vm.compileNode(source.value)) + end + end + baseNode:merge(vm.getNode(source)) + vm.setNode(source, baseNode, true) + vm.compileNode(source.node) + end) + : case 'getlocal' + : call(function (source) + local baseNode = compileLocalBase(source.node) + vm.setNode(source, baseNode, true) + vm.compileNode(source.node) + end) + : case 'setfield' + : case 'setmethod' + : case 'setindex' + : call(function (source) + compileByLocalID(source) + local key = guide.getKeyName(source) + if key == nil then + return + end + vm.compileByParentNode(source.node, key, function (src) + if src.type == 'doc.type.field' + or src.type == 'doc.field' then + vm.setNode(source, vm.compileNode(src)) + end + end) + end) + : case 'getfield' + : case 'getmethod' + : case 'getindex' + : call(function (source) + compileByLocalID(source) + local key = guide.getKeyName(source) + if key == nil and source.index then + key = vm.compileNode(source.index) + end + if key == nil then + return + end + if type(key) == 'table' then + local uri = guide.getUri(source) + local value = vm.getTableValue(uri, vm.compileNode(source.node), key) + if value then + vm.setNode(source, value) + end + else + vm.compileByParentNode(source.node, key, function (src) + vm.setNode(source, vm.compileNode(src)) + end) + end + end) + : case 'setglobal' + : call(function (source) + if source.node[1] ~= '_ENV' then + return + end + local key = guide.getKeyName(source) + vm.compileByParentNode(source.node, key, function (src) + if src.type == 'doc.type.field' + or src.type == 'doc.field' then + vm.setNode(source, vm.compileNode(src)) + end + end) + end) + : case 'getglobal' + : call(function (source) + if source.node[1] ~= '_ENV' then + return + end + local key = guide.getKeyName(source) + vm.compileByParentNode(source.node, key, function (src) + vm.setNode(source, vm.compileNode(src)) + end) + end) + : case 'tablefield' + : case 'tableindex' + : call(function (source) + local hasMarkDoc + if source.bindDocs then + hasMarkDoc = bindDocs(source) + end + + if source.value then + if not hasMarkDoc or guide.isLiteral(source.value) then + if source.value.type == 'table' then + vm.setNode(source, source.value) + elseif source.value.type ~= 'nil' then + vm.setNode(source, vm.compileNode(source.value)) + end + end + end + + if not hasMarkDoc then + vm.compileByParentNode(source.parent, guide.getKeyName(source), function (src) + vm.setNode(source, vm.compileNode(src)) + end) + end + end) + : case 'field' + : case 'method' + : call(function (source) + vm.setNode(source, vm.compileNode(source.parent)) + end) + : case 'tableexp' + : call(function (source) + vm.setNode(source, vm.compileNode(source.value)) + end) + : case 'function.return' + : call(function (source) + local func = source.parent + local index = source.index + local hasMarkDoc + if func.bindDocs then + local sign = getObjectSign(func) + for _, doc in ipairs(func.bindDocs) do + if doc.type == 'doc.return' then + for _, rtn in ipairs(doc.returns) do + if rtn.returnIndex == index then + hasMarkDoc = true + local hasGeneric + if sign then + guide.eachSourceType(rtn, 'doc.generic.name', function (src) + hasGeneric = true + end) + end + if hasGeneric then + vm.setNode(source, genericMgr(rtn, sign)) + else + vm.setNode(source, vm.compileNode(rtn)) + end + end + end + end + end + end + if func.returns and not hasMarkDoc then + for _, rtn in ipairs(func.returns) do + selectNode(source, rtn, index) + end + end + end) + : case 'main' + : call(function (source) + if source.returns then + for _, rtn in ipairs(source.returns) do + if rtn[1] then + vm.setNode(source, vm.compileNode(rtn[1])) + end + end + end + end) + : case 'select' + : call(function (source) + local vararg = source.vararg + if vararg.type == 'call' then + local node = getReturn(vararg.node, source.sindex, vararg.args) + if not node then + return + end + for n in node:eachObject() do + if n.type == 'global' + and n.cate == 'type' + and n.name == '...' then + return + end + end + vm.setNode(source, node) + end + if vararg.type == 'varargs' then + vm.setNode(source, vm.compileNode(vararg)) + end + end) + : case 'varargs' + : call(function (source) + if source.node then + vm.setNode(source, vm.compileNode(source.node)) + end + end) + : case 'call' + : call(function (source) + local node = getReturn(source.node, 1, source.args) + if not node then + return + end + for n in node:eachObject() do + if n.type == 'global' + and n.cate == 'type' + and n.name == '...' then + return + end + end + vm.setNode(source, node) + end) + : case 'in' + : call(function (source) + if not source._iterator then + -- for k, v in pairs(t) do + --> for k, v in iterator, status, initValue do + --> local k, v = iterator(status, initValue) + source._iterator = {} + source._iterArgs = {{}, {}} + -- iterator + selectNode(source._iterator, source.exps, 1) + -- status + selectNode(source._iterArgs[1], source.exps, 2) + -- initValue + selectNode(source._iterArgs[2], source.exps, 3) + end + if source.keys then + for i, loc in ipairs(source.keys) do + local node = getReturn(source._iterator, i, source._iterArgs) + if node then + vm.setNode(loc, node) + end + end + end + end) + : case 'doc.type' + : call(function (source) + for _, typeUnit in ipairs(source.types) do + vm.setNode(source, vm.compileNode(typeUnit)) + end + end) + : case 'doc.type.integer' + : case 'doc.type.string' + : case 'doc.type.boolean' + : call(function (source) + vm.setNode(source, source) + end) + : case 'doc.generic.name' + : call(function (source) + vm.setNode(source, source) + end) + : case 'doc.type.name' + : call(function (source) + if source.signs then + local uri = guide.getUri(source) + vm.setNode(source, source) + local global = globalMgr.getGlobal('type', source[1]) + for _, set in ipairs(global:getSets(uri)) do + if set.type == 'doc.class' then + if set.extends then + for _, ext in ipairs(set.extends) do + if ext.type == 'doc.type.table' then + if ext._generic then + local resolved = ext._generic:resolve(uri, source.signs) + vm.setNode(source, resolved) + end + end + end + end + end + if set.type == 'doc.alias' then + if set.extends._generic then + local resolved = set.extends._generic:resolve(uri, source.signs) + vm.setNode(source, resolved) + end + end + end + end + end) + : case 'doc.class.name' + : call(function (source) + vm.setNode(source, vm.compileNode(source.parent)) + end) + : case 'doc.field' + : call(function (source) + if not source.extends then + return + end + vm.setNode(source, vm.compileNode(source.extends)) + end) + : case 'doc.type.field' + : call(function (source) + if not source.extends then + return + end + vm.setNode(source, vm.compileNode(source.extends)) + end) + : case 'doc.param' + : call(function (source) + if not source.extends then + return + end + vm.setNode(source, vm.compileNode(source.extends)) + end) + : case 'doc.vararg' + : call(function (source) + if not source.vararg then + return + end + vm.setNode(source, vm.compileNode(source.vararg)) + end) + : case '...' + : call(function (source) + local func = source.parent.parent + if func.type ~= 'function' then + return + end + if not func.bindDocs then + return + end + for _, doc in ipairs(func.bindDocs) do + if doc.type == 'doc.vararg' then + vm.setNode(source, vm.compileNode(doc)) + end + if doc.type == 'doc.param' and doc.param[1] == '...' then + vm.setNode(source, vm.compileNode(doc)) + end + end + end) + : case 'doc.overload' + : call(function (source) + vm.setNode(source, vm.compileNode(source.overload)) + end) + : case 'doc.see.name' + : call(function (source) + local type = globalMgr.getGlobal('type', source[1]) + if type then + vm.setNode(source, vm.compileNode(type)) + end + end) + : case 'doc.type.arg' + : call(function (source) + if source.extends then + vm.setNode(source, vm.compileNode(source.extends)) + else + vm.setNode(source, globalMgr.getGlobal('type', 'any')) + end + end) + : case 'generic' + : call(function (source) + vm.setNode(source, source) + end) + : case 'unary' + : call(function (source) + if source.op.type == 'not' then + local result = vm.test(source[1]) + if result == nil then + vm.setNode(source, globalMgr.getGlobal('type', 'boolean')) + return + else + vm.setNode(source, { + type = 'boolean', + start = source.start, + finish = source.finish, + parent = source, + [1] = not result, + }) + return + end + end + if source.op.type == '#' then + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + end + if source.op.type == '-' then + local v = vm.getNumber(source[1]) + if v == nil then + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + else + vm.setNode(source, { + type = 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = -v, + }) + return + end + end + if source.op.type == '~' then + local v = vm.getInteger(source[1]) + if v == nil then + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + else + vm.setNode(source, { + type = 'integer', + start = source.start, + finish = source.finish, + parent = source, + [1] = ~v, + }) + return + end + end + end) + : case 'binary' + : call(function (source) + if source.op.type == 'and' then + local r1 = vm.test(source[1]) + if r1 == true then + vm.setNode(source, vm.compileNode(source[2])) + return + end + if r1 == false then + vm.setNode(source, vm.compileNode(source[1])) + return + end + return + end + if source.op.type == 'or' then + local r1 = vm.test(source[1]) + if r1 == true then + vm.setNode(source, vm.compileNode(source[1])) + return + end + if r1 == false then + vm.setNode(source, vm.compileNode(source[2])) + return + end + return + end + if source.op.type == '==' then + local result = vm.equal(source[1], source[2]) + if result == nil then + vm.setNode(source, globalMgr.getGlobal('type', 'boolean')) + return + else + vm.setNode(source, { + type = 'boolean', + start = source.start, + finish = source.finish, + parent = source, + [1] = result, + }) + return + end + end + if source.op.type == '~=' then + local result = vm.equal(source[1], source[2]) + if result == nil then + vm.setNode(source, globalMgr.getGlobal('type', 'boolean')) + return + else + vm.setNode(source, { + type = 'boolean', + start = source.start, + finish = source.finish, + parent = source, + [1] = not result, + }) + return + end + end + if source.op.type == '<<' then + local a = vm.getInteger(source[1]) + local b = vm.getInteger(source[2]) + if a and b then + vm.setNode(source, { + type = 'integer', + start = source.start, + finish = source.finish, + parent = source, + [1] = a << b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + end + end + if source.op.type == '>>' then + local a = vm.getInteger(source[1]) + local b = vm.getInteger(source[2]) + if a and b then + vm.setNode(source, { + type = 'integer', + start = source.start, + finish = source.finish, + parent = source, + [1] = a >> b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + end + end + if source.op.type == '&' then + local a = vm.getInteger(source[1]) + local b = vm.getInteger(source[2]) + if a and b then + vm.setNode(source, { + type = 'integer', + start = source.start, + finish = source.finish, + parent = source, + [1] = a & b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + end + end + if source.op.type == '|' then + local a = vm.getInteger(source[1]) + local b = vm.getInteger(source[2]) + if a and b then + vm.setNode(source, { + type = 'integer', + start = source.start, + finish = source.finish, + parent = source, + [1] = a | b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + end + end + if source.op.type == '~' then + local a = vm.getInteger(source[1]) + local b = vm.getInteger(source[2]) + if a and b then + vm.setNode(source, { + type = 'integer', + start = source.start, + finish = source.finish, + parent = source, + [1] = a ~ b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'integer')) + return + end + end + if source.op.type == '+' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b then + local result = a + b + vm.setNode(source, { + type = math.type(result) == 'integer' and 'integer' or 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = result, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '-' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b then + local result = a - b + vm.setNode(source, { + type = math.type(result) == 'integer' and 'integer' or 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = result, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '*' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b then + local result = a * b + vm.setNode(source, { + type = math.type(result) == 'integer' and 'integer' or 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = result, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '/' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b then + vm.setNode(source, { + type = 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = a / b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '%' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b then + local result = a % b + vm.setNode(source, { + type = math.type(result) == 'integer' and 'integer' or 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = result, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '^' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b then + vm.setNode(source, { + type = 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = a ^ b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '//' then + local a = vm.getNumber(source[1]) + local b = vm.getNumber(source[2]) + if a and b and b ~= 0 then + local result = a // b + vm.setNode(source, { + type = math.type(result) == 'integer' and 'integer' or 'number', + start = source.start, + finish = source.finish, + parent = source, + [1] = result, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'number')) + return + end + end + if source.op.type == '..' then + local a = vm.getString(source[1]) + or vm.getNumber(source[1]) + local b = vm.getString(source[2]) + or vm.getNumber(source[2]) + if a and b then + if type(a) == 'number' or type(b) == 'number' then + local uri = guide.getUri(source) + local version = config.get(uri, 'Lua.runtime.version') + if math.tointeger(a) and math.type(a) == 'float' then + if version == 'Lua 5.3' or version == 'Lua 5.4' then + a = ('%.1f'):format(a) + else + a = ('%.0f'):format(a) + end + end + if math.tointeger(b) and math.type(b) == 'float' then + if version == 'Lua 5.3' or version == 'Lua 5.4' then + b = ('%.1f'):format(b) + else + b = ('%.0f'):format(b) + end + end + end + vm.setNode(source, { + type = 'string', + start = source.start, + finish = source.finish, + parent = source, + [1] = a .. b, + }) + return + else + vm.setNode(source, globalMgr.getGlobal('type', 'string')) + return + end + end + end) + +---@param source vm.object +local function compileByNode(source) + compilerSwitch(source.type, source) +end + +---@param source vm.object +local function compileByGlobal(source) + local global = source._globalNode + if not global then + return + end + local root = guide.getRoot(source) + local uri = guide.getUri(source) + if not root._globalBase then + root._globalBase = {} + end + local name = global:asKeyName() + if not root._globalBase[name] then + root._globalBase[name] = { + type = 'globalbase', + parent = root, + } + end + local globalNode = vm.getNode(root._globalBase[name]) + if globalNode then + vm.setNode(source, globalNode, true) + return + end + globalNode = vm.createNode(global) + vm.setNode(root._globalBase[name], globalNode, true) + vm.setNode(source, globalNode, true) + + if global.cate == 'variable' then + local hasMarkDoc + for _, set in ipairs(global:getSets(uri)) do + if set.bindDocs then + if bindDocs(set) then + globalNode:merge(vm.compileNode(set)) + hasMarkDoc = true + end + end + end + for _, set in ipairs(global:getSets(uri)) do + if set.value then + if not hasMarkDoc or guide.isLiteral(set.value) then + if set.value.type ~= 'nil' then + globalNode:merge(vm.compileNode(set.value)) + end + end + end + end + end + if global.cate == 'type' then + for _, set in ipairs(global:getSets(uri)) do + if set.type == 'doc.class' then + if set.extends then + for _, ext in ipairs(set.extends) do + if ext.type == 'doc.type.table' then + if not ext._generic then + globalNode:merge(vm.compileNode(ext)) + end + end + end + end + end + if set.type == 'doc.alias' then + if not set.extends._generic then + globalNode:merge(vm.compileNode(set.extends)) + end + end + end + end +end + +---@param source vm.object +---@return vm.node +function vm.compileNode(source) + if not source then + error('Can not compile nil node') + end + + if source.type == 'global' then + return source + end + + local cache = vm.getNode(source) + if cache ~= nil then + return cache + end + + local node = vm.createNode() + vm.setNode(source, node, true) + compileByGlobal(source) + compileByNode(source) + + node = vm.getNode(source) + + return node +end diff --git a/script/vm/def.lua b/script/vm/def.lua new file mode 100644 index 00000000..40eb7b90 --- /dev/null +++ b/script/vm/def.lua @@ -0,0 +1,240 @@ +---@class vm +local vm = require 'vm.vm' +local util = require 'utility' +local guide = require 'parser.guide' +local localID = require 'vm.local-id' +local globalMgr = require 'vm.global-manager' + +local simpleSwitch + +local function searchGetLocal(source, node, pushResult) + local key = guide.getKeyName(source) + for _, ref in ipairs(node.node.ref) do + if ref.type == 'getlocal' + and ref.next + and guide.isSet(ref.next) + and guide.getKeyName(ref.next) == key then + pushResult(ref.next) + end + end +end + +simpleSwitch = util.switch() + : case 'local' + : call(function (source, pushResult) + pushResult(source) + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + pushResult(ref) + end + end + end + end) + : case 'sellf' + : call(function (source, pushResult) + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + pushResult(ref) + end + end + end + for _, res in ipairs(vm.getDefs(source.method.node)) do + pushResult(res) + end + end) + : case 'getlocal' + : case 'setlocal' + : call(function (source, pushResult) + simpleSwitch('local', source.node, pushResult) + end) + : case 'field' + : call(function (source, pushResult) + local parent = source.parent + if parent.type ~= 'tablefield' then + simpleSwitch(parent.type, parent, pushResult) + end + end) + : case 'setfield' + : case 'getfield' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + return + end + end) + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + end + end) + : case 'goto' + : call(function (source, pushResult) + if source.node then + pushResult(source.node) + end + end) + +local searchFieldSwitch = util.switch() + : case 'table' + : call(function (suri, obj, key, pushResult) + for _, field in ipairs(obj) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if guide.getKeyName(field) == key then + pushResult(field) + end + end + end + end) + : case 'global' + ---@param obj vm.object + ---@param key string + : call(function (suri, obj, key, pushResult) + if obj.cate == 'variable' then + local newGlobal = globalMgr.getGlobal('variable', obj.name, key) + if newGlobal then + for _, set in ipairs(newGlobal:getSets(suri)) do + pushResult(set) + end + end + end + if obj.cate == 'type' then + vm.getClassFields(suri, obj, key, pushResult) + end + end) + : case 'local' + : call(function (suri, obj, key, pushResult) + local sources = localID.getSources(obj, key) + if sources then + for _, src in ipairs(sources) do + if guide.isSet(src) then + pushResult(src) + end + end + end + end) + : case 'doc.type.table' + : call(function (suri, obj, key, pushResult) + for _, field in ipairs(obj.fields) do + local fieldKey = field.name + if fieldKey.type == 'doc.field.name' then + if fieldKey[1] == key then + pushResult(field) + end + end + end + end) + +local searchByParentNode +local nodeSwitch = util.switch() + : case 'field' + : case 'method' + : call(function (source, pushResult) + searchByParentNode(source.parent, pushResult) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local parentNode = vm.compileNode(source.node) + local uri = guide.getUri(source) + local key = guide.getKeyName(source) + for pn in parentNode:eachObject() do + searchFieldSwitch(pn.type, uri, pn, key, pushResult) + end + end) + : case 'tableindex' + : case 'tablefield' + : call(function (source, pushResult) + local tbl = source.parent + local uri = guide.getUri(source) + searchFieldSwitch(tbl.type, uri, tbl, guide.getKeyName(source), pushResult) + end) + : case 'doc.see.field' + : call(function (source, pushResult) + local parentNode = vm.compileNode(source.parent.name) + local uri = guide.getUri(source) + for pn in parentNode:eachObject() do + searchFieldSwitch(pn.type, uri, pn, source[1], pushResult) + end + end) + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchBySimple(source, pushResult) + simpleSwitch(source.type, source, pushResult) +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByLocalID(source, pushResult) + local idSources = localID.getSources(source) + if not idSources then + return + end + for _, src in ipairs(idSources) do + if guide.isSet(src) then + pushResult(src) + end + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +function searchByParentNode(source, pushResult) + nodeSwitch(source.type, source, pushResult) +end + +local function searchByNode(source, pushResult) + local node = vm.compileNode(source) + local suri = guide.getUri(source) + for n in node:eachObject() do + if n.type == 'global' then + for _, set in ipairs(n:getSets(suri)) do + pushResult(set) + end + else + pushResult(n) + end + end +end + +---@param source parser.object +---@return parser.object[] +function vm.getDefs(source) + local results = {} + local mark = {} + + local hasLocal + local function pushResult(src) + if src.type == 'local' then + if hasLocal then + return + end + hasLocal = true + end + if not mark[src] then + mark[src] = true + if guide.isSet(src) + or guide.isLiteral(src) then + results[#results+1] = src + end + end + end + + searchBySimple(source, pushResult) + searchByLocalID(source, pushResult) + searchByParentNode(source, pushResult) + searchByNode(source, pushResult) + + return results +end diff --git a/script/vm/getDocs.lua b/script/vm/doc.lua index 4aeda446..1367bbc2 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/doc.lua @@ -3,80 +3,22 @@ local guide = require 'parser.guide' ---@class vm local vm = require 'vm.vm' local config = require 'config' -local collector = require 'core.collector' 'searcher' -local define = require 'proto.define' -local noder = require 'core.noder' +local globalMgr = require 'vm.global-manager' ---获取class与alias +---@param suri uri ---@param name? string ----@return parser.guide.object[] -function vm.getDocDefines(uri, name) - if type(name) ~= 'string' then - return {} - end - local cache = vm.getCache 'getDocDefines' - if cache[name] then - return cache[name] - end - local results = {} - if name == '*' then - for noders in collector:each(uri, 'def:dn:') do - for id in noder.eachID(noders) do - if id:sub(1, 3) == 'dn:' - and not id:find(noder.SPLIT_CHAR) then - for source in noder.eachSource(noders, id) do - if guide.isSet(source) then - results[#results+1] = source - end - end - end - end +---@return parser.object[] +function vm.getDocSets(suri, name) + if name then + local global = globalMgr.getGlobal('type', name) + if not global then + return {} end + return global:getSets(suri) else - local id = 'dn:' .. name - for noders in collector:each(uri, 'def:' .. id) do - for source in noder.eachSource(noders, id) do - if source.type == 'doc.class.name' - or source.type == 'doc.alias.name' then - results[#results+1] = source - end - end - end - end - cache[name] = results - return results -end - -function vm.isDocDefined(uri, name) - if define.BuiltinType[name] then - return true - end - local id = 'def:dn:' .. name - if collector:has(uri, id) then - return true + return globalMgr.getGlobalSets(suri, 'type') end - return false -end - -function vm.isBuiltinType(name) - return define.BuiltinType[name] == true -end - -function vm.getDocEnums(doc) - if not doc then - return nil - end - local defs = vm.getDefs(doc) - local results = {} - - for _, def in ipairs(defs) do - if def.type == 'doc.type.enum' - or def.type == 'doc.resume' then - results[#results+1] = def - end - end - - return results end function vm.isMetaFile(uri) diff --git a/script/vm/eachDef.lua b/script/vm/eachDef.lua deleted file mode 100644 index ea14ed9f..00000000 --- a/script/vm/eachDef.lua +++ /dev/null @@ -1,11 +0,0 @@ ----@class vm -local vm = require 'vm.vm' -local searcher = require 'core.searcher' - -function vm.getDefs(source, field) - return searcher.requestDefinition(source, field) -end - -function vm.getAllDefs(source, field) - return searcher.requestAllDefinition(source, field) -end diff --git a/script/vm/eachRef.lua b/script/vm/eachRef.lua deleted file mode 100644 index 899c04c6..00000000 --- a/script/vm/eachRef.lua +++ /dev/null @@ -1,11 +0,0 @@ ----@class vm -local vm = require 'vm.vm' -local searcher = require 'core.searcher' - -function vm.getRefs(source, field) - return searcher.requestReference(source, field) -end - -function vm.getAllRefs(source, field) - return searcher.requestAllReference(source, field) -end diff --git a/script/vm/field.lua b/script/vm/field.lua new file mode 100644 index 00000000..0968df11 --- /dev/null +++ b/script/vm/field.lua @@ -0,0 +1,41 @@ +---@class vm +local vm = require 'vm.vm' +local util = require 'utility' +local guide = require 'parser.guide' + +local searchByNodeSwitch = util.switch() + : case 'global' + ---@param global vm.global + : call(function (suri, global, pushResult) + for _, set in ipairs(global:getSets(suri)) do + pushResult(set) + end + end) + : default(function (suri, source, pushResult) + pushResult(source) + end) + +local function searchByNode(source, pushResult) + local uri = guide.getUri(source) + vm.compileByParentNode(source, nil, function (field) + searchByNodeSwitch(field.type, uri, field, pushResult) + end) +end + +---@param source parser.object +---@return parser.object[] +function vm.getFields(source) + local results = {} + local mark = {} + + local function pushResult(src) + if not mark[src] then + mark[src] = true + results[#results+1] = src + end + end + + searchByNode(source, pushResult) + + return results +end diff --git a/script/vm/generic.lua b/script/vm/generic.lua new file mode 100644 index 00000000..b3981ff8 --- /dev/null +++ b/script/vm/generic.lua @@ -0,0 +1,138 @@ +local vm = require 'vm.vm' + +---@class parser.object +---@field _generic vm.generic + +---@class vm.generic +---@field sign vm.sign +---@field proto vm.object +local mt = {} +mt.__index = mt +mt.type = 'generic' + +---@param source parser.object +---@param resolved? table<string, vm.node> +---@return parser.object | vm.node +local function cloneObject(source, resolved) + if not resolved then + return source + end + if source.type == 'doc.generic.name' then + local key = source[1] + local newName = { + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + [1] = source[1], + } + if resolved[key] then + vm.setNode(newName, resolved[key], true) + end + return newName + end + if source.type == 'doc.type' then + local newType = { + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + types = {}, + } + for i, typeUnit in ipairs(source.types) do + local newObj = cloneObject(typeUnit, resolved) + newType.types[i] = newObj + end + return newType + end + if source.type == 'doc.type.arg' then + local newArg = { + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + name = source.name, + extends = cloneObject(source.extends, resolved) + } + return newArg + end + if source.type == 'doc.type.array' then + local newArray = { + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + node = cloneObject(source.node, resolved), + } + return newArray + end + if source.type == 'doc.type.table' then + local newTable = { + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + fields = {}, + } + for i, field in ipairs(source.fields) do + local newField = { + type = field.type, + start = field.start, + finish = field.finish, + parent = newTable, + name = cloneObject(field.name, resolved), + extends = cloneObject(field.extends, resolved), + } + newTable.fields[i] = newField + end + return newTable + end + if source.type == 'doc.type.function' then + local newDocFunc = { + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + args = {}, + returns = {}, + } + for i, arg in ipairs(source.args) do + local newObj = cloneObject(arg, resolved) + newObj.optional = arg.optional + newDocFunc.args[i] = newObj + end + for i, ret in ipairs(source.returns) do + local newObj = cloneObject(ret, resolved) + newObj.parent = newDocFunc + newObj.optional = ret.optional + newDocFunc.returns[i] = cloneObject(ret, resolved) + end + return newDocFunc + end + return source +end + +---@param uri uri +---@param args parser.object +---@return parser.object +function mt:resolve(uri, args) + local resolved = self.sign:resolve(uri, args) + local protoNode = vm.compileNode(self.proto) + local result = vm.createNode() + for nd in protoNode:eachObject() do + local clonedNode = vm.compileNode(cloneObject(nd, resolved)) + result:merge(clonedNode) + end + return result +end + +---@param proto vm.object +---@param sign vm.sign +---@return vm.generic +return function (proto, sign) + local generic = setmetatable({ + sign = sign, + proto = proto, + }, mt) + return generic +end diff --git a/script/vm/getGlobals.lua b/script/vm/getGlobals.lua deleted file mode 100644 index 8af21d45..00000000 --- a/script/vm/getGlobals.lua +++ /dev/null @@ -1,53 +0,0 @@ -local collector = require 'core.collector' 'searcher' -local guide = require 'parser.guide' ----@class vm -local vm = require 'vm.vm' -local noder = require 'core.noder' - -function vm.hasGlobalSets(uri, name) - local id - if type(name) == 'string' then - id = ('def:g:%s%s'):format(noder.STRING_CHAR, name) - else - id = ('def:g:%s'):format(noder.STRING_CHAR, name) - end - return collector:has(uri, id) -end - -function vm.getGlobalSets(uri, name) - local cache = vm.getCache 'getGlobalSets' - if cache[name] then - return cache[name] - end - local results = {} - cache[name] = results - if name == '*' then - for noders in collector:each(uri, 'def:g:') do - for id in noder.eachID(noders) do - if id:sub(1, 2) == 'g:' - and not id:find(noder.SPLIT_CHAR) then - for source in noder.eachSource(noders, id) do - if guide.isSet(source) then - results[#results+1] = source - end - end - end - end - end - else - local id - if type(name) == 'string' then - id = ('g:%s%s'):format(noder.STRING_CHAR, name) - else - id = ('g:%s'):format(noder.STRING_CHAR, name) - end - for noders in collector:each(uri, 'def:' .. id) do - for source in noder.eachSource(noders, id) do - if guide.isSet(source) then - results[#results+1] = source - end - end - end - end - return results -end diff --git a/script/vm/getLinks.lua b/script/vm/getLinks.lua deleted file mode 100644 index 8571fb46..00000000 --- a/script/vm/getLinks.lua +++ /dev/null @@ -1,63 +0,0 @@ -local guide = require 'parser.guide' ----@class vm -local vm = require 'vm.vm' -local files = require 'files' -local rpath = require 'workspace.require-path' - -local function getFileLinks(uri) - local links = {} - local state = files.getState(uri) - if not state then - return links - end - tracy.ZoneBeginN('getFileLinks') - guide.eachSpecialOf(state.ast, 'require', function (source) - local call = source.parent - if not call or call.type ~= 'call' then - return - end - local args = call.args - if not args or not args[1] or args[1].type ~= 'string' then - return - end - local uris = rpath.findUrisByRequirePath(uri, args[1][1]) - for _, u in ipairs(uris) do - if not links[u] then - links[u] = {} - end - links[u][#links[u]+1] = call - end - end) - tracy.ZoneEnd() - return links -end - -local function getFileLinksOrCache(uri) - local cache = files.getCache(uri) - cache.links = cache.links or getFileLinks(uri) - return cache.links -end - -local function getLinksTo(uri) - local links = {} - for u in files.eachFile(uri) do - local ls = getFileLinksOrCache(u) - if ls[uri] then - for _, l in ipairs(ls[uri]) do - links[#links+1] = l - end - end - end - return links -end - --- 获取所有 require(uri) 的文件 -function vm.getLinksTo(uri) - local cache = vm.getCache('getLinksTo')[uri] - if cache ~= nil then - return cache - end - cache = getLinksTo(uri) - vm.getCache('getLinksTo')[uri] = cache - return cache -end diff --git a/script/vm/global-manager.lua b/script/vm/global-manager.lua new file mode 100644 index 00000000..9752621e --- /dev/null +++ b/script/vm/global-manager.lua @@ -0,0 +1,355 @@ +local util = require 'utility' +local guide = require 'parser.guide' +local globalBuilder = require 'vm.global' +local signMgr = require 'vm.sign' +local genericMgr = require 'vm.generic' +---@class vm +local vm = require 'vm.vm' + +---@class parser.object +---@field _globalNode vm.global + +---@class vm.global-manager +local m = {} +---@type table<string, vm.global> +m.globals = {} +---@type table<uri, table<string, boolean>> +m.globalSubs = util.multiTable(2) + +m.ID_SPLITE = '\x1F' + +local compilerGlobalSwitch = util.switch() + : case 'local' + : call(function (source) + if source.special ~= '_G' then + return + end + if source.ref then + for _, ref in ipairs(source.ref) do + m.compileObject(ref) + end + end + end) + : case 'getlocal' + : call(function (source) + if source.special ~= '_G' then + return + end + if not source.next then + return + end + m.compileObject(source.next) + end) + : case 'setglobal' + : call(function (source) + local uri = guide.getUri(source) + local name = guide.getKeyName(source) + local global = m.declareGlobal('variable', name, uri) + global:addSet(uri, source) + source._globalNode = global + end) + : case 'getglobal' + : call(function (source) + local uri = guide.getUri(source) + local name = guide.getKeyName(source) + local global = m.declareGlobal('variable', name, uri) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileObject(nxt) + end + end) + : case 'setfield' + : case 'setmethod' + : case 'setindex' + ---@param source parser.object + : call(function (source) + local name + local keyName = guide.getKeyName(source) + if not keyName then + return + end + if source.node._globalNode then + local parentName = source.node._globalNode:getName() + if parentName == '_G' then + name = keyName + else + name = ('%s%s%s'):format(parentName, m.ID_SPLITE, keyName) + end + elseif source.node.special == '_G' then + name = keyName + end + if not name then + return + end + local uri = guide.getUri(source) + local global = m.declareGlobal('variable', name, uri) + global:addSet(uri, source) + source._globalNode = global + end) + : case 'getfield' + : case 'getmethod' + : case 'getindex' + ---@param source parser.object + : call(function (source) + local name + local keyName = guide.getKeyName(source) + if not keyName then + return + end + if source.node._globalNode then + local parentName = source.node._globalNode:getName() + if parentName == '_G' then + name = keyName + else + name = ('%s%s%s'):format(parentName, m.ID_SPLITE, keyName) + end + elseif source.node.special == '_G' then + name = keyName + end + local uri = guide.getUri(source) + local global = m.declareGlobal('variable', name, uri) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileObject(nxt) + end + end) + : case 'call' + : call(function (source) + if source.node.special == 'rawset' + or source.node.special == 'rawget' then + local g = source.args[1] + local key = source.args[2] + if g and key and g.special == '_G' then + local name = guide.getKeyName(key) + if name then + local uri = guide.getUri(source) + local global = m.declareGlobal('variable', name, uri) + if source.node.special == 'rawset' then + global:addSet(uri, source) + source.value = source.args[3] + else + global:addGet(uri, source) + end + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileObject(nxt) + end + end + end + end + end) + : case 'doc.class' + ---@param source parser.object + : call(function (source) + local uri = guide.getUri(source) + local name = guide.getKeyName(source) + local class = m.declareGlobal('type', name, uri) + class:addSet(uri, source) + source._globalNode = class + + if source.signs then + source._sign = signMgr() + for _, sign in ipairs(source.signs) do + source._sign:addSign(vm.compileNode(sign)) + end + if source.extends then + for _, ext in ipairs(source.extends) do + if ext.type == 'doc.type.table' then + ext._generic = genericMgr(ext, source._sign) + end + end + end + end + end) + : case 'doc.alias' + : call(function (source) + local uri = guide.getUri(source) + local name = guide.getKeyName(source) + local alias = m.declareGlobal('type', name, uri) + alias:addSet(uri, source) + source._globalNode = alias + + if source.signs then + source._sign = signMgr() + for _, sign in ipairs(source.signs) do + source._sign:addSign(vm.compileNode(sign)) + end + source.extends._generic = genericMgr(source.extends, source._sign) + end + end) + : case 'doc.type.name' + : call(function (source) + local uri = guide.getUri(source) + local name = source[1] + local type = m.declareGlobal('type', name, uri) + type:addGet(uri, source) + if not source.signs then + source._globalNode = type + end + end) + : case 'doc.extends.name' + : call(function (source) + local uri = guide.getUri(source) + local name = source[1] + local class = m.declareGlobal('type', name, uri) + class:addGet(uri, source) + source._globalNode = class + end) + + +---@alias vm.global.cate '"variable"' | '"type"' + +---@param cate vm.global.cate +---@param name string +---@param uri uri +---@return vm.global +function m.declareGlobal(cate, name, uri) + local key = cate .. '|' .. name + m.globalSubs[uri][key] = true + if not m.globals[key] then + m.globals[key] = globalBuilder(name, cate) + end + return m.globals[key] +end + +---@param cate vm.global.cate +---@param name string +---@param field? string +---@return vm.global? +function m.getGlobal(cate, name, field) + local key = cate .. '|' .. name + if field then + key = key .. m.ID_SPLITE .. field + end + return m.globals[key] +end + +---@param cate vm.global.cate +---@param name string +---@return vm.global[] +function m.getFields(cate, name) + local globals = {} + local key = cate .. '|' .. name + + -- TODO: optimize + for gid, global in pairs(m.globals) do + if gid ~= key + and util.stringStartWith(gid, key) + and gid:sub(#key + 1, #key + 1) == m.ID_SPLITE + and not gid:find(m.ID_SPLITE, #key + 2) then + globals[#globals+1] = global + end + end + + return globals +end + +---@param cate vm.global.cate +---@return vm.global[] +function m.getGlobals(cate) + local globals = {} + + -- TODO: optimize + for gid, global in pairs(m.globals) do + if util.stringStartWith(gid, cate) + and not gid:find(m.ID_SPLITE) then + globals[#globals+1] = global + end + end + + return globals +end + +---@param suri uri +---@param cate vm.global.cate +---@return parser.object[] +function m.getGlobalSets(suri, cate) + local globals = m.getGlobals(cate) + local result = {} + for _, global in ipairs(globals) do + local sets = global:getSets(suri) + for _, set in ipairs(sets) do + result[#result+1] = set + end + end + return result +end + +---@param suri uri +---@param cate vm.global.cate +---@param name string +---@return boolean +function m.hasGlobalSets(suri, cate, name) + local global = m.getGlobal(cate, name) + if not global then + return false + end + local sets = global:getSets(suri) + if #sets == 0 then + return false + end + return true +end + +---@param source parser.object +function m.compileObject(source) + if source._globalNode ~= nil then + return + end + source._globalNode = false + compilerGlobalSwitch(source.type, source) +end + +---@param source parser.object +function m.compileAst(source) + local env = guide.getENV(source) + m.compileObject(env) + guide.eachSpecialOf(source, 'rawset', function (src) + m.compileObject(src.parent) + end) + guide.eachSpecialOf(source, 'rawget', function (src) + m.compileObject(src.parent) + end) + guide.eachSourceTypes(source.docs, { + 'doc.class', + 'doc.alias', + 'doc.type.name', + 'doc.extends.name', + }, function (src) + m.compileObject(src) + end) +end + +---@return vm.global +function m.getNode(source) + if source.type == 'field' + or source.type == 'method' then + source = source.parent + end + return source._globalNode +end + +---@param uri uri +function m.dropUri(uri) + local globalSub = m.globalSubs[uri] + m.globalSubs[uri] = nil + for key in pairs(globalSub) do + local global = m.globals[key] + if global then + global:dropUri(uri) + if not global:isAlive() then + m.globals[key] = nil + end + end + end +end + +return m diff --git a/script/vm/global.lua b/script/vm/global.lua new file mode 100644 index 00000000..1c46c9a3 --- /dev/null +++ b/script/vm/global.lua @@ -0,0 +1,125 @@ +local util = require 'utility' +local scope= require 'workspace.scope' + +---@class vm.global.link +---@field gets parser.object[] +---@field sets parser.object[] + +---@class vm.global +---@field links table<uri, vm.global.link> +---@field setsCache table<uri, parser.object[]> +---@field getsCache table<uri, parser.object[]> +---@field cate vm.global.cate +local mt = {} +mt.__index = mt +mt.type = 'global' +mt.name = '' + +local ID_SPLITE = '\x1F' + +---@param uri uri +---@param source parser.object +function mt:addSet(uri, source) + local link = self.links[uri] + if not link.sets then + link.sets = {} + end + link.sets[#link.sets+1] = source + self.setsCache = nil +end + +---@param uri uri +---@param source parser.object +function mt:addGet(uri, source) + local link = self.links[uri] + if not link.gets then + link.gets = {} + end + link.gets[#link.gets+1] = source + self.getsCache = nil +end + +---@return parser.object[] +function mt:getSets(suri) + if not self.setsCache then + self.setsCache = {} + end + local scp = scope.getScope(suri) + local cacheUri = scp.uri or '<callback>' + if self.setsCache[cacheUri] then + return self.setsCache[cacheUri] + end + self.setsCache[cacheUri] = {} + local cache = self.setsCache[cacheUri] + for uri, link in pairs(self.links) do + if link.sets then + if scp:isVisible(uri) then + for _, source in ipairs(link.sets) do + cache[#cache+1] = source + end + end + end + end + return cache +end + +---@return parser.object[] +function mt:getGets(suri) + if not self.getsCache then + self.getsCache = {} + end + local scp = scope.getScope(suri) + local cacheUri = scp.uri or '<callback>' + if self.getsCache[cacheUri] then + return self.getsCache[cacheUri] + end + self.getsCache[cacheUri] = {} + local cache = self.getsCache[cacheUri] + for uri, link in pairs(self.links) do + if link.gets then + if scp:isVisible(uri) then + for _, source in ipairs(link.gets) do + cache[#cache+1] = source + end + end + end + end + return cache +end + +---@param uri uri +function mt:dropUri(uri) + self.links[uri] = nil + self.setsCache = nil + self.getsCache = nil +end + +---@return string +function mt:getName() + return self.name +end + +---@return string +function mt:asKeyName() + return self.cate .. '|' .. self.name +end + +---@return string +function mt:getKeyName() + return self.name:match('[^' .. ID_SPLITE .. ']+$') +end + +---@return boolean +function mt:isAlive() + return next(self.links) ~= nil +end + +---@param cate vm.global.cate +---@return vm.global +return function (name, cate) + return setmetatable({ + name = name, + cate = cate, + links = util.multiTable(2), + }, mt) +end diff --git a/script/vm/infer.lua b/script/vm/infer.lua new file mode 100644 index 00000000..a5b113d6 --- /dev/null +++ b/script/vm/infer.lua @@ -0,0 +1,386 @@ +local util = require 'utility' +local config = require 'config' +local guide = require 'parser.guide' +local vm = require 'vm.vm' + +---@class vm.infer-manager +local m = {} + +---@class vm.infer +---@field views table<string, boolean> +---@field cachedView? string +---@field node? vm.node +---@field uri? uri +local mt = {} +mt.__index = mt +mt._hasNumber = false +mt._hasTable = false +mt._hasClass = false +mt._hasFunctionDef = false +mt._hasDocFunction = false +mt._isParam = false +mt._isLocal = false + +m.NULL = setmetatable({}, mt) + +local inferSorted = { + ['boolean'] = - 100, + ['string'] = - 99, + ['number'] = - 98, + ['integer'] = - 97, + ['function'] = - 96, + ['table'] = - 95, + ['true'] = 1, + ['false'] = 2, + ['nil'] = 100, +} + +local viewNodeSwitch = util.switch() + : case 'nil' + : case 'boolean' + : case 'string' + : case 'integer' + : call(function (source, infer) + return source.type + end) + : case 'number' + : call(function (source, infer) + infer._hasNumber = true + return source.type + end) + : case 'table' + : call(function (source, infer) + if source.type == 'table' then + if #source == 1 and source[1].type == 'varargs' then + local node = m.getInfer(source[1]):view() + return ('%s[]'):format(node) + end + end + + infer._hasTable = true + end) + : case 'function' + : call(function (source, infer) + local parent = source.parent + if guide.isSet(parent) then + infer._hasFunctionDef = true + end + return source.type + end) + : case 'local' + : call(function (source, infer) + if source.parent == 'funcargs' then + infer._isParam = true + else + infer._isLocal = true + end + end) + : case 'global' + : call(function (source, infer) + if source.cate == 'type' then + infer._hasClass = true + if source.name == 'number' then + infer._hasNumber = true + end + return source.name + end + end) + : case 'doc.type.name' + : call(function (source, infer) + infer._hasClass = true + if source.signs then + local buf = {} + for i, sign in ipairs(source.signs) do + buf[i] = m.getInfer(sign):view() + end + return ('%s<%s>'):format(source[1], table.concat(buf, ', ')) + else + return source[1] + end + end) + : case 'generic' + : call(function (source, infer) + return m.getInfer(source.proto):view() + end) + : case 'doc.generic.name' + : call(function (source, infer) + return ('<%s>'):format(source[1]) + end) + : case 'doc.type.array' + : call(function (source, infer) + infer._hasClass = true + local view = m.getInfer(source.node):view() + if source.node.type == 'doc.type' then + view = '(' .. view .. ')' + end + return view .. '[]' + end) + : case 'doc.type.table' + : call(function (source, infer) + infer._hasTable = true + end) + : case 'doc.type.string' + : case 'doc.type.integer' + : case 'doc.type.boolean' + : call(function (source, infer) + return ('%q'):format(source[1]) + end) + : case 'doc.type.function' + : call(function (source, infer) + infer._hasDocFunction = true + local args = {} + local rets = {} + local argView = '' + local regView = '' + for i, arg in ipairs(source.args) do + args[i] = string.format('%s%s: %s' + , arg.name[1] + , arg.optional and '?' or '' + , m.getInfer(arg):view() + ) + end + if #args > 0 then + argView = table.concat(args, ', ') + end + for i, ret in ipairs(source.returns) do + rets[i] = string.format('%s%s' + , m.getInfer(ret):view() + , ret.optional and '?' or '' + ) + end + if #rets > 0 then + regView = ':' .. table.concat(rets, ', ') + end + return ('fun(%s)%s'):format(argView, regView) + end) + +---@param source parser.object +---@return vm.infer +function m.getInfer(source) + local node = vm.compileNode(source) + if node.lastInfer then + return node.lastInfer + end + local infer = setmetatable({ + node = node, + uri = guide.getUri(source), + }, mt) + node.lastInfer = infer + + return infer +end + +function mt:_trim() + if self._hasNumber then + self.views['integer'] = nil + end + if self._hasDocFunction then + if self._hasFunctionDef then + for view in pairs(self.views) do + if view:sub(1, 4) == 'fun(' then + self.views[view] = nil + end + end + else + self.views['function'] = nil + end + end + if self._hasTable and not self._hasClass then + self.views['table'] = true + end + if self._hasClass then + self:_eraseAlias() + end +end + +function mt:_eraseAlias() + local expandAlias = config.get(self.uri, 'Lua.hover.expandAlias') + for n in self.node:eachObject() do + if n.type == 'global' and n.cate == 'type' then + for _, set in ipairs(n:getSets(self.uri)) do + if set.type == 'doc.alias' then + if expandAlias then + self.views[n.name] = nil + else + for _, ext in ipairs(set.extends.types) do + local view = viewNodeSwitch(ext.type, ext, {}) + if view and view ~= n.name then + self.views[view] = nil + end + end + end + end + end + end + end +end + +---@param tp string +---@return boolean +function mt:hasType(tp) + self:_computeViews() + return self.views[tp] == true +end + +---@return boolean +function mt:hasClass() + self:_computeViews() + return self._hasClass == true +end + +---@return boolean +function mt:hasFunction() + self:_computeViews() + return self.views['function'] == true + or self._hasDocFunction == true +end + +function mt:_computeViews() + if self.views then + return + end + + self.views = {} + + for n in self.node:eachObject() do + local view = viewNodeSwitch(n.type, n, self) + if view then + self.views[view] = true + end + end + + self:_trim() +end + +---@param default? string +---@param uri? uri +---@return string +function mt:view(default, uri) + self:_computeViews() + + if self.views['any'] then + return 'any' + end + + if not next(self.views) then + return default or 'unknown' + end + + if self.cachedView then + return self.cachedView + end + + local array = {} + for view in pairs(self.views) do + array[#array+1] = view + end + + table.sort(array, function (a, b) + local sa = inferSorted[a] or 0 + local sb = inferSorted[b] or 0 + if sa == sb then + return a < b + end + return sa < sb + end) + + local max = #array + local limit = config.get(uri or self.uri, 'Lua.hover.enumsLimit') + + if max > limit then + local view = string.format('%s...(+%d)' + , table.concat(array, '|', 1, limit) + , max - limit + ) + + self.cachedView = view + + return view + else + local view = table.concat(array, '|') + + self.cachedView = view + + return view + end +end + +function mt:eachView() + self:_computeViews() + return next, self.views +end + +---@param other vm.infer +---@return vm.infer +function mt:merge(other) + if self == m.NULL then + return other + end + if other == m.NULL then + return self + end + + local infer = setmetatable({ + node = vm.createNode(self.node, other.node), + uri = self.uri, + }, mt) + + return infer +end + +---@return string? +function mt:viewLiterals() + if not self.node then + return nil + end + local mark = {} + local literals = {} + for n in self.node:eachObject() do + if n.type == 'string' + or n.type == 'number' + or n.type == 'integer' + or n.type == 'boolean' then + local literal = util.viewLiteral(n[1]) + if not mark[literal] then + literals[#literals+1] = literal + mark[literal] = true + end + end + end + if #literals == 0 then + return nil + end + table.sort(literals) + return table.concat(literals, '|') +end + +---@return string? +function mt:viewClass() + if not self.node then + return nil + end + local mark = {} + local class = {} + for n in self.node:eachObject() do + if n.type == 'global' and n.cate == 'type' then + local name = n.name + if not mark[name] then + class[#class+1] = name + mark[name] = true + end + end + end + if #class == 0 then + return nil + end + table.sort(class) + return table.concat(class, '|') +end + +---@param source parser.object +---@return string? +function m.viewObject(source) + return viewNodeSwitch(source.type, source, {}) +end + +return m diff --git a/script/vm/init.lua b/script/vm/init.lua index 935f39e3..0058c698 100644 --- a/script/vm/init.lua +++ b/script/vm/init.lua @@ -1,8 +1,12 @@ local vm = require 'vm.vm' -require 'vm.getGlobals' -require 'vm.getDocs' -require 'vm.getLibrary' -require 'vm.eachDef' -require 'vm.eachRef' -require 'vm.getLinks' +require 'vm.compiler' +require 'vm.value' +require 'vm.node' +require 'vm.def' +require 'vm.ref' +require 'vm.field' +require 'vm.doc' +require 'vm.type' +require 'vm.library' +require 'vm.manager' return vm diff --git a/script/vm/getLibrary.lua b/script/vm/library.lua index 1a8d8ffd..49f7adb0 100644 --- a/script/vm/getLibrary.lua +++ b/script/vm/library.lua @@ -22,7 +22,7 @@ local globalLibraryNames = { 'setmetatable', 'tonumber', 'tostring', 'type', '_VERSION', 'warn', 'xpcall', 'require', 'unpack', 'bit32', 'coroutine', 'debug', 'io', 'math', 'os', 'package', 'string', 'table', - 'utf8', + 'utf8', 'newproxy', } local globalLibraryNamesMap function vm.isGlobalLibraryName(name) diff --git a/script/vm/local-id.lua b/script/vm/local-id.lua new file mode 100644 index 00000000..cda49d23 --- /dev/null +++ b/script/vm/local-id.lua @@ -0,0 +1,195 @@ +local util = require 'utility' +local guide = require 'parser.guide' + +---@class parser.object +---@field _localID string +---@field _localIDs table<string, parser.object[]> + +---@class vm.local-id +local m = {} + +m.ID_SPLITE = '\x1F' + +local compileSwitch = util.switch() + : case 'local' + : case 'self' + : call(function (source) + source._localID = ('%d'):format(source.start) + if not source.ref then + return + end + for _, ref in ipairs(source.ref) do + m.compileLocalID(ref) + end + end) + : case 'getlocal' + : call(function (source) + source._localID = ('%d'):format(source.node.start) + m.compileLocalID(source.next) + end) + : case 'getfield' + : case 'setfield' + : call(function (source) + local parentID = source.node._localID + if not parentID then + return + end + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + source._localID = parentID .. m.ID_SPLITE .. key + source.field._localID = source._localID + if source.type == 'getfield' then + m.compileLocalID(source.next) + end + end) + : case 'getmethod' + : case 'setmethod' + : call(function (source) + local parentID = source.node._localID + if not parentID then + return + end + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + source._localID = parentID .. m.ID_SPLITE .. key + source.method._localID = source._localID + if source.type == 'getmethod' then + m.compileLocalID(source.next) + end + end) + : case 'getindex' + : case 'setindex' + : call(function (source) + local parentID = source.node._localID + if not parentID then + return + end + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + source._localID = parentID .. m.ID_SPLITE .. key + source.index._localID = source._localID + if source.type == 'setindex' then + m.compileLocalID(source.next) + end + end) + +local leftSwitch = util.switch() + : case 'field' + : case 'method' + : call(function (source) + return m.getLocal(source.parent) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + : call(function (source) + return m.getLocal(source.node) + end) + : case 'getlocal' + : call(function (source) + return source.node + end) + : case 'local' + : call(function (source) + return source + end) + +---@param source parser.object +---@return parser.object? +function m.getLocal(source) + return leftSwitch(source.type, source) +end + +function m.compileLocalID(source) + if not source then + return + end + source._localID = false + if not compileSwitch:has(source.type) then + return + end + compileSwitch(source.type, source) + if not source._localID then + return + end + local root = guide.getRoot(source) + if not root._localIDs then + root._localIDs = util.multiTable(2) + end + local sources = root._localIDs[source._localID] + sources[#sources+1] = source +end + +---@param source parser.object +---@return string|boolean +function m.getID(source) + if source._localID ~= nil then + return source._localID + end + source._localID = false + local loc = m.getLocal(source) + if not loc then + return source._localID + end + m.compileLocalID(loc) + return source._localID +end + +---@param source parser.object +---@param key? string +---@return parser.object[]? +function m.getSources(source, key) + local id = m.getID(source) + if not id then + return nil + end + local root = guide.getRoot(source) + if not root._localIDs then + return nil + end + if key then + if type(key) ~= 'string' then + return nil + end + id = id .. m.ID_SPLITE .. key + end + return root._localIDs[id] +end + +---@param source parser.object +---@return parser.object[] +function m.getFields(source) + local id = m.getID(source) + if not id then + return nil + end + local root = guide.getRoot(source) + if not root._localIDs then + return nil + end + -- TODO:optimize + local fields = {} + for lid, sources in pairs(root._localIDs) do + if lid ~= id + and util.stringStartWith(lid, id) + and lid:sub(#id + 1, #id + 1) == m.ID_SPLITE + -- only one field + and not lid:find(m.ID_SPLITE, #id + 2) then + for _, src in ipairs(sources) do + fields[#fields+1] = src + end + end + end + return fields +end + +return m diff --git a/script/vm/local-manager.lua b/script/vm/local-manager.lua new file mode 100644 index 00000000..51bafb24 --- /dev/null +++ b/script/vm/local-manager.lua @@ -0,0 +1,40 @@ +local util = require 'utility' +local guide = require 'parser.guide' + +---@class vm.local-node +local m = {} +---@type table<uri, parser.object[]> +m.locals = util.multiTable(2) +---@type table<parser.object, table<parser.object, boolean>> +m.localSubs = util.multiTable(2, function () + return setmetatable({}, util.MODE_K) +end) +---@type table<parser.object, boolean> +m.allLocals = {} + +---@param source parser.object +function m.declareLocal(source) + if m.allLocals[source] then + return + end + m.allLocals[source] = true + local uri = guide.getUri(source) + local locals = m.locals[uri] + locals[#locals+1] = source +end + +---@param uri uri +function m.dropUri(uri) + local locals = m.locals[uri] + m.locals[uri] = nil + for _, loc in ipairs(locals) do + m.allLocals[loc] = nil + local localSubs = m.localSubs[loc] + m.localSubs[loc] = nil + for source in pairs(localSubs) do + source._node = nil + end + end +end + +return m diff --git a/script/vm/manager.lua b/script/vm/manager.lua new file mode 100644 index 00000000..c9103ff9 --- /dev/null +++ b/script/vm/manager.lua @@ -0,0 +1,33 @@ + +local files = require 'files' +local globalManager = require 'vm.global-manager' +local localManager = require 'vm.local-manager' + +---@alias vm.object parser.object | vm.global | vm.generic + +---@class vm.state +local m = {} +for uri in files.eachFile() do + local state = files.getState(uri) + if state then + globalManager.compileAst(state.ast) + end +end + +files.watch(function (ev, uri) + if ev == 'update' then + globalManager.dropUri(uri) + localManager.dropUri(uri) + local state = files.getState(uri) + if state then + globalManager.compileAst(state.ast) + end + end + if ev == 'remove' then + globalManager.dropUri(uri) + localManager.dropUri(uri) + end +end) + + +return m diff --git a/script/vm/node.lua b/script/vm/node.lua new file mode 100644 index 00000000..ea6a5874 --- /dev/null +++ b/script/vm/node.lua @@ -0,0 +1,257 @@ +local files = require 'files' +local localMgr = require 'vm.local-manager' +---@class vm +local vm = require 'vm.vm' + +---@type table<vm.object, vm.node> +vm.nodeCache = {} + +---@class vm.node +local mt = {} +mt.__index = mt +mt.type = 'vm.node' +mt.optional = nil +mt.lastInfer = nil +mt.data = nil +---@type vm.node[] +mt._childs = nil +mt._locked = false + +---@param node vm.node | vm.object +function mt:merge(node) + if not node then + return + end + if node.type == 'vm.node' then + if node == self then + return + end + if node:isOptional() then + self.optional = true + end + if node._locked then + if not self._childs then + self._childs = {} + end + if not self._childs[node] then + self._childs[#self._childs+1] = node + self._childs[node] = true + end + else + for _, obj in ipairs(node) do + if not self[obj] then + self[obj] = true + self[#self+1] = obj + end + end + end + else + if not self[node] then + self[node] = true + self[#self+1] = node + end + end +end + +function mt:_each(mark, callback) + if mark[self] then + return + end + mark[self] = true + for i = 1, #self do + callback(self[i]) + end + local childs = self._childs + if not childs then + return + end + for i = 1, #childs do + local child = childs[i] + if not child:isLocked() then + child:_each(mark, callback) + end + end +end + +function mt:_expand() + local childs = self._childs + if not childs then + return + end + self._childs = nil + + local mark = {} + mark[self] = true + + local function insert(obj) + if not self[obj] then + self[obj] = true + self[#self+1] = obj + end + end + + for i = 1, #childs do + local child = childs[i] + if child:isLocked() then + if not self._childs then + self._childs = {} + end + if not self._childs[child] then + self._childs[#self._childs+1] = child + self._childs[child] = true + end + else + child:_each(mark, insert) + end + end +end + +---@return boolean +function mt:isEmpty() + self:_expand() + return #self == 0 +end + +---@param n integer +---@return vm.object? +function mt:get(n) + self:_expand() + return self[n] +end + +function mt:lock() + self._locked = true +end + +function mt:unlock() + self._locked = false +end + +function mt:isLocked() + return self._locked == true +end + +function mt:setData(k, v) + if not self.data then + self.data = {} + end + self.data[k] = v +end + +function mt:getData(k) + if not self.data then + return nil + end + return self.data[k] +end + +function mt:addOptional() + if self:isOptional() then + return self + end + self.optional = true +end + +function mt:removeOptional() + if not self:isOptional() then + return self + end + self:_expand() + for i = #self, 1, -1 do + local n = self[i] + if n.type == 'nil' + or (n.type == 'boolean' and n[1] == false) + or (n.type == 'doc.type.boolean' and n[1] == false) then + self[i] = self[#self] + self[#self] = nil + end + end +end + +---@return boolean +function mt:isOptional() + if self.optional ~= nil then + return self.optional + end + self:_expand() + for _, c in ipairs(self) do + if c.type == 'nil' + or (c.type == 'boolean' and c[1] == false) + or (c.type == 'doc.type.boolean' and c[1] == false) then + self.optional = true + return true + end + end + self.optional = false + return false +end + +---@return fun():vm.object +function mt:eachObject() + self:_expand() + local i = 0 + return function () + i = i + 1 + return self[i] + end +end + +---@param source parser.object | vm.generic +---@param node vm.node | vm.object +---@param cover? boolean +function vm.setNode(source, node, cover) + if not node then + error('Can not set nil node') + end + if source.type == 'global' then + error('Can not set node to global') + end + if cover then + vm.nodeCache[source] = node + return + end + local me = vm.nodeCache[source] + if me then + me:merge(node) + else + if node.type == 'vm.node' then + vm.nodeCache[source] = node + else + vm.nodeCache[source] = vm.createNode(node) + end + end +end + +---@return vm.node? +function vm.getNode(source) + return vm.nodeCache[source] +end + +---@param source vm.object +function vm.removeNode(source) + vm.nodeCache[source] = nil +end + +function vm.clearNodeCache() + vm.nodeCache = {} +end + +---@param a? vm.node | vm.object +---@param b? vm.node | vm.object +---@return vm.node +function vm.createNode(a, b) + local node = setmetatable({}, mt) + if a then + node:merge(a) + end + if b then + node:merge(b) + end + return node +end + +files.watch(function (ev, uri) + if ev == 'version' then + vm.clearNodeCache() + end +end) diff --git a/script/vm/ref.lua b/script/vm/ref.lua new file mode 100644 index 00000000..65e8fdab --- /dev/null +++ b/script/vm/ref.lua @@ -0,0 +1,320 @@ +---@class vm +local vm = require 'vm.vm' +local util = require 'utility' +local guide = require 'parser.guide' +local localID = require 'vm.local-id' +local globalMgr = require 'vm.global-manager' +local files = require 'files' +local await = require 'await' +local progress = require 'progress' +local lang = require 'language' + +local simpleSwitch + +local function searchGetLocal(source, node, pushResult) + local key = guide.getKeyName(source) + for _, ref in ipairs(node.node.ref) do + if ref.type == 'getlocal' + and ref.next + and guide.getKeyName(ref.next) == key then + pushResult(ref.next) + end + end +end + +simpleSwitch = util.switch() + : case 'local' + : call(function (source, pushResult) + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' + or ref.type == 'getlocal' then + pushResult(ref) + end + end + end + end) + : case 'getlocal' + : case 'setlocal' + : call(function (source, pushResult) + simpleSwitch('local', source.node, pushResult) + end) + : case 'field' + : call(function (source, pushResult) + local parent = source.parent + if parent.type ~= 'tablefield' then + simpleSwitch(parent.type, parent, pushResult) + end + end) + : case 'setfield' + : case 'getfield' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + return + end + end) + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + end + end) + : case 'goto' + : call(function (source, pushResult) + if source.node then + simpleSwitch('label', source.node, pushResult) + pushResult(source.node) + end + end) + : case 'label' + : call(function (source, pushResult) + pushResult(source) + if source.ref then + for _, ref in ipairs(source.ref) do + pushResult(ref) + end + end + end) + +---@async +local function searchInAllFiles(suri, searcher, notify) + searcher(suri) + + local uris = {} + for uri in files.eachFile(suri) do + if not vm.isMetaFile(uri) + and suri ~= uri then + uris[#uris+1] = uri + end + end + + local loading <close> = progress.create(suri, lang.script.WINDOW_SEARCHING_IN_FILES, 1) + local cancelled + loading:onCancel(function () + cancelled = true + end) + for i, uri in ipairs(uris) do + if notify then + local continue = notify(uri) + if continue == false then + break + end + end + loading:setMessage(('%03d/%03d'):format(i, #uris)) + loading:setPercentage(i / #uris * 100) + await.delay() + if cancelled then + break + end + searcher(uri) + end +end + +---@async +local function searchField(source, pushResult, defMap, fileNotify) + local key = guide.getKeyName(source) + + ---@param src parser.object + local function checkDef(src) + for _, def in ipairs(vm.getDefs(src)) do + if defMap[def] then + pushResult(src) + return + end + end + end + + local pat = '[:.]%s*' .. key + + ---@async + local function findWord(uri) + local text = files.getText(uri) + if not text then + return + end + if not text:match(pat) then + return + end + local state = files.getState(uri) + if not state then + return + end + ---@async + guide.eachSourceType(state.ast, 'getfield', function (src) + if src.field and src.field[1] == key then + checkDef(src) + await.delay() + end + end) + ---@async + guide.eachSourceType(state.ast, 'getmethod', function (src) + if src.method and src.method[1] == key then + checkDef(src) + await.delay() + end + end) + ---@async + guide.eachSourceType(state.ast, 'getindex', function (src) + if src.index and src.index.type == 'string' and src.index[1] == key then + checkDef(src) + await.delay() + end + end) + end + + searchInAllFiles(guide.getUri(source), findWord, fileNotify) +end + +---@async +local function searchFunction(source, pushResult, defMap, fileNotify) + ---@param src parser.object + local function checkDef(src) + for _, def in ipairs(vm.getDefs(src)) do + if defMap[def] then + pushResult(src) + return + end + end + end + + ---@async + local function findCall(uri) + local state = files.getState(uri) + if not state then + return + end + ---@async + guide.eachSourceType(state.ast, 'call', function (src) + checkDef(src.node) + await.delay() + end) + end + + searchInAllFiles(guide.getUri(source), findCall, fileNotify) +end + +local searchByParentNode +local nodeSwitch = util.switch() + : case 'field' + : case 'method' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + searchByParentNode(source.parent, pushResult, defMap, fileNotify) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + + searchField(source, pushResult, defMap, fileNotify) + end) + : case 'tablefield' + : case 'tableindex' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + searchField(source, pushResult, defMap, fileNotify) + end) + : case 'function' + : case 'doc.type.function' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + searchFunction(source, pushResult, defMap, fileNotify) + end) + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchBySimple(source, pushResult) + simpleSwitch(source.type, source, pushResult) +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByLocalID(source, pushResult) + local idSources = localID.getSources(source) + if not idSources then + return + end + for _, src in ipairs(idSources) do + pushResult(src) + end +end + +---@async +---@param source parser.object +---@param pushResult fun(src: parser.object) +---@param fileNotify fun(uri: uri): boolean +function searchByParentNode(source, pushResult, defMap, fileNotify) + nodeSwitch(source.type, source, pushResult, defMap, fileNotify) +end + +local function searchByNode(source, pushResult) + local node = vm.compileNode(source) + if not node then + return + end + local uri = guide.getUri(source) + for n in node:eachObject() do + if n.type == 'global' then + for _, get in ipairs(n:getGets(uri)) do + pushResult(get) + end + end + end +end + +local function searchByDef(source, pushResult) + local defMap = {} + if source.type == 'function' + or source.type == 'doc.type.function' then + defMap[source] = true + return defMap + end + local defs = vm.getDefs(source) + for _, def in ipairs(defs) do + pushResult(def) + defMap[def] = true + end + return defMap +end + +---@async +---@param source parser.object +---@param fileNotify fun(uri: uri): boolean +function vm.getRefs(source, fileNotify) + local results = {} + local mark = {} + + local hasLocal + local function pushResult(src) + if src.type == 'local' then + if hasLocal then + return + end + hasLocal = true + end + if not mark[src] then + mark[src] = true + results[#results+1] = src + end + end + + searchBySimple(source, pushResult) + searchByLocalID(source, pushResult) + searchByNode(source, pushResult) + local defMap = searchByDef(source, pushResult) + searchByParentNode(source, pushResult, defMap, fileNotify) + + return results +end diff --git a/script/vm/sign.lua b/script/vm/sign.lua new file mode 100644 index 00000000..2d45a5a7 --- /dev/null +++ b/script/vm/sign.lua @@ -0,0 +1,164 @@ +local guide = require 'parser.guide' +local vm = require 'vm.vm' +local infer = require 'vm.infer' + +---@class vm.sign +---@field parent parser.object +---@field signList vm.node[] +local mt = {} +mt.__index = mt +mt.type = 'sign' + +---@param node vm.node +function mt:addSign(node) + self.signList[#self.signList+1] = node +end + +---@param uri uri +---@param args parser.object +---@return table<string, vm.node> +function mt:resolve(uri, args) + if not args then + return nil + end + local globalMgr = require 'vm.global-manager' + local resolved = {} + + ---@param object parser.object + ---@param node vm.node + local function resolve(object, node) + if object.type == 'doc.generic.name' then + local key = object[1] + if object.literal then + -- 'number' -> `T` + for n in node:eachObject() do + if n.type == 'string' then + local type = globalMgr.declareGlobal('type', n[1], guide.getUri(n)) + resolved[key] = vm.createNode(type, resolved[key]) + end + end + else + -- number -> T + resolved[key] = vm.createNode(node, resolved[key]) + end + end + if object.type == 'doc.type.array' then + for n in node:eachObject() do + if n.type == 'doc.type.array' then + -- number[] -> T[] + resolve(object.node, vm.compileNode(n.node)) + end + end + end + if object.type == 'doc.type.table' then + for _, ufield in ipairs(object.fields) do + local ufieldNode = vm.compileNode(ufield.name) + local uvalueNode = vm.compileNode(ufield.extends) + if ufieldNode:get(1).type == 'doc.generic.name' and uvalueNode:get(1).type == 'doc.generic.name' then + -- { [number]: number} -> { [K]: V } + local tfieldNode = vm.getTableKey(uri, node, 'any') + local tvalueNode = vm.getTableValue(uri, node, 'any') + resolve(ufieldNode:get(1), tfieldNode) + resolve(uvalueNode:get(1), tvalueNode) + else + if ufieldNode:get(1).type == 'doc.generic.name' then + -- { [number]: number}|number[] -> { [K]: number } + local tnode = vm.getTableKey(uri, node, uvalueNode) + resolve(ufieldNode:get(1), tnode) + elseif uvalueNode:get(1).type == 'doc.generic.name' then + -- { [number]: number}|number[] -> { [number]: V } + local tnode = vm.getTableValue(uri, node, ufieldNode) + resolve(uvalueNode:get(1), tnode) + end + end + end + end + end + + ---@param sign vm.node + ---@return table<string, true> + ---@return table<string, true> + local function getSignInfo(sign) + local knownTypes = {} + local genericsNames = {} + for obj in sign:eachObject() do + if obj.type == 'doc.generic.name' then + genericsNames[obj[1]] = true + goto CONTINUE + end + if obj.type == 'doc.type.table' + or obj.type == 'doc.type.function' + or obj.type == 'doc.type.array' then + local hasGeneric + guide.eachSourceType(obj, 'doc.generic.name', function (src) + hasGeneric = true + genericsNames[src[1]] = true + end) + if hasGeneric then + goto CONTINUE + end + end + local view = infer.viewObject(obj) + if view then + knownTypes[view] = true + end + ::CONTINUE:: + end + return knownTypes, genericsNames + end + + -- remove un-generic type + ---@param argNode vm.node + ---@param knownTypes table<string, true> + ---@return vm.node + local function buildArgNode(argNode, knownTypes) + local newArgNode = vm.createNode() + for n in argNode:eachObject() do + if argNode:isOptional() and vm.isFalsy(n) then + goto CONTINUE + end + local view = infer.viewObject(n) + if knownTypes[view] then + goto CONTINUE + end + newArgNode:merge(n) + ::CONTINUE:: + end + return newArgNode + end + + ---@param genericNames table<string, true> + local function isAllResolved(genericNames) + for n in pairs(genericNames) do + if not resolved[n] then + return false + end + end + return true + end + + for i, arg in ipairs(args) do + local sign = self.signList[i] + if not sign then + break + end + local argNode = vm.compileNode(arg) + local knownTypes, genericNames = getSignInfo(sign) + if not isAllResolved(genericNames) then + local newArgNode = buildArgNode(argNode, knownTypes) + for n in sign:eachObject() do + resolve(n, newArgNode) + end + end + end + + return resolved +end + +---@return vm.sign +return function () + local genericMgr = setmetatable({ + signList = {}, + }, mt) + return genericMgr +end diff --git a/script/vm/type.lua b/script/vm/type.lua new file mode 100644 index 00000000..5c8a8088 --- /dev/null +++ b/script/vm/type.lua @@ -0,0 +1,147 @@ +local globalMgr = require 'vm.global-manager' +---@class vm +local vm = require 'vm.vm' + +---@param uri uri +---@param child vm.node|string +---@param parent vm.node|string +---@param mark? table +---@return boolean +function vm.isSubType(uri, child, parent, mark) + if type(parent) == 'string' then + parent = vm.createNode(globalMgr.getGlobal('type', parent)) + end + if type(child) == 'string' then + child = vm.createNode(globalMgr.getGlobal('type', child)) + end + + if not child or not parent then + return false + end + + mark = mark or {} + for obj in child:eachObject() do + if obj.type ~= 'global' + or obj.cate ~= 'type' then + goto CONTINUE_CHILD + end + if mark[obj.name] then + return false + end + mark[obj.name] = true + for parentNode in parent:eachObject() do + if parentNode.type ~= 'global' + or parentNode.cate ~= 'type' then + goto CONTINUE_PARENT + end + if parentNode.name == 'any' or obj.name == 'any' then + return true + end + + if parentNode.name == obj.name then + return true + end + + for _, set in ipairs(obj:getSets(uri)) do + if set.type == 'doc.class' and set.extends then + for _, ext in ipairs(set.extends) do + if ext.type == 'doc.extends.name' + and vm.isSubType(uri, ext[1], parentNode.name, mark) then + return true + end + end + end + if set.type == 'doc.alias' and set.extends then + for _, ext in ipairs(set.extends.types) do + if ext.type == 'doc.type.name' + and vm.isSubType(uri, ext[1], parentNode.name, mark) then + return true + end + end + end + end + ::CONTINUE_PARENT:: + end + ::CONTINUE_CHILD:: + end + + return false +end + +---@param uri uri +---@param tnode vm.node +---@param knode vm.node +---@return vm.node? +function vm.getTableValue(uri, tnode, knode) + local result = vm.createNode() + for tn in tnode:eachObject() do + if tn.type == 'doc.type.table' then + for _, field in ipairs(tn.fields) do + if vm.isSubType(uri, vm.compileNode(field.name), knode) then + result:merge(vm.compileNode(field.extends)) + end + end + end + if tn.type == 'doc.type.array' then + result:merge(vm.compileNode(tn.node)) + end + if tn.type == 'table' then + for _, field in ipairs(tn) do + if field.type == 'tableindex' then + result:merge(vm.compileNode(field.value)) + end + if field.type == 'tablefield' then + if vm.isSubType(uri, knode, 'string') then + result:merge(vm.compileNode(field.value)) + end + end + if field.type == 'tableexp' then + if vm.isSubType(uri, knode, 'integer') and field.tindex == 1 then + result:merge(vm.compileNode(field.value)) + end + end + end + end + end + if result:isEmpty() then + return nil + end + return result +end + +---@param uri uri +---@param tnode vm.node +---@param vnode vm.node +---@return vm.node? +function vm.getTableKey(uri, tnode, vnode) + local result = vm.createNode() + for tn in tnode:eachObject() do + if tn.type == 'doc.type.table' then + for _, field in ipairs(tn.fields) do + if vm.isSubType(uri, vm.compileNode(field.extends), vnode) then + result:merge(vm.compileNode(field.name)) + end + end + end + if tn.type == 'doc.type.array' then + result:merge(globalMgr.getGlobal('type', 'integer')) + end + if tn.type == 'table' then + for _, field in ipairs(tn) do + if field.type == 'tableindex' then + result:merge(vm.compileNode(field.index)) + end + if field.type == 'tablefield' then + result:merge(globalMgr.getGlobal('type', 'string')) + end + if field.type == 'tableexp' then + result:merge(globalMgr.getGlobal('type', 'integer')) + end + end + end + end + if result:isEmpty() then + return nil + end + return result +end diff --git a/script/vm/value.lua b/script/vm/value.lua new file mode 100644 index 00000000..10107212 --- /dev/null +++ b/script/vm/value.lua @@ -0,0 +1,234 @@ +local guide = require 'parser.guide' +---@class vm +local vm = require 'vm.vm' + +---@param source parser.object +---@return boolean|nil +function vm.test(source) + local node = vm.compileNode(source) + local hasTrue, hasFalse + for n in node:eachObject() do + if n.type == 'boolean' + or n.type == 'doc.type.boolean' then + if n[1] == true then + hasTrue = true + end + if n[1] == false then + hasTrue = false + end + end + if n.type == 'nil' then + hasFalse = true + end + if n.type == 'string' + or n.type == 'number' + or n.type == 'integer' + or n.type == 'table' + or n.type == 'function' then + hasTrue = true + end + end + if hasTrue == hasFalse then + return nil + end + if hasTrue then + return true + else + return false + end +end + +---@param source parser.object +---@return boolean +function vm.isFalsy(source) + if source.type == 'nil' then + return true + end + if source.type == 'boolean' + or source.type == 'doc.type.boolean' then + return source[1] == false + end + return false +end + +---@param v vm.object +---@return string? +local function getUnique(v) + if v.type == 'local' then + return ('loc:%s@%d'):format(guide.getUri(v), v.start) + end + if v.type == 'global' then + return ('%s:%s'):format(v.cate, v.name) + end + if v.type == 'boolean' then + if v[1] == nil then + return false + end + return ('%s'):format(v[1]) + end + if v.type == 'number' then + if not v[1] then + return false + end + return ('num:%s'):format(v[1]) + end + if v.type == 'integer' then + if not v[1] then + return false + end + return ('num:%s'):format(v[1]) + end + if v.type == 'table' then + return ('table:%s@%d'):format(guide.getUri(v), v.start) + end + if v.type == 'function' then + return ('func:%s@%d'):format(guide.getUri(v), v.start) + end + return false +end + +---@param a vm.node +---@param b vm.node +---@return boolean|nil +function vm.equal(a, b) + if not a or not b then + return false + end + local nodeA = vm.compileNode(a) + local nodeB = vm.compileNode(b) + local mapA = {} + for obj in nodeA:eachObject() do + local unique = getUnique(obj) + if not unique then + return nil + end + mapA[unique] = true + end + for obj in nodeB:eachObject() do + local unique = getUnique(obj) + if not unique then + return nil + end + if not mapA[unique] then + return false + end + end + return true +end + +---@param v vm.node +---@return integer? +function vm.getInteger(v) + local node = vm.compileNode(v) + local result + for n in node:eachObject() do + if n.type == 'integer' then + if result then + return nil + else + result = n[1] + end + elseif n.type == 'number' then + if result then + return nil + elseif not math.tointeger(n[1]) then + return nil + else + result = math.tointeger(n[1]) + end + elseif n.type ~= 'local' + and n.type ~= 'global' then + return nil + end + end + return result +end + +---@param v vm.node +---@return integer? +function vm.getString(v) + local node = vm.compileNode(v) + local result + for n in node:eachObject() do + if n.type == 'string' then + if result then + return nil + else + result = n[1] + end + elseif n.type ~= 'local' + and n.type ~= 'global' then + return nil + end + end + return result +end + +---@param v vm.node +---@return number? +function vm.getNumber(v) + local node = vm.compileNode(v) + local result + for n in node:eachObject() do + if n.type == 'number' + or n.type == 'integer' then + if result then + return nil + else + result = n[1] + end + elseif n.type ~= 'local' + and n.type ~= 'global' then + return nil + end + end + return result +end + +---@param v vm.node +---@return boolean|nil +function vm.getBoolean(v) + local node = vm.compileNode(v) + local result + for n in node:eachObject() do + if n.type == 'boolean' then + if result then + return nil + else + result = n[1] + end + elseif n.type ~= 'local' + and n.type ~= 'global' then + return nil + end + end + return result +end + +---@param v vm.node +---@return table<any, boolean>? +function vm.getLiterals(v) + local map + local node = vm.compileNode(v) + for n in node:eachObject() do + local literal + if n.type == 'boolean' + or n.type == 'string' + or n.type == 'number' + or n.type == 'integer' then + literal = n[1] + end + if n.type == 'doc.type.string' + or n.type == 'doc.type.integer' + or n.type == 'doc.type.boolean' then + literal = n[1] + end + if literal ~= nil then + if not map then + map = {} + end + map[literal] = true + end + end + return map +end diff --git a/script/vm/vm.lua b/script/vm/vm.lua index aa18ea73..d370de0d 100644 --- a/script/vm/vm.lua +++ b/script/vm/vm.lua @@ -1,11 +1,8 @@ local guide = require 'parser.guide' -local util = require 'utility' local files = require 'files' local timer = require 'timer' local setmetatable = setmetatable -local running = coroutine.running -local ipairs = ipairs local log = log local xpcall = xpcall local mathHuge = math.huge @@ -17,23 +14,6 @@ _ENV = nil ---@class vm local m = {} -function m.getArgInfo(source) - local callargs = source.parent - if not callargs or callargs.type ~= 'callargs' then - return nil - end - local call = callargs.parent - if not call or call.type ~= 'call' then - return nil - end - for i = 1, #callargs do - if callargs[i] == source then - return call.node, i - end - end - return nil -end - function m.getSpecial(source) if not source then return nil @@ -69,6 +49,18 @@ function m.getKeyType(source) return guide.getKeyType(source) end +---@param source parser.object +---@return parser.object? +function m.getObjectValue(source) + if source.value then + return source.value + end + if source.special == 'rawset' then + return source.args and source.args[3] + end + return nil +end + m.cacheTracker = setmetatable({}, weakMT) function m.flushCache() diff --git a/script/workspace/loading.lua b/script/workspace/loading.lua index 3cdeaad6..258c67d6 100644 --- a/script/workspace/loading.lua +++ b/script/workspace/loading.lua @@ -85,7 +85,7 @@ function mt:loadFile(uri, libraryUri) files.addRef(uri) end self._cache[uri] = true - log.info(('Skip loaded file: %s'):format(uri)) + log.debug(('Skip loaded file: %s'):format(uri)) else local content = pub.awaitTask('loadFile', furi.decode(uri)) self.read = self.read + 1 @@ -93,7 +93,7 @@ function mt:loadFile(uri, libraryUri) if not content then return end - log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) + log.debug(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) files.setText(uri, content, false) if not self._cache[uri] then files.addRef(uri) @@ -101,7 +101,7 @@ function mt:loadFile(uri, libraryUri) self._cache[uri] = true end if libraryUri then - log.info('++++As library of:', libraryUri) + log.debug('++++As library of:', libraryUri) end end elseif files.isDll(uri) then @@ -116,7 +116,7 @@ function mt:loadFile(uri, libraryUri) files.addRef(uri) end self._cache[uri] = true - log.info(('Skip loaded file: %s'):format(uri)) + log.debug(('Skip loaded file: %s'):format(uri)) else local content = pub.awaitTask('loadFile', furi.decode(uri)) self.read = self.read + 1 @@ -124,7 +124,7 @@ function mt:loadFile(uri, libraryUri) if not content then return end - log.info(('Preload dll at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) + log.debug(('Preload dll at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) files.saveDll(uri, content) if not self._cache[uri] then files.addRef(uri) @@ -132,7 +132,7 @@ function mt:loadFile(uri, libraryUri) self._cache[uri] = true end if libraryUri then - log.info('++++As library of:', libraryUri) + log.debug('++++As library of:', libraryUri) end end end @@ -142,7 +142,6 @@ end ---@async function mt:loadAll() while self.read < self.max do - log.info(('Loaded %d/%d files'):format(self.read, self.max)) self:update() local loader = table.remove(self._stash) if loader then @@ -177,7 +176,7 @@ m._loadings = setmetatable({}, { __mode = 'k' }) function m.create(scp) local loading = setmetatable({ scp = scp, - _bar = progress.create(scp, lang.script('WORKSPACE_LOADING', scp.uri), 0.5), + _bar = progress.create(scp.uri, lang.script('WORKSPACE_LOADING', scp.uri), 0.5), _stash = {}, _cache = {}, }, mt) diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index 56c94424..aec298a6 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -123,12 +123,14 @@ function m.findUrisByRequirePath(suri, path) local clt = scope.getScope(suri):get('requireName') if clt then for _, uri in clt:each(suri, fspath) do - local infos = m.getVisiblePath(suri, furi.decode(uri)) - for _, info in ipairs(infos) do - local fsexpect = info.expect:gsub('%' .. separator, '/') - if fsexpect == fspath then - results[#results+1] = uri - searchers[uri] = info.searcher + if uri ~= suri then + local infos = m.getVisiblePath(suri, furi.decode(uri)) + for _, info in ipairs(infos) do + local fsexpect = info.expect:gsub('%' .. separator, '/') + if fsexpect == fspath then + results[#results+1] = uri + searchers[uri] = info.searcher + end end end end @@ -152,14 +154,18 @@ local function removeVisiblePath(uri) return end for _, scp in ipairs(workspace.folders) do - scp:get('visiblePath')[path] = nil + if scp:get('visiblePath') then + scp:get('visiblePath')[path] = nil + end ---@type collector local clt = scp:get('requireName') if clt then clt:dropUri(uri) end end - scope.fallback:get('visiblePath')[path] = nil + if scope.fallback:get('visiblePath') then + scope.fallback:get('visiblePath')[path] = nil + end ---@type collector local clt = scope.fallback:get('requireName') if clt then diff --git a/script/workspace/scope.lua b/script/workspace/scope.lua index be112af2..a0f4fbf7 100644 --- a/script/workspace/scope.lua +++ b/script/workspace/scope.lua @@ -1,5 +1,8 @@ local gc = require 'gc' +---@class scope.manager +local m = {} + ---@alias scope.type '"override"'|'"folder"'|'"fallback"' ---@class scope @@ -60,6 +63,14 @@ function mt:isLinkedUri(uri) end ---@param uri uri +---@return boolean +function mt:isVisible(uri) + return self:isChildUri(uri) + or self:isLinkedUri(uri) + or self == m.getScope(uri) +end + +---@param uri uri ---@return uri? function mt:getLinkedUri(uri) if not uri then @@ -122,9 +133,6 @@ local function createScope(scopeType) return scope end ----@class scope.manager -local m = {} - function m.reset() ---@type scope[] m.folders = {} diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index 80df7d9c..91923bb8 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -12,6 +12,7 @@ local util = require 'utility' local fw = require 'filewatch' local scope = require 'workspace.scope' local loading = require 'workspace.loading' +local inspect = require 'inspect' ---@class workspace local m = {} @@ -100,7 +101,7 @@ function m.getNativeMatcher(scp) local pattern = {} for path, ignore in pairs(config.get(scp.uri, 'files.exclude')) do if ignore then - log.info('Ignore by exclude:', path) + log.debug('Ignore by exclude:', path) pattern[#pattern+1] = path end end @@ -109,7 +110,7 @@ function m.getNativeMatcher(scp) if buf then for line in buf:gmatch '[^\r\n]+' do if line:sub(1, 1) ~= '#' then - log.info('Ignore by .gitignore:', line) + log.debug('Ignore by .gitignore:', line) pattern[#pattern+1] = line end end @@ -118,7 +119,7 @@ function m.getNativeMatcher(scp) if buf then for line in buf:gmatch '[^\r\n]+' do if line:sub(1, 1) ~= '#' then - log.info('Ignore by .git/info/exclude:', line) + log.debug('Ignore by .git/info/exclude:', line) pattern[#pattern+1] = line end end @@ -128,7 +129,7 @@ function m.getNativeMatcher(scp) local buf = util.loadFile(furi.decode(scp.uri) .. '/.gitmodules') if buf then for path in buf:gmatch('path = ([^\r\n]+)') do - log.info('Ignore by .gitmodules:', path) + log.debug('Ignore by .gitmodules:', path) pattern[#pattern+1] = path end end @@ -136,12 +137,12 @@ function m.getNativeMatcher(scp) for path in pairs(config.get(scp.uri, 'Lua.workspace.library')) do path = m.getAbsolutePath(scp.uri, path) if path then - log.info('Ignore by library:', path) - pattern[#pattern+1] = path + log.debug('Ignore by library:', path) + debug[#pattern+1] = path end end for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.ignoreDir')) do - log.info('Ignore directory:', path) + log.debug('Ignore directory:', path) pattern[#pattern+1] = path end @@ -160,17 +161,17 @@ function m.getLibraryMatchers(scp) if scp:get 'libraryMatcher' then return scp:get 'libraryMatcher' end - log.info('Build library matchers:', scp) + log.debug('Build library matchers:', scp) local pattern = {} for path, ignore in pairs(config.get(scp.uri, 'files.exclude')) do if ignore then - log.info('Ignore by exclude:', path) + log.debug('Ignore by exclude:', path) pattern[#pattern+1] = path end end for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.ignoreDir')) do - log.info('Ignore directory:', path) + log.debug('Ignore directory:', path) pattern[#pattern+1] = path end @@ -181,7 +182,7 @@ function m.getLibraryMatchers(scp) librarys[m.normalize(path)] = true end end - log.info('meta path:', scp:get 'metaPath') + log.debug('meta path:', scp:get 'metaPath') if scp:get 'metaPath' then librarys[m.normalize(scp:get 'metaPath')] = true end @@ -202,7 +203,7 @@ function m.getLibraryMatchers(scp) end scp:set('libraryMatcher', matchers) - log.debug('library matcher:', util.dump(matchers)) + log.debug('library matcher:', inspect(matchers)) return matchers end @@ -241,8 +242,9 @@ function m.awaitLoadFile(uri) log.info('Scan files at:', uri) ---@async native:scan(furi.decode(uri), function (path) - scp:get('cachedUris')[furi.encode(path)] = true - ld:loadFile(furi.encode(path)) + local uri = files.getRealUri(furi.encode(path)) + scp:get('cachedUris')[uri] = true + ld:loadFile(uri) end) ld:loadAll() end @@ -282,8 +284,9 @@ function m.awaitPreload(scp) log.info('Scan files at:', scp:getName()) ---@async native:scan(furi.decode(scp.uri), function (path) - scp:get('cachedUris')[furi.encode(path)] = true - ld:loadFile(furi.encode(path)) + local uri = files.getRealUri(furi.encode(path)) + scp:get('cachedUris')[uri] = true + ld:loadFile(uri) end) end @@ -292,8 +295,9 @@ function m.awaitPreload(scp) scp:addLink(libMatcher.uri) ---@async libMatcher.matcher:scan(furi.decode(libMatcher.uri), function (path) - scp:get('cachedUris')[furi.encode(path)] = true - ld:loadFile(furi.encode(path), libMatcher.uri) + local uri = files.getRealUri(furi.encode(path)) + scp:get('cachedUris')[uri] = true + ld:loadFile(uri, libMatcher.uri) end) scp:gc(fw.watch(furi.decode(libMatcher.uri))) end @@ -51,9 +51,9 @@ end local function testAll() test 'basic' - test 'references' test 'definition' test 'type_inference' + test 'references' test 'hover' test 'completion' test 'crossfile' diff --git a/test/basic/init.lua b/test/basic/init.lua index 1b698493..8490d51c 100644 --- a/test/basic/init.lua +++ b/test/basic/init.lua @@ -1,2 +1 @@ require 'basic.textmerger' -require 'basic.noder' diff --git a/test/basic/linker.txt b/test/basic/linker.txt deleted file mode 100644 index ea3ba180..00000000 --- a/test/basic/linker.txt +++ /dev/null @@ -1,141 +0,0 @@ -ast -> linkers = { - ['g|"X"|"Y"|"Z"'] = {src1, src2, src3}, - ['g|"X"|"Y"'] = {src4, src5, src6}, - ['g|"X"'] = {src7, src8, src9}, - ['l|7'] = {src10}, - ['l|7|"x"'] = {src11}, - ['l|11|"k"'] = {src12}, -} - -```lua -x.y.<?z?> = <!f!> - -<?g?> = x.y.z - -t.<!z!> = 1 -x.y = t - -x = { - y = { - <!z!> = 1 - } -} -``` - -expect: 'l|x|y|z' -forward: 'l|x|y|z' -> f -backward: 'l|x|y|z' -> g -last: 'l|x|y' + 'z' - -expect: 'l|x|y' + '|z' -forward: 'l|t' + '|z' -> 'l|t|z' -> t.z -backward: nil -last: 'l|x' + '|y|z' - -expect: 'l|x' + '|y|z' -forward: 'l|0' + '|y|z' -> 'l|0|y|z' -backward: nil -last: nil - -expect: 'l|0|y|z' -forward: nil -backward: nil -last: 'l|0|y' + '|z' - -expect: 'l|0|y' + '|z' -forward: 'l|1'+ '|z' -> 'l|1|z' -> field z -backward: nil -last: 'l|0' + '|y|z' - - -```lua -a = { - b = { - <?c?> = 1, - } -} - -print(a.b.<!c!>) -``` - -expect: 't|3|c' -forward: nil -backward: nil -last: 't|3' + '|c' - -expect: 't|3' + '|c' -forward: nil -backward: 't|2|b' + '|c' -last: nil - -expect: 't|2|b|c' -forward: nil -backward: 't|2|b' + '|c' -last: nil - -```lua ----@return <?A?> -local function f() -end - -local <!x!> = f() -``` - -'d|A' -'f|1|#1' -'f|1' + '|#1' -'l|1' + '|#1' -'s|1' + '|#1' - -```lua ----@generic T ----@param a T ----@return T -local function f(a) end - -local <?c?> - -local <!v!> = f(c) -``` - -'l1' -'l2|@1' -'f|1|@1' -'f|1|#1' - -``` ----@generic T ----@param p T ----@return T -local function f(p) end - -local <?r?> = f(<!k!>) -``` - -l:r -s:1#1 call -l:f#1 call -f:1#1 call -> f:1&T = l:k -l:f@1 --> 从保存的call信息里找到 f:1&T = l:k -l:k - - - -``` ----@generic T, V ----@param p T ----@return fun(V):T, V -local function f(p) end - -local f2 = f(<!k!>) -local <?r?> = f2() -``` - -l:r -s:2|#1 call1 -l:f2|#1 call1 -f:2|#1 call1 -s:1#1|#1 call2 -f:1#1|#1 call2 -> f:1&T = l:k -dfun:1|#1 -dn:V -> f:1&T = l:k diff --git a/test/basic/noder.lua b/test/basic/noder.lua deleted file mode 100644 index 49585ee8..00000000 --- a/test/basic/noder.lua +++ /dev/null @@ -1,143 +0,0 @@ -local noder = require 'core.noder' -local files = require 'files' -local util = require 'utility' -local guide = require 'parser.guide' -local catch = require 'catch' - -local function getSource(pos) - local ast = files.getState('') - return guide.eachSourceContain(ast.ast, pos, function (source) - if source.type == 'local' - or source.type == 'getlocal' - or source.type == 'setlocal' - or source.type == 'setglobal' - or source.type == 'getglobal' - or source.type == 'setfield' - or source.type == 'getfield' - or source.type == 'setmethod' - or source.type == 'getmethod' - or source.type == 'tablefield' - or source.type == 'setindex' - or source.type == 'getindex' - or source.type == 'tableindex' - or source.type == 'label' - or source.type == 'goto' then - return source - end - end) -end - -local CARE = {} -local function TEST(script) - return function (expect) - local newScript, catched = catch(script, '?') - files.setText('', newScript) - local source = getSource(catched['?'][1][1]) - assert(source) - local result = { - id = noder.getID(source), - } - - expect['id'] = expect['id']:gsub('|', '\x1F') - - for key in pairs(CARE) do - assert(result[key] == expect[key]) - end - files.remove('') - end -end - -CARE['id'] = true -TEST [[ -local <?x?> -]] { - id = 'l:6', -} - -TEST [[ -local x -print(<?x?>) -]] { - id = 'l:6', -} - -TEST [[ -local x -<?x?> = 1 -]] { - id = 'l:6', -} - -TEST [[ -print(<?X?>) -]] { - id = 'g:.X', -} - -TEST [[ -print(<?X?>) -]] { - id = 'g:.X', -} - -TEST [[ -local x -print(x.y.<?z?>) -]] { - id = 'l:6|.y|.z', -} - -TEST [[ -local x -function x:<?f?>() end -]] { - id = 'l:6|.f', -} - -TEST [[ -print(X.Y.<?Z?>) -]] { - id = 'g:.X|.Y|.Z', -} - -TEST [[ -function x:<?f?>() end -]] { - id = 'g:.x|.f', -} - -TEST [[ -{ - <?x?> = 1, -} -]] { - id = 't:0|.x', -} - -TEST [[ -return <?X?> -]] { - id = 'g:.X', -} - -TEST [[ -function f() - return <?X?> -end -]] { - id = 'g:.X', -} - -TEST [[ -::<?label?>:: -goto label -]] { - id = 'l:2', -} - -TEST [[ -::label:: -goto <?label?> -]] { - id = 'l:2', -} diff --git a/test/catch.lua b/test/catch.lua index 849be09d..01aac665 100644 --- a/test/catch.lua +++ b/test/catch.lua @@ -41,6 +41,10 @@ return function (script, seps) local result = {} local marks = {} + for s in seps:gmatch '.' do + result[s] = catchedTable() + end + local lineOffset = 1 local line = 0 local skipOffset = 0 @@ -68,9 +72,6 @@ return function (script, seps) local mark = marks[j] if mark.char == text then local position = line * 10000 + offset - skipOffset - lineOffset - if not result[text] then - result[text] = catchedTable() - end result[text][#result[text]+1] = { mark.position, position } table.remove(marks, j) break diff --git a/test/code_action/init.lua b/test/code_action/init.lua index 01423850..67528d59 100644 --- a/test/code_action/init.lua +++ b/test/code_action/init.lua @@ -113,6 +113,36 @@ return function(<?a?>, b, c) end }, } +TEST [[ +f = function (<?a?>, b) end +]] +{ + { + title = lang.script('ACTION_SWAP_PARAMS', { + node = 'f', + index = 2, + }), + kind = 'refactor.rewrite', + edit = EXISTS, + }, +} + +TEST [[ +local t = { + f = function (<?a?>, b) end +} +]] +{ + { + title = lang.script('ACTION_SWAP_PARAMS', { + node = 'f', + index = 2, + }), + kind = 'refactor.rewrite', + edit = EXISTS, + }, +} + --TEST [[ --<?print(1) --print(2)?> diff --git a/test/completion/common.lua b/test/completion/common.lua index 787549f7..4667305f 100644 --- a/test/completion/common.lua +++ b/test/completion/common.lua @@ -76,6 +76,29 @@ zac<??> }, } +config.set(nil, 'Lua.completion.callSnippet', 'Disable') +TEST [[ +ass<??> +]] +{ + { + label = 'assert(v, message)', + kind = define.CompletionItemKind.Function, + }, +} + +config.set(nil, 'Lua.completion.callSnippet', 'Replace') +TEST [[ +ass<??> +]] +{ + { + label = 'assert(v, message)', + kind = define.CompletionItemKind.Function, + }, +} + +config.set(nil, 'Lua.completion.callSnippet', 'Both') TEST [[ ass<??> ]] @@ -1169,10 +1192,51 @@ TEST [[ } TEST [[ +---@class ZBBC +---@class ZBBC : Z<??> +]] +(nil) + +TEST [[ ---@class ZABC ---@class ZBBC : <??> ]] -(EXISTS) +(function (results) + local ok + for _, res in ipairs(results) do + if res.label == 'ZABC' then + ok = true + end + if res.label == 'ZBBC' then + error('ZBBC should not be here') + end + end + assert(ok, 'ZABC should be here') +end) + +TEST [[ +---@class ZBBC +---@class ZBBC : <??> +]] +(function (results) + for _, res in ipairs(results) do + if res.label == 'ZBBC' then + error('ZBBC should not be here') + end + end +end) + +TEST [[ +---@class ZABC +---@class ZABC +---@class ZBBC : Z<??> +]] +{ + { + label = 'ZABC', + kind = define.CompletionItemKind.Class, + }, +} TEST [[ ---@class zabc @@ -1462,7 +1526,7 @@ mt.<??> } TEST [[ ----@param x string | "'AAA'" | "'BBB'" | "'CCC'" +---@param x string | "AAA" | "BBB" | "CCC" function f(y, x) end @@ -1470,21 +1534,21 @@ f(1, <??>) ]] { { - label = "'AAA'", + label = '"AAA"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'BBB'", + label = '"BBB"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'CCC'", + label = '"CCC"', kind = define.CompletionItemKind.EnumMember, } } TEST [[ ----@param x string | "'AAA'" | "'BBB'" | "'CCC'" +---@param x string | "AAA" | "BBB" | "CCC" function f(y, x) end @@ -1492,21 +1556,21 @@ f(1,<??>) ]] { { - label = "'AAA'", + label = '"AAA"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'BBB'", + label = '"BBB"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'CCC'", + label = '"CCC"', kind = define.CompletionItemKind.EnumMember, } } TEST [[ ----@param x string | "'AAA'" | "'BBB'" | "'CCC'" +---@param x string | "AAA" | "BBB" | "CCC" function f(x) end @@ -1514,21 +1578,21 @@ f(<??>) ]] { { - label = "'AAA'", + label = '"AAA"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'BBB'", + label = '"BBB"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'CCC'", + label = '"CCC"', kind = define.CompletionItemKind.EnumMember, } } TEST [[ ----@alias Option string | "'AAA'" | "'BBB'" | "'CCC'" +---@alias Option string | "AAA" | "BBB" | "CCC" ---@param x Option function f(x) end @@ -1537,21 +1601,21 @@ f(<??>) ]] { { - label = "'AAA'", + label = '"AAA"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'BBB'", + label = '"BBB"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'CCC'", + label = '"CCC"', kind = define.CompletionItemKind.EnumMember, } } TEST [[ ----@param x string | "'AAA'" | "'BBB'" | "'CCC'" +---@param x string | "AAA" | "BBB" | "CCC" function f(x) end @@ -1582,10 +1646,10 @@ TEST [[ ---@alias XXXX ---comment 1 ---comment 1 ----| '1' +---| 1 ---comment 2 ---comment 2 ----| '2' +---| 2 ---@param x XXXX local function f(x) @@ -1611,10 +1675,10 @@ TEST [[ ---@alias XXXX ---comment 1 ---comment 1 ----| '1' +---| 1 ---comment 2 ---comment 2 ----| '2' +---| 2 ---@param x XXXX local function f(x) end @@ -1622,7 +1686,7 @@ end ---comment 3 ---comment 3 ----| '3' +---| 3 f(<??>) ]] @@ -1696,20 +1760,20 @@ global zzz: integer = 1 TEST [[ ---@param x string ----| "'选项1'" # 注释1 ----| "'选项2'" # 注释2 +---| "选项1" # 注释1 +---| "选项2" # 注释2 function f(x) end f(<??>) ]] { { - label = "'选项1'", + label = '"选项1"', kind = define.CompletionItemKind.EnumMember, description = '注释1', }, { - label = "'选项2'", + label = '"选项2"', kind = define.CompletionItemKind.EnumMember, description = '注释2', }, @@ -1728,49 +1792,49 @@ utf8.charpatter<??> } TEST [[ ----@type "'a'"|"'b'"|"'c'" +---@type "a"|"b"|"c" local x print(x == <??>) ]] { { - label = "'a'", + label = '"a"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'b'", + label = '"b"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'c'", + label = '"c"', kind = define.CompletionItemKind.EnumMember, }, } TEST [[ ----@type "'a'"|"'b'"|"'c'" +---@type "a"|"b"|"c" local x x = <??> ]] { { - label = "'a'", + label = '"a"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'b'", + label = '"b"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'c'", + label = '"c"', kind = define.CompletionItemKind.EnumMember, }, } TEST [[ ----@type "'a'"|"'b'"|"'c'" +---@type "a"|"b"|"c" local x print(x == '<??>') @@ -1794,7 +1858,7 @@ print(x == '<??>') } TEST [[ ----@type "'a'"|"'b'"|"'c'" +---@type "a"|"b"|"c" local x x = '<??>' @@ -1872,6 +1936,12 @@ f(<??>) function (${1:x}, ${2:y})\ \t$0\ end", + description = "\z +```lua\ +function (x, y)\ +\t\ +end\ +```" }, } @@ -2031,85 +2101,85 @@ field cc.aaa: number Cared['description'] = nil TEST [[ ----@type table<string, "'a'"|"'b'"|"'c'"> +---@type table<string, "a"|"b"|"c"> local x x.a = <??> ]] { { - label = "'a'", + label = '"a"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'b'", + label = '"b"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'c'", + label = '"c"', kind = define.CompletionItemKind.EnumMember, }, } TEST [[ ----@type table<string, "'a'"|"'b'"|"'c'"> +---@type table<string, "a"|"b"|"c"> local x x['a'] = <??> ]] { { - label = "'a'", + label = '"a"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'b'", + label = '"b"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'c'", + label = '"c"', kind = define.CompletionItemKind.EnumMember, }, } TEST [[ ----@type table<string, "'a'"|"'b'"|"'c'"> +---@type table<string, "a"|"b"|"c"> local x = { a = <??> } ]] { { - label = "'a'", + label = '"a"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'b'", + label = '"b"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'c'", + label = '"c"', kind = define.CompletionItemKind.EnumMember, }, } TEST [[ ----@type table<string, "'a'"|"'b'"|"'c'"> +---@type table<string, "a"|"b"|"c"> local x = { ['a'] = <??> } ]] { { - label = "'a'", + label = '"a"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'b'", + label = '"b"', kind = define.CompletionItemKind.EnumMember, }, { - label = "'c'", + label = '"c"', kind = define.CompletionItemKind.EnumMember, }, } @@ -2373,6 +2443,108 @@ zzzz<??> insertText = 'zzzz(${1:a: any}, ${2:b: any})', }, } + +TEST [[ +---@param a any +---@param b? any +---@param c? any +---@vararg any +local function foo(a, b, c, ...) end +foo<??> +]] +{ + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Function, + insertText = 'foo', + }, + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Snippet, + insertText = 'foo(${1:a: any})', + }, +} + +TEST [[ +---@param a any +---@param b? any +---@param c? any +---@vararg any +local function foo(a, b, c, ...) end +foo<??> +]] +{ + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Function, + insertText = 'foo', + }, + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Snippet, + insertText = 'foo(${1:a: any})', + }, +} + +TEST [[ +---@param a? any +---@param b? any +---@param c? any +---@vararg any +local function foo(a, b, c, ...) end +foo<??> +]] +{ + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Function, + insertText = 'foo', + }, + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Snippet, + insertText = 'foo($1)', + }, +} + +TEST [[ +---@param a? any +---@param b any +---@param c? any +---@vararg any +local function foo(a, b, c, ...) end +foo<??> +]] +{ + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Function, + insertText = 'foo', + }, + { + label = 'foo(a, b, c, ...)', + kind = define.CompletionItemKind.Snippet, + insertText = 'foo(${1:a?: any}, ${2:b: any})', + }, +} + +TEST [[ +---@param f fun(a: any, b: any) +local function foo(f) end +foo<??> +]] +{ + { + label = 'foo(f)', + kind = define.CompletionItemKind.Function, + insertText = 'foo', + }, + { + label = 'foo(f)', + kind = define.CompletionItemKind.Snippet, + insertText = 'foo(${1:f: fun(a: any, b: any)})', + }, +} Cared['insertText'] = false TEST [[ @@ -2640,9 +2812,9 @@ class2:<??> TEST [[ --- @class Emit ---- @field on fun(eventName: string, cb: function) ---- @field on fun(eventName: '"died"', cb: fun(i: integer)) ---- @field on fun(eventName: '"won"', cb: fun(s: string)) +--- @field on fun(self: Emit, eventName: string, cb: function) +--- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string)) local emit = {} emit:on('<??>') @@ -2656,6 +2828,22 @@ TEST [[ --- @field on fun(eventName: '"won"', cb: fun(s: string)) local emit = {} +emit.on('died', <??>) +]] +{ + [1] = { + label = 'fun(i: integer)', + kind = define.CompletionItemKind.Function, + } +} + +TEST [[ +--- @class Emit +--- @field on fun(self: Emit, eventName: string, cb: function) +--- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string)) +local emit = {} + emit:on('won', <??>) ]] { @@ -2666,6 +2854,22 @@ emit:on('won', <??>) } TEST [[ +--- @class Emit +--- @field on fun(self: Emit, eventName: string, cb: function) +--- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string)) +local emit = {} + +emit.on(emit, 'won', <??>) +]] +{ + [1] = { + label = 'fun(s: string)', + kind = define.CompletionItemKind.Function, + } +} + +TEST [[ local function f() local inferCache in<??> @@ -3088,3 +3292,22 @@ local t = x[<??>] assert(res.label ~= 'do') end end) + +TEST [[ +---@class ZZZZZ.XXXX +---@class ZZZZZ.XXXX +---@class ZZZZZ.XXXX +---@class ZZZZZ.XXXX +---@class ZZZZZ.XXXX + +---@type <??> +]] +(function (results) + local count = 0 + for _, res in ipairs(results) do + if res.label == 'ZZZZZ.XXXX' then + count = count + 1 + end + end + assert(count == 1) +end) diff --git a/test/crossfile/allreferences.lua b/test/crossfile/allreferences.lua deleted file mode 100644 index 2753d785..00000000 --- a/test/crossfile/allreferences.lua +++ /dev/null @@ -1,218 +0,0 @@ -local config = require 'config' - -TEST { - { - path = 'a.lua', - content = [[ - local <!f!> = require 'lib' - ]], - }, - { - path = 'lib.lua', - content = [[ - return <!<?function?> () - end!> - ]], - }, -} - -TEST { - { - path = 'a.lua', - content = [[ - local m = {} - function m.<~func~>() - end - return m - ]], - }, - { - path = 'b.lua', - content = [[ - local t = require 'a' - t.<!func!>() - ]], - }, -} - -TEST { - { - path = 'a.lua', - content = [[ - return <~function () end~> - ]], - }, - { - path = 'b.lua', - content = [[ - local t = require 'a' - ]], - }, - { - path = 'b.lua', - content = [[ - local t = require 'a' - ]], - }, - { - path = 'b.lua', - content = [[ - local t = require 'a' - ]], - }, - { - path = 'b.lua', - content = [[ - local <!t!> = require 'a' - ]], - }, -} - -config.set(nil, 'Lua.IntelliSense.traceBeSetted', true) -TEST { - { - path = 'a.lua', - content = [[ - local function <~f~>() - end - - return { - <!f!> = <!f!>, - } - ]] - }, - { - path = 'b.lua', - content = [[ - local t = require 'a' - local <!f!> = t.<!f!> - - <!f!>() - - return { - <!f!> = <!f!>, - } - ]] - } -} - -TEST { - { - path = 'a.lua', - content = [[ - local function <!f!>() - end - - return { - <!f!> = <!f!>, - } - ]] - }, - { - path = 'c.lua', - content = [[ - local t = require 'a' - local <!f!> = t.<!f!> - - <!f!>() - - return { - <!f!> = <!f!>, - } - ]] - }, - { - path = 'b.lua', - content = [[ - local t = require 'a' - local <!f!> = t.<!f!> - - <~f~>() - - return { - <!f!> = <!f!>, - } - ]] - } -} - -TEST { - { - path = 'a.lua', - content = [[ - local function <~f~>() - end - - return { - <!f!> = <!f!>, - } - ]] - }, - { - path = 'b1.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, - { - path = 'b2.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, - { - path = 'b3.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, - { - path = 'b4.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, - { - path = 'b5.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, - { - path = 'b6.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, - { - path = 'b7.lua', - content = [[ - local t = require 'a' - t.<!f!>() - ]] - }, -} - -TEST { - { - path = 'a.lua', - content = [[ - local <~t~> = require 'b' - return <!t!> - ]] - }, - { - path = 'b.lua', - content = [[ - local <!t!> = require 'a' - return <!t!> - ]] - }, -} -config.set(nil, 'Lua.IntelliSense.traceBeSetted', false) diff --git a/test/crossfile/completion.lua b/test/crossfile/completion.lua index c271f598..79762646 100644 --- a/test/crossfile/completion.lua +++ b/test/crossfile/completion.lua @@ -837,7 +837,7 @@ TEST { kind = CompletionItemKind.Variable, description = [[ ```lua -local z: {} +local z: table ```]] }, } diff --git a/test/crossfile/definition.lua b/test/crossfile/definition.lua index eaf12b73..45f21697 100644 --- a/test/crossfile/definition.lua +++ b/test/crossfile/definition.lua @@ -47,8 +47,8 @@ function TEST(datas) uri, } end - if catched['?'] or catched['~'] then - sourceList = catched['?'] or catched['~'] + if #catched['?'] > 0 or #catched['~'] > 0 then + sourceList = catched['?'] + catched['~'] sourceUri = uri end files.setText(uri, newScript) @@ -149,7 +149,7 @@ config.set(nil, 'Lua.runtime.pathStrict', false) TEST { { path = 'a.lua', - content = 'local <!t!> = 1; return t', + content = 'return <!function () end!>', }, { path = 'b.lua', @@ -160,7 +160,7 @@ TEST { TEST { { path = 'a.lua', - content = 'local <!t!> = 1; return t', + content = 'return <!function () end!>', }, { path = 'b.lua', @@ -188,7 +188,7 @@ local <~t~> TEST { { path = 'a.lua', - content = 'local <!t!> = 1; return t', + content = 'return <!function () end!>', }, { path = 'b.lua', @@ -355,9 +355,7 @@ TEST { { path = 'a.lua', content = [[ - return <!{ - a = 1, - }!> + return <!function () end!> ]], }, { @@ -395,8 +393,8 @@ TEST { { path = 'a.lua', content = [[ - local function <!f!>() - end + local <!function f() + end!> return f ]] }, @@ -455,11 +453,11 @@ TEST { { path = 'a.lua', content = [[ - local function <!f!>() - end + local <!function f() + end!> return { - <!f!> = f, + f = f, } ]] }, @@ -610,7 +608,7 @@ TEST { path = 'a.lua', content = [[ ---@class Class - local <!obj!> + local obj ]] }, { @@ -636,7 +634,7 @@ TEST { path = 'b.lua', content = [[ ---@class Class - local <!obj!> + local obj ]] }, } @@ -800,14 +798,11 @@ TEST { }, } -config.set(nil, 'Lua.IntelliSense.traceFieldInject', true) TEST { { path = 'a.lua', content = [[ -local t = GlobalTable - -t.settings = { +GlobalTable.settings = { <!test!> = 1 } ]] @@ -821,7 +816,27 @@ print(b.<?test?>) ]] } } -config.set(nil, 'Lua.IntelliSense.traceFieldInject', false) + +TEST { + { + path = 'a.lua', + content = [[ +GlobalTable = { + settings = { + <!test!> = 1 + } +} + ]] + }, + { + path = 'b.lua', + content = [[ +local b = GlobalTable.settings + +print(b.<?test?>) + ]] + } +} TEST { { @@ -962,3 +977,14 @@ print(t.<?x?>) config.set(nil, 'Lua.runtime.pathStrict', false) config.set(nil, 'Lua.runtime.path', originRuntimePath) + +-- Don't require self +TEST { + { + path = 'a.lua', + content = [[ +local <~f~> = require 'a' +return function () end + ]] + } +} diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index cb9e83aa..4461e775 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -44,7 +44,7 @@ function TEST(expect) local script, list = catch(file.content, '?') local uri = furi.encode(file.path) files.setText(uri, script) - if list['?'] then + if #list['?'] > 0 then sourceUri = uri sourcePos = (list['?'][1][1] + list['?'][1][2]) // 2 end @@ -362,22 +362,22 @@ TEST { path = 'b.lua', content = [[ ---@param x string - ---| "'选项1'" # 注释1 - ---| > "'选项2'" # 注释2 + ---| "选项1" # 注释1 + ---| > "选项2" # 注释2 function <?f?>(x) end ]] }, hover = [[ ```lua -function f(x: string|'选项1'|'选项2') +function f(x: string|"选项1"|"选项2") ``` --- ```lua -x: string - | '选项1' -- 注释1 - -> '选项2' -- 注释2 +x: + | "选项1" -- 注释1 + -> "选项2" -- 注释2 ```]] } @@ -390,23 +390,23 @@ TEST { path = 'b.lua', content = [[ ---@alias option - ---| "'选项1'" # 注释1 - ---| > "'选项2'" # 注释2 + ---| "选项1" # 注释1 + ---| > "选项2" # 注释2 ---@param x option function <?f?>(x) end ]] }, hover = [[ ```lua -function f(x: '选项1'|'选项2') +function f(x: "选项1"|"选项2") ``` --- ```lua -x: option - | '选项1' -- 注释1 - -> '选项2' -- 注释2 +x: + | "选项1" -- 注释1 + -> "选项2" -- 注释2 ```]] } @@ -419,8 +419,8 @@ TEST { path = 'b.lua', content = [[ ---@alias option - ---| "'选项1'" # 注释1 - ---| > "'选项2'" # 注释2 + ---| "选项1" # 注释1 + ---| > "选项2" # 注释2 ---@return option x function <?f?>() end ]] @@ -428,15 +428,15 @@ TEST { hover = [[ ```lua function f() - -> x: '选项1'|'选项2' + -> x: "选项1"|"选项2" ``` --- ```lua -x: option - | '选项1' -- 注释1 - -> '选项2' -- 注释2 +x: + | "选项1" -- 注释1 + -> "选项2" -- 注释2 ```]] } @@ -449,8 +449,8 @@ TEST { path = 'b.lua', content = [[ ---@alias option - ---| "'选项1'" # 注释1 - ---| > "'选项2'" # 注释2 + ---| "选项1" # 注释1 + ---| > "选项2" # 注释2 ---@return option function <?f?>() end ]] @@ -458,15 +458,15 @@ TEST { hover = [[ ```lua function f() - -> '选项1'|'选项2' + -> "选项1"|"选项2" ``` --- ```lua -return #1: option - | '选项1' -- 注释1 - -> '选项2' -- 注释2 +return #1: + | "选项1" -- 注释1 + -> "选项2" -- 注释2 ```]] } @@ -673,8 +673,8 @@ TEST {{ path = 'a.lua', content = '', }, { path = 'b.lua', content = [[ ---@param a boolean # xxx - ---| 'true' # ttt - ---| 'false' # fff + ---| true # ttt + ---| false # fff local function <?f?>(a) end ]] @@ -689,7 +689,7 @@ function f(a: boolean|true|false) @*param* `a` — xxx ```lua -a: boolean +a: | true -- ttt | false -- fff ```]]} @@ -825,7 +825,7 @@ local <?food?> }, hover = [[ ```lua -local food: any +local food: unknown ``` --- @@ -976,17 +976,17 @@ end }, hover = [[ ```lua -function f(p: a|b) +function f(p: "a"|"b") ``` --- ```lua -p: T - | a -- comment 1 - -- comment 2 - | b -- comment 3 - -- comment 4 +p: + | "a" -- comment 1 + -- comment 2 + | "b" -- comment 3 + -- comment 4 ```]]} --TEST {{ path = 'a.lua', content = '', }, { @@ -1044,13 +1044,13 @@ end for _, x in ipairs({} and {}) do - print(<?x?>) -- `x` is infered as `string` + print(<?x?>) -- `x` is infered as `string` (fixed bug) end ]], }, hover = [[ ```lua -local x: any +local x: unknown ```]] } diff --git a/test/crossfile/init.lua b/test/crossfile/init.lua index 1ed2a943..aec9a044 100644 --- a/test/crossfile/init.lua +++ b/test/crossfile/init.lua @@ -1,6 +1,5 @@ require 'crossfile.definition' require 'crossfile.references' -require 'crossfile.allreferences' require 'crossfile.hover' require 'crossfile.completion' require 'crossfile.diagnostic' diff --git a/test/crossfile/references.lua b/test/crossfile/references.lua index 18e9c5f9..1a9f2508 100644 --- a/test/crossfile/references.lua +++ b/test/crossfile/references.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: await-in-sync local files = require 'files' local furi = require 'file-uri' local core = require 'core.reference' @@ -68,7 +69,7 @@ function TEST(datas) } end end - if catched['?'] or catched['~'] then + if #catched['?'] > 0 or #catched['~'] > 0 then sourceList = catched['?'] + catched['~'] sourceUri = uri end @@ -100,16 +101,15 @@ end TEST { { - path = 'lib.lua', + path = 'a.lua', content = [[ - return <!function () - end!> + <!ROOT!> = 1 ]], }, { - path = 'a.lua', + path = 'b.lua', content = [[ - local <~f~> = require 'lib' + print(<~ROOT~>) ]], }, } @@ -118,13 +118,13 @@ TEST { { path = 'a.lua', content = [[ - <!ROOT!> = 1 + <~ROOT~> = 1 ]], }, { path = 'b.lua', content = [[ - print(<~ROOT~>) + print(<!ROOT!>) ]], }, } @@ -133,15 +133,41 @@ TEST { { path = 'a.lua', content = [[ - <~ROOT~> = 1 - ]], + ---@type A + local t + + t.<!f!>() + ]] }, { path = 'b.lua', content = [[ - print(<!ROOT!>) - ]], + ---@class A + local mt + + function mt.<~f~>() + end + ]] + } +} + +TEST { + { + path = 'a.lua', + content = [[ + local t = {} + t.<~x~> = 1 + return t + ]] }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + + print(t.<!x!>) + ]] + } } TEST { @@ -149,14 +175,13 @@ TEST { path = 'a.lua', content = [[ local f = require 'lib' - local <~o~> = f() + <!f!>() ]], }, { path = 'lib.lua', content = [[ - return function () - return <!{}!> + return <?function?> () end ]], }, @@ -166,20 +191,78 @@ TEST { { path = 'a.lua', content = [[ - ---@type A - local t + local m = {} + function m.<~func~>() + end + return m + ]], + }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + t.<!func!>() + ]], + }, +} - t.<!f!>() - ]] +TEST { + { + path = 'a.lua', + content = [[ + return <?function?> () end + ]], }, { path = 'b.lua', content = [[ - ---@class A - local mt + local t = require 'a' + ]], + }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + ]], + }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + ]], + }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + <!t!>() + ]], + }, +} - function mt.<~f~>() +TEST { + { + path = 'a.lua', + content = [[ + local function <~f~>() end + + return { + f = <!f!>, + } + ]] + }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + local f = t.f + + f() + + return { + f = f, + } ]] } } @@ -188,17 +271,105 @@ TEST { { path = 'a.lua', content = [[ - local t = {} - t.<~x~> = 1 - return t + local <?function?> f() + end + + return { + f = f, + } ]] }, { path = 'b.lua', content = [[ local t = require 'a' + local f = t.f - print(t.<!x!>) + <!f!>() + + return { + f = f, + } ]] } } + +TEST { + { + path = 'a.lua', + content = [[ + local <?function?> f() + end + + return { + f = f, + } + ]] + }, + { + path = 'b1.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, + { + path = 'b2.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, + { + path = 'b3.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, + { + path = 'b4.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, + { + path = 'b5.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, + { + path = 'b6.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, + { + path = 'b7.lua', + content = [[ + local t = require 'a' + t.<!f!>() + ]] + }, +} + +TEST { + { + path = 'a.lua', + content = [[ + local <~t~> = require 'b' + return <!t!> + ]] + }, + { + path = 'b.lua', + content = [[ + local t = require 'a' + return t + ]] + }, +} diff --git a/test/definition/bug.lua b/test/definition/bug.lua index 77797f40..b564f764 100644 --- a/test/definition/bug.lua +++ b/test/definition/bug.lua @@ -134,7 +134,7 @@ t:<?add?>() TEST [[ local t = {} -t.<!f1!> = 1 +t.f1 = 1 t.<!f2!> = t.f1 print(t.<?f2?>) @@ -142,9 +142,9 @@ print(t.<?f2?>) TEST [[ local t = {} -t.<!f1!> = 1 +t.f1 = 1 t.<!f2!> = t.f1 -t.<!f1!> = t.f2 +t.f1 = t.f2 print(t.<?f2?>) ]] @@ -172,67 +172,65 @@ string.xx:<?format?>() --v.<?bar1?> --]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', true) TEST [[ local A, B function A:get1() - local <!a!> = B:get() + local a = B:get() return a end function A:get2() - local <!a!> = B:get() + local a = B:get() return a end function A:get3() - local <!a!> = B:get() + local a = B:get() return a end function A:get4() - local <!a!> = B:get() + local a = B:get() return a end function A:get5() - local <!a!> = B:get() + local a = B:get() return a end function A:get6() - local <!a!> = B:get() + local a = B:get() return a end function A:get7() - local <!a!> = B:get() + local a = B:get() return a end function A:get8() - local <!a!> = B:get() + local a = B:get() return a end function B:get() - local <!b!> - <!b!> = A:get1() - <!b!> = A:get2() - <!b!> = A:get3() - <!b!> = A:get4() - <!b!> = A:get5() - <!b!> = A:get6() - <!b!> = A:get7() - <!b!> = A:get8() + local b + b = A:get1() + b = A:get2() + b = A:get3() + b = A:get4() + b = A:get5() + b = A:get6() + b = A:get7() + b = A:get8() return b end local <!b!> = B:get() print(<?b?>) ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', false) TEST [[ g[a.b.c] = 1 @@ -253,80 +251,3 @@ local <!v!> = t[a] t[a] = <?v?> ]] - -TEST [[ ----@class A ----@field x number - ----@class B: A ----@field <!x!> boolean - ----@type B -local t - -local <!<?v?>!> = t.x -]] - -TEST [[ ----@class A ----@field <!x!> number - ----@class B: A - ----@type B -local t - -local <!<?v?>!> = t.x -]] - -TEST [[ ----@class A -local A - -function A:x() end - ----@class B: A -local B - -function B:<!x!>() end - ----@type B -local t - -local <!<?v?>!> = t.x -]] - -TEST [[ ----@class A -local A - -function A:<!x!>() end - ----@class B: A -local B - ----@type B -local t - -local <!<?v?>!> = t.x -]] - --- TODO ---TEST [[ ------@class A ---local A --- ------@return A ---function A:x() end --- ------@class B: A ---local <!B!> --- ------@return B ---function B:x() end --- ------@type B ---local t --- ---local <!<?v?>!> = t.x() ---]] diff --git a/test/definition/call.lua b/test/definition/call.lua deleted file mode 100644 index 15364396..00000000 --- a/test/definition/call.lua +++ /dev/null @@ -1,8 +0,0 @@ -TEST [[ -function f() - local <!x!> - return x -end -local <!y!> = f() -print(<?y?>) -]] diff --git a/test/definition/field.lua b/test/definition/field.lua new file mode 100644 index 00000000..5b5b67f9 --- /dev/null +++ b/test/definition/field.lua @@ -0,0 +1,23 @@ +TEST [[ +X.<!y!> = 1 + +local t = X + +print(t.<?y?>) +]] + +TEST [[ +X.x.<!y!> = 1 + +local t = X.x + +print(t.<?y?>) +]] + +TEST [[ +X.x.<!y!> = 1 + +local t = X + +print(t.x.<?y?>) +]] diff --git a/test/definition/function.lua b/test/definition/function.lua index f5d32e78..95dd1b57 100644 --- a/test/definition/function.lua +++ b/test/definition/function.lua @@ -24,6 +24,6 @@ end ]] TEST [[ -local <!f!> = function () end +local <!f!> = <!function () end!> <?f?>() ]] diff --git a/test/definition/init.lua b/test/definition/init.lua index fc2903c1..3badaadf 100644 --- a/test/definition/init.lua +++ b/test/definition/init.lua @@ -34,9 +34,9 @@ function TEST(script) positions[i] = { result.target.start, result.target.finish } end end - assert(founded(catched['!'] or {}, positions)) + assert(founded(catched['!'], positions)) else - assert(catched['!'] == nil) + assert(#catched['!'] == 0) end files.remove('') @@ -44,12 +44,12 @@ end require 'definition.local' require 'definition.set' +require 'definition.field' require 'definition.arg' require 'definition.function' require 'definition.table' require 'definition.method' require 'definition.label' -require 'definition.call' require 'definition.special' require 'definition.bug' require 'definition.luadoc' diff --git a/test/definition/luadoc.lua b/test/definition/luadoc.lua index 8124261c..d7ac9a5b 100644 --- a/test/definition/luadoc.lua +++ b/test/definition/luadoc.lua @@ -17,7 +17,7 @@ local x TEST [[ ---@class Class -local <!t!> +local t ---@type Class local <?<!x!>?> ]] @@ -35,21 +35,59 @@ obj:<?cast?>() TEST [[ ---@class A -local <!mt!> = {} -function mt:cast() -end +---@field x number ----@type A -local <!obj!> -<?obj?>:cast() +---@class B: A +---@field <!x!> boolean + +---@type B +local t + +t.<?x?> ]] TEST [[ ----@type A -local <?<!obj!>?> +---@class A +---@field <!x!> number + +---@class B: A + +---@type B +local t +t.<?x?> +]] + +TEST [[ ---@class A -local <!mt!> +local A + +function A:x() end + +---@class B: A +local B + +<!function B:x() end!> + +---@type B +local t + +local <!<?v?>!> = t.x +]] + +TEST [[ +---@class A +local A + +<!function A:x() end!> + +---@class B: A +local B + +---@type B +local t + +local <!<?v?>!> = t.x ]] TEST [[ @@ -105,6 +143,20 @@ function f(<?...?>) end ]] TEST [[ +---@alias A <!fun()!> + +---@type A +local <!<?x?>!> +]] + +TEST [[ +---@class A: <!{}!> + +---@type A +local <!<?x?>!> +]] + +TEST [[ ---@overload <!fun(y: boolean)!> ---@param x number ---@param y boolean @@ -116,11 +168,12 @@ print(<?f?>) TEST [[ local function f() - return 1 + local x + return x end ---@class Class -local <!mt!> +local mt ---@type Class local <?<!x!>?> = f() @@ -153,7 +206,7 @@ y.<?a?> TEST [[ ---@class <!loli!> -local <!unit!> +local unit!> function unit:pants() end @@ -184,34 +237,6 @@ AAAA.a.<?SSDF?> ]] TEST [[ ----@class Cat -local <!m!> ---hahaha ----@class Dog -local m2 ----@type Cat -local <?<!v!>?> -]] - -TEST [[ ----@class Cat -local <!m!> --hahaha ----@class Dog -local m2 ----@type Cat -local <?<!v!>?> -]] - -TEST [[ ----@class Cat - local <!m!> ---hahaha - - ---@class Dog - local m2 - ---@type Cat - local <?<!v!>?> -]] - -TEST [[ ---@return <!fun()!> local function f() end @@ -224,8 +249,8 @@ TEST [[ ---@return T local function f(p) end -local <!k!> -local <?<!r!>?> = f(<!k!>) +local k = <!function () end!> +local <?<!r!>?> = f(k) ]] TEST [[ @@ -286,22 +311,28 @@ print(v1.<?bar1?>) TEST [[ ---@class A -local <!t!> +local t + +t.<!x!> = 1 ---@type A[] local b -local <?<!c!>?> = b[1] +local c = b[1] +c.<?x?> ]] TEST [[ ---@class A -local <!t!> +local t ----@type table<number, A> +t.<!x!> = 1 + +---@type { [number]: A } local b -local <?<!c!>?> = b[1] +local c = b[1] +c.<?x?> ]] TEST [[ @@ -309,7 +340,7 @@ TEST [[ local Foo = {} function Foo:<!bar1!>() end ----@type table<number, Foo> +---@type { [number]: Foo } local v1 print(v1[1].<?bar1?>) ]] @@ -323,7 +354,7 @@ function Foo:<!bar1!>() end local Foo2 = {} function Foo2:bar1() end ----@type Foo2<number, Foo> +---@type { [number]: Foo } local v1 print(v1[1].<?bar1?>) ]] @@ -355,16 +386,16 @@ TEST [[ ---@type fun(x: T):T local f -local <?<!v2!>?> = f(<!{}!>) +local <?<!v2!>?> = f(<!function () end!>) ]] TEST [[ ---@generic T ---@param x T ---@return fun():T -local function f(<!x!>) end +local function f(x) end -local v1 = f(<!{}!>) +local v1 = f(<!function () end!>) local <?<!v2!>?> = v1() ]] @@ -373,7 +404,7 @@ TEST [[ ---@type fun(x: T):fun():T local f -local v1 = f(<!{}!>) +local v1 = f(<!function () end!>) local <?<!v2!>?> = v1() ]] @@ -383,7 +414,7 @@ TEST [[ local function f(x) end local v1 = f() -local <?<!v2!>?> = v1(<!{}!>) +local <?<!v2!>?> = v1(<!function () end!>) ]] TEST [[ @@ -393,72 +424,150 @@ TEST [[ local function f(x) end ---@class A -local <!a!> +local a +a.<!x!> = 1 ---@type A[] local b -local <?<!c!>?> = f(b) +local c = f(b) +c.<?x?> ]] TEST [[ ---@generic V ----@param x table<number, V> +---@param x { [number]: V } ---@return V local function f(x) end ---@class A -local <!a!> +local a +a.<!x!> = 1 ----@type table<number, A> +---@type { [number]: A } local b -local <?<!c!>?> = f(b) +local c = f(b) +c.<?x?> ]] TEST [[ ---@generic V ----@param x V[] +---@param x { [number]: V } +---@return V +local function f(x) end + +---@class A +local a +a.<!x!> = 1 + +---@type { [integer]: A } +local b + +local c = f(b) +c.<?x?> +]] + +TEST [[ +---@generic V +---@param x { [integer]: V } ---@return V local function f(x) end ---@class A -local <!a!> +local a +a.x = 1 ----@type table<number, A> +---@type { [number]: A } local b -local <?<!c!>?> = f(b) +local c = f(b) +c.<?x?> ]] TEST [[ ---@generic V ----@param x table<number, V> +---@param x { [number]: V } ---@return V local function f(x) end ---@class A -local <!a!> +local a +a.<!x!> = 1 ---@type A[] local b -local <?<!c!>?> = f(b) +local c = f(b) +c.<?x?> +]] + +TEST [[ +---@generic K +---@param x { [K]: number } +---@return K +local function f(x) end + +---@class A +local a +a.<!x!> = 1 + +---@type { [A]: number } +local b + +local c = f(b) +c.<?x?> ]] TEST [[ ---@generic K ----@param x table<K, number> +---@param x { [K]: A } ---@return K local function f(x) end ---@class A -local <!a!> +local a +a.x = 1 ----@type table<A, number> +---@type A[] local b -local <?<!c!>?> = f(b) +local c = f(b) +c.<?x?> +]] + +TEST [[ +---@generic K +---@param x { [K]: number } +---@return K +local function f(x) end + +---@class A +local a +a.<!x!> = 1 + +---@type { [A]: integer } +local b + +local c = f(b) +c.<?x?> +]] + +TEST [[ +---@generic K +---@param x { [K]: integer } +---@return K +local function f(x) end + +---@class A +local a +a.x = 1 + +---@type { [A]: number } +local b + +local c = f(b) +c.<?x?> ]] TEST [[ @@ -466,9 +575,6 @@ TEST [[ ---@return fun(t: V[]):V local function f() end ----@class A -local <!a!> - ---@type A[] local b @@ -485,59 +591,95 @@ TEST [[ local function f(t) end ---@class A -local <!a!> +local a +a.<!x!> = 1 ---@type A[] local b local f2, c = f(b) -local <?<!d!>?> = f2(c) +local d = f2(c) +d.<?x?> ]] TEST [[ ----@class C -local <!v1!> - ---@generic V, T ---@param t T ---@return fun(t: V): V ---@return T local function iterator(t) end -for <!v!> in iterator(<!v1!>) do +for <!v!> in iterator(<!function () end!>) do print(<?v?>) end ]] TEST [[ ----@class C -local <!v!> +---@alias C <!fun()!> ----@type C -local <!v1!> +---@type C[] +local v1 ---@generic V, T ---@param t T ----@return fun(t: V): V +---@return fun(t: V[]): V ---@return T local function iterator(t) end -for <!v!> in iterator(<!v1!>) do +for <!v!> in iterator(v1) do print(<?v?>) end ]] TEST [[ ----@class C -local <!v!> +---@class TT<V>: { <!x: V!> } ----@type C[] +---@type TT<A> +local t + +---@class A: <!{}!> + +print(t.<?x?>) +]] + +TEST [[ +---@alias TT<V> { <!x: V!> } + +---@type TT<A> +local t + +---@class A: <!{}!> + +print(t.<?x?>) +]] + +TEST [[ +---@class TT<V>: { [number]: V } + +---@type TT<<!{}!>> local v1 ---@generic V, T ---@param t T ----@return fun(t: V[]): V +---@return fun(t: { [number]: V }): V +---@return T +local function iterator(t) end + +for <!v!> in iterator(v1) do + print(<?v?>) +end +]] + +TEST [[ +---@class TT<K, V>: { [K]: V } + +---@type TT<number, <!{}!>> +local v1 + +---@generic V, T +---@param t T +---@return fun(t: { [number]: V }): V ---@return T local function iterator(t) end @@ -551,12 +693,32 @@ TEST [[ local Foo = {} function Foo:<!bar1!>() end +---@type { [number]: Foo } +local v1 + +---@generic T: table, V +---@param t T +---@return fun(table: { [number]: V }, i?: integer):integer, V +---@return T +---@return integer i +local function ipairs(t) end + +for i, v in ipairs(v1) do + print(v.<?bar1?>) +end +]] + +TEST [[ +---@class Foo +local Foo = {} +function Foo:<!bar1!>() end + ---@type table<number, Foo> local v1 ---@generic T: table, V ---@param t T ----@return fun(table: V[], i?: integer):integer, V +---@return fun(table: { [number]: V }, i?: integer):integer, V ---@return T ---@return integer i local function ipairs(t) end @@ -615,7 +777,7 @@ function Foo:<!bar1!>() end ---@generic T: table, V ---@param t T ----@return fun(table: V[], i?: integer):integer, V +---@return fun(table: table<number, V>, i?: integer):integer, V ---@return T ---@return integer i local function ipairs(t) end @@ -660,22 +822,22 @@ print(t.<?x?>) TEST [[ ---@class A ----@field <![1]!>? boolean +---@field [1]? <!{}!> local t -print(t[<?1?>]) +local <!<?v?>!> = t[1] ]] TEST [[ ----@type { <![1]?: boolean!> } +---@type { [1]?: <!{}!> } local t -print(t[<?1?>]) +local <!<?v?>!> = t[1] ]] TEST [[ ---@class A ----@field <!['xx']!>? boolean +---@field <!['xx']!>? <!{}!> local t print(t.<?xx?>) @@ -697,3 +859,25 @@ end <?t?> ]] + +TEST [[ +---@class A +local t = { + <!x!> = nil, +} + +---@type A +local f +f.<?x?> +]] + +TEST [[ +---@class A +G = { + <!x!> = nil, +} + +---@type A +local f +f.<?x?> +]] diff --git a/test/definition/special.lua b/test/definition/special.lua index 3f0d076a..531c5d93 100644 --- a/test/definition/special.lua +++ b/test/definition/special.lua @@ -32,24 +32,28 @@ local obj = setmetatable({}, { __index = mt }) obj:<?method1?>() ]] -TEST [[ -local mt -function mt:<!method1!>() -end - -setmetatable(api, { __index = mt }) -api:<?method1?>() -]] +-- 不再支持在变量的引用中使用 setmetatable 操作。 +-- 这会将引用转换为定义,为了搜索定义去检查引用性价比太差了。 +-- 如果有必要,请使用 ---@class 系统。 -TEST [[ -local mt -local api -function mt:<!method1!>() -end +--TEST [[ +--local mt +--function mt:<!method1!>() +--end +-- +--setmetatable(api, { __index = mt }) +--api:<?method1?>() +--]] -setmetatable(api, { __index = mt }) -api:<?method1?>() -]] +--TEST [[ +--local mt +--local api +--function mt:<!method1!>() +--end +-- +--setmetatable(api, { __index = mt }) +--api:<?method1?>() +--]] -- TODO 不支持从方法内部找外部的赋值 --TEST [[ diff --git a/test/definition/table.lua b/test/definition/table.lua index 61e8746d..66e71b0c 100644 --- a/test/definition/table.lua +++ b/test/definition/table.lua @@ -134,32 +134,44 @@ local y = { t.<?insert?>() ]] + +TEST [[ +local x +x.y.<!z!> = 1 +print(x.y.<?z?>) +]] + + +TEST [[ +local x +x.y = { + <!z!> = 1 +} +print(x.y.<?z?>) +]] + +TEST [[ +local x = { + y = { + <!z!> = 1 + } +} +print(x.y.<?z?>) +]] + TEST [[ local function f() - local t = {} - t.field1 = { + local t = { <!x!> = 1, - y = 1, - z = 1, - } - t.field2 = { - x = 1, - y = 1, - z = 1, - } - t.field3 = { - x = 1, - y = 1, - z = 1, } return t end local t = f() -t.field1.<?x?> +t.<?x?> ]] -TEST [[ -local t = { <!a!> } - -print(t[<?1?>]) -]] +--TEST [[ +--local t = { <!a!> } +-- +--print(t[<?1?>]) +--]] diff --git a/test/diagnostics/common.lua b/test/diagnostics/common.lua new file mode 100644 index 00000000..6aa1dd6a --- /dev/null +++ b/test/diagnostics/common.lua @@ -0,0 +1,1389 @@ +local config = require 'config' + +TEST [[ +local <!x!> +]] + +TEST [[ +local y +local x <close> = y +]] + +TEST [[ +local function x() +end +x() +]] + +TEST [[ +return function (x) + x.a = 1 +end +]] + +TEST [[ +local <!t!> = {} +<!t!>.a = 1 +]] + +TEST [[ +local <!function <!x!>() +end!> +]] + + +TEST [[ +local <!x!> = <!function () end!> +]] + +TEST [[ +local <!x!> +<!x!> = <!function () end!> +]] + +TEST [[ +local <!function x() +end!> +local <!function <!y!>() + x() +end!> +]] + +TEST [[ +local print, _G +print(<!x!>) +print(<!log!>) +print(<!X!>) +print(<!Log!>) +print(<!y!>) +print(Z) +print(_G) +Z = 1 +]] + +TEST [[ +::<!LABEL!>:: +]] + +TEST [[ +<! !> +]] + +TEST [[ + +<! !> +]] + +TEST [[ +X = 1<! !> +]] + +TEST [[ +X = [=[ + ]=] +]] + +TEST [[ +local x +print(x) +local <!x!> +print(x) +]] + +TEST [[ +local x +print(x) +local <!x!> +print(x) +local <!x!> +print(x) +]] + +TEST [[ +local _ +print(_) +local _ +print(_) +local _ENV +<!print!>(_ENV) -- 由于重定义了_ENV,因此print变为了未定义全局变量 +]] + +TEST [[ +local x +return x, function (<!x!>) + return x +end +]] + +TEST [[ +print(1) +_ENV = nil +]] + +TEST [[ +local _ENV = { print = print } +print(1) +]] + +config.get(nil, 'Lua.diagnostics.disable')['undefined-env-child'] = true +TEST [[ +_ENV = nil +<!GLOBAL!> = 1 --> _ENV.GLOBAL = 1 +]] + +TEST [[ +_ENV = nil +local _ = <!print!> --> local _ = _ENV.print +]] + +TEST [[ +_ENV = {} +GLOBAL = 1 --> _ENV.GLOBAL = 1 +]] + +TEST [[ +_ENV = {} +local _ = print --> local _ = _ENV.print +]] + +TEST [[ +GLOBAL = 1 +_ENV = nil +]] + +config.get(nil, 'Lua.diagnostics.disable')['undefined-env-child'] = nil +TEST [[ +<!print() +('string')!>:sub(1, 1) +]] + +TEST [[ +print() +('string') +]] + +TEST [[ +pairs +{} +{} +]] + +TEST [[ +local x +return x + : f(1) + : f(1) +]] + +TEST [[ +return { + <!print + 'string'!> +} +]] + +TEST [[ +return { + <!print + { + x = 1, + }!> +} +]] + +TEST [[ +print() +'string' +]] + +TEST [[ +print +{ + x = 1, +} +]] + +TEST [[ +local function x(a, b) + return a, b +end +x(1, 2, <!3!>) +]] + +TEST [[ +local function x(a, b, ...) + return a, b, ... +end +x(1, 2, 3, 4, 5) +]] + +TEST [[ +local m = {} +function m:x(a, b) + return a, b +end +m:x(1, 2, <!3!>) +]] + +TEST [[ +local m = {} +function m:x(a, b) + return a, b +end +m.x(1, 2, 3, <!4!>) +]] + +TEST [[ +local m = {} +function m.x(a, b) + return a, b +end +m:x(1, <!2!>, <!3!>, <!4!>) +]] + +TEST [[ +local m = {} +function m.x() +end +m:x() +]] + +TEST [[ +InstanceName = 1 +Instance = _G[InstanceName] +]] + +TEST [[ +local _ = (''):sub(1, 2) +]] + +TEST [=[ +return [[ + +]] +]=] + +config.get(nil, 'Lua.diagnostics.disable')['close-non-object'] = true +TEST [[ +local _ <close> = function () end +]] + +config.get(nil, 'Lua.diagnostics.disable')['close-non-object'] = nil +TEST [[ +local _ <close> = <!1!> +]] + +config.get(nil, 'Lua.diagnostics.disable')['unused-local'] = true +TEST [[ +local f = <!function () end!> +]] + +TEST [[ +local f;f = <!function () end!> +]] + +TEST [[ +local <!function f() end!> +]] + +config.get(nil, 'Lua.diagnostics.disable')['unused-local'] = nil +TEST [[ +local mt, x +function mt:m() + function x:m() + end +end +return mt, x +]] + +TEST [[ +local mt = {} +function mt:f() +end +return mt +]] + +TEST [[ +local <!mt!> = {} +function <!mt!>:f() +end +]] + +TEST [[ +local <!x!> = {} +<!x!>.a = 1 +]] + +TEST [[ +local <!x!> = {} +<!x!>['a'] = 1 +]] + +TEST [[ +local function f(<!self!>) +end +f() +]] + +TEST [[ +local function f(<!...!>) +end +f() +]] + +TEST [[ +local function f(var) + print(var) +end +local var +f(var) +]] + +TEST [[ +local function f(a, b) + return a, b +end +f(1, 2, <!3!>, <!4!>) +]] + +TEST [[ +local mt = {} +function mt:f(a, b) + return a, b +end +mt.f(1, 2, 3, <!4!>) +]] + + +TEST [[ +local mt = {} +function mt.f(a, b) + return a, b +end +mt:f(1, <!2!>, <!3!>, <!4!>) +]] + +TEST [[ +local mt = {} +function mt:f(a, b) + return a, b +end +mt:f(1, 2, <!3!>, <!4!>) +]] + +TEST [[ +local function f(a, b, ...) + return a, b, ... +end +f(1, 2, 3, 4) +]] + +TEST [[ +local _ = next({}, 1, <!2!>) +print(1, 2, 3, 4, 5) +]] + +TEST [[ +local function f(callback) + callback(1, 2, 3) +end +f(function () end) +]] + +--TEST [[ +--local realTostring = tostring +--tostring = function () end +--tostring(<!1!>) +--tostring = realTostring +--tostring(1) +--]] + +TEST [[ +<!aa!> = 1 +tostring = 1 +ROOT = 1 +_G.bb = 1 +]] + +TEST [[ +local f = load('') +f(1, 2, 3) +]] + +TEST [[ +local _ = <!unpack!>() +]] + +TEST [[ +X = table[<!x!>] +]] + +TEST [[ +return { + <!x!> = 1, + y = 2, + <!x!> = 3, +} +]] + +TEST [[ +return { + x = 1, + y = 2, +}, { + x = 1, + y = 2, +} +]] + +TEST [[ +local m = {} +function m.open() +end + +m:open() +]] + +TEST [[ +local m = {} +function m:open() +end + +m.open('ok') +]] + +TEST [[ +<!if true then +end!> +]] + +TEST [[ +<!if true then +else +end!> +]] + +TEST [[ +if true then +else + return +end +]] + +TEST [[ +while true do +end +]] + +TEST [[ +<!for _ = 1, 10 do +end!> +]] + +TEST [[ +<!for _ in pairs(_VERSION) do +end!> +]] + +TEST [[ +local _ = 1, <!2!> +]] + +TEST [[ +_ = 1, <!2!> +]] + +TEST [[ +local function x() + do + local k + print(k) + x() + end + local k = 1 + print(k) +end +]] + +TEST [[ +local function x() + local loc + x() + print(loc) +end +]] + +TEST [[ +local <!t!> = {} +<!t!>[1] = 1 +]] + +TEST [[ +T1 = 1 +_ENV.T2 = 1 +_G.T3 = 1 +_ENV._G.T4 = 1 +_G._G._G.T5 = 1 +rawset(_G, 'T6', 1) +rawset(_ENV, 'T7', 1) +print(T1) +print(T2) +print(T3) +print(T4) +print(T5) +print(T6) +print(T7) +]] + +TEST [[ +local x +x = <!x or 0 + 1!> +]] + +TEST [[ +local x, y +x = <!x + y or 0!> +]] + +TEST [[ +local x, y, z +x = x and y or '' .. z +]] + +TEST [[ +local x +x = x or -1 +]] + +TEST [[ +local x +x = x or (0 + 1) +]] + +TEST [[ +local x, y +x = (x + y) or 0 +]] + +TEST [[ +local t = {} +t.a = 1 +t.a = 2 +return t +]] + +TEST [[ +table.insert({}, 1, 2, <!3!>) +]] + +TEST [[ +while true do + break + <!print() + print()!> +end +]] + +TEST [[ +local x, <!y!>, <!z!> = 1 +print(x, y, z) +]] + +TEST [[ +local x, y, <!z!> = 1, 2 +print(x, y, z) +]] + +TEST [[ +local x, y, z = print() +print(x, y, z) +]] + +TEST [[ +local x, y, z +print(x, y, z) +]] + +TEST [[ +local x, y, z +x, <!y!>, <!z!> = 1 +print(x, y, z) +]] + +TEST [[ +X, <!Y!>, <!Z!> = 1 +]] + +TEST [[ +T = {} +T.x, <!T.y!>, <!T.z!> = 1 +]] + +TEST [[ +T = {} +T['x'], <!T['y']!>, <!T['z']!> = 1 +]] + +--TEST [[ +-----@class <!Class!> +-----@class <!Class!> +--]] + +TEST [[ +---@alias <!A!> integer +---@alias <!A!> integer +]] + +TEST [[ +---@class A : <!B!> +]] + +TEST [[ +---@class <!A : B!> +---@class <!B : C!> +---@class <!C : D!> +---@class <!D : A!> +]] + +TEST [[ +---@class A : B +---@class B : C +---@class C : D +---@class D +]] + +TEST [[ +---@type <!A!> +]] + +TEST [[ +---@class A +---@type A|<!B!>|<!C!> +]] + +TEST [[ +---@class AAA +---@alias B AAA + +---@type B +]] + +TEST [[ +---@alias B <!AAA!> +]] + +TEST [[ +---@class A +---@class B +---@alias <!A!> B +]] + +TEST [[ +---@param x <!Class!> +]] + +TEST [[ +---@class Class +---@param <!y!> Class +local function f(x) + return x +end +f() +]] + +TEST [[ +---@class Class +---@param <!y!> Class +function F(x) + return x +end +F() +]] + +TEST [[ +---@class Class +---@param <!x!> Class +---@param y Class +---@param <!x!> Class +local function f(x, y) + return x, y +end +f() +]] + +TEST [[ +---@field <!x Class!> +---@class Class +]] + +TEST [[ +---@class Class + +---@field <!x Class!> +]] + +TEST [[ +---@class Class +--- +---@field x Class +]] + +TEST [[ +---@class Class +---@field x Class +---@field <!x!> Class +]] + +TEST [[ +---@class Class : any +]] + +TEST [[ +---@type fun(a: integer) +local f +f() +]] + +TEST [[ +---@class c +c = {} +]] + +TEST [[ +---@generic T: any +---@param v T +---@param message any +---@return T +function assert(v, message) + return v, message +end +]] + +TEST [[ +---@type string +---| +]] + +TEST [[ +---@type +---| 'xx' +]] + +TEST [[ +---@class class +local t +]] +---[==[ +-- checkUndefinedField 通用 +TEST [[ +---@class Foo +---@field field1 integer +local mt = {} +function mt:Constructor() + self.field2 = 1 +end +function mt:method1() return 1 end +function mt.method2() return 2 end + +---@class Bar: Foo +---@field field4 integer +local mt2 = {} + +---@type Foo +local v +print(v.field1 + 1) +print(v.<!field2!> + 1) +print(v.<!field3!> + 1) +print(v:method1()) +print(v.method2()) +print(v:<!method3!>()) + +---@type Bar +local v2 +print(v2.field1 + 1) +print(v2.<!field2!> + 1) +print(v2.<!field3!> + 1) +print(v2.field4 + 1) +print(v2:method1()) +print(v2.method2()) +print(v2:<!method3!>()) + +local v3 = {} +print(v3.abc) + +---@class Bar2 +local mt3 +function mt3:method() return 1 end +print(mt3:method()) +]] + +-- checkUndefinedField 通过type找到class +TEST [[ +---@class Foo +local Foo +function Foo:method1() end + +---@type Foo +local v +v:method1() +v:<!method2!>() -- doc.class.name +]] + +-- checkUndefinedField 通过type找到class,涉及到 class 继承版 +TEST [[ +---@class Foo +local Foo +function Foo:method1() end +---@class Bar: Foo +local Bar +function Bar:method3() end + +---@type Bar +local v +v:method1() +v:<!method2!>() -- doc.class.name +v:method3() +]] + +-- checkUndefinedField 类名和类变量同名,类变量被直接使用 +TEST [[ +---@class Foo +local Foo +function Foo:method1() end +Foo:<!method2!>() -- doc.class +Foo:<!method2!>() -- doc.class +]] + +-- checkUndefinedField 没有@class的不检测 +TEST [[ +local Foo +function Foo:method1() + return Foo:method2() -- table +end +]] + +-- checkUndefinedField 类名和类变量不同名,类变量被直接使用、使用self +TEST [[ +---@class Foo +local mt +function mt:method1() + mt.<!method2!>() -- doc.class + self.method1() + return self.<!method2!>() -- doc.class.name +end +]] + +-- checkUndefinedField 当会推导成多个class类型时 +TEST [[ +---@class Foo +local mt +function mt:method1() end + +---@class Bar +local mt2 +function mt2:method2() end + +---@type Foo +local v +---@type Bar +local v2 +v2 = v -- TODO 这里应该给警告 +v2:<!method1!>() +v2:method2() +]] + +TEST [[ +---@type table +T1 = {} +print(T1.f1) +---@type tablelib +T2 = {} +print(T2.<!f2!>) +]] +--]==] +TEST [[ +---@overload fun(...) +local function f() end + +f(1) +]] + +TEST [[ +for i = <!10, 1!> do + print(i) +end +]] + +TEST [[ +for i = <!10, 1, 5!> do + print(i) +end +]] + +TEST [[ +for i = 1, 1 do + print(i) +end +]] + +TEST [[ +---@param a number +return function (<!a!>) +end +]] + +TEST [[ +---@meta + +---@param a number +return function (a) +end +]] + +TEST [[ +local m = {} + +function <!m:fff!>() +end + +function <!m:fff!>() +end + +return m +]] + +TEST [[ +local m = {} + +function m:fff() +end + +do + function m:fff() + end +end + +return m +]] + +TEST [[ +local m = {} + +m.x = true +m.x = false + +return m +]] + +TEST [[ +local m = {} + +m.x = io.open() +m.x = nil + +return m +]] + +TEST [[ +---@class A +---@field a boolean + +---@return A +local function f() end + +local r = f() +r.x = 1 + +return r.x +]] + +TEST [[ +---@diagnostic disable-next-line +x = 1 +]] + +TEST [[ +---@diagnostic disable-next-line: lowercase-global +x = 1 +]] + +TEST [[ +---@diagnostic disable-next-line: unused-local +<!x!> = 1 +]] + +TEST [[ +---@diagnostic disable +x = 1 +]] + +TEST [[ +---@diagnostic disable +---@diagnostic enable +<!x!> = 1 +]] + +TEST [[ +---@diagnostic disable +---@diagnostic disable +---@diagnostic enable +x = 1 +]] + +TEST [[ +---@diagnostic disable-next-line: <!xxx!> +]] + +TEST [[ +local mt = {} + +function mt:a(x) + return self, x +end + +function mt:b(y) + self:a(1):b(2) + return y +end + +return mt +]] + +TEST [[ +local function each() + return function () + end +end + +for x in each() do + print(x) +end +]] + +TEST [[ +---@type string +local s + +print(s:upper()) +]] + +TEST [[ +local t = (). +return t +]] + +TEST [[ +return { + [1] = 1, + ['1'] = 1, +} +]] + +TEST [[ +return { + [print()] = 1, + [print()] = 1, +} +]] + +TEST [[ +---@type { x: number, y: number} +---| "'resume'" +]] + +TEST [[ +return { + 1, <!2!>, 3, + [<!2!>] = 4, +} +]] + +TEST [[ +--- @class Emit +--- @field on fun(eventName: string, cb: function) +--- @field on fun(eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(eventName: '"won"', cb: fun(s: string)) +local emit = {} +]] + +TEST [[ +--- @class Emit +--- @field on fun(eventName: string, cb: function) +--- @field on fun(eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(eventName: '"won"', cb: fun(s: string)) +--- @field <!on!> fun(eventName: '"died"', cb: fun(i: integer)) +local emit = {} +]] + +-- redundant-return +TEST [[ +local function f() + <!return!> +end +f() +]] + +TEST [[ +local function f() + return nil +end +f() +]] + +TEST [[ +local function f() + local function x() + <!return!> + end + x() + return true +end +f() +]] + +TEST [[ +local function f() + local function x() + return true + end + return x() +end +f() +]] + +TEST [[ +---@type file* +local f +local _ = f:read '*a' +local _ = f:read('*a') +]] + +TEST [[ +function F() + <!coroutine.yield!>() +end +]] + +TEST [[ +---@async +function F() + coroutine.yield() +end +]] + +TEST [[ +---@type async fun() +local f + +function F() + <!f!>() +end +]] + +TEST [[ +---@type async fun() +local f + +---@async +function F() + f() +end +]] + +TEST [[ +local function f(cb) + cb() +end + +<!f!>(function () ---@async + return nil +end) +]] + +TEST [[ +local function f(cb) + pcall(cb) +end + +<!f!>(function () ---@async + return nil +end) +]] + +TEST [[ +---@param c any +local function f(c) + return c +end + +f(function () ---@async + return nil +end) +]] + +TEST [[ +---@param ... any +local function f(...) + return ... +end + +f(function () ---@async + return nil +end) +]] + +TEST [[ +---@vararg any +local function f(...) + return ... +end + +f(function () ---@async + return nil +end) +]] + +TEST [[ +local function f(...) + return ... +end + +f(function () ---@async + return nil +end) +]] + +TEST [[ +local function f(...) + return ... +end + +f(1, function () ---@async + return nil +end) +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +<!f()!> +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +X = f() +]] + +config.get(nil, 'Lua.diagnostics.neededFileStatus')['not-yieldable'] = 'Any' +TEST [[ +---@param cb fun() +local function f(cb) + return cb +end + +---@async +local function af() + return nil +end + +f(<!af!>) +]] + +TEST [[ +---@param cb async fun() +local function f(cb) + return cb +end + +---@async +local function af() + return nil +end + +f(af) +]] + +TEST [[ +local function f(cb) + cb() +end + +local function af() + <!f!>(function () ---@async + return nil + end) +end + +return af +]] + +TEST [[ +local function f(cb) + cb() +end + +---@async +local function af() + f(function () ---@async + return nil + end) +end + +return af +]] + +TEST [[ +local _ = type(function () ---@async + return nil +end) +]] + +TEST [[ +---@param ... number +local function f(...) + return ... +end + +return f +]] + +TEST [[ +---@type fun(...: string) +]] + +TEST [[ +---@type fun(xxx, yyy, ...): boolean +]] + +TEST [[ +local <!x!> + +return { + x = 1, +} +]] + +TEST [[ +---@class A #1 +]] + +TEST [[ +---@class A 1 +]] + +TEST [[ +return ('1'):gsub() +]] + +TEST [[ +local value +value = '1' +value = value:gsub() +]] diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua index 58414b60..75d9da6c 100644 --- a/test/diagnostics/init.lua +++ b/test/diagnostics/init.lua @@ -43,1543 +43,11 @@ function TEST(script, ...) error(('%s\n%s'):format(util.dump(catched['!']), util.dump(results))) end else - assert(catched['!'] == nil) + assert(#catched['!'] == 0) end files.remove('') end -TEST [[ -local <!x!> -]] - -TEST [[ -local y -local x <close> = y -]] - -TEST [[ -local function x() -end -x() -]] - -TEST [[ -return function (x) - x.a = 1 -end -]] - -TEST [[ -local <!t!> = {} -<!t!>.a = 1 -]] - -TEST [[ -local <!function <!x!>() -end!> -]] - - -TEST [[ -local <!x!> = <!function () end!> -]] - -TEST [[ -local <!x!> -<!x!> = <!function () end!> -]] - -TEST [[ -local <!function x() -end!> -local <!function <!y!>() - x() -end!> -]] - -TEST [[ -local print, _G -print(<!x!>) -print(<!log!>) -print(<!X!>) -print(<!Log!>) -print(<!y!>) -print(Z) -print(_G) -Z = 1 -]] - -TEST [[ -::<!LABEL!>:: -]] - -TEST [[ -<! !> -]] - -TEST [[ - -<! !> -]] - -TEST [[ -X = 1<! !> -]] - -TEST [[ -X = [=[ - ]=] -]] - -TEST [[ -local x -print(x) -local <!x!> -print(x) -]] - -TEST [[ -local x -print(x) -local <!x!> -print(x) -local <!x!> -print(x) -]] - -TEST [[ -local _ -print(_) -local _ -print(_) -local _ENV -<!print!>(_ENV) -- 由于重定义了_ENV,因此print变为了未定义全局变量 -]] - -TEST [[ -local x -return x, function (<!x!>) - return x -end -]] - -TEST [[ -print(1) -_ENV = nil -]] - -TEST [[ -local _ENV = { print = print } -print(1) -]] - -config.get(nil, 'Lua.diagnostics.disable')['undefined-env-child'] = true -TEST [[ -_ENV = nil -<!GLOBAL!> = 1 --> _ENV.GLOBAL = 1 -]] - -TEST [[ -_ENV = nil -local _ = <!print!> --> local _ = _ENV.print -]] - -TEST [[ -_ENV = {} -GLOBAL = 1 --> _ENV.GLOBAL = 1 -]] - -TEST [[ -_ENV = {} -local _ = print --> local _ = _ENV.print -]] - -TEST [[ -GLOBAL = 1 -_ENV = nil -]] - -config.get(nil, 'Lua.diagnostics.disable')['undefined-env-child'] = nil -TEST [[ -<!print() -('string')!>:sub(1, 1) -]] - -TEST [[ -print() -('string') -]] - -TEST [[ -pairs -{} -{} -]] - -TEST [[ -local x -return x - : f(1) - : f(1) -]] - -TEST [[ -return { - <!print - 'string'!> -} -]] - -TEST [[ -return { - <!print - { - x = 1, - }!> -} -]] - -TEST [[ -print() -'string' -]] - -TEST [[ -print -{ - x = 1, -} -]] - -TEST [[ -local function x(a, b) - return a, b -end -x(1, 2, <!3!>) -]] - -TEST [[ -local function x(a, b, ...) - return a, b, ... -end -x(1, 2, 3, 4, 5) -]] - -TEST [[ -local m = {} -function m:x(a, b) - return a, b -end -m:x(1, 2, <!3!>) -]] - -TEST [[ -local m = {} -function m:x(a, b) - return a, b -end -m.x(1, 2, 3, <!4!>) -]] - -TEST [[ -local m = {} -function m.x(a, b) - return a, b -end -m:x(1, <!2!>, <!3!>, <!4!>) -]] - -TEST [[ -local m = {} -function m.x() -end -m:x() -]] - -TEST [[ -InstanceName = 1 -Instance = _G[InstanceName] -]] - -TEST [[ -local _ = (''):sub(1, 2) -]] - -TEST [=[ -return [[ - -]] -]=] - -config.get(nil, 'Lua.diagnostics.disable')['close-non-object'] = true -TEST [[ -local _ <close> = function () end -]] - -config.get(nil, 'Lua.diagnostics.disable')['close-non-object'] = nil -TEST [[ -local _ <close> = <!1!> -]] - -config.get(nil, 'Lua.diagnostics.disable')['unused-local'] = true -TEST [[ -local f = <!function () end!> -]] - -TEST [[ -local f;f = <!function () end!> -]] - -TEST [[ -local <!function f() end!> -]] - -config.get(nil, 'Lua.diagnostics.disable')['unused-local'] = nil -TEST [[ -local mt, x -function mt:m() - function x:m() - end -end -return mt, x -]] - -TEST [[ -local mt = {} -function mt:f() -end -return mt -]] - -TEST [[ -local <!mt!> = {} -function <!mt!>:f() -end -]] - -TEST [[ -local <!x!> = {} -<!x!>.a = 1 -]] - -TEST [[ -local <!x!> = {} -<!x!>['a'] = 1 -]] - -TEST [[ -local function f(<!self!>) -end -f() -]] - -TEST [[ -local function f(<!...!>) -end -f() -]] - -TEST [[ -local function f(var) - print(var) -end -local var -f(var) -]] - -TEST [[ -local function f(a, b) - return a, b -end -f(1, 2, <!3!>, <!4!>) -]] - -TEST [[ -local mt = {} -function mt:f(a, b) - return a, b -end -mt.f(1, 2, 3, <!4!>) -]] - - -TEST [[ -local mt = {} -function mt.f(a, b) - return a, b -end -mt:f(1, <!2!>, <!3!>, <!4!>) -]] - -TEST [[ -local mt = {} -function mt:f(a, b) - return a, b -end -mt:f(1, 2, <!3!>, <!4!>) -]] - -TEST [[ -local function f(a, b, ...) - return a, b, ... -end -f(1, 2, 3, 4) -]] - -TEST [[ -local _ = next({}, 1, <!2!>) -print(1, 2, 3, 4, 5) -]] - -TEST [[ -local function f(callback) - callback(1, 2, 3) -end -f(function () end) -]] - ---TEST [[ ---local realTostring = tostring ---tostring = function () end ---tostring(<!1!>) ---tostring = realTostring ---tostring(1) ---]] - -TEST [[ -<!aa!> = 1 -tostring = 1 -ROOT = 1 -_G.bb = 1 -]] - -TEST [[ -local f = load('') -f(1, 2, 3) -]] - -TEST [[ -local _ = <!unpack!>() -]] - -TEST [[ -X = table[<!x!>] -]] - -TEST [[ -return { - <!x!> = 1, - y = 2, - <!x!> = 3, -} -]] - -TEST [[ -return { - x = 1, - y = 2, -}, { - x = 1, - y = 2, -} -]] - -TEST [[ -local m = {} -function m.open() -end - -m:open() -]] - -TEST [[ -local m = {} -function m:open() -end - -m.open('ok') -]] - -TEST [[ -<!if true then -end!> -]] - -TEST [[ -<!if true then -else -end!> -]] - -TEST [[ -if true then -else - return -end -]] - -TEST [[ -while true do -end -]] - -TEST [[ -<!for _ = 1, 10 do -end!> -]] - -TEST [[ -<!for _ in pairs(_VERSION) do -end!> -]] - -TEST [[ -local _ = 1, <!2!> -]] - -TEST [[ -_ = 1, <!2!> -]] - -TEST [[ -local function x() - do - local k - print(k) - x() - end - local k = 1 - print(k) -end -]] - -TEST [[ -local function x() - local loc - x() - print(loc) -end -]] - -TEST [[ -local <!t!> = {} -<!t!>[1] = 1 -]] - -TEST [[ -T1 = 1 -_ENV.T2 = 1 -_G.T3 = 1 -_ENV._G.T4 = 1 -_G._G._G.T5 = 1 -rawset(_G, 'T6', 1) -rawset(_ENV, 'T7', 1) -print(T1) -print(T2) -print(T3) -print(T4) -print(T5) -print(T6) -print(T7) -]] - -TEST [[ -local x -x = <!x or 0 + 1!> -]] - -TEST [[ -local x, y -x = <!x + y or 0!> -]] - -TEST [[ -local x, y, z -x = x and y or '' .. z -]] - -TEST [[ -local x -x = x or -1 -]] - -TEST [[ -local x -x = x or (0 + 1) -]] - -TEST [[ -local x, y -x = (x + y) or 0 -]] - -TEST [[ -local t = {} -t.a = 1 -t.a = 2 -return t -]] - -TEST [[ -table.insert({}, 1, 2, <!3!>) -]] - -TEST [[ -while true do - break - <!print() - print()!> -end -]] - -TEST [[ -local x, <!y!>, <!z!> = 1 -print(x, y, z) -]] - -TEST [[ -local x, y, <!z!> = 1, 2 -print(x, y, z) -]] - -TEST [[ -local x, y, z = print() -print(x, y, z) -]] - -TEST [[ -local x, y, z -print(x, y, z) -]] - -TEST [[ -local x, y, z -x, <!y!>, <!z!> = 1 -print(x, y, z) -]] - -TEST [[ -X, <!Y!>, <!Z!> = 1 -]] - -TEST [[ -T = {} -T.x, <!T.y!>, <!T.z!> = 1 -]] - -TEST [[ -T = {} -T['x'], <!T['y']!>, <!T['z']!> = 1 -]] - ---TEST [[ ------@class <!Class!> ------@class <!Class!> ---]] - -TEST [[ ----@class A : <!B!> -]] - -TEST [[ ----@class <!A : B!> ----@class <!B : C!> ----@class <!C : D!> ----@class <!D : A!> -]] - -TEST [[ ----@class A : B ----@class B : C ----@class C : D ----@class D -]] - -TEST [[ ----@type <!A!> -]] - -TEST [[ ----@class A ----@type A|<!B!>|<!C!> -]] - -TEST [[ ----@class AAA ----@alias B AAA - ----@type B -]] - -TEST [[ ----@alias B <!AAA!> -]] - -TEST [[ ----@class A ----@class B ----@alias <!A B!> -]] - -TEST [[ ----@param x <!Class!> -]] - -TEST [[ ----@class Class ----@param <!y!> Class -local function f(x) - return x -end -f() -]] - -TEST [[ ----@class Class ----@param <!y!> Class -function F(x) - return x -end -F() -]] - -TEST [[ ----@class Class ----@param <!x!> Class ----@param y Class ----@param <!x!> Class -local function f(x, y) - return x, y -end -f() -]] - -TEST [[ ----@field <!x Class!> ----@class Class -]] - -TEST [[ ----@class Class - ----@field <!x Class!> -]] - -TEST [[ ----@class Class ---- ----@field x Class -]] - -TEST [[ ----@class Class ----@field x Class ----@field <!x!> Class -]] - -TEST [[ ----@class Class : any -]] - -TEST [[ ----@type fun(a: integer) -local f -f() -]] - -TEST [[ ----@class c -c = {} -]] - -TEST [[ ----@generic T: any ----@param v T ----@param message any ----@return T -function assert(v, message) - return v, message -end -]] - -TEST [[ ----@type string ----| -]] - -TEST [[ ----@type ----| 'xx' -]] - -TEST [[ ----@class class -local t -]] ----[==[ --- checkUndefinedField 通用 -TEST [[ ----@class Foo ----@field field1 integer -local mt = {} -function mt:Constructor() - self.field2 = 1 -end -function mt:method1() return 1 end -function mt.method2() return 2 end - ----@class Bar: Foo ----@field field4 integer -local mt2 = {} - ----@type Foo -local v -print(v.field1 + 1) -print(v.field2 + 1) -print(v.<!field3!> + 1) -print(v:method1()) -print(v.method2()) -print(v:<!method3!>()) - ----@type Bar -local v2 -print(v2.field1 + 1) -print(v2.field2 + 1) -print(v2.<!field3!> + 1) -print(v2.field4 + 1) -print(v2:method1()) -print(v2.method2()) -print(v2:<!method3!>()) - -local v3 = {} -print(v3.abc) - ----@class Bar2 -local mt3 -function mt3:method() return 1 end -print(mt3:method()) -]] - --- checkUndefinedField 通过type找到class -TEST [[ ----@class Foo -local Foo -function Foo:method1() end - ----@type Foo -local v -v:method1() -v:<!method2!>() -- doc.class.name -]] - --- checkUndefinedField 通过type找到class,涉及到 class 继承版 -TEST [[ ----@class Foo -local Foo -function Foo:method1() end ----@class Bar: Foo -local Bar -function Bar:method3() end - ----@type Bar -local v -v:method1() -v:<!method2!>() -- doc.class.name -v:method3() -]] - --- checkUndefinedField 类名和类变量同名,类变量被直接使用 -TEST [[ ----@class Foo -local Foo -function Foo:method1() end -Foo:<!method2!>() -- doc.class -Foo:<!method2!>() -- doc.class -]] - --- checkUndefinedField 没有@class的不检测 -TEST [[ -local Foo -function Foo:method1() - return Foo:method2() -- table -end -]] - --- checkUndefinedField 类名和类变量不同名,类变量被直接使用、使用self -TEST [[ ----@class Foo -local mt -function mt:method1() - mt.<!method2!>() -- doc.class - self.method1() - return self.<!method2!>() -- doc.class.name -end -]] - --- checkUndefinedField 当会推导成多个class类型时 -TEST [[ ----@class Foo -local mt -function mt:method1() end - ----@class Bar -local mt2 -function mt2:method2() end - ----@type Foo -local v ----@type Bar -local v2 -v2 = v -- TODO 这里应该给警告 -v2:<!method1!>() -v2:method2() -]] - -TEST [[ ----@type table -T1 = {} -print(T1.f1) ----@type tablelib -T2 = {} -print(T2.<!f2!>) -]] ---]==] -TEST [[ ----@overload fun(...) -local function f() end - -f(1) -]] - -TEST [[ -for i = <!10, 1!> do - print(i) -end -]] - -TEST [[ -for i = <!10, 1, 5!> do - print(i) -end -]] - -TEST [[ -for i = 1, 1 do - print(i) -end -]] - -TEST [[ ----@param a number -return function (<!a!>) -end -]] - -TEST [[ ----@meta - ----@param a number -return function (a) -end -]] - -TEST [[ -local m = {} - -function <!m:fff!>() -end - -function <!m:fff!>() -end - -return m -]] - -TEST [[ -local m = {} - -function m:fff() -end - -do - function m:fff() - end -end - -return m -]] - -TEST [[ -local m = {} - -m.x = true -m.x = false - -return m -]] - -TEST [[ -local m = {} - -m.x = io.open() -m.x = nil - -return m -]] - -TEST [[ ----@class A ----@field a boolean - ----@return A -local function f() end - -local r = f() -r.x = 1 - -return r.x -]] - -TEST [[ ----@diagnostic disable-next-line -x = 1 -]] - -TEST [[ ----@diagnostic disable-next-line: lowercase-global -x = 1 -]] - -TEST [[ ----@diagnostic disable-next-line: unused-local -<!x!> = 1 -]] - -TEST [[ ----@diagnostic disable -x = 1 -]] - -TEST [[ ----@diagnostic disable ----@diagnostic enable -<!x!> = 1 -]] - -TEST [[ ----@diagnostic disable ----@diagnostic disable ----@diagnostic enable -x = 1 -]] - -TEST [[ ----@diagnostic disable-next-line: <!xxx!> -]] - -TEST [[ -local mt = {} - -function mt:a(x) - return self, x -end - -function mt:b(y) - self:a(1):b(2) - return y -end - -return mt -]] - -TEST [[ -local function each() - return function () - end -end - -for x in each() do - print(x) -end -]] - -TEST [[ ----@type string -local s - -print(s:upper()) -]] - -TEST [[ -local t = (). -return t -]] - -TEST [[ -return { - [1] = 1, - ['1'] = 1, -} -]] - -TEST [[ -return { - [print()] = 1, - [print()] = 1, -} -]] - -TEST [[ ----@type { x: number, y: number} ----| "'resume'" -]] - -TEST [[ -return { - 1, <!2!>, 3, - [<!2!>] = 4, -} -]] - -TEST [[ ---- @class Emit ---- @field on fun(eventName: string, cb: function) ---- @field on fun(eventName: '"died"', cb: fun(i: integer)) ---- @field on fun(eventName: '"won"', cb: fun(s: string)) -local emit = {} -]] - -TEST [[ ---- @class Emit ---- @field on fun(eventName: string, cb: function) ---- @field on fun(eventName: '"died"', cb: fun(i: integer)) ---- @field on fun(eventName: '"won"', cb: fun(s: string)) ---- @field <!on!> fun(eventName: '"died"', cb: fun(i: integer)) -local emit = {} -]] - -config.get(nil, 'Lua.diagnostics.neededFileStatus')['unused-local'] = 'None' -TEST [[ ----@param table table ----@param metatable table ----@return table -function Setmetatable(table, metatable) end - -Setmetatable(<!1!>, {}) -]] - -TEST [[ ----@param table table ----@param metatable table ----@return table -function Setmetatable(table, metatable) end - -Setmetatable(<!'name'!>, {}) - -]] - -TEST [[ ----@param table table ----@param metatable table ----@return table -function Setmetatable(table, metatable) end - ----@type table -local name ----@type function -local mt ----err -Setmetatable(name, <!mt!>) -]] - -TEST [[ ----@param p1 string ----@param p2 number ----@return table -local function func1(p1, p2) end - ----@type string -local s ----@type table -local t ----err -func1(s, <!t!>) -]] - -TEST [[ ----@class bird ----@field wing string - ----@class eagle ----@field family bird - ----@class chicken ----@field family bird - ----@param bd eagle -local function fly(bd) end - ----@type chicken -local h -fly(<!h!>) -]] - -TEST [[ ----@overload fun(x: number, y: number) ----@param x boolean ----@param y boolean -local function f(x, y) end - -f(true, true) -- OK -f(0, 0) -- OK - -]] - -TEST [[ ----@class bird -local m = {} -setmetatable(m, {}) -- OK -]] - -TEST [[ ----@class childString: string -local s ----@param name string -local function f(name) end -f(s) -]] - -TEST [[ ----@class childString: string - ----@type string -local s ----@param name childString -local function f(name) end -f(<!s!>) -]] - -TEST [[ ----@alias searchmode '"ref"'|'"def"'|'"field"'|'"allref"'|'"alldef"'|'"allfield"' - ----@param mode searchmode -local function searchRefs(mode)end -searchRefs('ref') -]] - -TEST [[ ----@class markdown -local mt = {} ----@param language string ----@param text string|markdown -function mt:add(language, text) - if not text then - return - end -end ----@type markdown -local desc - -desc:add('md', 'hover') -]] - ----可选参数和枚举 -TEST [[ ----@param str string ----@param mode? '"left"'|'"right"' ----@return string -local function trim(str, mode) - if mode == "left" then - print(1) - end -end -trim('str', 'left') -trim('str', nil) -]] - -config.get(nil, 'Lua.diagnostics.neededFileStatus')['unused-local'] = 'Any' - ----不完整的函数参数定义,会跳过检查 -TEST [[ ----@param mode string -local function status(source, field, mode) - print(source, field, mode) -end -status(1, 2, 'name') -]] - - -TEST [[ ----@alias range {start: number, end: number} ----@param uri string ----@param range range -local function location(uri, range) - print(uri, range) -end ----@type range -local val = {} -location('uri', val) -]] - --- redundant-return -TEST [[ -local function f() - <!return!> -end -f() -]] - -TEST [[ -local function f() - return nil -end -f() -]] - -TEST [[ -local function f() - local function x() - <!return!> - end - x() - return true -end -f() -]] - -TEST [[ -local function f() - local function x() - return true - end - return x() -end -f() -]] - -TEST [[ ----@type file* -local f -local _ = f:read '*a' -local _ = f:read('*a') -]] - -TEST [[ -function F() - <!coroutine.yield!>() -end -]] - -TEST [[ ----@async -function F() - coroutine.yield() -end -]] - -TEST [[ ----@type async fun() -local f - -function F() - <!f!>() -end -]] - -TEST [[ ----@type async fun() -local f - ----@async -function F() - f() -end -]] - -TEST [[ -local function f(cb) - cb() -end - -<!f!>(function () ---@async - return nil -end) -]] - -TEST [[ -local function f(cb) - pcall(cb) -end - -<!f!>(function () ---@async - return nil -end) -]] - -TEST [[ ----@param c any -local function f(c) - return c -end - -f(function () ---@async - return nil -end) -]] - -TEST [[ ----@param ... any -local function f(...) - return ... -end - -f(function () ---@async - return nil -end) -]] - -TEST [[ ----@vararg any -local function f(...) - return ... -end - -f(function () ---@async - return nil -end) -]] - -TEST [[ -local function f(...) - return ... -end - -f(function () ---@async - return nil -end) -]] - -TEST [[ -local function f(...) - return ... -end - -f(1, function () ---@async - return nil -end) -]] - -TEST [[ ----@nodiscard -local function f() - return 1 -end - -<!f()!> -]] - -TEST [[ ----@nodiscard -local function f() - return 1 -end - -X = f() -]] - -config.get(nil, 'Lua.diagnostics.neededFileStatus')['not-yieldable'] = 'Any' -TEST [[ -local function f(cb) - return cb -end - ----@async -local function af() - return nil -end - -f(<!af!>) -]] - -TEST [[ ----@param cb async fun() -local function f(cb) - return cb -end - ----@async -local function af() - return nil -end - -f(af) -]] - -TEST [[ -local function f(cb) - cb() -end - -local function af() - <!f!>(function () ---@async - return nil - end) -end - -return af -]] - -TEST [[ -local function f(cb) - cb() -end - ----@async -local function af() - f(function () ---@async - return nil - end) -end - -return af -]] - -TEST [[ -local _ = type(function () ---@async - return nil -end) -]] - -TEST [[ ----@param ... number -local function f(...) - return ... -end - -return f -]] - -TEST [[ ----@type fun(...: string) -]] - -TEST [[ ----@type fun(xxx, yyy, ...): boolean -]] - -TEST [[ -local <!x!> - -return { - x = 1, -} -]] - -TEST [[ ----@class A #1 -]] - -TEST [[ ----@class A 1 -]] +require 'diagnostics.common' +--require 'diagnostics.type-check' diff --git a/test/diagnostics/type-check.lua b/test/diagnostics/type-check.lua new file mode 100644 index 00000000..9d9eb3ec --- /dev/null +++ b/test/diagnostics/type-check.lua @@ -0,0 +1,164 @@ +local config = require 'config' + +config.get(nil, 'Lua.diagnostics.neededFileStatus')['unused-local'] = 'None' +TEST [[ +---@param table table +---@param metatable table +---@return table +function Setmetatable(table, metatable) end + +Setmetatable(<!1!>, {}) +]] + +TEST [[ +---@param table table +---@param metatable table +---@return table +function Setmetatable(table, metatable) end + +Setmetatable(<!'name'!>, {}) + +]] + +TEST [[ +---@param table table +---@param metatable table +---@return table +function Setmetatable(table, metatable) end + +---@type table +local name +---@type function +local mt +---err +Setmetatable(name, <!mt!>) +]] + +TEST [[ +---@param p1 string +---@param p2 number +---@return table +local function func1(p1, p2) end + +---@type string +local s +---@type table +local t +---err +func1(s, <!t!>) +]] + +TEST [[ +---@class bird +---@field wing string + +---@class eagle +---@field family bird + +---@class chicken +---@field family bird + +---@param bd eagle +local function fly(bd) end + +---@type chicken +local h +fly(<!h!>) +]] + +TEST [[ +---@overload fun(x: number, y: number) +---@param x boolean +---@param y boolean +local function f(x, y) end + +f(true, true) -- OK +f(0, 0) -- OK + +]] + +TEST [[ +---@class bird +local m = {} +setmetatable(m, {}) -- OK +]] + +TEST [[ +---@class childString: string +local s +---@param name string +local function f(name) end +f(s) +]] + +TEST [[ +---@class childString: string + +---@type string +local s +---@param name childString +local function f(name) end +f(<!s!>) +]] + +TEST [[ +---@alias searchmode '"ref"'|'"def"'|'"field"'|'"allref"'|'"alldef"'|'"allfield"' + +---@param mode searchmode +local function searchRefs(mode)end +searchRefs('ref') +]] + +TEST [[ +---@class markdown +local mt = {} +---@param language string +---@param text string|markdown +function mt:add(language, text) + if not text then + return + end +end +---@type markdown +local desc + +desc:add('md', 'hover') +]] + +---可选参数和枚举 +TEST [[ +---@param str string +---@param mode? '"left"'|'"right"' +---@return string +local function trim(str, mode) + if mode == "left" then + print(1) + end +end +trim('str', 'left') +trim('str', nil) +]] + +config.get(nil, 'Lua.diagnostics.neededFileStatus')['unused-local'] = 'Any' + +---不完整的函数参数定义,会跳过检查 +TEST [[ +---@param mode string +local function status(source, field, mode) + print(source, field, mode) +end +status(1, 2, 'name') +]] + + +TEST [[ +---@alias range {start: number, end: number} +---@param uri string +---@param range range +local function location(uri, range) + print(uri, range) +end +---@type range +local val = {} +location('uri', val) +]] diff --git a/test/example/guide.txt b/test/example/guide.txt index cff05faf..1aef61ae 100644 --- a/test/example/guide.txt +++ b/test/example/guide.txt @@ -2718,7 +2718,7 @@ function m.viewInferType(infers) or src.type == 'doc.type.name' or src.type == 'doc.type.array' or src.type == 'doc.type.table' - or src.type == 'doc.type.enum' + or src.type == 'doc.type.string' or src.type == 'doc.resume' then local tp = infer.type or 'any' if not mark[tp] then diff --git a/test/full/example.lua b/test/full/example.lua index ed5c3b16..e8352547 100644 --- a/test/full/example.lua +++ b/test/full/example.lua @@ -5,7 +5,6 @@ local diag = require 'core.diagnostics' local config = require 'config' local fs = require 'bee.filesystem' local luadoc = require "parser.luadoc" -local noder = require 'core.noder' -- 临时 ---@diagnostic disable: await-in-sync diff --git a/test/full/self.lua b/test/full/self.lua index 5fb1fc7a..93cfe715 100644 --- a/test/full/self.lua +++ b/test/full/self.lua @@ -4,6 +4,7 @@ local furi = require 'file-uri' local diag = require 'provider.diagnostic' local config = require 'config' local ws = require 'workspace' +local guide = require 'parser.guide' local path = ROOT / 'script' @@ -35,6 +36,10 @@ local clock = os.clock() ---@diagnostic disable: await-in-sync for uri in files.eachFile() do + local status = files.getState(uri) + guide.eachSource(status.ast, function (src) + assert(src.parent ~= nil or src.type == 'main') + end) local fileClock = os.clock() diag.doDiagnostic(uri, true) print('诊断文件耗时:', os.clock() - fileClock, uri) diff --git a/test/highlight/init.lua b/test/highlight/init.lua index 8e07c5f0..0cca79f1 100644 --- a/test/highlight/init.lua +++ b/test/highlight/init.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: await-in-sync local core = require 'core.highlight' local files = require 'files' local catch = require 'catch' diff --git a/test/hover/init.lua b/test/hover/init.lua index bb1d02c3..199df06d 100644 --- a/test/hover/init.lua +++ b/test/hover/init.lua @@ -6,18 +6,19 @@ local config = require 'config' rawset(_G, 'TEST', true) local accept = { - ['local'] = true, - ['setlocal'] = true, - ['getlocal'] = true, - ['setglobal'] = true, - ['getglobal'] = true, - ['field'] = true, - ['method'] = true, - ['string'] = true, - ['number'] = true, - ['integer'] = true, - ['doc.type.name'] = true, - ['function'] = true, + ['local'] = true, + ['setlocal'] = true, + ['getlocal'] = true, + ['setglobal'] = true, + ['getglobal'] = true, + ['field'] = true, + ['method'] = true, + ['string'] = true, + ['number'] = true, + ['integer'] = true, + ['doc.type.name'] = true, + ['doc.class.name'] = true, + ['function'] = true, } ---@diagnostic disable: await-in-sync @@ -28,7 +29,7 @@ function TEST(script) local hover = core.byUri('', catched['?'][1][1]) assert(hover) expect = expect:gsub('^[\r\n]*(.-)[\r\n]*$', '%1'):gsub('\r\n', '\n') - local label = tostring(hover):match('```lua[\r\n]*(.-)[\r\n]*```'):gsub('\r\n', '\n') + local label = hover:string():gsub('\r\n', '\n'):match('```lua[\r\n]*(.-)[\r\n]*```') assert(expect == label) files.remove('') end @@ -149,7 +150,7 @@ obj.<?xxx?>() TEST [[ obj.<?xxx?>() ]] -[[global obj.xxx: any]] +[[global obj.xxx: unknown]] TEST [[ local <?x?> = 1 @@ -183,7 +184,7 @@ t = { TEST [[ local <?obj?> = {} ]] -"local obj: {}" +"local obj: table" --TEST [[ --local mt = {} @@ -272,9 +273,8 @@ TEST [[ local type w2l:get_default()[<?type?>] ]] -"local type: any" +"local type: unknown" --- TODO 可选参数(或多原型) TEST [[ <?load?>() ]] @@ -312,7 +312,7 @@ end ]] [[ function x() - -> any + -> unknown ]] TEST [[ @@ -368,19 +368,19 @@ local function f() end local <?n?> = f() ]] -[[local n: any]] +[[local n: unknown]] TEST [[ local <?n?> = table.unpack(t) ]] -[[local n: any]] +[[local n: unknown]] TEST [[ local <?n?> table.pack(n) ]] [[ -local n: any +local n: unknown ]] TEST [[ @@ -451,7 +451,7 @@ local any = collectgarbage() t[any] = any ]] [[ -local t: {} +local t: table ]] TEST[[ @@ -627,7 +627,7 @@ end local <?r?> = a(1) ]] [[ -local r: string +local r: string = "a" ]] TEST[[ @@ -637,7 +637,7 @@ end local _, <?r?> = pcall(a, 1) ]] [[ -local r: string +local r: string = "a" ]] TEST[[ @@ -647,15 +647,6 @@ local <?n?> = rawlen() local n: integer ]] --- TODO 暂未实现 ---TEST[[ ---local <?n?> = pairs() ---]] ---[[ ---function n<next>(table: table [, index: any]) --- -> key: any, value: any ---]] - TEST[[ local <?x?> = '\a' ]] @@ -699,8 +690,8 @@ end ]] [[ function f() - -> any - 2. any + -> nil + 2. nil ]] TEST [[ @@ -710,7 +701,7 @@ end local <?x?> = f() ]] [[ -local x: any +local x: nil ]] TEST [[ @@ -721,7 +712,7 @@ end ]] [[ function f() - -> integer + -> integer|nil ]] TEST [[ @@ -791,7 +782,7 @@ io.<?popen?>() [[ function io.popen(prog: string, mode?: "r"|"w") -> file*? - 2. errmsg?: string + 2. errmsg: string? ]] TEST [[ @@ -817,9 +808,9 @@ global _G: _G { loadstring: function, math: mathlib, module: function, + newproxy: function, next: function, - os: oslib, - ...(+21) + ...(+22) } ]] @@ -838,7 +829,6 @@ local t: { } ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', true) TEST [[ local x x = 1 @@ -849,7 +839,6 @@ print(<?x?>) [[ local x: number = 1 ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', false) TEST [[ local <?x?> <close> = 1 @@ -1068,7 +1057,7 @@ end local <?r?> = f(1) ]] [[ -local r: integer +local r: integer = 1 ]] TEST [[ @@ -1099,6 +1088,18 @@ TEST [[ ---@vararg Class local function f(...) + local <?t?> = {...} +end +]] +[[ +local t: Class[] +]] + +TEST [[ +---@class Class + +---@vararg Class +local function f(...) local t = {...} local <?v?> = t[1] end @@ -1340,7 +1341,7 @@ TEST [[ local <?f?> ]] [[ -local f: fun(x: boolean):boolean +local f: fun(x?: boolean):boolean? ]] TEST [[ @@ -1364,7 +1365,7 @@ end [[ function f(x: any, y: any) -> first: table - 2. second?: string + 2. second: string? ]] TEST [[ @@ -1396,7 +1397,7 @@ TEST [[ local <?t?> ]] [[ -local t: string|'enum1'|'enum2' +local t: string|"enum1"|"enum2" ]] TEST [[ @@ -1405,7 +1406,7 @@ TEST [[ ---@type <?A?> ]] [[ -展开为 string|'enum1'|'enum2' +展开为 string|"enum1"|"enum2" ]] TEST [[ @@ -1415,7 +1416,7 @@ TEST [[ local <?t?> ]] [[ -local t: string|'enum1'|'enum2' +local t: string|"enum1"|"enum2" ]] TEST [[ @@ -1425,7 +1426,7 @@ TEST [[ local <?t?> ]] [[ -local t: string|'enum1'|'enum2' +local t: string|"enum1"|"enum2" ]] TEST [[ @@ -1511,7 +1512,7 @@ local x --- @type boolean local <?y?> ]] [[ -local y: any +local y: unknown ]] TEST [[ @@ -1571,11 +1572,11 @@ TEST [[ local <?x?>--测试 ]] [[ -local x: any +local x: unknown ]] TEST [[ ----@type any +---@type unknown local <?t?> t.a = 1 ]] @@ -1593,7 +1594,7 @@ print(u.x) ]] [[ local u: number { - x: any, + x: unknown, } ]] @@ -1636,7 +1637,7 @@ local f <?f?>() ]] [[ -local f: any +local f: unknown ]] TEST [[ @@ -1675,7 +1676,7 @@ TEST [[ ]] [[ global a: { - b: integer, + b: integer = 600, } ]] @@ -1683,7 +1684,7 @@ TEST [[ a.<?b?> = 10 * 60 ]] [[ -global a.b: integer +global a.b: integer = 600 ]] TEST [[ @@ -1691,7 +1692,7 @@ a.<?b?>.c = 1 * 1 ]] [[ global a.b: { - c: integer, + c: integer = 1, } ]] @@ -1729,37 +1730,20 @@ local t = nil t.<?x?>() ]] [[ -field t.x: any +field t.x: unknown ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', true) TEST [[ ---@class A local a -local b = nil +local b b = a print(b.<?x?>) ]] [[ -field A.x: any -]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', false) - -TEST [[ ----@class A ----@field x number ----@field y number - ----@type A<string, number> -local <?t?> -]] -[[ -local t: A<string, number> { - x: number, - y: number, -} +field A.x: unknown ]] TEST [[ @@ -1803,3 +1787,100 @@ local <?x?> = 1 // 2 local x: integer = 1 ]] config.set(nil, 'Lua.runtime.nonstandardSymbol', {}) + +config.set(nil, 'Lua.hover.expandAlias', false) +TEST [[ +---@alias uri string + +---@type uri +local <?uri?> +]] +[[ +local uri: uri +]] + +config.set(nil, 'Lua.hover.expandAlias', true) +TEST [[ +---@alias uri string + +---@type uri +local <?uri?> +]] +[[ +local uri: string +]] + +TEST [[ +local <?x?> = '1' .. '2' +]] +[[ +local x: string = "12" +]] + +TEST [[ +local t = { + x = 1, + [1] = 'x', +} + +local <?x?> = t[#t] +]] +[[ +local x: string = "x" +]] + +TEST [[ +local x = { + a = 1, + b = 2, + [1] = 10, +} + +local y = { + _ = x.a, + _ = x.b, + [1] = <?x?>, +} +]] +[[ +local x: { + a: integer = 1, + b: integer = 2, + [1]: integer = 10, +} +]] + +TEST [[ +---@class A +---@field x string + +---@class B: A +---@field y string + +---@type B +local <?t?> +]] +[[ +local t: B { + x: string, + y: string, +} +]] + +TEST [[ +---@class A +---@field x string + +---@class B: A +---@field x integer +---@field y string + +---@type B +local <?t?> +]] +[[ +local t: B { + x: integer, + y: string, +} +]] diff --git a/test/references/all.lua b/test/references/all.lua index 4ba63579..9395df86 100644 --- a/test/references/all.lua +++ b/test/references/all.lua @@ -3,7 +3,7 @@ local config = require 'config' TEST [[ ---@class A local a = {} -a.<?x?> = 1 +a.<~x~> = 1 ---@return A local function f() end @@ -15,7 +15,7 @@ return b.<!x!> TEST [[ ---@class A local a = {} -a.<?x?> = 1 +a.<~x~> = 1 ---@return table ---@return A @@ -26,40 +26,42 @@ return a.x, b.<!x!> ]] TEST [[ -local <?mt?> = {} +local <~mt~> = {} function <!mt!>:x() - <!self!>:x() + self:x() end ]] TEST [[ local mt = {} -function mt:<?x?>() +function mt:<~x~>() self:<!x!>() end ]] -TEST [[ ----@class Dog -local mt = {} -function mt:<?eat?>() -end - ----@class Master -local mt2 = {} -function mt2:init() - ---@type Dog - local foo = self:doSomething() - ---@type Dog - self.dog = getDog() -end -function mt2:feed() - self.dog:<!eat!>() -end -function mt2:doSomething() -end -]] - +--TEST [[ +-----@class Dog +--local mt = {} +--function mt:<~eat~>() +--end +-- +-----@class Master +--local mt2 = {} +--function mt2:init() +-- ---@type Dog +-- local foo = self:doSomething() +-- ---@type Dog +-- self.dog = getDog() +--end +--function mt2:feed() +-- self.dog:<!eat!>() +--end +--function mt2:doSomething() +--end +--]] + +-- TODO: How to search references of function? +--[=[ TEST [[ local function f() return <~<!function~> () @@ -77,55 +79,20 @@ end local _, <!f2!> = f() ]] - -config.set(nil, 'Lua.IntelliSense.traceReturn', true) -TEST [[ -local <?x?> -local function f() - return <!x!> -end -local <!y!> = f() -]] - -TEST [[ -local <?x?> -local function f() - return function () - return <!x!> - end -end -local <!y!> = f()() -]] -config.set(nil, 'Lua.IntelliSense.traceReturn', false) +]=] TEST [[ ---@class A local t ---@class B: A -local <?v?> -]] - --- TODO --- 泛型的反向搜索 -do return end -TEST [[ ----@class Dog -local <?Dog?> = {} - ----@generic T ----@param type1 T ----@return T -function foobar(type1) -end - -local <!v1!> = foobar(<!Dog!>) +local <~v~> ]] TEST [[ ---@class Dog local Dog = {} -function Dog:<?eat?>() +function Dog:<~eat~>() end ---@generic T @@ -142,7 +109,7 @@ v1:<!eat!>() TEST [[ ---@class Dog local Dog = {} -function Dog:<?eat?>() +function Dog:<~eat~>() end ---@class Master @@ -159,51 +126,3 @@ end local v1 = Master:foobar("", Dog) v1.<!eat!>() ]] - -TEST [[ ----@class A -local <?A?> - ----@generic T ----@param self T ----@return T -function m.f(self) end - -local <!b!> = m.f(<!A!>) -]] - -TEST [[ ----@class A -local <?A?> - ----@generic T ----@param self T ----@return T -function m:f() end - -local <!b!> = m.f(<!A!>) -]] - -TEST [[ ----@class A -local <?A?> - ----@generic T ----@param self T ----@return T -function <!A!>.f(self) end - -local <!b!> = <!A!>:f() -]] - -TEST [[ ----@class A -local <?A?> - ----@generic T ----@param self T ----@return T -function <!A!>:f() end - -local <!b!> = <!A!>:f() -]] diff --git a/test/references/common.lua b/test/references/common.lua index b46aa81f..5217200d 100644 --- a/test/references/common.lua +++ b/test/references/common.lua @@ -1,35 +1,35 @@ local config = require "config" TEST [[ -local <?a?> = 1 +local <~a~> = 1 <!a!> = <!a!> ]] TEST [[ -<?a?> = 1 +<~a~> = 1 <!a!> = <!a!> ]] TEST [[ local t -t.<?a?> = 1 +t.<~a~> = 1 t.<!a!> = t.<!a!> ]] TEST [[ -t.<?a?> = 1 +t.<~a~> = 1 t.<!a!> = t.<!a!> ]] TEST [[ :: <!LABEL!> :: -goto <?LABEL?> +goto <~LABEL~> if true then goto <!LABEL!> end ]] TEST [[ -:: <?LABEL?> :: +:: <~LABEL~> :: goto <!LABEL!> if true then goto <!LABEL!> @@ -38,183 +38,128 @@ end TEST [[ local a = 1 -local <?a?> = 1 +local <~a~> = 1 <!a!> = <!a!> ]] TEST [[ -local <!a!> -local <?b?> = <!a!> -]] - -TEST [[ -local <?a?> -local <!b!> = <!a!> -]] - -TEST [[ -local t = { - <!a!> = 1 -} -print(t.<?a?>) +local <~a~> +local b = <!a!> ]] TEST [[ local t = { - <?a?> = 1 + <~a~> = 1 } print(t.<!a!>) ]] TEST [[ -t[<?'a'?>] = 1 +t[<~'a'~>] = 1 print(t.<!a!>) ]] TEST [[ local t = { - [<?'a'?>] = 1 + [<~'a'~>] = 1 } print(t.<!a!>) ]] TEST [[ table.<!dump!>() -function table.<?dump?>() +function table.<~dump~>() end ]] TEST [[ local t = {} -t.<?x?> = 1 +t.<~x~> = 1 t[a.b.c] = 1 ]] TEST [[ local t = {} t.x = 1 -t[a.b.<?x?>] = 1 -]] - -config.set(nil, 'Lua.IntelliSense.traceBeSetted', true) -TEST [[ -local t -local <!f!> = t.<?f?> - -<!f!>() - -return { - <!f!> = <!f!>, -} +t[a.b.<~x~>] = 1 ]] -config.set(nil, 'Lua.IntelliSense.traceBeSetted', false) TEST [[ self = { results = { - <?labels?> = {}, + <~labels~> = {}, } } self[self.results.<!labels!>] = lbl ]] TEST [[ -a.b.<?c?> = 1 +a.b.<~c~> = 1 print(a.b.<!c!>) ]] TEST [[ local <!mt!> = {} -function <!mt!>:x() - <?self?>:x() +function mt:x() + <~self~>:x() end ]] TEST [[ -local mt = {} -function mt:<!x!>() - self:<?x?>() +local <~mt~> = {} +function <!mt!>:x() + self:x() end ]] TEST [[ -a.<!b!>.c = 1 -print(a.<?b?>.c) -]] - -config.set(nil, 'Lua.IntelliSense.traceBeSetted', true) -TEST [[ -local <?f?> -local t = { - <!a!> = <!f!> -} -print(t.<!a!>) -]] -config.set(nil, 'Lua.IntelliSense.traceBeSetted', false) - -TEST [[ -local <!f!> -local <!t!> = <?f?> -]] - -config.set(nil, 'Lua.IntelliSense.traceBeSetted', true) -TEST [[ -local <!f!> -a.<!t!> = <?f?> -]] - -TEST [[ -<!t!>.<!f!> = <?t?> +local mt = {} +function mt:<!x!>() + self:<~x~>() +end ]] -config.set(nil, 'Lua.IntelliSense.traceBeSetted', false) TEST [[ -local <!f!> -local <?t?> = <!f!> +local mt = {} +function mt:<~x~>() + self:<!x!>() +end ]] -config.set(nil, 'Lua.IntelliSense.traceBeSetted', true) TEST [[ -local <!t!> -<!t!>.<!f!> = <?t?> +a.<!b!>.c = 1 +print(a.<~b~>.c) ]] -config.set(nil, 'Lua.IntelliSense.traceBeSetted', false) TEST [[ -_G.<?xxx?> = 1 +_G.<~xxx~> = 1 print(<!xxx!>) ]] TEST [[ ----@class <!Class!> ----@type <?Class?> ----@type <!Class!> -]] - -TEST [[ ----@class <?Class?> +---@class <~Class~> ---@type <!Class!> ---@type <!Class!> ]] TEST [[ ---@class Class -local <?t?> +local <~t~> ---@type Class -local <!x!> +local x ]] TEST [[ ---@class Class -local <!t!> +local t ---@type Class -local <?x?> +local <~x~> ]] -- BUG TEST [[ ----@return <?xxx?> +---@return <~xxx~> function f() end ]] @@ -223,19 +168,19 @@ TEST [[ ---@class B: A ---@type A -local <?t?> -]] - -TEST [[ ----@class A -local a - ----@type A -local b - ----@type A -local c - -b.<?x?> = 1 -c.<!x!> = 1 -]] +local <~t~> +]] + +--TEST [[ +-----@class A +--local a +-- +-----@type A +--local b +-- +-----@type A +--local c +-- +--b.<~x~> = 1 +--c.<!x!> = 1 +--]] diff --git a/test/references/init.lua b/test/references/init.lua index 81dfb481..1b1cc73b 100644 --- a/test/references/init.lua +++ b/test/references/init.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: await-in-sync local core = require 'core.reference' local files = require 'files' local catch = require 'catch' @@ -23,7 +24,7 @@ function TEST(script) files.setText('', newScript) local input = catched['?'] + catched['~'] - local expect = catched['!'] + catched['?'] + local expect = catched['!'] + catched['~'] local results = core('', input[1][1]) if results then local positions = {} diff --git a/test/rename/init.lua b/test/rename/init.lua index 31421ada..64e3916a 100644 --- a/test/rename/init.lua +++ b/test/rename/init.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: await-in-sync local core = require 'core.rename' local files = require 'files' local catch = require 'catch' @@ -90,7 +91,6 @@ local function f(b) end ]] ---config.set('Lua.IntelliSense.traceBeSetted', true) TEST ('a', '!!!') [[ t = { a = 0 diff --git a/test/tclient/tests/multi-workspace.lua b/test/tclient/tests/multi-workspace.lua index dde59322..c4636c53 100644 --- a/test/tclient/tests/multi-workspace.lua +++ b/test/tclient/tests/multi-workspace.lua @@ -78,4 +78,60 @@ lclient():start(function (client) assert(files.isLibrary(rootUri .. '/share/test.lua') == true) assert(files.isLibrary(rootUri .. '/lb1/test.lua') == true) assert(files.isLibrary(rootUri .. '/lb2/test.lua') == true) + + files.setText(rootUri .. '/ws1/unittest.lua', [[ +GLOBAL = 1 +---@class ZAAA +---@type Z + ]]) + + files.setText(rootUri .. '/ws2/unittest.lua', [[ +GLOBAL = 2 +---@class ZBBB +---@type Z + ]]) + + local defs1 = client:awaitRequest('textDocument/definition', { + textDocument = { + uri = rootUri .. '/ws1/unittest.lua', + }, + position = { + line = 0, + character = 0, + }, + }) + assert(#defs1 == 1) + + local defs2 = client:awaitRequest('textDocument/definition', { + textDocument = { + uri = rootUri .. '/ws2/unittest.lua', + }, + position = { + line = 0, + character = 0, + }, + }) + assert(#defs2 == 1) + + local comps1 = client:awaitRequest('textDocument/completion', { + textDocument = { + uri = rootUri .. '/ws1/unittest.lua', + }, + position = { + line = 2, + character = 10, + }, + }) + assert(#comps1.items == 1) + + local comps2 = client:awaitRequest('textDocument/completion', { + textDocument = { + uri = rootUri .. '/ws2/unittest.lua', + }, + position = { + line = 2, + character = 10, + }, + }) + assert(#comps2.items == 1) end) diff --git a/test/tclient/tests/single-mode.lua b/test/tclient/tests/single-mode.lua index 4e14415b..25463f55 100644 --- a/test/tclient/tests/single-mode.lua +++ b/test/tclient/tests/single-mode.lua @@ -15,6 +15,9 @@ lclient():start(function (client) text = [[ local x print(x) + +TEST = 1 +print(TEST) ]] } }) @@ -42,4 +45,19 @@ print(x) }) assert(#locations > 0) + + local locations = client:awaitRequest('textDocument/definition', { + textDocument = { uri = 'file://single-file.lua' }, + position = { line = 3, character = 0 }, + }) + + assert(util.equal(locations, { + { + uri = 'file://single-file.lua', + range = { + start = { line = 3, character = 0 }, + ['end'] = { line = 3, character = 4 }, + } + } + })) end) diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index ef2d1f62..df45dd9d 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -1,6 +1,6 @@ local files = require 'files' local guide = require 'parser.guide' -local infer = require 'core.infer' +local infer = require 'vm.infer' local config = require 'config' local catch = require 'catch' @@ -8,17 +8,21 @@ rawset(_G, 'TEST', true) local function getSource(pos) local ast = files.getState('') - return guide.eachSourceContain(ast.ast, pos, function (source) + local result + guide.eachSourceContain(ast.ast, pos, function (source) if source.type == 'local' or source.type == 'getlocal' or source.type == 'setlocal' or source.type == 'setglobal' or source.type == 'getglobal' or source.type == 'field' - or source.type == 'method' then - return source + or source.type == 'method' + or source.type == 'function' + or source.type == 'table' then + result = source end end) + return result end function TEST(wanted) @@ -27,9 +31,9 @@ function TEST(wanted) files.setText('', newScript) local source = getSource(catched['?'][1][1]) assert(source) - local result = infer.searchAndViewInfers(source) + local result = infer.getInfer(source):view() if wanted ~= result then - infer.searchAndViewInfers(source) + infer.getInfer(source):view() end assert(wanted == result) files.remove('') @@ -52,12 +56,38 @@ TEST 'number' [[ local <?var?> = 1.0 ]] +TEST 'unknown' [[ +local <?var?> +]] + +TEST 'unknown' [[ +local <?var?> +var = y +]] + +TEST 'any' [[ +function f(<?x?>) + +end +]] + +TEST 'any' [[ +function f(<?x?>) + x = 1 +end +]] + +TEST 'number' [[ +local <?var?> +var = 1 +var = 1.0 +]] + TEST 'string' [[ local var = '111' t.<?x?> = var ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', true) TEST 'string' [[ local <?var?> var = '111' @@ -68,7 +98,6 @@ local var var = '111' print(<?var?>) ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', false) TEST 'function' [[ function <?xx?>() @@ -80,19 +109,17 @@ local function <?xx?>() end ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', true) TEST 'function' [[ local xx <?xx?> = function () end ]] -config.set(nil, 'Lua.IntelliSense.traceLocalSet', false) TEST 'table' [[ local <?t?> = {} ]] -TEST 'any' [[ +TEST 'unknown' [[ <?x?>() ]] @@ -100,7 +127,7 @@ TEST 'boolean' [[ <?x?> = not y ]] -TEST 'any' [[ +TEST 'integer' [[ <?x?> = #y ]] @@ -112,7 +139,7 @@ TEST 'integer' [[ <?x?> = #{} ]] -TEST 'any' [[ +TEST 'number' [[ <?x?> = - y ]] @@ -120,7 +147,7 @@ TEST 'number' [[ <?x?> = - 1.0 ]] -TEST 'any' [[ +TEST 'integer' [[ <?x?> = ~ y ]] @@ -144,7 +171,7 @@ TEST 'boolean' [[ <?x?> = a == b ]] -TEST 'any' [[ +TEST 'integer' [[ <?x?> = a << b ]] @@ -152,7 +179,7 @@ TEST 'integer' [[ <?x?> = 1 << 2 ]] -TEST 'any' [[ +TEST 'string' [[ <?x?> = a .. b ]] @@ -160,7 +187,15 @@ TEST 'string' [[ <?x?> = 'a' .. 'b' ]] -TEST 'any' [[ +TEST 'string' [[ +<?x?> = 'a' .. 1 +]] + +TEST 'string' [[ +<?x?> = 'a' .. 1.0 +]] + +TEST 'number' [[ <?x?> = a + b ]] @@ -168,6 +203,10 @@ TEST 'number' [[ <?x?> = 1 + 2.0 ]] +TEST 'integer' [[ +<?x?> = 1 + 2 +]] + TEST 'tablelib' [[ ---@class tablelib table = {} @@ -185,29 +224,38 @@ TEST 'function' [[ ---@class stringlib local string -string.sub = function () end +string.xxx = function () end + +return ('x').<?xxx?> +]] + +TEST 'function' [[ +---@class stringlib +String = {} + +String.xxx = function () end -return ('x').<?sub?> +return ('x').<?xxx?> ]] TEST 'function' [[ ---@class stringlib local string -string.sub = function () end +string.xxx = function () end -<?x?> = ('x').sub +<?x?> = ('x').xxx ]] TEST 'function' [[ ---@class stringlib local string -string.sub = function () end +string.xxx = function () end _VERSION = 'Lua 5.4' -<?x?> = _VERSION.sub +<?x?> = _VERSION.xxx ]] TEST 'table' [[ @@ -221,7 +269,7 @@ end <?y?> = x() ]] -TEST 'integer' [[ +TEST 'integer|nil' [[ local function x() return 1 return nil @@ -229,7 +277,7 @@ end <?y?> = x() ]] -TEST 'any' [[ +TEST 'unknown|nil' [[ local function x() return a return nil @@ -237,7 +285,7 @@ end <?y?> = x() ]] -TEST 'any' [[ +TEST 'unknown|nil' [[ local function x() return nil return f() @@ -245,6 +293,14 @@ end <?y?> = x() ]] +TEST 'unknown' [[ +local function x() + return nil + return f() +end +_, <?y?> = x() +]] + TEST 'integer' [[ local function x() return 1 @@ -292,7 +348,7 @@ local <?x?> = f() --]] -- 不根据对方函数内的使用情况来推测 -TEST 'any' [[ +TEST 'unknown' [[ local function x(a) _ = a + 1 end @@ -300,7 +356,7 @@ local b x(<?b?>) ]] -TEST 'any' [[ +TEST 'unknown' [[ local function x(a, ...) local _, <?b?>, _ = ... end @@ -308,18 +364,18 @@ x(nil, 'xx', 1, true) ]] -- 引用不跨越参数 -TEST 'any' [[ +TEST 'unknown' [[ local function x(a, ...) return true, 'ss', ... end local _, _, _, <?b?>, _ = x(nil, true, 1, 'yy') ]] -TEST 'any' [[ +TEST 'unknown' [[ local <?x?> = next() ]] -TEST 'any' [[ +TEST 'unknown' [[ local a, b function a() return b() @@ -342,6 +398,11 @@ TEST 'string' [[ local <?x?> ]] +TEST '1' [[ +---@type 1 +local <?v?> +]] + TEST 'string[]' [[ ---@class string @@ -358,7 +419,62 @@ local <?x?> ]] TEST '"enum1"|"enum2"' [[ ----@type '"enum1"' | '"enum2"' +---@type 'enum1' | 'enum2' +local <?x?> +]] + +TEST '"enum1"|"enum2"' [[ +---@type 'enum1' | 'enum2' +local <?x?> +]] + +config.set(nil, 'Lua.hover.expandAlias', false) +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' + +---@type A +local <?x?> +]] + +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' | A + +---@type A +local <?x?> +]] + +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' | B + +---@type A +local <?x?> +]] +config.set(nil, 'Lua.hover.expandAlias', true) +TEST '"enum1"|"enum2"' [[ +---@alias A 'enum1' | 'enum2' + +---@type A +local <?x?> +]] + +TEST '"enum1"|"enum2"' [[ +---@alias A 'enum1' | 'enum2' | A + +---@type A +local <?x?> +]] + +TEST '"enum1"|"enum2"|B' [[ +---@alias A 'enum1' | 'enum2' | B + +---@type A +local <?x?> +]] + +TEST '1|true' [[ +---@alias A 1 | true + +---@type A local <?x?> ]] @@ -367,6 +483,21 @@ TEST 'fun()' [[ local <?x?> ]] +TEST 'fun(a: string, b: any, ...: any)' [[ +---@type fun(a: string, b, ...) +local <?x?> +]] + +TEST 'fun(a: string, b: any, c?: boolean, ...: any):c, d?, ...' [[ +---@type fun(a: string, b, c?: boolean, ...):c, d?, ... +local <?x?> +]] + +TEST 'table' [[ +---@type { [string]: string } +local <?x?> +]] + TEST 'table<string, number>' [[ ---@class string ---@class number @@ -414,13 +545,37 @@ print(t.<?a?>) ]] TEST '"aaa"|"bbb"' [[ ----@type table<string, '"aaa"'|'"bbb"'> +---@type table<string, 'aaa'|'bbb'> local t = {} print(t.<?a?>) ]] TEST 'integer' [[ +---@generic K +---@type fun(a?: K):K +local f + +local <?n?> = f(1) +]] + +TEST 'unknown' [[ +---@generic K +---@type fun(a?: K):K +local f + +local <?n?> = f(nil) +]] + +TEST 'unknown' [[ +---@generic K +---@type fun(a: K|integer):K +local f + +local <?n?> = f(1) +]] + +TEST 'integer' [[ ---@class integer ---@generic T: table, V @@ -537,22 +692,120 @@ local t local <?k?>, v = f2(t) ]] +TEST 'fun(a: <V>):integer, <V>' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local <?f2?> = f(1) +]] + +TEST 'integer' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local f2 = f(1) +local <?i?>, v = f2(true) +]] + +TEST 'boolean' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local f2 = f(1) +local i, <?v?> = f2(true) +]] + +TEST 'fun(table: table<<K>, <V>>, index?: <K>):<K>, <V>' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local <?next?> = pairs(dummy) +]] + TEST 'string' [[ ----@class string +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) +---@type table<string, boolean> +local t +local <?k?>, v = next(t) +]] + +TEST 'boolean' [[ ---@generic T: table, K, V ---@param t T ----@return fun(table: table<K, V>, index: K):K, V +---@return fun(table: table<K, V>, index?: K):K, V ---@return T ---@return nil local function pairs(t) end -local f = pairs(t) +local next = pairs(dummy) ---@type table<string, boolean> local t +local k, <?v?> = next(t) +]] -for <?k?>, v in f, t do +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t +local <?k?>, v = next(t, nil) +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t +local k, <?v?> = next(t, nil) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t + +for <?k?>, v in next, t do end ]] @@ -575,8 +828,6 @@ end ]] TEST 'string' [[ ----@class string - ---@generic T: table, K, V ---@param t T ---@return fun(table: table<K, V>, index?: K):K, V @@ -591,8 +842,6 @@ end ]] TEST 'boolean' [[ ----@class boolean - ---@generic T: table, K, V ---@param t T ---@return fun(table: table<K, V>, index: K):K, V @@ -608,8 +857,6 @@ end ]] TEST 'boolean' [[ ----@class boolean - ---@generic T: table, V ---@param t T ---@return fun(table: V[], i?: integer):integer, V @@ -625,8 +872,6 @@ end ]] TEST 'boolean' [[ ----@class boolean - ---@generic T: table, K, V ---@param t T ---@return fun(table: table<K, V>, index: K):K, V @@ -642,8 +887,6 @@ end ]] TEST 'integer' [[ ----@class integer - ---@generic T: table, K, V ---@param t T ---@return fun(table: table<K, V>, index?: K):K, V @@ -749,13 +992,13 @@ string.gsub():gsub():<?gsub?>() ]] config.set(nil, 'Lua.hover.enumsLimit', 5) -TEST 'a|b|c|d|e...(+5)' [[ +TEST '"a"|"b"|"c"|"d"|"e"...(+5)' [[ ---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' local <?t?> ]] config.set(nil, 'Lua.hover.enumsLimit', 1) -TEST 'a...(+9)' [[ +TEST '"a"...(+9)' [[ ---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' local <?t?> ]] @@ -828,13 +1071,18 @@ TEST 'fun():number, boolean' [[ local <?t?> ]] ---[[ -l:value -l:work|&1|&1 -f:|&1|&1 -dfun:|&1 -dn:Class + +TEST 'fun(value: Class)' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +work(<?function?> (value) +end) ]] + TEST 'Class' [[ ---@class Class @@ -846,14 +1094,14 @@ work(function (<?value?>) end) ]] -TEST 'Class' [[ +TEST 'fun(value: Class)' [[ ---@class Class ---@param callback fun(value: Class) function work(callback) end -pcall(work, function (<?value?>) +pcall(work, <?function?> (value) end) ]] @@ -919,11 +1167,22 @@ end --f = function (<?x?>) end --]] +TEST 'fun(i: integer)' [[ +--- @class Emit +--- @field on fun(eventName: string, cb: function) +--- @field on fun(eventName: 'died', cb: fun(i: integer)) +--- @field on fun(eventName: 'won', cb: fun(s: string)) +local emit = {} + +emit.on("died", <?function?> (i) +end) +]] + TEST 'integer' [[ --- @class Emit --- @field on fun(eventName: string, cb: function) ---- @field on fun(eventName: '"died"', cb: fun(i: integer)) ---- @field on fun(eventName: '"won"', cb: fun(s: string)) +--- @field on fun(eventName: 'died', cb: fun(i: integer)) +--- @field on fun(eventName: 'won', cb: fun(s: string)) local emit = {} emit.on("died", function (<?i?>) @@ -933,11 +1192,22 @@ end) TEST 'integer' [[ --- @class Emit --- @field on fun(self: Emit, eventName: string, cb: function) +--- @field on fun(self: Emit, eventName: 'died', cb: fun(i: integer)) +--- @field on fun(self: Emit, eventName: 'won', cb: fun(s: string)) +local emit = {} + +emit:on("died", function (<?i?>) +end) +]] + +TEST 'integer' [[ +--- @class Emit +--- @field on fun(self: Emit, eventName: string, cb: function) --- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer)) --- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string)) local emit = {} -emit:on("died", function (<?i?>) +emit.on(self, "died", function (<?i?>) end) ]] @@ -960,14 +1230,14 @@ local x <?x?> = 1 ]] -TEST 'any' [[ +TEST 'unknown' [[ ---@return number local function f(x) local <?y?> = x() end ]] -TEST 'any' [[ +TEST 'unknown' [[ local mt ---@return number @@ -976,7 +1246,7 @@ function mt:f() end local <?v?> = mt() ]] -TEST 'any' [[ +TEST 'unknown' [[ local <?mt?> ---@class X @@ -990,7 +1260,7 @@ local mt function mt:f(<?x?>) end ]] -TEST 'any' [[ +TEST 'unknown' [[ local <?mt?> ---@type number @@ -1020,6 +1290,22 @@ mt:loop(function (<?i?>) end) ]] +TEST 'C' [[ +---@class D +---@field y integer # D comment + +---@class C +---@field x integer # C comment +---@field d D + +---@param c C +local function f(c) end + +f <?{?> + x = , +} +]] + TEST 'integer' [[ ---@class D ---@field y integer # D comment @@ -1065,3 +1351,134 @@ function F(<?x?>) end ---@param x boolean function F(x) end ]] + +TEST 'B' [[ +---@class A +local A + +---@return A +function A:x() end + +---@class B: A +local B + +---@return B +function B:x() end + +---@type B +local t + +local <?v?> = t.x() +]] + +TEST 'function' [[ +---@overload fun() +function <?f?>() end +]] + +TEST 'integer' [[ +---@type table<string, integer> +local t + +t.<?a?> +]] + +TEST '"a"|"b"|"c"' [[ +---@type table<string, "a"|"b"|"c"> +local t + +t.<?a?> +]] + +TEST 'integer' [[ +---@class A +---@field x integer + +---@type A +local t +t.<?x?> +]] + +TEST 'boolean' [[ +local <?var?> = true +var = 1 +var = 1.0 +]] + +TEST 'unknown' [[ +---@return ... +local function f() end + +local <?x?> = f() +]] + +TEST 'unknown' [[ +---@return ... +local function f() end + +local _, <?x?> = f() +]] + +TEST 'unknown' [[ +local t = { + x = 1, + y = 2, +} + +local <?x?> = t[#t] +]] + +TEST 'string' [[ +local t = { + x = 1, + [1] = 'x', +} + +local <?x?> = t[#t] +]] + +TEST 'string' [[ +local t = { 'x' } + +local <?x?> = t[#t] +]] + +TEST '(string|integer)[]' [[ +---@type (string|integer)[] +local <?x?> +]] + +TEST 'boolean' [[ +---@type table<string, boolean> +local t + +---@alias uri string + +---@type string +local uri + +local <?v?> = t[uri] +]] + +TEST 'A' [[ +---@class A +G = {} + +<?G?>:A() +]] + +TEST 'A' [[ +---@type A +local <?x?> = nil +]] + +TEST 'A' [[ +---@class A +---@field b B +local mt + +function mt:f() + self.b:x() + print(<?self?>) +end +]] diff --git a/tools/love-api.lua b/tools/love-api.lua index 5b3c754b..54369acb 100644 --- a/tools/love-api.lua +++ b/tools/love-api.lua @@ -1,6 +1,6 @@ package.path = package.path .. ';3rd/love-api/?.lua' -local lua51 = require 'Lua51' +local lua51 = require 'lua51' local api = lua51.require 'love_api' local fs = require 'bee.filesystem' local fsu = require 'fs-utility' diff --git a/tools/Lua51.lua b/tools/lua51.lua index 0951f255..fb13d294 100644 --- a/tools/Lua51.lua +++ b/tools/lua51.lua @@ -62,11 +62,11 @@ end local function findTable(name) local pg = {} - local current = lua51 + local current = lua51._G for id in stringGmatch(name, '[^%.]+') do id = stringMatch(id, '^%s*(.-)%s*$') pg[#pg+1] = id - local field = current[id] + local field = rawget(current, id) if field == nil then field = {} current[id] = field @@ -80,7 +80,7 @@ end local function setfenv(f, tbl) local tp = type(f) - if tp ~= 'function' and tp ~= 'userdata' and tp ~= 'thead' then + if tp ~= 'function' and tp ~= 'userdata' and tp ~= 'thread' then error [['setfenv' cannot change environment of given object]] end FenvCache[f] = tbl @@ -111,10 +111,10 @@ end local function requireLoad(name) local msg = '' - if type(package.searchers) ~= 'table' then - error("'package.searchers' must be a table", 3) + if type(lua51._G.package.loaders) ~= 'table' then + error("'package.loaders' must be a table", 3) end - for _, searcher in ipairs(package.searchers) do + for _, searcher in ipairs(lua51._G.package.loaders) do local f = searcher(name) if type(f) == 'function' then return f @@ -126,7 +126,7 @@ local function requireLoad(name) end local function requireWithEnv(name, env) - local loaded = package.loaded + local loaded = lua51._G.package.loaded if type(name) ~= 'string' then error(("bad argument #1 to 'require' (string expected, got %s)"):format(type(name)), 2) end @@ -175,18 +175,18 @@ lua51.getmetatable = getmetatable lua51.ipairs = ipairs function lua51.load(func, name) checkType(func, 'function') - return load(func, name, 'bt', lua51) + return load(func, name, 'bt', lua51._G) end function lua51.loadfile(name) - return loadfile(name, 'bt', lua51) + return loadfile(name, 'bt', lua51._G) end function lua51.loadstring(str, name) checkType(str, 'string') - return load(str, name, 'bt', lua51) + return load(str, name, 'bt', lua51._G) end function lua51.module(name, ...) checkType(name, 'string') - local loaded = lua51.package.loaded + local loaded = lua51._G.package.loaded local mod = loaded[name] if type(mod) ~= 'table' then local err @@ -231,7 +231,7 @@ function lua51.xpcall(f, msgh) return xpcall(f, msgh) end function lua51.require(name) - return requireWithEnv(name, lua51) + return requireWithEnv(name, lua51._G) end lua51.unpack = table.unpack @@ -397,7 +397,7 @@ function lua51.package.seeall(mod) mt = {} setmetatable(mod, mt) end - mt.__index = lua51 + mt.__index = lua51._G end -- WTF ('').format |