summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexCai2019 <89138532+AlexCai2019@users.noreply.github.com>2022-04-15 03:24:58 +0800
committerGitHub <noreply@github.com>2022-04-15 03:24:58 +0800
commit756adfb2283e9ebe4a55257cf6cb51d27eb5ad2e (patch)
treec359ac5a6016d7b359f0439e3b227b1e45a429c5
parent798e8af99bc052c51afe53bb77026e9fb04f4b4c (diff)
parentc45d862d3a3f0e0588a27b9e7cd8ba84afd454a8 (diff)
downloadlua-language-server-756adfb2283e9ebe4a55257cf6cb51d27eb5ad2e.zip
Merge branch 'sumneko:master' into master
-rw-r--r--.editorconfig3
-rw-r--r--.github/workflows/build.yml7
-rw-r--r--.gitmodules3
-rw-r--r--.luarc.json4
-rw-r--r--.vscode/launch.json3
-rw-r--r--.vscode/settings.json1
m---------3rd/EmmyLuaCodeStyle0
m---------3rd/bee.lua0
m---------3rd/love-api0
m---------3rd/lovr-api0
m---------3rd/luamake0
m---------3rd/rcedit0
-rw-r--r--README.md3
-rw-r--r--changelog.md41
-rw-r--r--locale/en-us/meta.lua6
-rw-r--r--locale/en-us/script.lua11
-rw-r--r--locale/pt-br/script.lua2
-rw-r--r--locale/zh-cn/meta.lua6
-rw-r--r--locale/zh-cn/script.lua4
-rw-r--r--main.lua12
-rw-r--r--make.lua19
-rw-r--r--make/code_format.lua19
-rw-r--r--make/modules.cpp2
-rw-r--r--meta/3rd/lovr/library/lovr.math.lua42
-rw-r--r--meta/template/basic.lua16
-rw-r--r--meta/template/builtin.lua10
-rw-r--r--meta/template/debug.lua2
-rw-r--r--meta/template/table.lua31
-rw-r--r--script/await.lua4
-rw-r--r--script/client.lua11
-rw-r--r--script/config/config.lua5
-rw-r--r--script/config/loader.lua3
-rw-r--r--script/core/code-action.lua6
-rw-r--r--script/core/completion/completion.lua352
-rw-r--r--script/core/completion/postfix.lua5
-rw-r--r--script/core/definition.lua38
-rw-r--r--script/core/diagnostics/circle-doc-class.lua9
-rw-r--r--script/core/diagnostics/deprecated.lua35
-rw-r--r--script/core/diagnostics/duplicate-doc-alias.lua (renamed from script/core/diagnostics/duplicate-doc-class.lua)11
-rw-r--r--script/core/diagnostics/duplicate-doc-field.lua33
-rw-r--r--script/core/diagnostics/duplicate-index.lua6
-rw-r--r--script/core/diagnostics/duplicate-set-field.lua6
-rw-r--r--script/core/diagnostics/init.lua6
-rw-r--r--script/core/diagnostics/no-unknown.lua (renamed from script/core/diagnostics/no-implicit-any.lua)6
-rw-r--r--script/core/diagnostics/not-yieldable.lua6
-rw-r--r--script/core/diagnostics/redefined-local.lua3
-rw-r--r--script/core/diagnostics/redundant-parameter.lua6
-rw-r--r--script/core/diagnostics/type-check.lua458
-rw-r--r--script/core/diagnostics/undefined-doc-class.lua10
-rw-r--r--script/core/diagnostics/undefined-doc-name.lua2
-rw-r--r--script/core/diagnostics/undefined-field.lua20
-rw-r--r--script/core/diagnostics/undefined-global.lua36
-rw-r--r--script/core/diagnostics/unused-local.lua3
-rw-r--r--script/core/document-symbol.lua5
-rw-r--r--script/core/generic.lua283
-rw-r--r--script/core/highlight.lua15
-rw-r--r--script/core/hint.lua8
-rw-r--r--script/core/hover/args.lua (renamed from script/core/hover/arg.lua)39
-rw-r--r--script/core/hover/description.lua80
-rw-r--r--script/core/hover/init.lua4
-rw-r--r--script/core/hover/label.lua41
-rw-r--r--script/core/hover/name.lua6
-rw-r--r--script/core/hover/return.lua130
-rw-r--r--script/core/hover/table.lua115
-rw-r--r--script/core/infer.lua639
-rw-r--r--script/core/noder.lua1914
-rw-r--r--script/core/reference.lua72
-rw-r--r--script/core/rename.lua56
-rw-r--r--script/core/searcher.lua1350
-rw-r--r--script/core/semantic-tokens.lua70
-rw-r--r--script/core/signature.lua4
-rw-r--r--script/core/type-definition.lua26
-rw-r--r--script/core/workspace-symbol.lua3
-rw-r--r--script/files.lua26
-rw-r--r--script/fs-utility.lua39
-rw-r--r--script/gc.lua1
-rw-r--r--script/global.d.lua3
-rw-r--r--script/inspect.lua337
-rw-r--r--script/jsonrpc.lua4
-rw-r--r--script/library.lua13
-rw-r--r--script/log.lua21
-rw-r--r--script/parser/ast.lua1
-rw-r--r--script/parser/guide.lua229
-rw-r--r--script/parser/luadoc.lua494
-rw-r--r--script/parser/newparser.lua57
-rw-r--r--script/progress.lua12
-rw-r--r--script/proto/define.lua23
-rw-r--r--script/proto/proto.lua32
-rw-r--r--script/provider/completion.lua11
-rw-r--r--script/provider/diagnostic.lua13
-rw-r--r--script/provider/formatting.lua6
-rw-r--r--script/provider/provider.lua60
-rw-r--r--script/pub/pub.lua2
-rw-r--r--script/service/service.lua4
-rw-r--r--script/service/telemetry.lua8
-rw-r--r--script/utility.lua113
-rw-r--r--script/vm/compiler.lua1670
-rw-r--r--script/vm/def.lua240
-rw-r--r--script/vm/doc.lua (renamed from script/vm/getDocs.lua)78
-rw-r--r--script/vm/eachDef.lua11
-rw-r--r--script/vm/eachRef.lua11
-rw-r--r--script/vm/field.lua41
-rw-r--r--script/vm/generic.lua138
-rw-r--r--script/vm/getGlobals.lua53
-rw-r--r--script/vm/getLinks.lua63
-rw-r--r--script/vm/global-manager.lua355
-rw-r--r--script/vm/global.lua125
-rw-r--r--script/vm/infer.lua386
-rw-r--r--script/vm/init.lua16
-rw-r--r--script/vm/library.lua (renamed from script/vm/getLibrary.lua)2
-rw-r--r--script/vm/local-id.lua195
-rw-r--r--script/vm/local-manager.lua40
-rw-r--r--script/vm/manager.lua33
-rw-r--r--script/vm/node.lua257
-rw-r--r--script/vm/ref.lua320
-rw-r--r--script/vm/sign.lua164
-rw-r--r--script/vm/type.lua147
-rw-r--r--script/vm/value.lua234
-rw-r--r--script/vm/vm.lua32
-rw-r--r--script/workspace/loading.lua15
-rw-r--r--script/workspace/require-path.lua22
-rw-r--r--script/workspace/scope.lua14
-rw-r--r--script/workspace/workspace.lua40
-rw-r--r--test.lua2
-rw-r--r--test/basic/init.lua1
-rw-r--r--test/basic/linker.txt141
-rw-r--r--test/basic/noder.lua143
-rw-r--r--test/catch.lua7
-rw-r--r--test/code_action/init.lua30
-rw-r--r--test/completion/common.lua335
-rw-r--r--test/crossfile/allreferences.lua218
-rw-r--r--test/crossfile/completion.lua2
-rw-r--r--test/crossfile/definition.lua66
-rw-r--r--test/crossfile/hover.lua74
-rw-r--r--test/crossfile/init.lua1
-rw-r--r--test/crossfile/references.lua223
-rw-r--r--test/definition/bug.lua119
-rw-r--r--test/definition/call.lua8
-rw-r--r--test/definition/field.lua23
-rw-r--r--test/definition/function.lua2
-rw-r--r--test/definition/init.lua6
-rw-r--r--test/definition/luadoc.lua380
-rw-r--r--test/definition/special.lua36
-rw-r--r--test/definition/table.lua52
-rw-r--r--test/diagnostics/common.lua1389
-rw-r--r--test/diagnostics/init.lua1538
-rw-r--r--test/diagnostics/type-check.lua164
-rw-r--r--test/example/guide.txt2
-rw-r--r--test/full/example.lua1
-rw-r--r--test/full/self.lua5
-rw-r--r--test/highlight/init.lua1
-rw-r--r--test/hover/init.lua235
-rw-r--r--test/references/all.lua145
-rw-r--r--test/references/common.lua165
-rw-r--r--test/references/init.lua3
-rw-r--r--test/rename/init.lua2
-rw-r--r--test/tclient/tests/multi-workspace.lua56
-rw-r--r--test/tclient/tests/single-mode.lua18
-rw-r--r--test/type_inference/init.lua545
-rw-r--r--tools/love-api.lua2
-rw-r--r--tools/lua51.lua (renamed from tools/Lua51.lua)26
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
diff --git a/README.md b/README.md
index 0c3a6737..1e637a75 100644
--- a/README.md
+++ b/README.md
@@ -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 =
'无法读取设置文件:{}'
diff --git a/main.lua b/main.lua
index 82edcdf7..f61b0e32 100644
--- a/main.lua
+++ b/main.lua
@@ -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'
diff --git a/make.lua b/make.lua
index d7d32acd..0b65c67c 100644
--- a/make.lua
+++ b/make.lua
@@ -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
diff --git a/test.lua b/test.lua
index ec42b5b1..c22bd18c 100644
--- a/test.lua
+++ b/test.lua
@@ -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